Merge branch 'master' into jetty-9.1
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..362c7b1
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+*.sh eol=lf
+*.bat eol=crlf
+*.txt eol=lf
+*.js eol=lf
\ No newline at end of file
diff --git a/README.txt b/README.TXT
similarity index 100%
rename from README.txt
rename to README.TXT
diff --git a/VERSION.txt b/VERSION.txt
index 408221c..3357c31 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1,16 +1,61 @@
-jetty-9.0.8-SNAPSHOT
+jetty-9.1.1-SNAPSHOT
-jetty-9.0.7.v20131107 - 07 November 2013
+jetty-9.1.0.v20131115 - 15 November 2013
+ + 397167 Remote Access documentation is wrong
+ + 416477 QueuedThreadPool does not reuse interrupted threads
+ + 420776 complete error pages after startAsync
+ + 421362 When using the jetty.osgi.boot ContextHandler service feature the
+ wrong ContextHandler can be undeployed
+
+jetty-9.1.0.RC2 - 7 November 2013
+ + 410656 WebSocketSession.suspend() hardcoded to return null
+ + 417223 removed deprecated ThreadPool.dispatch
+ + 418741 Threadlocal cookie buffer in response
+ + 420359 fixed thread warnings
+ + 420572 IOTest explicitly uses 127.0.0.1
+ + 420692 set soTimeout to try to avoid hang
+ + 420844 Connection:close on exceptional errors
+ + 420930 Use Charset to specify character encoding
+ + 421197 synchronize gzip output finish
+ + 421198 onComplete never call onComplete in BufferingResponseListener in 9.1.
+
+jetty-9.1.0.RC1 - 31 October 2013
+ + 294531 Unpacking webapp twice to the same directory name causes problems
+ with updated jars in WEB-INF/lib
+ + 397049 Cannot Provide Custom Credential to JDBCLoginService
+ + 403591 improve the Blocking Q implementation.
+ 407716 fixed logs
+ + 410840 Change SSLSession.getPeerCertificateChain() to
+ SSLSession.getPeerCertificates().
+ + 415118 WebAppClassLoader.getResource(name) should strip .class from name
+ + 415609 spdy replace SessionInvoker with IteratingCallback. Introduce Flusher
+ class to separate queuing/flushing logic from StandardSession
+ + 416300 Order ServletContainerInitializer callbacks
+ 416597 Allow classes and jars on the webappcontext extraclasspath to be
scanned for annotations by jetty-maven-plugin
+ + 417356 Add SOCKS support to jetty client.
+ + 417932 resources.mod should make ${jetty.base}/resources/ directory
+ + 417933 logging.mod ini template should include commented log.class settings
+ + 418212 org.eclipse.jetty.spdy.server.http.SSLExternalServerTest hangs.
+ + 418441 Use of OPTIONS= in Jetty 9.1 should display WARNING message
+ + 418596 Faults in JARs during class scanning should report the jar that
+ caused the problem
+ + 418603 cannot specify a custom ServerEndpointConfig.Configurator
+ + 418625 WebSocket / Jsr RemoteEndpoint.sendObject(java.nio.HeapByteBuffer)
+ doesn't find encoder
+ + 418632 WebSocket / Jsr annotated @OnMessage with InputStream fails to be
+ called
+ 418636 Name anonymous filter and holders with classname-hashcode
+ 418732 Add whiteListByPath mode to IPAccessHandler
+ 418767 run-forked goal ingores test scope dependencies with
useTestScope=true
+ 418792 Session getProtocolVersion always returns null
+ 418892 SSL session caching so unreliable it effectively does not work.
+ + 418922 Missing parameterization of etc/jetty-xinetd.xml
+ + 418923 Missing parameterization of etc/jetty-proxy.xml
+ + 419146 Parameterize etc/jetty-requestlog.xml values
+ 419309 Added symlink checker to test webapp
+ + 419330 Allow access to setters on jetty-jspc-maven-plugin
+ 419333 treat // as an alias in path
+ 419344 NPNServerConnection does not close the EndPoint if it reads -1.
+ 419350 Do not borrow space from passed arrays
@@ -20,57 +65,193 @@
+ 419799 Async timeout dispatches to error page
+ 419814 Annotation properties maxMessageSize and inputBufferSize don't work
+ 419846 JDBCSessionManager doesn't determine dirty state correctly
+ + 419899 Do not wrap SSL Exception as EoFException
+ 419901 Client always adds extra user-agent header.
+ + 419904 Data corruption on proxy PUT requests.
+ + 419914 QueuedThreadPool uses nanoTime
+ 419937 Request isSecure cleared on recycle
+ 419950 Provide constructor for StringContentProvider that takes Charset.
+ 419964 InputStreamContentProvider does not close provided InputStream.
+ + 420012 Improve ProxyServlet.Transparent configuration in case prefix="/".
+ 420033 AsyncContext.onTimeout exceptions passed to onError
+ + 420034 Removed threads/timers from Date caching
+ 420039 BufferingResponseListener continues processing after aborting
request.
+ 420048 DefaultServlet alias checks configured resourceBase
+ + 420103 Split out jmx-remote module from existing jmx module
+ 420142 reimplemented graceful shutdown
+ 420362 Response/request listeners called too many times.
+ + 420364 Bad synchronization in HttpConversation.
+ 420374 Call super.close() in a finally block
+ 420530 AbstractLoginModule never fails a login
- + 420572 IOTest explicitly uses 127.0.0.1
+ + 420687 XML errors in jetty-plus/src/test/resources/web-fragment-*.xml
+ 420776 complete error pages after startAsync
- + 420844 Connection:close on exceptional errors
- + 420930 Use Charset to specify character encoding
- + 421197 synchronize gzip output finish
-jetty-9.0.6.v20130930 - 30 September 2013
+jetty-9.1.0.RC0 - 30 September 2013
+ + 412469 make module for jetty-jaspi
+ + 416453 Add comments to embedded SplitFileServer example
+ + 416577 enhanced shutdown handler to send shutdown at startup
+ + 416674 run all jetty-ant tests on random ports
+ + 416940 avoid download of spring-beans.dtd
+ + 417152 WebSocket / Do all setup in websocket specific
+ ServletContainerInitializer
+ + 417239 re-implemented Request.getContentRead()
+ + 417284 Precompiled regex in HttpField
+ + 417289 SPDY replace use of direct buffers with indirect buffers or make it
+ configurable
+ + 417340 Upgrade JDT compiler to one that supports source/target of Java 1.7
+ + 417382 Upgrade to asm 4.1 and refactor annotation parsing
+ + 417475 Do not null context Trie during dynamic deploy
+ + 417490 WebSocket / @PathParam annotated parameters are null when the servlet
+ mapping uses a wildcard
+ + 417561 Refactor annotation related code: change log messages
+ + 417574 Setting options with _JAVA_OPTIONS breaks run-forked with
+ <waitForChild>true</waitForChild>
+ + 417831 Remove jetty-logging.properties from distro/resources
+ + 417938 Startup / Sort properties presented in --list-config alphabetically
+ + 418014 Handle NTFS canonical exceptions during alias check
+ + 418068 WebSocketClient has lazy or injected Executor
+ + 418212 org.eclipse.jetty.spdy.server.http.SSLExternalServerTest hangs
+ + 418227 Null cookie value test
+
+jetty-9.1.0.M0 - 16 September 2013
+ + 393473 Add support for JSR-356 (javax.websocket) draft
+ + 395444 Websockets not working with Chrome (deflate problem)
+ + 396562 Add an implementation of RequestLog that supports Slf4j
+ + 398467 Servlet 3.1 Non Blocking IO
+ + 402984 WebSocket Upgrade must honor case insensitive header fields in
+ upgrade request
+ + 403280 Update to javax.el 2.2.4
+ + 403380 Introduce WebSocketTimeoutException to differentiate between EOF on
+ write and Timeout
+ + 403510 HttpSession maxInactiveInterval is not serialized in HashSession
+ + 403591 do not use the ConcurrentArrayBlockingQueue for thread pool, selector
+ and async request log
+ + 403817 Use of WebSocket Session.close() results in invalid status code
+ + 405188 HTTP 1.0 with GET returns internal IP address.
+ + 405422 Implement servlet3.1 spec sections 4.4.3 and 8.1.4 for new
+ HttpSessionIdListener class
+ + 405432 Check implementation of section 13.4.1 @ServletSecurity for
+ @HttpConstraint and HttpMethodConstraint clarifications
+ + 405435 Implement servlet3.1 section 13.6.3 for 303 redirects for Form auth
+ + 405437 Implement section 13.8.4 Uncovered HTTP methods
+ + 405525 Throw IllegalArgumentException if filter or servlet name is null or
+ empty string in ServletContext.addXXX() methods
+ + 405526 Deployment must fail if more than 1 servlet maps to same url pattern
+ + 405531 Implement Part.getSubmittedFileName()
+ + 405533 Implement special role ** for security constraints
+ + 405535 Implement Request.isUserInRole(role) check security-role-refs
+ defaulting to security-role if no matching ref
+ + 405944 Check annotation and resource injection is supported for
+ AsyncListener
+ + 406759 supressed stacktrace in ReferrerPushStrategyTest
+ + 407708 HttpUpgradeHandler must support injection
+ + 408782 Transparent Proxy - rewrite URL is ignoring query strings.
+ + 408904 Enhance CommandlineBuilder to not escape strings inside single quotes
+ + 409403 fix IllegalStateException when SPDY is used and the response is
+ written through BufferUtil.writeTo byte by byte
+ + 409796 fix and cleanup ReferrerPushStrategy. There's more work to do here,
+ so it remains @Ignore for now
+ + 409953 return buffer.slice() instead of buffer.asReadOnlyBuffer() in
+ ResourceCache to avoid using inefficent path in BufferUtil.writeTo
+ + 410083 Jetty clients submits incomplete URL to proxy.
+ + 410098 inject accept-encoding header for all http requests through SPDY as
+ SPDY clients MUST support spdy. Also remove two new tests that have been to
+ implementation agnostic and not needed anymore due to recent code changes
+ + 410246 HttpClient with proxy does not tunnel HTTPS requests.
+ + 410341 suppress stacktraces that happen during test setup shutdown after
+ successful test run
+ + 410800 Make RewritePatternRule queryString aware
+ 411069 better set compiler defaults to 1.7, including webdefault.xml for jsp
+ 411934 War overlay configuration assumes src/main/webapp exists
+ + 412205 SSL handshake failure leads to unresponsive UpgradeConnection
+ + 412418 HttpTransportOverSPDY fix race condition while sending push streams
+ that could cause push data not to be sent. Fixes intermittent test issues in
+ ReferrerPushStrategyTest
+ + 412729 SPDYClient needs a Promise-based connect() method.
+ + 412829 Allow any mappings from web-default.xml to be overridden by web.xml
+ + 412830 Error Page match ServletException then root cause
+ + 412840 remove Future in SPDYClient.connect() and return Session instead in
+ blocking version
+ + 412934 Ignore any re-definition of an init-param within a descriptor
+ + 412935 setLocale is not an explicit set of character encoding
+ + 412940 minor threadsafe fixes
+ + 413018 ServletContext.addListener() should throw IllegalArgumentException if
+ arg is not correct type of listener
+ + 413020 Second call to HttpSession.invalidate() should throw exception 413019
+ HttpSession.getCreateTime() should throw exception after session is
+ invalidated
+ + 413291 Avoid SPDY double dispatch
+ + 413387 onResponseHeaders is not called multiple times when multiple
+ redirects occur.
+ 413484 setAttribute in nosql session management better handles _dirty status
+ + 413531 Introduce pluggable transports for HttpClient.
+ 413684 deprecated unsafe alias checkers
+ 413737 hide stacktrace in ReferrerPushStrategyTest
+ + 413901 isAsyncStarted remains true while original request is dispatched
+ + 414167 WebSocket handshake upgrade from FireFox fails due to keep-alive
+ 414431 Avoid debug NPE race
+ + 414635 Modular start.d and jetty.base property
+ + 414640 HTTP header value encoding
+ + 414725 Annotation Scanning should exclude webapp basedir from path
+ validation checks
+ + 414731 Request.getCookies() should return null if there are no cookies
+ + 414740 Removed the parent peeking Loader
+ + 414891 Errors thrown by ReadListener and WriteListener not handled
+ correctly.
+ 414898 Only upgrade v0 to v1 cookies on dquote , ; backslash space and tab
in the value
+ + 414913 WebSocket / Performance - reduce ByteBuffer allocation/copying during
+ generation/writing
+ + 414923 CompactPathRule needs to also compact the uri
+ + 415047 Create URIs lazily in HttpClient.
+ + 415062 SelectorManager wakeup optimisation.
+ + 415131 Avoid autoboxing on debug
+ 415192 <jsp-file> maps to JspPropertyGroupServlet instead of JspServlet
+ 415194 Deployer gives management of context to context collection
+ 415302
+ + 415314 Jetty should not commit response on output if <
+ Response.setBufferSize() bytes are written
+ 415330 Avoid multiple callbacks at EOF
- + 415401 Add initalizeDefaults call to SpringConfigurationProcessor
+ + 415401 WebAppProvider: override XmlConfiguration.initializeDefaults
+ 415548 migrate ProxyHTTPToSPDYTest to use HttpClient to avoid intermittent
NPE part 2
+ 415605 fix status code logging for async requests
+ + 415641 Remove remaining calls to deprecated HttpTranspoert.send
+ + 415656 SPDY - add IdleTimeout per Stream functionality
+ + 415744 Reduce Future usage in websocket
+ + 415745 Include followed by forward using a PrintWriter incurs unnecessary
+ delay
+ + 415780 fix StreamAlreadyCommittedException in spdy build
+ + 415825 fix stop support in modular start setup
+ + 415826 modules initialised with --add-to-start and --add-to-startd
+ + 415827 jetty-start / update --help text for new command line options
+ + 415830 jetty-start / add more TestUseCases for home + base + modules
+ configurations
+ + 415831 rename ini keyword from MODULES= to --module=
+ + 415832 jetty-start / fix ClassNotFound exception when starting from empty
+ base directory
+ + 415839 jetty-start / warning about need for --exec given when not needed by
+ default configuration
+ + 415899 jetty-start / add --lib=<cp> capability from Jetty 7/8
+ + 415913 support bootlib and download in modules
+ 415999 Fix some of FindBugs warnings
+ 416015 Handle null Accept-Language and other headers
+ + 416026 improve error handlig in SPDY parsers
+ 416096 DefaultServlet leaves open file descriptors with file sizes greater
than response buffer
+ 416102 Clean up of async sendContent process
+ 416103 Added AllowSymLinkAliasChecker.java
+ + 416143 mod file format uses [type]
+ + 416242 respect persistence headers in ProxyHTTPSPDYConnection
+ 416251 ProxyHTTPToSPDYConnection now sends a 502 to the client if it
receives a rst frame from the upstream spdy server
+ 416266 HttpServletResponse.encodeURL() encodes on first request when only
SessionTrackingMode.COOKIE is used
+ 416314 jetty async client wrong behaviour for HEAD Method + Redirect.
+ 416321 handle failure during blocked committing write
- + 416453 Add comments to embedded SplitFileServer example
+ 416477 Improved consumeAll error handling
+ 416568 Simplified servlet exception logging
- + 416577 enhanced shutdown handler to send shutdown at startup
+ 416585 WebInfConfiguration examines webapp classloader first instead of its
parent when looking for container jars
+ 416597 Allow classes and jars on the webappcontext extraclasspath to be
@@ -78,12 +259,30 @@
+ 416663 Content-length set by resourcehandler
+ 416674 run all jetty-ant tests on random ports
+ 416679 Change warning to debug if no transaction manager present
+ + 416680 remove uncovered constraint warning
+ + 416681 Remove unnecessary security constraints in test-jetty-webapp
+ + 416763 WebSocket / Jsr Session.getPathParameters() is empty
+ + 416764 WebSocket / Jsr Session.getRequestURI() is missing scheme + host +
+ port + query parameters
+ 416787 StringIndexOutOfBounds with a pathMap of ""
- + 416940 avoid download of spring-beans.dtd
+ + 416812 Don't start WebSocketClient for every context
+ 416990 JMX names statically unique
+ + 417022 Request attribute access to Server,HttpChannel & HttpConnection
+ + 417023 Add Default404Servlet if no default servlet set
+ + 417108 demo-base uses HTTPS
+ + 417109 Demo / Jaas test fails to find etc/login.conf
+ 417110 Demo / html body end tag missing in authfail.html
+ + 417111 Demo / login with admin/admin fails
+ + 417133 WebSocket / deflate-frame should accumulate decompress byte buffers
+ properly
+ + 417134 WebSocket / Jsr
+ ServerEndpointConfig.Configurator.getNegotiatedExtensions() is never used
+ 417225 added Container.addEventListener method
+ 417260 Protected targets matched as true URI path segments
+ jetty-9.0.6.v20130930 - 30 September 2013
+ + 416453 Add comments to embedded SplitFileServer example
+ + 416577 enhanced shutdown handler to send shutdown at startup
+ + 416940 avoid download of spring-beans.dtd
+ 417289 SPDY replace use of direct buffers with indirect buffers or make it
configurable
+ 417475 Do not null context Trie during dynamic deploy
diff --git a/aggregates/jetty-all/pom.xml b/aggregates/jetty-all/pom.xml
index ddfd58f..46b77d0 100644
--- a/aggregates/jetty-all/pom.xml
+++ b/aggregates/jetty-all/pom.xml
@@ -2,13 +2,12 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty.aggregate</groupId>
<artifactId>jetty-all</artifactId>
- <version>9.0.8-SNAPSHOT</version>
<name>Jetty :: Aggregate :: All core Jetty</name>
<url>http://www.eclipse.org/jetty</url>
<build>
@@ -26,7 +25,7 @@
<configuration>
<excludes>**/MANIFEST.MF,javax/**</excludes>
<excludeArtifactIds>javax</excludeArtifactIds>
- <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
+ <excludeGroupIds>javax,org.eclipse.jetty.orbit,org.mortbay.jetty.npn</excludeGroupIds>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
@@ -42,9 +41,9 @@
<classifier>sources</classifier>
<includes>**/*</includes>
<excludes>META-INF/**,**/Servlet3Continuation*,**/Jetty6Continuation*</excludes>
- <includeGroupIds>org.eclipse.jetty</includeGroupIds>
+ <includeGroupIds>org.eclipse.jetty,org.eclipse.jetty.websocket</includeGroupIds>
<excludeArtifactIds>javax</excludeArtifactIds>
- <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
+ <excludeGroupIds>javax,org.eclipse.jetty.orbit,org.mortbay.jetty.npn</excludeGroupIds>
<outputDirectory>${project.build.directory}/sources</outputDirectory>
<overWriteReleases>true</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
@@ -94,91 +93,132 @@
</executions>
</plugin>
</plugins>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-deploy</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
- <artifactId>websocket-server</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <artifactId>websocket-servlet</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>javax-websocket-server-impl</artifactId>
+ <version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-client</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-http-server</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
- <scope>compile</scope>
- </dependency>
- <dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jmx</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-plus</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-annotations</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jaspi</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jndi</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-rewrite</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>${project.version}</version>
<scope>provided</scope>
</dependency>
+ <!-- dependencies that jetty-all needs (some optional) -->
+ <dependency>
+ <groupId>javax.websocket</groupId>
+ <artifactId>javax.websocket-api</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.transaction</groupId>
+ <artifactId>javax.transaction-api</artifactId>
+ <scope>compile</scope>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.orbit</groupId>
+ <artifactId>javax.mail.glassfish</artifactId>
+ <scope>compile</scope>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <scope>compile</scope>
+ <optional>true</optional>
+ </dependency>
</dependencies>
</project>
diff --git a/aggregates/jetty-websocket-all/pom.xml b/aggregates/jetty-websocket-all/pom.xml
new file mode 100644
index 0000000..26138ee
--- /dev/null
+++ b/aggregates/jetty-websocket-all/pom.xml
@@ -0,0 +1,163 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-project</artifactId>
+ <version>9.1.0-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.eclipse.jetty.aggregate</groupId>
+ <artifactId>jetty-websocket-all</artifactId>
+ <name>Jetty :: Aggregate :: All WebSocket Server + Client Classes</name>
+ <url>http://www.eclipse.org/jetty</url>
+ <build>
+ <sourceDirectory>${project.build.directory}/sources</sourceDirectory>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>unpack-dependencies</id>
+ <goals>
+ <goal>unpack-dependencies</goal>
+ </goals>
+ <configuration>
+ <excludes>**/MANIFEST.MF</excludes>
+ <excludeGroupIds>org.slf4j,org.eclipse.jetty.orbit,org.mortbay.jetty.npn</excludeGroupIds>
+ <outputDirectory>${project.build.directory}/classes</outputDirectory>
+ <overWriteReleases>false</overWriteReleases>
+ <overWriteSnapshots>true</overWriteSnapshots>
+ </configuration>
+ </execution>
+ <execution>
+ <id>unpack-source</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>unpack-dependencies</goal>
+ </goals>
+ <configuration>
+ <classifier>sources</classifier>
+ <includes>**/*</includes>
+ <excludes>META-INF/**,**/Servlet3Continuation*,**/Jetty6Continuation*</excludes>
+ <includeGroupIds>org.eclipse.jetty,org.eclipse.jetty.websocket</includeGroupIds>
+ <excludeArtifactIds>javax</excludeArtifactIds>
+ <excludeGroupIds>javax,org.eclipse.jetty.orbit,org.mortbay.jetty.npn</excludeGroupIds>
+ <outputDirectory>${project.build.directory}/sources</outputDirectory>
+ <overWriteReleases>true</overWriteReleases>
+ <overWriteSnapshots>true</overWriteSnapshots>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins
+ </groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>package</id>
+ <phase>package</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ <configuration>
+ <archive>
+ <manifest>
+ </manifest>
+ <manifestEntries>
+ <mode>development</mode>
+ <url>http://eclipse.org/jetty</url>
+ <Built-By>${user.name}</Built-By>
+ <package>org.eclipse.jetty</package>
+ <Bundle-License>http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/NOTICE.txt</Bundle-License>
+ <Bundle-Name>Jetty</Bundle-Name>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>javadoc-jar</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>websocket-servlet</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>javax-websocket-server-impl</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>websocket-client</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-plus</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-annotations</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- dependencies that jetty-all needs (some optional) -->
+ <dependency>
+ <groupId>javax.websocket</groupId>
+ <artifactId>javax.websocket-api</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <scope>runtime</scope>
+ <optional>true</optional>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/examples/async-rest/async-rest-jar/pom.xml b/examples/async-rest/async-rest-jar/pom.xml
index b44c363..712f388 100644
--- a/examples/async-rest/async-rest-jar/pom.xml
+++ b/examples/async-rest/async-rest-jar/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>example-async-rest</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty.example-async-rest</groupId>
@@ -22,8 +22,9 @@
<version>${project.version}</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>3.1-b08</version>
<scope>provided</scope>
</dependency>
</dependencies>
diff --git a/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java b/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java
index 98f9974..9775e00 100644
--- a/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java
+++ b/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java
@@ -163,7 +163,7 @@
out.close();
}
- private abstract class AsyncRestRequest extends Response.Listener.Empty
+ private abstract class AsyncRestRequest extends Response.Listener.Adapter
{
final Utf8StringBuilder _content = new Utf8StringBuilder();
diff --git a/examples/async-rest/async-rest-webapp/pom.xml b/examples/async-rest/async-rest-webapp/pom.xml
index 6526ae6..ac90ff3 100644
--- a/examples/async-rest/async-rest-webapp/pom.xml
+++ b/examples/async-rest/async-rest-webapp/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>example-async-rest</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty.example-async-rest</groupId>
@@ -25,8 +25,9 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>3.1-b08</version>
<scope>provided</scope>
</dependency>
</dependencies>
diff --git a/examples/async-rest/pom.xml b/examples/async-rest/pom.xml
index 57f7603..fefa64b 100644
--- a/examples/async-rest/pom.xml
+++ b/examples/async-rest/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty.examples</groupId>
<artifactId>examples-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/embedded/pom.xml b/examples/embedded/pom.xml
index d3d6140..4fcf7c9 100644
--- a/examples/embedded/pom.xml
+++ b/examples/embedded/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty.examples</groupId>
<artifactId>examples-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -44,6 +44,11 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>javax-websocket-server-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-server</artifactId>
<version>${project.version}</version>
</dependency>
@@ -68,6 +73,10 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>javax.transaction</groupId>
+ <artifactId>javax.transaction-api</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-proxy</artifactId>
<version>${project.version}</version>
diff --git a/examples/embedded/src/main/java/HelloWorld.java b/examples/embedded/src/main/java/HelloWorld.java
index 2806fc3..5adc5a2 100644
--- a/examples/embedded/src/main/java/HelloWorld.java
+++ b/examples/embedded/src/main/java/HelloWorld.java
@@ -16,12 +16,14 @@
// ========================================================================
//
+import java.io.IOException;
+
+import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import javax.servlet.ServletException;
-import java.io.IOException;
-import org.eclipse.jetty.server.Server;
+
import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
public class HelloWorld extends AbstractHandler
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java
index 5142049..c96d5b3 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java
@@ -25,6 +25,7 @@
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
public class ExampleServer
{
@@ -44,7 +45,6 @@
handlers.setHandlers(new Handler[]{context,new DefaultHandler()});
server.setHandler(handlers);
-
server.start();
server.join();
}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
index b30373e..4df7f3d 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
@@ -48,7 +48,9 @@
public static void main(String[] args) throws Exception
{
String jetty_home = System.getProperty("jetty.home","../../jetty-distribution/target/distribution");
+ String jetty_base = System.getProperty("jetty.home","../../jetty-distribution/target/distribution/demo-base");
System.setProperty("jetty.home",jetty_home);
+ System.setProperty("jetty.base",jetty_base);
// === jetty.xml ===
@@ -132,7 +134,7 @@
deployer.setContextAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",".*/servlet-api-[^/]*\\.jar$");
WebAppProvider webapp_provider = new WebAppProvider();
- webapp_provider.setMonitoredDirName(jetty_home + "/webapps");
+ webapp_provider.setMonitoredDirName(jetty_base + "/webapps");
webapp_provider.setDefaultsDescriptor(jetty_home + "/etc/webdefault.xml");
webapp_provider.setScanInterval(1);
webapp_provider.setExtractWars(true);
@@ -176,7 +178,7 @@
// === test-realm.xml ===
HashLoginService login = new HashLoginService();
login.setName("Test Realm");
- login.setConfig(jetty_home + "/etc/realm.properties");
+ login.setConfig(jetty_base + "/etc/realm.properties");
login.setRefreshInterval(0);
server.addBean(login);
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java
index c119f5e..b18b659 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java
@@ -112,7 +112,7 @@
{
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
- Map params = request.getParameterMap();
+ Map<String,String[]> params = request.getParameterMap();
if (params.size() > 0)
{
response.setContentType("text/plain");
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java
index d94100f..5532784 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java
@@ -54,6 +54,7 @@
server.join();
}
+ @SuppressWarnings("serial")
public static class HelloServlet extends HttpServlet
{
@Override
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java
index 3e91ec4..74fa52e 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java
@@ -32,9 +32,10 @@
context.setContextPath("/");
server.setHandler(context);
- // Server content from tmp
+ // Serve content from java.io.tmpdir
ServletHolder holder = context.addServlet(org.eclipse.jetty.servlet.DefaultServlet.class,"/tmp/*");
- holder.setInitParameter("resourceBase","/tmp");
+ String tmpDir = System.getProperty("java.io.tmpdir");
+ holder.setInitParameter("resourceBase",tmpDir);
holder.setInitParameter("pathInfoOnly","true");
// A Dump Servlet
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java
index 299097c..2013f84 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java
@@ -18,10 +18,11 @@
package org.eclipse.jetty.embedded;
+import java.lang.management.ManagementFactory;
+
+import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
-import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.webapp.WebAppContext;
public class OneWebApp
@@ -32,6 +33,10 @@
// a randomly available port will be assigned that you can either look in the logs for the port,
// or programmatically obtain it for use in test cases.
Server server = new Server(8080);
+
+ // Setup JMX
+ MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
+ server.addBean(mbContainer);
// The WebAppContext is the entity that controls the environment in which a web application lives and
// breathes. In this example the context path is being set to "/" so it is suitable for serving root context
@@ -40,7 +45,7 @@
// PlusConfiguration) to choosing where the webapp will unpack itself.
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
- webapp.setWar("../../tests/test-webapps/test-jetty-webapp/target/test-jetty-webapp-9.0.0-SNAPSHOT.war");
+ webapp.setWar("../../jetty-distribution/target/distribution/demo-base/webapps/test.war");
// A WebAppContext is a ContextHandler as well so it needs to be set to the server so it is aware of where to
// send the appropriate requests.
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java
index e7aead7..f01714c 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java
@@ -32,6 +32,7 @@
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(8888);
+ server.addConnector(connector);
// Setup proxy handler to handle CONNECT methods
ConnectHandler proxy = new ConnectHandler();
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java
index ff66425..4abeda6 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java
@@ -19,8 +19,6 @@
package org.eclipse.jetty.embedded;
import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
@@ -74,7 +72,6 @@
security.setConstraintMappings(Collections.singletonList(mapping));
security.setAuthenticator(new BasicAuthenticator());
security.setLoginService(loginService);
- security.setStrict(false);
// The Hello Handler is the handler we are securing so we create one, and then set it as the handler on the
// security handler to complain the simple handler chain.
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithAnnotations.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithAnnotations.java
index 9b56916..d1d0a41 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithAnnotations.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithAnnotations.java
@@ -43,7 +43,8 @@
//Create a WebApp
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
- webapp.setWar("../../tests/test-webapps/test-servlet-spec/test-spec-webapp/target/test-spec-webapp-9.0.4-SNAPSHOT.war");
+ webapp.setWar("../../tests/test-webapps/test-servlet-spec/test-spec-webapp/target/test-spec-webapp-9.1.0-SNAPSHOT.war");
+ webapp.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",".*/javax.servlet-[^/]*\\.jar$|.*/servlet-api-[^/]*\\.jar$");
server.setHandler(webapp);
//Register new transaction manager in JNDI
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJNDI.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJNDI.java
index dd27587..decafff 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJNDI.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJNDI.java
@@ -21,6 +21,7 @@
import java.util.Properties;
+
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java
index 0c5b69e..1746e87 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java
@@ -24,8 +24,8 @@
import org.eclipse.jetty.deploy.providers.WebAppProvider;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
-import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.AsyncNCSARequestLog;
+import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java
new file mode 100644
index 0000000..d1e628f
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+
+/**
+ * Example of setting up a javax.websocket server with Jetty embedded
+ */
+public class WebSocketJsrServer
+{
+ /**
+ * A server socket endpoint
+ */
+ @ServerEndpoint(value = "/echo")
+ public static class EchoJsrSocket
+ {
+ @OnMessage
+ public void onMessage(Session session, String message)
+ {
+ session.getAsyncRemote().sendText(message);
+ }
+ }
+
+ public static void main(String[] args) throws Exception
+ {
+ Server server = new Server(8080);
+
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+ context.setContextPath("/");
+ server.setHandler(context);
+
+ // Enable javax.websocket configuration for the context
+ ServerContainer wsContainer = WebSocketServerContainerInitializer.configureContext(context);
+
+ // Add your websockets to the container
+ wsContainer.addEndpoint(EchoJsrSocket.class);
+
+ server.start();
+ context.dumpStdErr();
+ server.join();
+ }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketServer.java
new file mode 100644
index 0000000..2c50f6b
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketServer.java
@@ -0,0 +1,79 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+/**
+ * Example of setting up a Jetty WebSocket server
+ * <p>
+ * Note: this uses the Jetty WebSocket API, not the javax.websocket API.
+ */
+public class WebSocketServer
+{
+ /**
+ * Example of a Jetty API WebSocket Echo Socket
+ */
+ @WebSocket
+ public static class EchoSocket
+ {
+ @OnWebSocketMessage
+ public void onMessage(Session session, String message)
+ {
+ session.getRemote().sendStringByFuture(message);
+ }
+ }
+
+ /**
+ * Servlet layer
+ */
+ @SuppressWarnings("serial")
+ public static class EchoServlet extends WebSocketServlet
+ {
+ @Override
+ public void configure(WebSocketServletFactory factory)
+ {
+ // Register the echo websocket with the basic WebSocketCreator
+ factory.register(EchoSocket.class);
+ }
+ }
+
+ public static void main(String[] args) throws Exception
+ {
+ Server server = new Server(8080);
+
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+ context.setContextPath("/");
+ server.setHandler(context);
+
+ // Add the echo socket servlet to the /echo path map
+ context.addServlet(new ServletHolder(EchoServlet.class),"/echo");
+
+ server.start();
+ context.dumpStdErr();
+ server.join();
+ }
+}
diff --git a/examples/pom.xml b/examples/pom.xml
index 32321f8..52d0e48 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>org.eclipse.jetty.examples</groupId>
diff --git a/jetty-annotations/pom.xml b/jetty-annotations/pom.xml
index 826ff19..dfd2ae1 100644
--- a/jetty-annotations/pom.xml
+++ b/jetty-annotations/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-annotations</artifactId>
@@ -43,7 +43,7 @@
</goals>
<configuration>
<instructions>
- <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
+ <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",*</Import-Package>
</instructions>
</configuration>
</execution>
@@ -98,12 +98,16 @@
<version>${project.version}</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.annotation</artifactId>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>org.objectweb.asm</artifactId>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm-commons</artifactId>
</dependency>
</dependencies>
</project>
diff --git a/jetty-annotations/src/main/config/modules/annotations.mod b/jetty-annotations/src/main/config/modules/annotations.mod
new file mode 100644
index 0000000..65e4654
--- /dev/null
+++ b/jetty-annotations/src/main/config/modules/annotations.mod
@@ -0,0 +1,17 @@
+#
+# Jetty Annotation Scanning Module
+#
+
+[depend]
+# Annotations needs plus, and jndi features
+plus
+
+[lib]
+# Annotations needs jetty annotation jars
+lib/jetty-annotations-${jetty.version}.jar
+# Need annotation processing jars too
+lib/annotations/*.jar
+
+[xml]
+# Enable annotation scanning webapp configurations
+etc/jetty-annotations.xml
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AbstractDiscoverableAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AbstractDiscoverableAnnotationHandler.java
index a9afd89..f811102 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AbstractDiscoverableAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AbstractDiscoverableAnnotationHandler.java
@@ -18,62 +18,30 @@
package org.eclipse.jetty.annotations;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
-import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.annotations.AnnotationParser.AbstractHandler;
import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext;
/**
* DiscoverableAnnotationHandler
*
- *
+ * Base class for handling the discovery of an annotation.
+ *
*/
-public abstract class AbstractDiscoverableAnnotationHandler implements DiscoverableAnnotationHandler
+public abstract class AbstractDiscoverableAnnotationHandler extends AbstractHandler
{
protected WebAppContext _context;
- protected List<DiscoveredAnnotation> _annotations;
- protected Resource _resource;
+
public AbstractDiscoverableAnnotationHandler(WebAppContext context)
{
- this(context, null);
- }
-
- public AbstractDiscoverableAnnotationHandler(WebAppContext context, List<DiscoveredAnnotation> list)
- {
_context = context;
- if (list == null)
- _annotations = new ArrayList<DiscoveredAnnotation>();
- else
- _annotations = list;
- }
-
- public Resource getResource()
- {
- return _resource;
}
- public void setResource(Resource resource)
- {
- _resource = resource;
- }
-
- public List<DiscoveredAnnotation> getAnnotationList ()
- {
- return _annotations;
- }
-
- public void resetList()
- {
- _annotations.clear();
- }
public void addAnnotation (DiscoveredAnnotation a)
{
- _annotations.add(a);
+ _context.getMetaData().addDiscoveredAnnotation(a);
}
}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java
index 0ca2e43..ccafc4f 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java
@@ -18,21 +18,32 @@
package org.eclipse.jetty.annotations;
+import java.io.IOException;
+import java.net.MalformedURLException;
import java.net.URI;
import java.util.ArrayList;
-import java.util.EventListener;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.ServiceLoader;
-import java.util.StringTokenizer;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.annotation.HandlesTypes;
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
+import org.eclipse.jetty.annotations.AnnotationParser.Handler;
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
-import org.eclipse.jetty.util.ArrayUtil;
-import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.ConcurrentHashSet;
+import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
@@ -50,16 +61,58 @@
public class AnnotationConfiguration extends AbstractConfiguration
{
private static final Logger LOG = Log.getLogger(AnnotationConfiguration.class);
+
+ public static final String SERVLET_CONTAINER_INITIALIZER_ORDER = "org.eclipse.jetty.containerInitializerOrder";
public static final String CLASS_INHERITANCE_MAP = "org.eclipse.jetty.classInheritanceMap";
public static final String CONTAINER_INITIALIZERS = "org.eclipse.jetty.containerInitializers";
- public static final String CONTAINER_INITIALIZER_LISTENER = "org.eclipse.jetty.containerInitializerListener";
-
+ public static final String CONTAINER_INITIALIZER_STARTER = "org.eclipse.jetty.containerInitializerStarter";
+ public static final String MULTI_THREADED = "org.eclipse.jetty.annotations.multiThreaded";
+ public static final String MAX_SCAN_WAIT = "org.eclipse.jetty.annotations.maxWait";
- protected List<DiscoverableAnnotationHandler> _discoverableAnnotationHandlers = new ArrayList<DiscoverableAnnotationHandler>();
+ public static final int DEFAULT_MAX_SCAN_WAIT = 60; /* time in sec */
+ public static final boolean DEFAULT_MULTI_THREADED = true;
+
+ protected List<AbstractDiscoverableAnnotationHandler> _discoverableAnnotationHandlers = new ArrayList<AbstractDiscoverableAnnotationHandler>();
protected ClassInheritanceHandler _classInheritanceHandler;
protected List<ContainerInitializerAnnotationHandler> _containerInitializerAnnotationHandlers = new ArrayList<ContainerInitializerAnnotationHandler>();
+ protected List<ParserTask> _parserTasks;
+ protected WebAppClassNameResolver _webAppClassNameResolver;
+ protected ContainerClassNameResolver _containerClassNameResolver;
+
+
+ /**
+ * ParserTask
+ *
+ * Task to executing scanning of a resource for annotations.
+ *
+ */
+ public class ParserTask implements Callable<Void>
+ {
+ protected Exception _exception;
+ protected final AnnotationParser _parser;
+ protected final Set<? extends Handler> _handlers;
+ protected final ClassNameResolver _resolver;
+ protected final Resource _resource;
+
+
+ public ParserTask (AnnotationParser parser, Set<? extends Handler>handlers, Resource resource, ClassNameResolver resolver)
+ {
+ _parser = parser;
+ _handlers = handlers;
+ _resolver = resolver;
+ _resource = resource;
+ }
+
+ public Void call() throws Exception
+ {
+ if (_parser != null)
+ _parser.parse(_handlers, _resource, _resolver);
+ return null;
+ }
+ }
+
/**
* WebAppClassNameResolver
*
@@ -135,22 +188,166 @@
}
}
+
+ /**
+ * ServletContainerInitializerOrdering
+ *
+ * A list of classnames of ServletContainerInitializers in the order in which
+ * they are to be called back. One name only in the list can be "*", which is a
+ * wildcard which matches any other ServletContainerInitializer name not already
+ * matched.
+ */
+ public class ServletContainerInitializerOrdering
+ {
+ private Map<String, Integer> _indexMap = new HashMap<String, Integer>();
+ private Integer _star = null;
+ private String _ordering = null;
+
+ public ServletContainerInitializerOrdering (String ordering)
+ {
+ if (ordering != null)
+ {
+ _ordering = ordering;
+
+ String[] tmp = ordering.split(",");
+
+ for (int i=0; i<tmp.length; i++)
+ {
+ String s = tmp[i].trim();
+ _indexMap.put(s, Integer.valueOf(i));
+ if ("*".equals(s))
+ {
+ if (_star != null)
+ throw new IllegalArgumentException("Duplicate wildcards in ServletContainerInitializer ordering "+ordering);
+ _star = Integer.valueOf(i);
+ }
+
+ }
+ }
+ }
+
+ /**
+ * True if "*" is one of the values.
+ * @return
+ */
+ public boolean hasWildcard()
+ {
+ return _star != null;
+ }
+
+ /**
+ * Get the index of the "*" element, if it is specified. -1 otherwise.
+ * @return
+ */
+ public int getWildcardIndex()
+ {
+ if (!hasWildcard())
+ return -1;
+ return _star.intValue();
+ }
+
+ /**
+ * True if the ordering contains a single value of "*"
+ * @return
+ */
+ public boolean isDefaultOrder ()
+ {
+ return (getSize() == 1 && hasWildcard());
+ }
+
+ /**
+ * Get the order index of the given classname
+ * @param name
+ * @return
+ */
+ public int getIndexOf (String name)
+ {
+ Integer i = _indexMap.get(name);
+ if (i == null)
+ return -1;
+ return i.intValue();
+ }
+
+ /**
+ * Get the number of elements of the ordering
+ * @return
+ */
+ public int getSize()
+ {
+ return _indexMap.size();
+ }
+
+ public String toString()
+ {
+ if (_ordering == null)
+ return "";
+ return _ordering;
+ }
+ }
+
+
+
+ /**
+ * ServletContainerInitializerComparator
+ *
+ * Comparator impl that orders a set of ServletContainerInitializers according to the
+ * list of classnames (optionally containing a "*" wildcard character) established in a
+ * ServletContainerInitializerOrdering.
+ * @see ServletContainerInitializerOrdering
+ */
+ public class ServletContainerInitializerComparator implements Comparator<ServletContainerInitializer>
+ {
+ private ServletContainerInitializerOrdering _ordering;
+
+
+ public ServletContainerInitializerComparator (ServletContainerInitializerOrdering ordering)
+ {
+ _ordering = ordering;
+ }
+
+ @Override
+ public int compare(ServletContainerInitializer sci1, ServletContainerInitializer sci2)
+ {
+ String c1 = (sci1 != null? sci1.getClass().getName() : null);
+ String c2 = (sci2 != null? sci2.getClass().getName() : null);
+
+ if (c1 == null && c2 == null)
+ return 0;
+
+ int i1 = _ordering.getIndexOf(c1);
+ if (i1 < 0 && _ordering.hasWildcard())
+ i1 = _ordering.getWildcardIndex();
+ int i2 = _ordering.getIndexOf(c2);
+ if (i2 < 0 && _ordering.hasWildcard())
+ i2 = _ordering.getWildcardIndex();
+
+ return Integer.compare(i1, i2);
+ }
+ }
+
@Override
public void preConfigure(final WebAppContext context) throws Exception
{
+ _webAppClassNameResolver = new WebAppClassNameResolver(context);
+ _containerClassNameResolver = new ContainerClassNameResolver(context);
}
+ public void addDiscoverableAnnotationHandler(AbstractDiscoverableAnnotationHandler handler)
+ {
+ _discoverableAnnotationHandlers.add(handler);
+ }
+
@Override
public void deconfigure(WebAppContext context) throws Exception
{
context.removeAttribute(CLASS_INHERITANCE_MAP);
context.removeAttribute(CONTAINER_INITIALIZERS);
- ServletContainerInitializerListener listener = (ServletContainerInitializerListener)context.getAttribute(CONTAINER_INITIALIZER_LISTENER);
- if (listener != null)
+ ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(CONTAINER_INITIALIZER_STARTER);
+ if (starter != null)
{
- context.removeBean(listener);
- context.removeAttribute(CONTAINER_INITIALIZER_LISTENER);
+ context.removeBean(starter);
+ context.removeAttribute(CONTAINER_INITIALIZER_STARTER);
}
}
@@ -160,13 +357,11 @@
@Override
public void configure(WebAppContext context) throws Exception
{
- boolean metadataComplete = context.getMetaData().isMetaDataComplete();
context.addDecorator(new AnnotationDecorator(context));
-
//Even if metadata is complete, we still need to scan for ServletContainerInitializers - if there are any
- AnnotationParser parser = null;
- if (!metadataComplete)
+
+ if (!context.getMetaData().isMetaDataComplete())
{
//If metadata isn't complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we need to search for annotations
if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered())
@@ -176,31 +371,13 @@
_discoverableAnnotationHandlers.add(new WebListenerAnnotationHandler(context));
}
}
- else
- if (LOG.isDebugEnabled()) LOG.debug("Metadata-complete==true, not processing discoverable servlet annotations for context "+context);
-
-
//Regardless of metadata, if there are any ServletContainerInitializers with @HandlesTypes, then we need to scan all the
//classes so we can call their onStartup() methods correctly
createServletContainerInitializerAnnotationHandlers(context, getNonExcludedInitializers(context));
if (!_discoverableAnnotationHandlers.isEmpty() || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
- {
- parser = createAnnotationParser();
- if (LOG.isDebugEnabled()) LOG.debug("Scanning all classses for annotations: webxmlVersion="+context.getServletContext().getEffectiveMajorVersion()+" configurationDiscovered="+context.isConfigurationDiscovered());
- parseContainerPath(context, parser);
- //email from Rajiv Mordani jsrs 315 7 April 2010
- // If there is a <others/> then the ordering should be
- // WEB-INF/classes the order of the declared elements + others.
- // In case there is no others then it is
- // WEB-INF/classes + order of the elements.
- parseWebInfClasses(context, parser);
- parseWebInfLib (context, parser);
-
- for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
- context.getMetaData().addDiscoveredAnnotations(((AbstractDiscoverableAnnotationHandler)h).getAnnotationList());
- }
+ scanForAnnotations(context);
}
@@ -211,34 +388,174 @@
@Override
public void postConfigure(WebAppContext context) throws Exception
{
- MultiMap map = (MultiMap)context.getAttribute(CLASS_INHERITANCE_MAP);
- if (map != null)
- map.clear();
+ ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap = (ConcurrentHashMap<String, ConcurrentHashSet<String>>)context.getAttribute(CLASS_INHERITANCE_MAP);
+ List<ContainerInitializer> initializers = (List<ContainerInitializer>)context.getAttribute(CONTAINER_INITIALIZERS);
context.removeAttribute(CLASS_INHERITANCE_MAP);
+ if (classMap != null)
+ classMap.clear();
- List<ContainerInitializer> initializers = (List<ContainerInitializer>)context.getAttribute(CONTAINER_INITIALIZERS);
+ context.removeAttribute(CONTAINER_INITIALIZERS);
if (initializers != null)
initializers.clear();
+
if (_discoverableAnnotationHandlers != null)
_discoverableAnnotationHandlers.clear();
-
+
_classInheritanceHandler = null;
if (_containerInitializerAnnotationHandlers != null)
_containerInitializerAnnotationHandlers.clear();
-
+
+ if (_parserTasks != null)
+ {
+ _parserTasks.clear();
+ _parserTasks = null;
+ }
+
super.postConfigure(context);
}
-
+
+
+
/**
- * @return a new AnnotationParser. This method can be overridden to use a different impleemntation of
+ * Perform scanning of classes for annotations
+ *
+ * @param context
+ * @throws Exception
+ */
+ protected void scanForAnnotations (WebAppContext context)
+ throws Exception
+ {
+ AnnotationParser parser = createAnnotationParser();
+ _parserTasks = new ArrayList<ParserTask>();
+
+ long start = 0;
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("Scanning for annotations: webxml={}, metadatacomplete={}, configurationDiscovered={}, multiThreaded={}, maxScanWait={}",
+ context.getServletContext().getEffectiveMajorVersion(),
+ context.getMetaData().isMetaDataComplete(),
+ context.isConfigurationDiscovered(),
+ isUseMultiThreading(context),
+ getMaxScanWait(context));
+ }
+
+ parseContainerPath(context, parser);
+ //email from Rajiv Mordani jsrs 315 7 April 2010
+ // If there is a <others/> then the ordering should be
+ // WEB-INF/classes the order of the declared elements + others.
+ // In case there is no others then it is
+ // WEB-INF/classes + order of the elements.
+ parseWebInfClasses(context, parser);
+ parseWebInfLib (context, parser);
+
+
+ if (LOG.isDebugEnabled())
+ start = System.nanoTime();
+
+ //execute scan, either effectively synchronously (1 thread only), or asychronously (limited by number of processors available)
+ final Semaphore task_limit = (isUseMultiThreading(context)? new Semaphore(Runtime.getRuntime().availableProcessors()):new Semaphore(1));
+ final CountDownLatch latch = new CountDownLatch(_parserTasks.size());
+ final MultiException me = new MultiException();
+
+ for (final ParserTask p:_parserTasks)
+ {
+ task_limit.acquire();
+ context.getServer().getThreadPool().execute(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ p.call();
+ }
+ catch (Exception e)
+ {
+ me.add(e);
+ }
+ finally
+ {
+ task_limit.release();
+ latch.countDown();
+ }
+ }
+ });
+ }
+
+ boolean timeout = !latch.await(getMaxScanWait(context), TimeUnit.SECONDS);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Annotation parsing millisec={}",(TimeUnit.MILLISECONDS.convert(System.nanoTime()-start, TimeUnit.NANOSECONDS)));
+
+ if (timeout)
+ me.add(new Exception("Timeout scanning annotations"));
+ me.ifExceptionThrow();
+ }
+
+
+
+ /**
+ * @return a new AnnotationParser. This method can be overridden to use a different implementation of
* the AnnotationParser. Note that this is considered internal API.
*/
protected AnnotationParser createAnnotationParser()
{
return new AnnotationParser();
}
+
+ /**
+ * Check if we should use multiple threads to scan for annotations or not
+ * @param context
+ * @return
+ */
+ protected boolean isUseMultiThreading(WebAppContext context)
+ {
+ //try context attribute to see if we should use multithreading
+ Object o = context.getAttribute(MULTI_THREADED);
+ if (o instanceof Boolean)
+ {
+ return ((Boolean)o).booleanValue();
+ }
+ //try server attribute to see if we should use multithreading
+ o = context.getServer().getAttribute(MULTI_THREADED);
+ if (o instanceof Boolean)
+ {
+ return ((Boolean)o).booleanValue();
+ }
+ //try system property to see if we should use multithreading
+ return Boolean.valueOf(System.getProperty(MULTI_THREADED, Boolean.toString(DEFAULT_MULTI_THREADED)));
+ }
+
+
+ /**
+ * Work out how long we should wait for the async scanning to occur.
+ *
+ * @param context
+ * @return
+ */
+ protected int getMaxScanWait (WebAppContext context)
+ {
+ //try context attribute to get max time in sec to wait for scan completion
+ Object o = context.getAttribute(MAX_SCAN_WAIT);
+ if (o != null && o instanceof Number)
+ {
+ return ((Number)o).intValue();
+ }
+ //try server attribute to get max time in sec to wait for scan completion
+ o = context.getServer().getAttribute(MAX_SCAN_WAIT);
+ if (o != null && o instanceof Number)
+ {
+ return ((Number)o).intValue();
+ }
+ //try system property to get max time in sec to wait for scan completion
+ return Integer.getInteger(MAX_SCAN_WAIT, DEFAULT_MAX_SCAN_WAIT).intValue();
+ }
+
+
+
/**
* @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext)
*/
@@ -258,12 +575,8 @@
public void createServletContainerInitializerAnnotationHandlers (WebAppContext context, List<ServletContainerInitializer> scis)
throws Exception
{
-
-
if (scis == null || scis.isEmpty())
return; // nothing to do
-
-
List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
context.setAttribute(CONTAINER_INITIALIZERS, initializers);
@@ -271,23 +584,21 @@
for (ServletContainerInitializer service : scis)
{
HandlesTypes annotation = service.getClass().getAnnotation(HandlesTypes.class);
- ContainerInitializer initializer = new ContainerInitializer();
- initializer.setTarget(service);
- initializers.add(initializer);
+ ContainerInitializer initializer = null;
if (annotation != null)
- {
+ {
//There is a HandlesTypes annotation on the on the ServletContainerInitializer
Class[] classes = annotation.value();
if (classes != null)
{
- initializer.setInterestedTypes(classes);
-
+ initializer = new ContainerInitializer(service, classes);
//If we haven't already done so, we need to register a handler that will
//process the whole class hierarchy to satisfy the ServletContainerInitializer
if (context.getAttribute(CLASS_INHERITANCE_MAP) == null)
{
- MultiMap map = new MultiMap();
+ //MultiMap<String> map = new MultiMap<>();
+ ConcurrentHashMap<String, ConcurrentHashSet<String>> map = new ConcurrentHashMap<String, ConcurrentHashSet<String>>();
context.setAttribute(CLASS_INHERITANCE_MAP, map);
_classInheritanceHandler = new ClassInheritanceHandler(map);
}
@@ -299,42 +610,71 @@
if (c.isAnnotation())
{
if (LOG.isDebugEnabled()) LOG.debug("Registering annotation handler for "+c.getName());
-
_containerInitializerAnnotationHandlers.add(new ContainerInitializerAnnotationHandler(initializer, c));
}
}
}
else
+ {
+ initializer = new ContainerInitializer(service, null);
if (LOG.isDebugEnabled()) LOG.debug("No classes in HandlesTypes on initializer "+service.getClass());
+ }
}
else
+ {
+ initializer = new ContainerInitializer(service, null);
if (LOG.isDebugEnabled()) LOG.debug("No annotation on initializer "+service.getClass());
+ }
+
+ initializers.add(initializer);
}
-
-
-
- //add a bean which will call the servletcontainerinitializers when appropriate
- ServletContainerInitializerListener listener = (ServletContainerInitializerListener)context.getAttribute(CONTAINER_INITIALIZER_LISTENER);
- if (listener != null)
- throw new IllegalStateException("ServletContainerInitializerListener already exists");
- listener = new ServletContainerInitializerListener();
- listener.setWebAppContext(context);
- context.setAttribute(CONTAINER_INITIALIZER_LISTENER, listener);
- context.addBean(listener, true);
+
+
+ //add a bean to the context which will call the servletcontainerinitializers when appropriate
+ ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(CONTAINER_INITIALIZER_STARTER);
+ if (starter != null)
+ throw new IllegalStateException("ServletContainerInitializersStarter already exists");
+ starter = new ServletContainerInitializersStarter(context);
+ context.setAttribute(CONTAINER_INITIALIZER_STARTER, starter);
+ context.addBean(starter, true);
}
+
+ public Resource getJarFor (ServletContainerInitializer service)
+ throws MalformedURLException, IOException
+ {
+ String loadingJarName = Thread.currentThread().getContextClassLoader().getResource(service.getClass().getName().replace('.','/')+".class").toString();
+ int i = loadingJarName.indexOf(".jar");
+ if (i < 0)
+ return null; //not from a jar
+
+ loadingJarName = loadingJarName.substring(0,i+4);
+ loadingJarName = (loadingJarName.startsWith("jar:")?loadingJarName.substring(4):loadingJarName);
+ return Resource.newResource(loadingJarName);
+ }
+
/**
* Check to see if the ServletContainerIntializer loaded via the ServiceLoader came
* from a jar that is excluded by the fragment ordering. See ServletSpec 3.0 p.85.
* @param context
- * @param service
- * @return
+ * @param sci
+ * @return true if excluded
*/
- public boolean isFromExcludedJar (WebAppContext context, ServletContainerInitializer service)
+ public boolean isFromExcludedJar (WebAppContext context, ServletContainerInitializer sci, Resource sciResource)
throws Exception
{
+ if (sci == null)
+ throw new IllegalArgumentException("ServletContainerInitializer null");
+ if (context == null)
+ throw new IllegalArgumentException("WebAppContext null");
+
+ //A ServletContainerInitializer that came from the container's classpath cannot be excluded by an ordering
+ //of WEB-INF/lib jars
+ if (sci.getClass().getClassLoader()==context.getClassLoader().getParent())
+ return false;
+
List<Resource> orderedJars = context.getMetaData().getOrderedWebInfJars();
//If no ordering, nothing is excluded
@@ -345,15 +685,10 @@
if (orderedJars.isEmpty())
return true;
- String loadingJarName = Thread.currentThread().getContextClassLoader().getResource(service.getClass().getName().replace('.','/')+".class").toString();
-
- int i = loadingJarName.indexOf(".jar");
- if (i < 0)
+ if (sciResource == null)
return false; //not from a jar therefore not from WEB-INF so not excludable
-
- loadingJarName = loadingJarName.substring(0,i+4);
- loadingJarName = (loadingJarName.startsWith("jar:")?loadingJarName.substring(4):loadingJarName);
- URI loadingJarURI = Resource.newResource(loadingJarName).getURI();
+
+ URI loadingJarURI = sciResource.getURI();
boolean found = false;
Iterator<Resource> itor = orderedJars.iterator();
while (!found && itor.hasNext())
@@ -369,29 +704,121 @@
/**
* @param context
- * @return
+ * @return list of non-excluded {@link ServletContainerInitializer}s
* @throws Exception
*/
public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context)
throws Exception
{
- List<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
+ ArrayList<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
+
//We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect
+ long start = 0;
+ if (LOG.isDebugEnabled())
+ start = System.nanoTime();
ServiceLoader<ServletContainerInitializer> loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class, context.getClassLoader());
+ if (LOG.isDebugEnabled())
+ LOG.debug("Service loaders found in {}ms", (TimeUnit.MILLISECONDS.convert((System.nanoTime()-start), TimeUnit.NANOSECONDS)));
+
+ //no ServletContainerInitializers found
+ if (loadedInitializers == null)
+ return Collections.emptyList();
- if (loadedInitializers != null)
+ ServletContainerInitializerOrdering initializerOrdering = getInitializerOrdering(context);
+
+ if (initializerOrdering != null && !initializerOrdering.isDefaultOrder())
{
- for (ServletContainerInitializer service : loadedInitializers)
+ if (LOG.isDebugEnabled())
+ LOG.debug("Ordering ServletContainerInitializers with "+initializerOrdering);
+
+ //There is an ordering that is not just "*".
+ //Arrange ServletContainerInitializers according to the ordering of classnames given, irrespective of coming from container or webapp classpaths
+ for (ServletContainerInitializer sci:loadedInitializers)
{
- if (!isFromExcludedJar(context, service))
- nonExcludedInitializers.add(service);
+ Resource sciResource = getJarFor(sci);
+ if (!isFromExcludedJar(context, sci, sciResource))
+ {
+ String name = sci.getClass().getName();
+ if (initializerOrdering.getIndexOf(name) >= 0 || initializerOrdering.hasWildcard())
+ nonExcludedInitializers.add(sci);
+ }
}
+
+ //apply the ordering
+ Collections.sort(nonExcludedInitializers, new ServletContainerInitializerComparator(initializerOrdering));
}
+ else
+ {
+ //No ordering specified, or just the wildcard value "*" specified.
+ //Fallback to ordering the ServletContainerInitializers according to:
+ //container classpath first, WEB-INF/clases then WEB-INF/lib (obeying any web.xml jar ordering)
+ if (LOG.isDebugEnabled())
+ LOG.debug("Ordering ServletContainerInitializers as container path, webapp path");
+
+ Map<ServletContainerInitializer,Resource> webappPathInitializerResourceMap = new HashMap<ServletContainerInitializer,Resource>();
+ for (ServletContainerInitializer sci : loadedInitializers)
+ {
+ //if its on the container's classpath then add it
+ if (sci.getClass().getClassLoader() == context.getClassLoader().getParent())
+ {
+ nonExcludedInitializers.add(sci);
+ }
+ else
+ {
+ //if on the webapp's classpath then check the containing jar is not excluded from consideration
+ Resource sciResource = getJarFor(sci);
+ if (!isFromExcludedJar(context, sci, sciResource))
+ {
+ webappPathInitializerResourceMap.put(sci, sciResource);
+ }
+ }
+ }
+
+ //add the webapp classpath ones according to any web.xml ordering
+ if (context.getMetaData().getOrdering() == null)
+ nonExcludedInitializers.addAll(webappPathInitializerResourceMap.keySet()); //no ordering, just add them
+ else
+ {
+ //add in any ServletContainerInitializers which are not in a jar, as they must be from WEB-INF/classes
+ for (Map.Entry<ServletContainerInitializer, Resource> entry:webappPathInitializerResourceMap.entrySet())
+ {
+ if (entry.getValue() == null)
+ nonExcludedInitializers.add(entry.getKey());
+ }
+
+ //add ServletContainerInitializers according to the ordering of its containing jar
+ for (Resource webInfJar:context.getMetaData().getOrderedWebInfJars())
+ {
+ for (Map.Entry<ServletContainerInitializer, Resource> entry:webappPathInitializerResourceMap.entrySet())
+ {
+ if (webInfJar.equals(entry.getValue()))
+ nonExcludedInitializers.add(entry.getKey());
+ }
+ }
+ }
+ }
+
return nonExcludedInitializers;
}
+ /**
+ * Jetty-specific extension that allows an ordering to be applied across ALL ServletContainerInitializers.
+ *
+ * @return
+ */
+ public ServletContainerInitializerOrdering getInitializerOrdering (WebAppContext context)
+ {
+ if (context == null)
+ return null;
+
+ String tmp = (String)context.getAttribute(SERVLET_CONTAINER_INITIALIZER_ORDER);
+ if (tmp == null || "".equals(tmp.trim()))
+ return null;
+
+ return new ServletContainerInitializerOrdering(tmp);
+ }
/**
@@ -401,33 +828,24 @@
* @param parser
* @throws Exception
*/
- public void parseContainerPath (final WebAppContext context, final AnnotationParser parser)
- throws Exception
+ public void parseContainerPath (final WebAppContext context, final AnnotationParser parser) throws Exception
{
//if no pattern for the container path is defined, then by default scan NOTHING
- LOG.debug("Scanning container jars");
-
+ LOG.debug("Scanning container jars");
+
//always parse for discoverable annotations as well as class hierarchy and servletcontainerinitializer related annotations
- parser.clearHandlers();
- for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
- {
- if (h instanceof AbstractDiscoverableAnnotationHandler)
- ((AbstractDiscoverableAnnotationHandler)h).setResource(null); //
- }
- parser.registerHandlers(_discoverableAnnotationHandlers);
- parser.registerHandler(_classInheritanceHandler);
- parser.registerHandlers(_containerInitializerAnnotationHandlers);
+ final Set<Handler> handlers = new HashSet<Handler>();
+ handlers.addAll(_discoverableAnnotationHandlers);
+ handlers.addAll(_containerInitializerAnnotationHandlers);
+ if (_classInheritanceHandler != null)
+ handlers.add(_classInheritanceHandler);
- //Convert from Resource to URI
- ArrayList<URI> containerUris = new ArrayList<URI>();
for (Resource r : context.getMetaData().getContainerResources())
{
- URI uri = r.getURI();
- containerUris.add(uri);
- }
-
- parser.parse (containerUris.toArray(new URI[containerUris.size()]),
- new ContainerClassNameResolver (context));
+ //queue it up for scanning if using multithreaded mode
+ if (_parserTasks != null)
+ _parserTasks.add(new ParserTask(parser, handlers, r, _containerClassNameResolver));
+ }
}
@@ -438,9 +856,10 @@
* @param parser
* @throws Exception
*/
- public void parseWebInfLib (final WebAppContext context, final AnnotationParser parser)
- throws Exception
- {
+ public void parseWebInfLib (final WebAppContext context, final AnnotationParser parser) throws Exception
+ {
+ LOG.debug("Scanning WEB-INF/lib jars");
+
List<FragmentDescriptor> frags = context.getMetaData().getFragments();
//email from Rajiv Mordani jsrs 315 7 April 2010
@@ -457,9 +876,8 @@
for (Resource r : jars)
{
//for each jar, we decide which set of annotations we need to parse for
- parser.clearHandlers();
+ final Set<Handler> handlers = new HashSet<Handler>();
- URI uri = r.getURI();
FragmentDescriptor f = getFragmentFromJar(r, frags);
//if its from a fragment jar that is metadata complete, we should skip scanning for @webservlet etc
@@ -469,27 +887,24 @@
if (f == null || !isMetaDataComplete(f) || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
{
//register the classinheritance handler if there is one
- parser.registerHandler(_classInheritanceHandler);
-
+ if (_classInheritanceHandler != null)
+ handlers.add(_classInheritanceHandler);
+
//register the handlers for the @HandlesTypes values that are themselves annotations if there are any
- parser.registerHandlers(_containerInitializerAnnotationHandlers);
-
+ handlers.addAll(_containerInitializerAnnotationHandlers);
+
//only register the discoverable annotation handlers if this fragment is not metadata complete, or has no fragment descriptor
if (f == null || !isMetaDataComplete(f))
- {
- for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
- {
- if (h instanceof AbstractDiscoverableAnnotationHandler)
- ((AbstractDiscoverableAnnotationHandler)h).setResource(r);
- }
- parser.registerHandlers(_discoverableAnnotationHandlers);
- }
+ handlers.addAll(_discoverableAnnotationHandlers);
- parser.parse(uri, new WebAppClassNameResolver(context));
+ if (_parserTasks != null)
+ _parserTasks.add (new ParserTask(parser, handlers,r, _webAppClassNameResolver));
}
}
+
}
+
/**
* Scan classes in WEB-INF/classes
*
@@ -500,32 +915,29 @@
public void parseWebInfClasses (final WebAppContext context, final AnnotationParser parser)
throws Exception
{
- LOG.debug("Scanning classes in WEB-INF/classes");
-
- parser.clearHandlers();
- for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
- {
- if (h instanceof AbstractDiscoverableAnnotationHandler)
- ((AbstractDiscoverableAnnotationHandler)h).setResource(null); //
- }
- parser.registerHandlers(_discoverableAnnotationHandlers);
- parser.registerHandler(_classInheritanceHandler);
- parser.registerHandlers(_containerInitializerAnnotationHandlers);
+ LOG.debug("Scanning WEB-INF/classes");
+
+ Set<Handler> handlers = new HashSet<Handler>();
+ handlers.addAll(_discoverableAnnotationHandlers);
+ if (_classInheritanceHandler != null)
+ handlers.add(_classInheritanceHandler);
+ handlers.addAll(_containerInitializerAnnotationHandlers);
for (Resource dir : context.getMetaData().getWebInfClassesDirs())
{
- parser.parseDir(dir, new WebAppClassNameResolver(context));
+ if (_parserTasks != null)
+ _parserTasks.add(new ParserTask(parser, handlers, dir, _webAppClassNameResolver));
}
}
- /**
+ /**
* Get the web-fragment.xml from a jar
*
* @param jar
* @param frags
- * @return
+ * @return the fragment if found, or null of not found
* @throws Exception
*/
public FragmentDescriptor getFragmentFromJar (Resource jar, List<FragmentDescriptor> frags)
@@ -549,6 +961,4 @@
{
return (d!=null && d.getMetaDataComplete() == MetaDataComplete.True);
}
-
-
}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationDecorator.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationDecorator.java
index d0fd954..b50fb45 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationDecorator.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationDecorator.java
@@ -18,19 +18,11 @@
package org.eclipse.jetty.annotations;
-import java.util.EventListener;
-
-import javax.servlet.Filter;
-import javax.servlet.Servlet;
-import javax.servlet.ServletException;
-
-import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler.Decorator;
-import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;
/**
- * WebAppDecoratorWrapper
+ * AnnotationDecorator
*
*
*/
@@ -53,99 +45,6 @@
_introspector.registerHandler(new ServletSecurityAnnotationHandler(context));
}
- /* ------------------------------------------------------------ */
- /**
- * @param filter
- * @throws ServletException
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateFilterHolder(org.eclipse.jetty.servlet.FilterHolder)
- */
- public void decorateFilterHolder(FilterHolder filter) throws ServletException
- {
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @param <T>
- * @param filter
- * @return the decorated filter
- * @throws ServletException
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateFilterInstance(javax.servlet.Filter)
- */
- public <T extends Filter> T decorateFilterInstance(T filter) throws ServletException
- {
- introspect(filter);
- return filter;
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @param <T>
- * @param listener
- * @return the decorated event listener instance
- * @throws ServletException
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateListenerInstance(java.util.EventListener)
- */
- public <T extends EventListener> T decorateListenerInstance(T listener) throws ServletException
- {
- introspect(listener);
- return listener;
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @param servlet
- * @throws ServletException
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateServletHolder(org.eclipse.jetty.servlet.ServletHolder)
- */
- public void decorateServletHolder(ServletHolder servlet) throws ServletException
- {
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @param <T>
- * @param servlet
- * @return the decorated servlet instance
- * @throws ServletException
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateServletInstance(javax.servlet.Servlet)
- */
- public <T extends Servlet> T decorateServletInstance(T servlet) throws ServletException
- {
- introspect(servlet);
- return servlet;
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @param f
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyFilterInstance(javax.servlet.Filter)
- */
- public void destroyFilterInstance(Filter f)
- {
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @param s
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyServletInstance(javax.servlet.Servlet)
- */
- public void destroyServletInstance(Servlet s)
- {
- }
-
-
-
-
-
- /* ------------------------------------------------------------ */
- /**
- * @param f
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyListenerInstance(java.util.EventListener)
- */
- public void destroyListenerInstance(EventListener f)
- {
- }
-
/**
* Look for annotations that can be discovered with introspection:
* <ul>
@@ -161,4 +60,17 @@
{
_introspector.introspect(o.getClass());
}
+
+ @Override
+ public Object decorate(Object o)
+ {
+ introspect(o);
+ return o;
+ }
+
+ @Override
+ public void destroy(Object o)
+ {
+
+ }
}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
index f4cf3db..f6e1cb6 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
@@ -24,39 +24,59 @@
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
+import org.eclipse.jetty.util.ConcurrentHashSet;
import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.JarScanner;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.commons.EmptyVisitor;
+import org.objectweb.asm.Opcodes;
+
/**
* AnnotationParser
*
- * Use asm to scan classes for annotations. A SAX-style parsing is done, with
- * a handler being able to be registered to handle each annotation type.
+ * Use asm to scan classes for annotations. A SAX-style parsing is done.
+ * Handlers are registered which will be called back when various types of
+ * entity are encountered, eg a class, a method, a field.
+ *
+ * Handlers are not called back in any particular order and are assumed
+ * to be order-independent.
+ *
+ * As a registered Handler will be called back for each annotation discovered
+ * on a class, a method, a field, the Handler should test to see if the annotation
+ * is one that it is interested in.
+ *
+ * For the servlet spec, we are only interested in annotations on classes, methods and fields,
+ * so the callbacks for handling finding a class, a method a field are themselves
+ * not fully implemented.
*/
public class AnnotationParser
{
private static final Logger LOG = Log.getLogger(AnnotationParser.class);
- protected Set<String> _parsedClassNames = new HashSet<String>();
- protected List<Handler> _handlers = new ArrayList<Handler>();
+ protected Set<String> _parsedClassNames = new ConcurrentHashSet<String>();
+
+ /**
+ * Convert internal name to simple name
+ *
+ * @param name
+ * @return
+ */
public static String normalize (String name)
{
if (name==null)
@@ -70,294 +90,356 @@
return name.replace('/', '.');
}
-
-
-
- public abstract class Value
+
+ /**
+ * Convert internal names to simple names.
+ *
+ * @param list
+ * @return
+ */
+ public static String[] normalize (String[] list)
{
- String _name;
-
- public Value (String name)
- {
- _name = name;
- }
-
- public String getName()
- {
- return _name;
- }
-
- public abstract Object getValue();
-
+ if (list == null)
+ return null;
+ String[] normalList = new String[list.length];
+ int i=0;
+ for (String s : list)
+ normalList[i++] = normalize(s);
+ return normalList;
}
-
-
-
- public class SimpleValue extends Value
+
+ /**
+ * ClassInfo
+ *
+ * Immutable information gathered by parsing class header.
+ *
+ */
+ public class ClassInfo
{
- Object _val;
-
- public SimpleValue(String name)
+ final Resource _containingResource;
+ final String _className;
+ final int _version;
+ final int _access;
+ final String _signature;
+ final String _superName;
+ final String[] _interfaces;
+
+ public ClassInfo(Resource resource, String className, int version, int access, String signature, String superName, String[] interfaces)
{
- super(name);
+ super();
+ _containingResource = resource;
+ _className = className;
+ _version = version;
+ _access = access;
+ _signature = signature;
+ _superName = superName;
+ _interfaces = interfaces;
}
- public void setValue(Object val)
+ public String getClassName()
{
- _val=val;
+ return _className;
}
- @Override
+
+ public int getVersion()
+ {
+ return _version;
+ }
+
+ public int getAccess()
+ {
+ return _access;
+ }
+
+ public String getSignature()
+ {
+ return _signature;
+ }
+
+ public String getSuperName()
+ {
+ return _superName;
+ }
+
+ public String[] getInterfaces()
+ {
+ return _interfaces;
+ }
+
+ public Resource getContainingResource()
+ {
+ return _containingResource;
+ }
+ }
+
+
+ /**
+ * MethodInfo
+ *
+ * Immutable information gathered by parsing a method on a class.
+ */
+ public class MethodInfo
+ {
+ final ClassInfo _classInfo;
+ final String _methodName;
+ final int _access;
+ final String _desc;
+ final String _signature;
+ final String[] _exceptions;
+
+ public MethodInfo(ClassInfo classInfo, String methodName, int access, String desc, String signature, String[] exceptions)
+ {
+ super();
+ _classInfo = classInfo;
+ _methodName = methodName;
+ _access = access;
+ _desc = desc;
+ _signature = signature;
+ _exceptions = exceptions;
+ }
+
+ public ClassInfo getClassInfo()
+ {
+ return _classInfo;
+ }
+
+ public String getMethodName()
+ {
+ return _methodName;
+ }
+
+ public int getAccess()
+ {
+ return _access;
+ }
+
+ public String getDesc()
+ {
+ return _desc;
+ }
+
+ public String getSignature()
+ {
+ return _signature;
+ }
+
+ public String[] getExceptions()
+ {
+ return _exceptions;
+ }
+ }
+
+
+
+ /**
+ * FieldInfo
+ *
+ * Immutable information gathered by parsing a field on a class.
+ *
+ */
+ public class FieldInfo
+ {
+ final ClassInfo _classInfo;
+ final String _fieldName;
+ final int _access;
+ final String _fieldType;
+ final String _signature;
+ final Object _value;
+
+ public FieldInfo(ClassInfo classInfo, String fieldName, int access, String fieldType, String signature, Object value)
+ {
+ super();
+ _classInfo = classInfo;
+ _fieldName = fieldName;
+ _access = access;
+ _fieldType = fieldType;
+ _signature = signature;
+ _value = value;
+ }
+
+ public ClassInfo getClassInfo()
+ {
+ return _classInfo;
+ }
+
+ public String getFieldName()
+ {
+ return _fieldName;
+ }
+
+ public int getAccess()
+ {
+ return _access;
+ }
+
+ public String getFieldType()
+ {
+ return _fieldType;
+ }
+
+ public String getSignature()
+ {
+ return _signature;
+ }
+
public Object getValue()
{
- return _val;
- }
-
- @Override
- public String toString()
- {
- return "("+getName()+":"+_val+")";
+ return _value;
}
}
-
- public class ListValue extends Value
- {
- List<Value> _val;
-
- public ListValue (String name)
- {
- super(name);
- _val = new ArrayList<Value>();
- }
-
- @Override
- public Object getValue()
- {
- return _val;
- }
-
- public List<Value> getList()
- {
- return _val;
- }
-
- public void addValue (Value v)
- {
- _val.add(v);
- }
-
- public int size ()
- {
- return _val.size();
- }
-
- @Override
- public String toString()
- {
- StringBuffer buff = new StringBuffer();
- buff.append("(");
- buff.append(getName());
- buff.append(":");
- for (Value n: _val)
- {
- buff.append(" "+n.toString());
- }
- buff.append(")");
-
- return buff.toString();
- }
- }
-
-
-
+
+
/**
* Handler
*
* Signature for all handlers that respond to parsing class files.
*/
- public interface Handler
+ public static interface Handler
{
-
+ public void handle(ClassInfo classInfo);
+ public void handle(MethodInfo methodInfo);
+ public void handle (FieldInfo fieldInfo);
+ public void handle (ClassInfo info, String annotationName);
+ public void handle (MethodInfo info, String annotationName);
+ public void handle (FieldInfo info, String annotationName);
}
/**
- * DiscoverableAnnotationHandler
+ * AbstractHandler
*
- * Processes an annotation when it is discovered on a class.
+ * Convenience base class to provide no-ops for all Handler methods.
+ *
*/
- public interface DiscoverableAnnotationHandler extends Handler
+ public static abstract class AbstractHandler implements Handler
{
- /**
- * Process an annotation that was discovered on a class
- * @param className
- * @param version
- * @param access
- * @param signature
- * @param superName
- * @param interfaces
- * @param annotation
- * @param values
- */
- public void handleClass (String className, int version, int access,
- String signature, String superName, String[] interfaces,
- String annotation, List<Value>values);
- /**
- * Process an annotation that was discovered on a method
- * @param className
- * @param methodName
- * @param access
- * @param desc
- * @param signature
- * @param exceptions
- * @param annotation
- * @param values
- */
- public void handleMethod (String className, String methodName, int access,
- String desc, String signature,String[] exceptions,
- String annotation, List<Value>values);
+ @Override
+ public void handle(ClassInfo classInfo)
+ {
+ //no-op
+ }
+
+ @Override
+ public void handle(MethodInfo methodInfo)
+ {
+ // no-op
+ }
+
+ @Override
+ public void handle(FieldInfo fieldInfo)
+ {
+ // no-op
+ }
+
+ @Override
+ public void handle(ClassInfo info, String annotationName)
+ {
+ // no-op
+ }
+
+ @Override
+ public void handle(MethodInfo info, String annotationName)
+ {
+ // no-op
+ }
+
+ @Override
+ public void handle(FieldInfo info, String annotationName)
+ {
+ // no-op
+ }
+ }
+
+
+
+ /**
+ * MyMethodVisitor
+ *
+ * ASM Visitor for parsing a method. We are only interested in the annotations on methods.
+ */
+ public class MyMethodVisitor extends MethodVisitor
+ {
+ final MethodInfo _mi;
+ final Set<? extends Handler> _handlers;
+
+ public MyMethodVisitor(final Set<? extends Handler> handlers,
+ final ClassInfo classInfo,
+ final int access,
+ final String name,
+ final String methodDesc,
+ final String signature,
+ final String[] exceptions)
+ {
+ super(Opcodes.ASM4);
+ _handlers = handlers;
+ _mi = new MethodInfo(classInfo, name, access, methodDesc,signature, exceptions);
+ }
/**
- * Process an annotation that was discovered on a field
- * @param className
- * @param fieldName
- * @param access
- * @param fieldType
- * @param signature
- * @param value
- * @param annotation
- * @param values
- */
- public void handleField (String className, String fieldName, int access,
- String fieldType, String signature, Object value,
- String annotation, List<Value>values);
-
-
- /**
- * Get the name of the annotation processed by this handler. Can be null
+ * We are only interested in finding the annotations on methods.
*
- * @return
+ * @see org.objectweb.asm.MethodVisitor#visitAnnotation(java.lang.String, boolean)
*/
- public String getAnnotationName();
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible)
+ {
+ String annotationName = normalize(desc);
+ for (Handler h:_handlers)
+ h.handle(_mi, annotationName);
+ return null;
+ }
}
/**
- * ClassHandler
+ * MyFieldVisitor
+ *
+ * An ASM visitor for parsing Fields.
+ * We are only interested in visiting annotations on Fields.
*
- * Responds to finding a Class
*/
- public interface ClassHandler extends Handler
- {
- public void handle (String className, int version, int access, String signature, String superName, String[] interfaces);
- }
-
+ public class MyFieldVisitor extends FieldVisitor
+ {
+ final FieldInfo _fieldInfo;
+ final Set<? extends Handler> _handlers;
+
-
- /**
- * MethodHandler
- *
- * Responds to finding a Method
- */
- public interface MethodHandler extends Handler
- {
- public void handle (String className, String methodName, int access, String desc, String signature,String[] exceptions);
- }
-
-
- /**
- * FieldHandler
- *
- * Responds to finding a Field
- */
- public interface FieldHandler extends Handler
- {
- public void handle (String className, String fieldName, int access, String fieldType, String signature, Object value);
- }
-
-
-
- /**
- * MyAnnotationVisitor
- *
- * ASM Visitor for Annotations
- */
- public class MyAnnotationVisitor implements AnnotationVisitor
- {
- List<Value> _annotationValues;
- String _annotationName;
-
- public MyAnnotationVisitor (String annotationName, List<Value> values)
+ public MyFieldVisitor(final Set<? extends Handler> handlers,
+ final ClassInfo classInfo,
+ final int access,
+ final String fieldName,
+ final String fieldType,
+ final String signature,
+ final Object value)
{
- _annotationValues = values;
- _annotationName = annotationName;
+ super(Opcodes.ASM4);
+ _handlers = handlers;
+ _fieldInfo = new FieldInfo(classInfo, fieldName, access, fieldType, signature, value);
}
- public List<Value> getAnnotationValues()
- {
- return _annotationValues;
- }
/**
- * Visit a single-valued (name,value) pair for this annotation
- * @see org.objectweb.asm.AnnotationVisitor#visit(java.lang.String, java.lang.Object)
+ * Parse an annotation found on a Field.
+ *
+ * @see org.objectweb.asm.FieldVisitor#visitAnnotation(java.lang.String, boolean)
*/
@Override
- public void visit(String aname, Object avalue)
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible)
{
- SimpleValue v = new SimpleValue(aname);
- v.setValue(avalue);
- _annotationValues.add(v);
- }
+ String annotationName = normalize(desc);
+ for (Handler h : _handlers)
+ h.handle(_fieldInfo, annotationName);
- /**
- * Visit a (name,value) pair whose value is another Annotation
- * @see org.objectweb.asm.AnnotationVisitor#visitAnnotation(java.lang.String, java.lang.String)
- */
- @Override
- public AnnotationVisitor visitAnnotation(String name, String desc)
- {
- String s = normalize(desc);
- ListValue v = new ListValue(s);
- _annotationValues.add(v);
- MyAnnotationVisitor visitor = new MyAnnotationVisitor(s, v.getList());
- return visitor;
- }
-
- /**
- * Visit an array valued (name, value) pair for this annotation
- * @see org.objectweb.asm.AnnotationVisitor#visitArray(java.lang.String)
- */
- @Override
- public AnnotationVisitor visitArray(String name)
- {
- ListValue v = new ListValue(name);
- _annotationValues.add(v);
- MyAnnotationVisitor visitor = new MyAnnotationVisitor(null, v.getList());
- return visitor;
- }
-
- /**
- * Visit a enum-valued (name,value) pair for this annotation
- * @see org.objectweb.asm.AnnotationVisitor#visitEnum(java.lang.String, java.lang.String, java.lang.String)
- */
- @Override
- public void visitEnum(String name, String desc, String value)
- {
- //TODO
- }
-
- @Override
- public void visitEnd()
- {
+ return null;
}
}
-
+
/**
@@ -365,77 +447,59 @@
*
* ASM visitor for a class.
*/
- public class MyClassVisitor extends EmptyVisitor
+ public class MyClassVisitor extends ClassVisitor
{
- String _className;
- int _access;
- String _signature;
- String _superName;
- String[] _interfaces;
- int _version;
+
+ final Resource _containingResource;
+ final Set<? extends Handler> _handlers;
+ ClassInfo _ci;
+
+ public MyClassVisitor(Set<? extends Handler> handlers, Resource containingResource)
+ {
+ super(Opcodes.ASM4);
+ _handlers = handlers;
+ _containingResource = containingResource;
+ }
@Override
- public void visit (int version,
+ public void visit (final int version,
final int access,
final String name,
final String signature,
final String superName,
final String[] interfaces)
- {
- _className = normalize(name);
- _access = access;
- _signature = signature;
- _superName = superName;
- _interfaces = interfaces;
- _version = version;
+ {
+ _ci = new ClassInfo(_containingResource, normalize(name), version, access, signature, normalize(superName), normalize(interfaces));
+
+ _parsedClassNames.add(_ci.getClassName());
- _parsedClassNames.add(_className);
- //call all registered ClassHandlers
- String[] normalizedInterfaces = null;
- if (interfaces!= null)
- {
- normalizedInterfaces = new String[interfaces.length];
- int i=0;
- for (String s : interfaces)
- normalizedInterfaces[i++] = normalize(s);
- }
-
- for (Handler h : AnnotationParser.this._handlers)
- {
- if (h instanceof ClassHandler)
- {
- ((ClassHandler)h).handle(_className, _version, _access, _signature, normalize(_superName), normalizedInterfaces);
- }
- }
+ for (Handler h:_handlers)
+ h.handle(_ci);
}
+
+ /**
+ * Visit an annotation on a Class
+ *
+ * @see org.objectweb.asm.ClassVisitor#visitAnnotation(java.lang.String, boolean)
+ */
@Override
public AnnotationVisitor visitAnnotation (String desc, boolean visible)
{
- MyAnnotationVisitor visitor = new MyAnnotationVisitor(normalize(desc), new ArrayList<Value>())
- {
- @Override
- public void visitEnd()
- {
- super.visitEnd();
+ String annotationName = normalize(desc);
+ for (Handler h : _handlers)
+ h.handle(_ci, annotationName);
- //call all AnnotationHandlers with classname, annotation name + values
- for (Handler h : AnnotationParser.this._handlers)
- {
- if (h instanceof DiscoverableAnnotationHandler)
- {
- DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
- if (_annotationName.equalsIgnoreCase(dah.getAnnotationName()))
- dah.handleClass(_className, _version, _access, _signature, _superName, _interfaces, _annotationName, _annotationValues);
- }
- }
- }
- };
-
- return visitor;
+ return null;
}
+
+ /**
+ * Visit a method to extract its annotations
+ *
+ * @see org.objectweb.asm.ClassVisitor#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])
+ */
@Override
public MethodVisitor visitMethod (final int access,
final String name,
@@ -444,35 +508,14 @@
final String[] exceptions)
{
- return new EmptyVisitor ()
- {
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible)
- {
- MyAnnotationVisitor visitor = new MyAnnotationVisitor (normalize(desc), new ArrayList<Value>())
- {
- @Override
- public void visitEnd()
- {
- super.visitEnd();
- //call all AnnotationHandlers with classname, method, annotation name + values
- for (Handler h : AnnotationParser.this._handlers)
- {
- if (h instanceof DiscoverableAnnotationHandler)
- {
- DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
- if (_annotationName.equalsIgnoreCase(dah.getAnnotationName()))
- dah.handleMethod(_className, name, access, methodDesc, signature, exceptions, _annotationName, _annotationValues);
- }
- }
- }
- };
-
- return visitor;
- }
- };
+ return new MyMethodVisitor(_handlers, _ci, access, name, methodDesc, signature, exceptions);
}
+ /**
+ * Visit a field to extract its annotations
+ *
+ * @see org.objectweb.asm.ClassVisitor#visitField(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object)
+ */
@Override
public FieldVisitor visitField (final int access,
final String fieldName,
@@ -480,153 +523,15 @@
final String signature,
final Object value)
{
-
- return new EmptyVisitor ()
- {
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible)
- {
- MyAnnotationVisitor visitor = new MyAnnotationVisitor(normalize(desc), new ArrayList<Value>())
- {
- @Override
- public void visitEnd()
- {
- super.visitEnd();
- for (Handler h : AnnotationParser.this._handlers)
- {
- if (h instanceof DiscoverableAnnotationHandler)
- {
- DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
- if (_annotationName.equalsIgnoreCase(dah.getAnnotationName()))
- dah.handleField(_className, fieldName, access, fieldType, signature, value, _annotationName, _annotationValues);
- }
- }
- }
- };
- return visitor;
- }
- };
+ return new MyFieldVisitor(_handlers, _ci, access, fieldName, fieldType, signature, value);
}
}
-
- /**
- * Register a handler that will be called back when the named annotation is
- * encountered on a class.
- *
- * @deprecated see registerHandler(Handler)
- * @param annotationName
- * @param handler
- */
- @Deprecated
- public void registerAnnotationHandler (String annotationName, DiscoverableAnnotationHandler handler)
- {
- _handlers.add(handler);
- }
-
-
- /**
- * @deprecated
- * @param annotationName
- * @return
- */
- @Deprecated
- public List<DiscoverableAnnotationHandler> getAnnotationHandlers(String annotationName)
- {
- List<DiscoverableAnnotationHandler> handlers = new ArrayList<DiscoverableAnnotationHandler>();
- for (Handler h:_handlers)
- {
- if (h instanceof DiscoverableAnnotationHandler)
- {
- DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
- if (annotationName.equals(dah.getAnnotationName()))
- handlers.add(dah);
- }
- }
- return handlers;
- }
-
- /**
- * @deprecated
- * @return
- */
- @Deprecated
- public List<DiscoverableAnnotationHandler> getAnnotationHandlers()
- {
- List<DiscoverableAnnotationHandler> allAnnotationHandlers = new ArrayList<DiscoverableAnnotationHandler>();
- for (Handler h:_handlers)
- {
- if (h instanceof DiscoverableAnnotationHandler)
- allAnnotationHandlers.add((DiscoverableAnnotationHandler)h);
- }
- return allAnnotationHandlers;
- }
-
- /**
- * @deprecated see registerHandler(Handler)
- * @param handler
- */
- @Deprecated
- public void registerClassHandler (ClassHandler handler)
- {
- _handlers.add(handler);
- }
-
-
-
- /**
- * Add a particular handler
- *
- * @param h
- */
- public void registerHandler(Handler h)
- {
- if (h == null)
- return;
-
- _handlers.add(h);
- }
-
-
- /**
- * Add a list of handlers
- *
- * @param handlers
- */
- public void registerHandlers(List<? extends Handler> handlers)
- {
- if (handlers == null)
- return;
- _handlers.addAll(handlers);
- }
-
-
- /**
- * Remove a particular handler
- *
- * @param h
- * @return
- */
- public boolean deregisterHandler(Handler h)
- {
- return _handlers.remove(h);
- }
-
-
- /**
- * Remove all registered handlers
- */
- public void clearHandlers()
- {
- _handlers.clear();
- }
-
/**
* True if the class has already been processed, false otherwise
* @param className
- * @return
*/
public boolean isParsed (String className)
{
@@ -642,7 +547,7 @@
* @param resolver
* @throws Exception
*/
- public void parse (String className, ClassNameResolver resolver)
+ public void parse (Set<? extends Handler> handlers, String className, ClassNameResolver resolver)
throws Exception
{
if (className == null)
@@ -653,11 +558,11 @@
if (!isParsed(className) || resolver.shouldOverride(className))
{
className = className.replace('.', '/')+".class";
- URL resource = Loader.getResource(this.getClass(), className, false);
+ URL resource = Loader.getResource(this.getClass(), className);
if (resource!= null)
{
Resource r = Resource.newResource(resource);
- scanClass(r.getInputStream());
+ scanClass(handlers, null, r.getInputStream());
}
}
}
@@ -673,7 +578,7 @@
* @param visitSuperClasses
* @throws Exception
*/
- public void parse (Class<?> clazz, ClassNameResolver resolver, boolean visitSuperClasses)
+ public void parse (Set<? extends Handler> handlers, Class<?> clazz, ClassNameResolver resolver, boolean visitSuperClasses)
throws Exception
{
Class<?> cz = clazz;
@@ -684,14 +589,15 @@
if (!isParsed(cz.getName()) || resolver.shouldOverride(cz.getName()))
{
String nameAsResource = cz.getName().replace('.', '/')+".class";
- URL resource = Loader.getResource(this.getClass(), nameAsResource, false);
+ URL resource = Loader.getResource(this.getClass(), nameAsResource);
if (resource!= null)
{
Resource r = Resource.newResource(resource);
- scanClass(r.getInputStream());
+ scanClass(handlers, null, r.getInputStream());
}
}
}
+
if (visitSuperClasses)
cz = cz.getSuperclass();
else
@@ -708,13 +614,13 @@
* @param resolver
* @throws Exception
*/
- public void parse (String[] classNames, ClassNameResolver resolver)
+ public void parse (Set<? extends Handler> handlers, String[] classNames, ClassNameResolver resolver)
throws Exception
{
if (classNames == null)
return;
- parse(Arrays.asList(classNames), resolver);
+ parse(handlers, Arrays.asList(classNames), resolver);
}
@@ -725,22 +631,32 @@
* @param resolver
* @throws Exception
*/
- public void parse (List<String> classNames, ClassNameResolver resolver)
+ public void parse (Set<? extends Handler> handlers, List<String> classNames, ClassNameResolver resolver)
throws Exception
{
+ MultiException me = new MultiException();
+
for (String s:classNames)
{
- if ((resolver == null) || (!resolver.isExcluded(s) && (!isParsed(s) || resolver.shouldOverride(s))))
+ try
{
- s = s.replace('.', '/')+".class";
- URL resource = Loader.getResource(this.getClass(), s, false);
- if (resource!= null)
+ if ((resolver == null) || (!resolver.isExcluded(s) && (!isParsed(s) || resolver.shouldOverride(s))))
{
- Resource r = Resource.newResource(resource);
- scanClass(r.getInputStream());
+ s = s.replace('.', '/')+".class";
+ URL resource = Loader.getResource(this.getClass(), s);
+ if (resource!= null)
+ {
+ Resource r = Resource.newResource(resource);
+ scanClass(handlers, null, r.getInputStream());
+ }
}
}
+ catch (Exception e)
+ {
+ me.add(new RuntimeException("Error scanning class "+s, e));
+ }
}
+ me.ifExceptionThrow();
}
@@ -751,7 +667,7 @@
* @param resolver
* @throws Exception
*/
- public void parseDir (Resource dir, ClassNameResolver resolver)
+ protected void parseDir (Set<? extends Handler> handlers, Resource dir, ClassNameResolver resolver)
throws Exception
{
//skip dirs whose name start with . (ie hidden)
@@ -760,40 +676,43 @@
if (LOG.isDebugEnabled()) {LOG.debug("Scanning dir {}", dir);};
+ MultiException me = new MultiException();
+
String[] files=dir.list();
for (int f=0;files!=null && f<files.length;f++)
{
- try
+ Resource res = dir.addPath(files[f]);
+ if (res.isDirectory())
+ parseDir(handlers, res, resolver);
+ else
{
- Resource res = dir.addPath(files[f]);
- if (res.isDirectory())
- parseDir(res, resolver);
- else
+ //we've already verified the directories, so just verify the class file name
+ File file = res.getFile();
+ if (isValidClassFileName((file==null?null:file.getName())))
{
- //we've already verified the directories, so just verify the class file name
- File file = res.getFile();
- if (isValidClassFileName((file==null?null:file.getName())))
+ try
{
String name = res.getName();
if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name))))
{
Resource r = Resource.newResource(res.getURL());
if (LOG.isDebugEnabled()) {LOG.debug("Scanning class {}", r);};
- scanClass(r.getInputStream());
+ scanClass(handlers, dir, r.getInputStream());
}
-
- }
- else
+ }
+ catch (Exception ex)
{
- if (LOG.isDebugEnabled()) LOG.debug("Skipping scan on invalid file {}", res);
+ me.add(new RuntimeException("Error scanning file "+files[f],ex));
}
}
- }
- catch (Exception ex)
- {
- LOG.warn(Log.EXCEPTION,ex);
+ else
+ {
+ if (LOG.isDebugEnabled()) LOG.debug("Skipping scan on invalid file {}", res);
+ }
}
}
+
+ me.ifExceptionThrow();
}
@@ -807,7 +726,7 @@
* @param resolver
* @throws Exception
*/
- public void parse (ClassLoader loader, boolean visitParents, boolean nullInclusive, final ClassNameResolver resolver)
+ public void parse (final Set<? extends Handler> handlers, ClassLoader loader, boolean visitParents, boolean nullInclusive, final ClassNameResolver resolver)
throws Exception
{
if (loader==null)
@@ -816,6 +735,8 @@
if (!(loader instanceof URLClassLoader))
return; //can't extract classes?
+ final MultiException me = new MultiException();
+
JarScanner scanner = new JarScanner()
{
@Override
@@ -823,17 +744,18 @@
{
try
{
- parseJarEntry(jarUri, entry, resolver);
+ parseJarEntry(handlers, Resource.newResource(jarUri), entry, resolver);
}
catch (Exception e)
{
- LOG.warn("Problem parsing jar entry: {}", entry.getName());
+ me.add(new RuntimeException("Error parsing entry "+entry.getName()+" from jar "+ jarUri, e));
}
}
};
scanner.scan(null, loader, nullInclusive, visitParents);
+ me.ifExceptionThrow();
}
@@ -844,24 +766,26 @@
* @param resolver
* @throws Exception
*/
- public void parse (URI[] uris, final ClassNameResolver resolver)
+ public void parse (final Set<? extends Handler> handlers, final URI[] uris, final ClassNameResolver resolver)
throws Exception
{
if (uris==null)
return;
+ MultiException me = new MultiException();
+
for (URI uri:uris)
{
try
{
- parse(uri, resolver);
+ parse(handlers, uri, resolver);
}
catch (Exception e)
{
- LOG.warn("Problem parsing classes from {}", uri);
+ me.add(new RuntimeException("Problem parsing classes from "+ uri, e));
}
}
-
+ me.ifExceptionThrow();
}
/**
@@ -870,15 +794,13 @@
* @param resolver
* @throws Exception
*/
- public void parse (URI uri, final ClassNameResolver resolver)
+ public void parse (final Set<? extends Handler> handlers, URI uri, final ClassNameResolver resolver)
throws Exception
{
if (uri == null)
return;
- parse (Resource.newResource(uri), resolver);
-
-
+ parse (handlers, Resource.newResource(uri), resolver);
}
@@ -888,7 +810,7 @@
* @param resolver
* @throws Exception
*/
- public void parse (Resource r, final ClassNameResolver resolver)
+ public void parse (final Set<? extends Handler> handlers, Resource r, final ClassNameResolver resolver)
throws Exception
{
if (r == null)
@@ -896,26 +818,28 @@
if (r.exists() && r.isDirectory())
{
- parseDir(r, resolver);
+ parseDir(handlers, r, resolver);
return;
}
String fullname = r.toString();
if (fullname.endsWith(".jar"))
{
- parseJar(r, resolver);
+ parseJar(handlers, r, resolver);
return;
}
if (fullname.endsWith(".class"))
{
- scanClass(r.getInputStream());
+ scanClass(handlers, null, r.getInputStream());
return;
}
if (LOG.isDebugEnabled()) LOG.warn("Resource not scannable for classes: {}", r);
}
+
+
/**
* Parse a resource that is a jar file.
@@ -924,37 +848,76 @@
* @param resolver
* @throws Exception
*/
- public void parseJar (Resource jarResource, final ClassNameResolver resolver)
+ protected void parseJar (Set<? extends Handler> handlers, Resource jarResource, final ClassNameResolver resolver)
throws Exception
{
if (jarResource == null)
return;
-
- URI uri = jarResource.getURI();
+
if (jarResource.toString().endsWith(".jar"))
{
if (LOG.isDebugEnabled()) {LOG.debug("Scanning jar {}", jarResource);};
-
- //treat it as a jar that we need to open and scan all entries from
- InputStream in = jarResource.getInputStream();
+
+ //treat it as a jar that we need to open and scan all entries from
+ //TODO alternative impl
+ /*
+ long start = System.nanoTime();
+ Collection<Resource> resources = Resource.newResource("jar:"+jarResource+"!/").getAllResources();
+ System.err.println(jarResource+String.valueOf(resources.size())+" resources listed in "+ ((TimeUnit.MILLISECONDS.convert(System.nanoTime()-start, TimeUnit.NANOSECONDS))));
+ for (Resource r:resources)
+ {
+ //skip directories
+ if (r.isDirectory())
+ continue;
+
+ String name = r.getName();
+ name = name.substring(name.indexOf("!/")+2);
+
+ //check file is a valid class file name
+ if (isValidClassFileName(name) && isValidClassFilePath(name))
+ {
+ String shortName = name.replace('/', '.').substring(0,name.length()-6);
+
+ if ((resolver == null)
+ ||
+ (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
+ {
+ if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", r);};
+ scanClass(handlers, jarResource, r.getInputStream());
+ }
+ }
+ }
+ */
+
+ InputStream in = jarResource.getInputStream();
if (in==null)
return;
+ MultiException me = new MultiException();
+
JarInputStream jar_in = new JarInputStream(in);
try
{
JarEntry entry = jar_in.getNextJarEntry();
while (entry!=null)
{
- parseJarEntry(uri, entry, resolver);
+ try
+ {
+ parseJarEntry(handlers, jarResource, entry, resolver);
+ }
+ catch (Exception e)
+ {
+ me.add(new RuntimeException("Error scanning entry "+entry.getName()+" from jar "+jarResource, e));
+ }
entry = jar_in.getNextJarEntry();
}
}
finally
{
jar_in.close();
- }
- }
+ }
+ me.ifExceptionThrow();
+ }
}
/**
@@ -964,7 +927,7 @@
* @param resolver
* @throws Exception
*/
- protected void parseJarEntry (URI jar, JarEntry entry, final ClassNameResolver resolver)
+ protected void parseJarEntry (Set<? extends Handler> handlers, Resource jar, JarEntry entry, final ClassNameResolver resolver)
throws Exception
{
if (jar == null || entry == null)
@@ -985,9 +948,9 @@
||
(!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
{
- Resource clazz = Resource.newResource("jar:"+jar+"!/"+name);
+ Resource clazz = Resource.newResource("jar:"+jar.getURI()+"!/"+name);
if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", clazz);};
- scanClass(clazz.getInputStream());
+ scanClass(handlers, jar, clazz.getInputStream());
}
}
}
@@ -997,14 +960,15 @@
/**
* Use ASM on a class
*
+ * @param containingResource the dir or jar that the class is contained within, can be null if not known
* @param is
* @throws IOException
*/
- protected void scanClass (InputStream is)
+ protected void scanClass (Set<? extends Handler> handlers, Resource containingResource, InputStream is)
throws IOException
{
ClassReader reader = new ClassReader(is);
- reader.accept(new MyClassVisitor(), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
+ reader.accept(new MyClassVisitor(handlers, containingResource), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
}
/**
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ClassInheritanceHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ClassInheritanceHandler.java
index ca6c61b..8c953ba 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ClassInheritanceHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ClassInheritanceHandler.java
@@ -18,10 +18,11 @@
package org.eclipse.jetty.annotations;
-import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
-import org.eclipse.jetty.annotations.AnnotationParser.ClassHandler;
-import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.annotations.AnnotationParser.AbstractHandler;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.util.ConcurrentHashSet;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -30,34 +31,33 @@
*
* As asm scans for classes, remember the type hierarchy.
*/
-public class ClassInheritanceHandler implements ClassHandler
+public class ClassInheritanceHandler extends AbstractHandler
{
private static final Logger LOG = Log.getLogger(ClassInheritanceHandler.class);
-
- MultiMap _inheritanceMap;
+ ConcurrentHashMap<String, ConcurrentHashSet<String>> _inheritanceMap;
+
- public ClassInheritanceHandler()
- {
- _inheritanceMap = new MultiMap();
- }
-
- public ClassInheritanceHandler(MultiMap map)
+ public ClassInheritanceHandler(ConcurrentHashMap<String, ConcurrentHashSet<String>> map)
{
_inheritanceMap = map;
}
- public void handle(String className, int version, int access, String signature, String superName, String[] interfaces)
+ public void handle(ClassInfo classInfo)
{
try
{
- for (int i=0; interfaces != null && i<interfaces.length;i++)
+ for (int i=0; classInfo.getInterfaces() != null && i < classInfo.getInterfaces().length;i++)
{
- _inheritanceMap.add (interfaces[i], className);
+ addToInheritanceMap(classInfo.getInterfaces()[i], classInfo.getClassName());
+ //_inheritanceMap.add (classInfo.getInterfaces()[i], classInfo.getClassName());
}
//To save memory, we don't record classes that only extend Object, as that can be assumed
- if (!"java.lang.Object".equals(superName))
- _inheritanceMap.add(superName, className);
+ if (!"java.lang.Object".equals(classInfo.getSuperName()))
+ {
+ addToInheritanceMap(classInfo.getSuperName(), classInfo.getClassName());
+ //_inheritanceMap.add(classInfo.getSuperName(), classInfo.getClassName());
+ }
}
catch (Exception e)
{
@@ -65,13 +65,21 @@
}
}
- public List getClassNamesExtendingOrImplementing (String className)
+ private void addToInheritanceMap (String interfaceOrSuperClassName, String implementingOrExtendingClassName)
{
- return _inheritanceMap.getValues(className);
- }
-
- public MultiMap getMap ()
- {
- return _inheritanceMap;
+
+ //As it is likely that the interfaceOrSuperClassName is already in the map, try getting it first
+ ConcurrentHashSet<String> implementingClasses = _inheritanceMap.get(interfaceOrSuperClassName);
+ //If it isn't in the map, then add it in, but test to make sure that someone else didn't get in
+ //first and add it
+ if (implementingClasses == null)
+ {
+ implementingClasses = new ConcurrentHashSet<String>();
+ ConcurrentHashSet<String> tmp = _inheritanceMap.putIfAbsent(interfaceOrSuperClassName, implementingClasses);
+ if (tmp != null)
+ implementingClasses = tmp;
+ }
+
+ implementingClasses.add(implementingOrExtendingClassName);
}
}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerAnnotationHandler.java
index 4ce68e2..a19e026 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerAnnotationHandler.java
@@ -19,10 +19,11 @@
package org.eclipse.jetty.annotations;
-import java.util.List;
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
+import org.eclipse.jetty.annotations.AnnotationParser.AbstractHandler;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo;
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
/**
@@ -32,10 +33,14 @@
* method level. The specified annotation is derived from an @HandlesTypes on
* a ServletContainerInitializer class.
*/
-public class ContainerInitializerAnnotationHandler implements DiscoverableAnnotationHandler
+/**
+ * @author janb
+ *
+ */
+public class ContainerInitializerAnnotationHandler extends AbstractHandler
{
- ContainerInitializer _initializer;
- Class _annotation;
+ final ContainerInitializer _initializer;
+ final Class _annotation;
public ContainerInitializerAnnotationHandler (ContainerInitializer initializer, Class annotation)
{
@@ -45,35 +50,44 @@
/**
* Handle finding a class that is annotated with the annotation we were constructed with.
- * @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handleClass(java.lang.String, int, int, java.lang.String, java.lang.String, java.lang.String[], java.lang.String, java.util.List)
+ *
+ * @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(ClassInfo, String)
*/
- public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotationName,
- List<Value> values)
+ public void handle(ClassInfo info, String annotationName)
{
- _initializer.addAnnotatedTypeName(className);
+ if (annotationName == null || !_annotation.getName().equals(annotationName))
+ return;
+
+ _initializer.addAnnotatedTypeName(info.getClassName());
}
- public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
- List<Value> values)
- {
- _initializer.addAnnotatedTypeName(className);
+ /**
+ * Handle finding a field that is annotated with the annotation we were constructed with.
+ *
+ * @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(FieldInfo, String)
+ */
+ public void handle(FieldInfo info, String annotationName)
+ {
+ if (annotationName == null || !_annotation.getName().equals(annotationName))
+ return;
+ _initializer.addAnnotatedTypeName(info.getClassInfo().getClassName());
}
- public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
- List<Value> values)
+ /**
+ * Handle finding a method that is annotated with the annotation we were constructed with.
+ *
+ * @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(MethodInfo, String)
+ */
+ public void handle(MethodInfo info, String annotationName)
{
- _initializer.addAnnotatedTypeName(className);
+ if (annotationName == null || !_annotation.getName().equals(annotationName))
+ return;
+ _initializer.addAnnotatedTypeName(info.getClassInfo().getClassName());
}
- @Override
- public String getAnnotationName()
- {
- return _annotation.getName();
- }
public ContainerInitializer getContainerInitializer()
{
return _initializer;
}
-
}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PostConstructAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PostConstructAnnotationHandler.java
index 2b962a5..5d5daea 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PostConstructAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PostConstructAnnotationHandler.java
@@ -44,7 +44,7 @@
public void doHandle(Class clazz)
{
//Check that the PostConstruct is on a class that we're interested in
- if (Util.isServletType(clazz))
+ if (Util.supportsPostConstructPreDestroy(clazz))
{
Method[] methods = clazz.getDeclaredMethods();
for (int i=0; i<methods.length; i++)
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PreDestroyAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PreDestroyAnnotationHandler.java
index 5450542..a536885 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PreDestroyAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PreDestroyAnnotationHandler.java
@@ -43,7 +43,7 @@
public void doHandle(Class clazz)
{
//Check that the PreDestroy is on a class that we're interested in
- if (Util.isServletType(clazz))
+ if (Util.supportsPostConstructPreDestroy(clazz))
{
Method[] methods = clazz.getDeclaredMethods();
for (int i=0; i<methods.length; i++)
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ResourceAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ResourceAnnotationHandler.java
index 875de2d..ce8d9a7 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ResourceAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ResourceAnnotationHandler.java
@@ -57,7 +57,7 @@
*/
public void doHandle(Class<?> clazz)
{
- if (Util.isServletType(clazz))
+ if (Util.supportsResourceInjection(clazz))
{
handleClass(clazz);
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java
index 6b4a638..3fb257b 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java
@@ -18,12 +18,9 @@
package org.eclipse.jetty.annotations;
-import java.util.List;
-
import javax.servlet.Servlet;
import org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler;
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
import org.eclipse.jetty.plus.annotation.RunAsCollection;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.log.Log;
@@ -87,14 +84,12 @@
}
- public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
- List<Value> values)
+ public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation)
{
LOG.warn ("@RunAs annotation not applicable for fields: "+className+"."+fieldName);
}
- public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
- List<Value> values)
+ public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation)
{
LOG.warn("@RunAs annotation ignored on method: "+className+"."+methodName+" "+signature);
}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializerListener.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializerListener.java
deleted file mode 100644
index b5ced5b..0000000
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializerListener.java
+++ /dev/null
@@ -1,145 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.annotations;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.eclipse.jetty.annotations.AnnotationConfiguration;
-import org.eclipse.jetty.plus.annotation.ContainerInitializer;
-import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.webapp.WebAppContext;
-
-/**
- * ServletContainerInitializerListener
- *
- *
- */
-public class ServletContainerInitializerListener extends AbstractLifeCycle
-{
- private static final Logger LOG = Log.getLogger(ServletContainerInitializerListener.class);
- protected WebAppContext _context = null;
-
-
- public void setWebAppContext (WebAppContext context)
- {
- _context = context;
- }
-
-
- /**
- * Call the doStart method of the ServletContainerInitializers
- * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
- */
- public void doStart()
- {
- List<ContainerInitializer> initializers = (List<ContainerInitializer>)_context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS);
- MultiMap classMap = (MultiMap)_context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP);
-
- if (initializers != null)
- {
- for (ContainerInitializer i : initializers)
- {
- //We have already found the classes that directly have an annotation that was in the HandlesTypes
- //annotation of the ServletContainerInitializer. For each of those classes, walk the inheritance
- //hierarchy to find classes that extend or implement them.
- if (i.getAnnotatedTypeNames() != null)
- {
- Set<String> annotatedClassNames = new HashSet<String>(i.getAnnotatedTypeNames());
- for (String name : annotatedClassNames)
- {
- //add the class with the annotation
- i.addApplicableTypeName(name);
- //add the classes that inherit the annotation
- if (classMap != null)
- {
- List<String> implementsOrExtends = (List<String>)classMap.getValues(name);
- if (implementsOrExtends != null && !implementsOrExtends.isEmpty())
- addInheritedTypes(classMap, i, implementsOrExtends);
- }
- }
- }
-
-
- //Now we need to look at the HandlesTypes classes that were not annotations. We need to
- //find all classes that extend or implement them.
- if (i.getInterestedTypes() != null)
- {
- for (Class c : i.getInterestedTypes())
- {
- if (!c.isAnnotation())
- {
- //add the classes that implement or extend the class.
- //TODO but not including the class itself?
- if (classMap != null)
- {
- List<String> implementsOrExtends = (List<String>)classMap.getValues(c.getName());
- if (implementsOrExtends != null && !implementsOrExtends.isEmpty())
- addInheritedTypes(classMap, i, implementsOrExtends);
- }
- }
- }
- }
-
- //instantiate ServletContainerInitializers, call doStart
- try
- {
- i.callStartup(_context);
- }
- catch (Exception e)
- {
- LOG.warn(e);
- throw new RuntimeException(e);
- }
- }
- }
-
- }
-
-
- void addInheritedTypes (MultiMap classMap, ContainerInitializer initializer, List<String> applicableTypes)
- {
- for (String s : applicableTypes)
- {
- //add the name of the class that extends or implements
- initializer.addApplicableTypeName(s);
-
- //walk the hierarchy and find all types that extend or implement it
- List<String> implementsOrExtends = (List<String>)classMap.getValues(s);
- if (implementsOrExtends != null && !implementsOrExtends.isEmpty())
- addInheritedTypes (classMap, initializer, implementsOrExtends);
- }
- }
-
-
-
- /**
- * Nothing to do for ServletContainerInitializers on stop
- * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
- */
- public void doStop()
- {
-
- }
-
-}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializersStarter.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializersStarter.java
new file mode 100644
index 0000000..75a22c0
--- /dev/null
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializersStarter.java
@@ -0,0 +1,161 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.annotations;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jetty.plus.annotation.ContainerInitializer;
+import org.eclipse.jetty.util.ConcurrentHashSet;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+
+/**
+ * ServletContainerInitializersStarter
+ *
+ * Call the onStartup() method on all ServletContainerInitializers, after having
+ * found all applicable classes (if any) to pass in as args.
+ */
+public class ServletContainerInitializersStarter extends AbstractLifeCycle
+{
+ private static final Logger LOG = Log.getLogger(ServletContainerInitializersStarter.class);
+ WebAppContext _context;
+
+
+ /**
+ * @param context
+ */
+ public ServletContainerInitializersStarter(WebAppContext context)
+ {
+ _context = context;
+ }
+
+ /**
+ * Call the doStart method of the ServletContainerInitializers
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ public void doStart()
+ {
+ List<ContainerInitializer> initializers = (List<ContainerInitializer>)_context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS);
+ if (initializers == null)
+ return;
+
+ ConcurrentHashMap<String, ConcurrentHashSet<String>> map = ( ConcurrentHashMap<String, ConcurrentHashSet<String>>)_context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP);
+
+ for (ContainerInitializer i : initializers)
+ {
+ configureHandlesTypes(_context, i, map);
+
+ //instantiate ServletContainerInitializers, call doStart
+ try
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Calling ServletContainerInitializer "+i.getTarget().getClass().getName());
+ i.callStartup(_context);
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+
+ private void configureHandlesTypes (WebAppContext context, ContainerInitializer initializer, ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap)
+ {
+ doHandlesTypesAnnotations(context, initializer, classMap);
+ doHandlesTypesClasses(context, initializer, classMap);
+ }
+
+ private void doHandlesTypesAnnotations(WebAppContext context, ContainerInitializer initializer, ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap)
+ {
+ if (initializer == null)
+ return;
+ if (context == null)
+ throw new IllegalArgumentException("WebAppContext null");
+
+ //We have already found the classes that directly have an annotation that was in the HandlesTypes
+ //annotation of the ServletContainerInitializer. For each of those classes, walk the inheritance
+ //hierarchy to find classes that extend or implement them.
+ Set<String> annotatedClassNames = initializer.getAnnotatedTypeNames();
+ if (annotatedClassNames != null && !annotatedClassNames.isEmpty())
+ {
+ if (classMap == null)
+ throw new IllegalStateException ("No class hierarchy");
+
+ for (String name : annotatedClassNames)
+ {
+ //add the class that has the annotation
+ initializer.addApplicableTypeName(name);
+
+ //find and add the classes that inherit the annotation
+ addInheritedTypes(classMap, initializer, (ConcurrentHashSet<String>)classMap.get(name));
+ }
+ }
+ }
+
+
+
+ private void doHandlesTypesClasses (WebAppContext context, ContainerInitializer initializer, ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap)
+ {
+ if (initializer == null)
+ return;
+ if (context == null)
+ throw new IllegalArgumentException("WebAppContext null");
+
+ //Now we need to look at the HandlesTypes classes that were not annotations. We need to
+ //find all classes that extend or implement them.
+ if (initializer.getInterestedTypes() != null)
+ {
+ if (classMap == null)
+ throw new IllegalStateException ("No class hierarchy");
+
+ for (Class c : initializer.getInterestedTypes())
+ {
+ if (!c.isAnnotation())
+ {
+ //find and add the classes that implement or extend the class.
+ //but not including the class itself
+ addInheritedTypes(classMap, initializer, (ConcurrentHashSet<String>)classMap.get(c.getName()));
+ }
+ }
+ }
+ }
+
+
+ private void addInheritedTypes (ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap, ContainerInitializer initializer, ConcurrentHashSet<String> names)
+ {
+ if (names == null || names.isEmpty())
+ return;
+
+ for (String s : names)
+ {
+ //add the name of the class
+ initializer.addApplicableTypeName(s);
+
+ //walk the hierarchy and find all types that extend or implement the class
+ addInheritedTypes(classMap, initializer, (ConcurrentHashSet<String>)classMap.get(s));
+ }
+ }
+}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletSecurityAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletSecurityAnnotationHandler.java
index 41c654c..00ac4b0 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletSecurityAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletSecurityAnnotationHandler.java
@@ -112,6 +112,9 @@
for (ConstraintMapping m:constraintMappings)
securityHandler.addConstraintMapping(m);
+
+ //Servlet Spec 3.1 requires paths with uncovered http methods to be reported
+ securityHandler.checkPathsWithUncoveredHttpMethods();
}
@@ -123,17 +126,10 @@
* @param rolesAllowed
* @param permitOrDeny
* @param transport
- * @return
*/
protected Constraint makeConstraint (Class servlet, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
{
return ConstraintSecurityHandler.createConstraint(servlet.getName(), rolesAllowed, permitOrDeny, transport);
-
-
-
-
-
-
}
@@ -141,7 +137,6 @@
/**
* Get the ServletMappings for the servlet's class.
* @param className
- * @return
*/
protected List<ServletMapping> getServletMappings(String className)
{
@@ -163,7 +158,6 @@
* Check if there are already <security-constraint> elements defined that match the url-patterns for
* the servlet.
* @param servletMappings
- * @return
*/
protected boolean constraintsExist (List<ServletMapping> servletMappings, List<ConstraintMapping> constraintMappings)
{
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java
index 37a22a3..08527c3 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java
@@ -68,6 +68,42 @@
return isServlet;
}
+
+
+ public static boolean supportsResourceInjection (Class c)
+ {
+ if (javax.servlet.Servlet.class.isAssignableFrom(c) ||
+ javax.servlet.Filter.class.isAssignableFrom(c) ||
+ javax.servlet.ServletContextListener.class.isAssignableFrom(c) ||
+ javax.servlet.ServletContextAttributeListener.class.isAssignableFrom(c) ||
+ javax.servlet.ServletRequestListener.class.isAssignableFrom(c) ||
+ javax.servlet.ServletRequestAttributeListener.class.isAssignableFrom(c) ||
+ javax.servlet.http.HttpSessionListener.class.isAssignableFrom(c) ||
+ javax.servlet.http.HttpSessionAttributeListener.class.isAssignableFrom(c) ||
+ javax.servlet.AsyncListener.class.isAssignableFrom(c) ||
+ javax.servlet.http.HttpUpgradeHandler.class.isAssignableFrom(c))
+ return true;
+
+ return false;
+ }
+
+
+ public static boolean supportsPostConstructPreDestroy (Class c)
+ {
+ if (javax.servlet.Servlet.class.isAssignableFrom(c) ||
+ javax.servlet.Filter.class.isAssignableFrom(c) ||
+ javax.servlet.ServletContextListener.class.isAssignableFrom(c) ||
+ javax.servlet.ServletContextAttributeListener.class.isAssignableFrom(c) ||
+ javax.servlet.ServletRequestListener.class.isAssignableFrom(c) ||
+ javax.servlet.ServletRequestAttributeListener.class.isAssignableFrom(c) ||
+ javax.servlet.http.HttpSessionListener.class.isAssignableFrom(c) ||
+ javax.servlet.http.HttpSessionAttributeListener.class.isAssignableFrom(c) ||
+ javax.servlet.AsyncListener.class.isAssignableFrom(c) ||
+ javax.servlet.http.HttpUpgradeHandler.class.isAssignableFrom(c))
+ return true;
+
+ return false;
+ }
public static boolean isEnvEntryType (Class type)
{
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotationHandler.java
index 1545977..fe5cc27 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotationHandler.java
@@ -18,12 +18,11 @@
package org.eclipse.jetty.annotations;
-import java.util.List;
-
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext;
/**
@@ -39,38 +38,31 @@
{
super(context);
}
-
- public WebFilterAnnotationHandler (WebAppContext context, List<DiscoveredAnnotation> list)
- {
- super(context, list);
- }
+
@Override
- public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
- List<Value> values)
+ public void handle(ClassInfo info, String annotationName)
{
- WebFilterAnnotation wfAnnotation = new WebFilterAnnotation(_context, className, _resource);
+ if (annotationName == null || !"javax.servlet.annotation.WebFilter".equals(annotationName))
+ return;
+
+ WebFilterAnnotation wfAnnotation = new WebFilterAnnotation(_context, info.getClassName(), info.getContainingResource());
addAnnotation(wfAnnotation);
}
@Override
- public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
- List<Value> values)
- {
- LOG.warn ("@WebFilter not applicable for fields: "+className+"."+fieldName);
+ public void handle(FieldInfo info, String annotationName)
+ {
+ if (annotationName == null || !"javax.servlet.annotation.WebFilter".equals(annotationName))
+ return;
+ LOG.warn ("@WebFilter not applicable for fields: "+info.getClassInfo().getClassName()+"."+info.getFieldName());
}
@Override
- public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
- List<Value> values)
- {
- LOG.warn ("@WebFilter not applicable for methods: "+className+"."+methodName+" "+signature);
+ public void handle(MethodInfo info, String annotationName)
+ {
+ if (annotationName == null || !"javax.servlet.annotation.WebFilter".equals(annotationName))
+ return;
+ LOG.warn ("@WebFilter not applicable for methods: "+info.getClassInfo().getClassName()+"."+info.getMethodName()+" "+info.getSignature());
}
-
- @Override
- public String getAnnotationName()
- {
- return "javax.servlet.annotation.WebFilter";
- }
-
}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotation.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotation.java
index 0f5c8c0..ad3e02c 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotation.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotation.java
@@ -23,6 +23,7 @@
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionIdListener;
import javax.servlet.http.HttpSessionListener;
import org.eclipse.jetty.util.log.Log;
@@ -78,7 +79,8 @@
ServletRequestListener.class.isAssignableFrom(clazz) ||
ServletRequestAttributeListener.class.isAssignableFrom(clazz) ||
HttpSessionListener.class.isAssignableFrom(clazz) ||
- HttpSessionAttributeListener.class.isAssignableFrom(clazz))
+ HttpSessionAttributeListener.class.isAssignableFrom(clazz) ||
+ HttpSessionIdListener.class.isAssignableFrom(clazz))
{
java.util.EventListener listener = (java.util.EventListener)clazz.newInstance();
MetaData metaData = _context.getMetaData();
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotationHandler.java
index ab16702..eacab7f 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotationHandler.java
@@ -18,12 +18,11 @@
package org.eclipse.jetty.annotations;
-import java.util.List;
-
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext;
public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotationHandler
@@ -35,37 +34,30 @@
super(context);
}
- public WebListenerAnnotationHandler (WebAppContext context, List<DiscoveredAnnotation> list)
- {
- super(context, list);
- }
-
+
/**
- * @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handleClass(java.lang.String, int, int, java.lang.String, java.lang.String, java.lang.String[], java.lang.String, java.util.List)
+ * @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(ClassInfo, String)
*/
- public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
- List<Value> values)
+ public void handle(ClassInfo info, String annotationName)
{
- WebListenerAnnotation wlAnnotation = new WebListenerAnnotation(_context, className, _resource);
+ if (annotationName == null || !"javax.servlet.annotation.WebListener".equals(annotationName))
+ return;
+
+ WebListenerAnnotation wlAnnotation = new WebListenerAnnotation(_context, info.getClassName(), info.getContainingResource());
addAnnotation(wlAnnotation);
}
- public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
- List<Value> values)
+ public void handle(FieldInfo info, String annotationName)
{
- LOG.warn ("@WebListener is not applicable to fields: "+className+"."+fieldName);
+ if (annotationName == null || !"javax.servlet.annotation.WebListener".equals(annotationName))
+ return;
+ LOG.warn ("@WebListener is not applicable to fields: "+info.getClassInfo().getClassName()+"."+info.getFieldName());
}
- public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
- List<Value> values)
+ public void handle(MethodInfo info, String annotationName)
{
- LOG.warn ("@WebListener is not applicable to methods: "+className+"."+methodName+" "+signature);
+ if (annotationName == null || !"javax.servlet.annotation.WebListener".equals(annotationName))
+ return;
+ LOG.warn ("@WebListener is not applicable to methods: "+info.getClassInfo().getClassName()+"."+info.getMethodName()+" "+info.getSignature());
}
-
- @Override
- public String getAnnotationName()
- {
- return "javax.servlet.annotation.WebListener";
- }
-
}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotationHandler.java
index 6522773..b7957a1 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotationHandler.java
@@ -18,12 +18,11 @@
package org.eclipse.jetty.annotations;
-import java.util.List;
-
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext;
/**
@@ -41,47 +40,37 @@
super(context);
}
- public WebServletAnnotationHandler (WebAppContext context, List<DiscoveredAnnotation> list)
- {
- super(context, list);
- }
-
/**
* Handle discovering a WebServlet annotation.
*
- *
- * @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handleClass(java.lang.String, int, int, java.lang.String, java.lang.String, java.lang.String[], java.lang.String, java.util.List)
+ * @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(ClassInfo, String)
*/
@Override
- public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotationName,
- List<Value> values)
+ public void handle(ClassInfo info, String annotationName)
{
- if (!"javax.servlet.annotation.WebServlet".equals(annotationName))
+ if (annotationName == null || !"javax.servlet.annotation.WebServlet".equals(annotationName))
return;
-
- WebServletAnnotation annotation = new WebServletAnnotation (_context, className, _resource);
+
+ WebServletAnnotation annotation = new WebServletAnnotation (_context, info.getClassName(), info.getContainingResource());
addAnnotation(annotation);
}
@Override
- public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
- List<Value> values)
+ public void handle(FieldInfo info, String annotationName)
{
+ if (annotationName == null || !"javax.servlet.annotation.WebServlet".equals(annotationName))
+ return;
+
LOG.warn ("@WebServlet annotation not supported for fields");
}
@Override
- public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
- List<Value> values)
+ public void handle(MethodInfo info, String annotationName)
{
+ if (annotationName == null || !"javax.servlet.annotation.WebServlet".equals(annotationName))
+ return;
+
LOG.warn ("@WebServlet annotation not supported for methods");
}
-
-
- @Override
- public String getAnnotationName()
- {
- return "javax.servlet.annotation.WebServlet";
- }
}
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationInheritance.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationInheritance.java
index b589f3d..402a317 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationInheritance.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationInheritance.java
@@ -24,15 +24,18 @@
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
-import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import javax.naming.Context;
import javax.naming.InitialContext;
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
-import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.annotations.AnnotationParser.AbstractHandler;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo;
+import org.eclipse.jetty.util.ConcurrentHashSet;
import org.junit.After;
import org.junit.Test;
@@ -44,34 +47,32 @@
List<String> classNames = new ArrayList<String>();
- class SampleHandler implements DiscoverableAnnotationHandler
+ class SampleHandler extends AbstractHandler
{
public final List<String> annotatedClassNames = new ArrayList<String>();
public final List<String> annotatedMethods = new ArrayList<String>();
public final List<String> annotatedFields = new ArrayList<String>();
- public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
- List<Value> values)
+ public void handle(ClassInfo info, String annotation)
{
- annotatedClassNames.add(className);
+ if (annotation == null || !"org.eclipse.jetty.annotations.Sample".equals(annotation))
+ return;
+
+ annotatedClassNames.add(info.getClassName());
}
- public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
- List<Value> values)
- {
- annotatedFields.add(className+"."+fieldName);
+ public void handle(FieldInfo info, String annotation)
+ {
+ if (annotation == null || !"org.eclipse.jetty.annotations.Sample".equals(annotation))
+ return;
+ annotatedFields.add(info.getClassInfo().getClassName()+"."+info.getFieldName());
}
- public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
- List<Value> values)
+ public void handle(MethodInfo info, String annotation)
{
- annotatedMethods.add(className+"."+methodName);
- }
-
- @Override
- public String getAnnotationName()
- {
- return "org.eclipse.jetty.annotations.Sample";
+ if (annotation == null || !"org.eclipse.jetty.annotations.Sample".equals(annotation))
+ return;
+ annotatedMethods.add(info.getClassInfo().getClassName()+"."+info.getMethodName());
}
}
@@ -92,8 +93,7 @@
SampleHandler handler = new SampleHandler();
AnnotationParser parser = new AnnotationParser();
- parser.registerHandler(handler);
- parser.parse(classNames, new ClassNameResolver ()
+ parser.parse(Collections.singleton(handler), classNames, new ClassNameResolver ()
{
public boolean isExcluded(String name)
{
@@ -129,8 +129,7 @@
{
SampleHandler handler = new SampleHandler();
AnnotationParser parser = new AnnotationParser();
- parser.registerAnnotationHandler("org.eclipse.jetty.annotations.Sample", handler);
- parser.parse(ClassB.class, new ClassNameResolver ()
+ parser.parse(Collections.singleton(handler), ClassB.class, new ClassNameResolver ()
{
public boolean isExcluded(String name)
{
@@ -166,8 +165,7 @@
{
AnnotationParser parser = new AnnotationParser();
SampleHandler handler = new SampleHandler();
- parser.registerAnnotationHandler("org.eclipse.jetty.annotations.Sample", handler);
- parser.parse(ClassA.class.getName(), new ClassNameResolver()
+ parser.parse(Collections.singleton(handler), ClassA.class.getName(), new ClassNameResolver()
{
public boolean isExcluded(String name)
{
@@ -187,7 +185,7 @@
handler.annotatedFields.clear();
handler.annotatedMethods.clear();
- parser.parse (ClassA.class.getName(), new ClassNameResolver()
+ parser.parse (Collections.singleton(handler), ClassA.class.getName(), new ClassNameResolver()
{
public boolean isExcluded(String name)
{
@@ -205,9 +203,10 @@
@Test
public void testTypeInheritanceHandling() throws Exception
{
+ ConcurrentHashMap<String, ConcurrentHashSet<String>> map = new ConcurrentHashMap<String, ConcurrentHashSet<String>>();
+
AnnotationParser parser = new AnnotationParser();
- ClassInheritanceHandler handler = new ClassInheritanceHandler();
- parser.registerClassHandler(handler);
+ ClassInheritanceHandler handler = new ClassInheritanceHandler(map);
class Foo implements InterfaceD
{
@@ -219,22 +218,22 @@
classNames.add(InterfaceD.class.getName());
classNames.add(Foo.class.getName());
- parser.parse(classNames, null);
+ parser.parse(Collections.singleton(handler), classNames, null);
- MultiMap map = handler.getMap();
assertNotNull(map);
assertFalse(map.isEmpty());
assertEquals(2, map.size());
- Map stringArrayMap = map.toStringArrayMap();
- assertTrue (stringArrayMap.keySet().contains("org.eclipse.jetty.annotations.ClassA"));
- assertTrue (stringArrayMap.keySet().contains("org.eclipse.jetty.annotations.InterfaceD"));
- String[] classes = (String[])stringArrayMap.get("org.eclipse.jetty.annotations.ClassA");
- assertEquals(1, classes.length);
- assertEquals ("org.eclipse.jetty.annotations.ClassB", classes[0]);
+
+
+ assertTrue (map.keySet().contains("org.eclipse.jetty.annotations.ClassA"));
+ assertTrue (map.keySet().contains("org.eclipse.jetty.annotations.InterfaceD"));
+ ConcurrentHashSet<String> classes = map.get("org.eclipse.jetty.annotations.ClassA");
+ assertEquals(1, classes.size());
+ assertEquals ("org.eclipse.jetty.annotations.ClassB", classes.iterator().next());
- classes = (String[])stringArrayMap.get("org.eclipse.jetty.annotations.InterfaceD");
- assertEquals(2, classes.length);
- assertEquals ("org.eclipse.jetty.annotations.ClassB", classes[0]);
- assertEquals(Foo.class.getName(), classes[1]);
+ classes = map.get("org.eclipse.jetty.annotations.InterfaceD");
+ assertEquals(2, classes.size());
+ assertTrue(classes.contains("org.eclipse.jetty.annotations.ClassB"));
+ assertTrue(classes.contains(Foo.class.getName()));
}
}
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java
index 5fb97a3..1f0eafc 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java
@@ -18,8 +18,11 @@
package org.eclipse.jetty.annotations;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileOutputStream;
@@ -28,12 +31,15 @@
import java.io.OutputStream;
import java.net.URL;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.Handler;
+import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
@@ -44,7 +50,7 @@
public class TestAnnotationParser
{
- public static class TrackingAnnotationHandler implements DiscoverableAnnotationHandler
+ public static class TrackingAnnotationHandler extends AnnotationParser.AbstractHandler
{
private final String annotationName;
public final Set<String> foundClasses;
@@ -56,30 +62,11 @@
}
@Override
- public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
- List<Value> values)
+ public void handle(ClassInfo info, String annotation)
{
- foundClasses.add(className);
- }
-
- @Override
- public void handleMethod(String className, String methodName, int access, String desc, String signature, String[] exceptions, String annotation,
- List<Value> values)
- {
- /* ignore */
- }
-
- @Override
- public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
- List<Value> values)
- {
- /* ignore */
- }
-
- @Override
- public String getAnnotationName()
- {
- return this.annotationName;
+ if (annotation == null || !annotationName.equals(annotation))
+ return;
+ foundClasses.add(info.getClassName());
}
}
@@ -93,47 +80,38 @@
{ "org.eclipse.jetty.annotations.ClassA" };
AnnotationParser parser = new AnnotationParser();
- class SampleAnnotationHandler implements DiscoverableAnnotationHandler
+ class SampleAnnotationHandler extends AnnotationParser.AbstractHandler
{
private List<String> methods = Arrays.asList("a","b","c","d","l");
- public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
- List<Value> values)
+ public void handle(ClassInfo info, String annotation)
{
- assertEquals("org.eclipse.jetty.annotations.ClassA",className);
+ if (annotation == null || !"org.eclipse.jetty.annotations.Sample".equals(annotation))
+ return;
+
+ assertEquals("org.eclipse.jetty.annotations.ClassA",info.getClassName());
}
- public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
- List<Value> values)
- {
- assertEquals("m",fieldName);
- assertEquals(org.objectweb.asm.Type.OBJECT,org.objectweb.asm.Type.getType(fieldType).getSort());
- assertEquals(1,values.size());
- Value anv1 = values.get(0);
- assertEquals("value",anv1.getName());
- assertEquals(7,anv1.getValue());
-
+ public void handle(FieldInfo info, String annotation)
+ {
+ if (annotation == null || !"org.eclipse.jetty.annotations.Sample".equals(annotation))
+ return;
+ assertEquals("m",info.getFieldName());
+ assertEquals(org.objectweb.asm.Type.OBJECT,org.objectweb.asm.Type.getType(info.getFieldType()).getSort());
}
- public void handleMethod(String className, String methodName, int access, String desc, String signature, String[] exceptions, String annotation,
- List<Value> values)
- {
- assertEquals("org.eclipse.jetty.annotations.ClassA",className);
- assertTrue(methods.contains(methodName));
+ public void handle(MethodInfo info, String annotation)
+ {
+ if (annotation == null || !"org.eclipse.jetty.annotations.Sample".equals(annotation))
+ return;
+ assertEquals("org.eclipse.jetty.annotations.ClassA",info.getClassInfo().getClassName());
+ assertTrue(methods.contains(info.getMethodName()));
assertEquals("org.eclipse.jetty.annotations.Sample",annotation);
}
-
- @Override
- public String getAnnotationName()
- {
- return "org.eclipse.jetty.annotations.Sample";
- }
}
- parser.registerHandler(new SampleAnnotationHandler());
-
- long start = System.currentTimeMillis();
- parser.parse(classNames,new ClassNameResolver()
+ //long start = System.currentTimeMillis();
+ parser.parse(Collections.singleton(new SampleAnnotationHandler()), classNames,new ClassNameResolver()
{
public boolean isExcluded(String name)
{
@@ -146,7 +124,7 @@
}
});
- long end = System.currentTimeMillis();
+ //long end = System.currentTimeMillis();
//System.err.println("Time to parse class: " + ((end - start)));
}
@@ -158,38 +136,33 @@
{ "org.eclipse.jetty.annotations.ClassB" };
AnnotationParser parser = new AnnotationParser();
- class MultiAnnotationHandler implements DiscoverableAnnotationHandler
+ class MultiAnnotationHandler extends AnnotationParser.AbstractHandler
{
- public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
- List<Value> values)
+ public void handle(ClassInfo info, String annotation)
{
- assertTrue("org.eclipse.jetty.annotations.ClassB".equals(className));
+ if (annotation == null || ! "org.eclipse.jetty.annotations.Multi".equals(annotation))
+ return;
+ assertTrue("org.eclipse.jetty.annotations.ClassB".equals(info.getClassName()));
}
- public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
- List<Value> values)
- {
+ public void handle(FieldInfo info, String annotation)
+ {
+ if (annotation == null || ! "org.eclipse.jetty.annotations.Multi".equals(annotation))
+ return;
// there should not be any
fail();
}
- public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
- List<Value> values)
- {
- assertTrue("org.eclipse.jetty.annotations.ClassB".equals(className));
- assertTrue("a".equals(methodName));
+ public void handle(MethodInfo info, String annotation)
+ {
+ if (annotation == null || ! "org.eclipse.jetty.annotations.Multi".equals(annotation))
+ return;
+ assertTrue("org.eclipse.jetty.annotations.ClassB".equals(info.getClassInfo().getClassName()));
+ assertTrue("a".equals(info.getMethodName()));
}
-
- @Override
- public String getAnnotationName()
- {
- return "org.eclipse.jetty.annotations.Multi";
- }
-
}
- parser.registerHandler(new MultiAnnotationHandler());
- parser.parse(classNames,null);
+ parser.parse(Collections.singleton(new MultiAnnotationHandler()), classNames,null);
}
@Test
@@ -197,7 +170,8 @@
{
File badClassesJar = MavenTestingUtils.getTestResourceFile("bad-classes.jar");
AnnotationParser parser = new AnnotationParser();
- parser.parse(badClassesJar.toURI(),null);
+ Set<Handler> emptySet = Collections.emptySet();
+ parser.parse(emptySet, badClassesJar.toURI(),null);
// only the valid classes inside bad-classes.jar should be parsed. If any invalid classes are parsed and exception would be thrown here
}
@@ -220,10 +194,9 @@
// Setup annotation scanning
AnnotationParser parser = new AnnotationParser();
- parser.registerHandler(tracker);
// Parse
- parser.parse(basedir.toURI(),null);
+ parser.parse(Collections.singleton(tracker), basedir.toURI(),null);
// Validate
Assert.assertThat("Found Class", tracker.foundClasses, contains(ClassA.class.getName()));
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestSecurityAnnotationConversions.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestSecurityAnnotationConversions.java
index 4ef220f..d0c07aa 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestSecurityAnnotationConversions.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestSecurityAnnotationConversions.java
@@ -115,21 +115,12 @@
introspector.registerHandler(annotationHandler);
- //set up the expected outcomes:
+ //set up the expected outcomes - no constraints at all as per Servlet Spec 3.1 pg 129
//1 ConstraintMapping per ServletMapping pathSpec
- Constraint expectedConstraint = new Constraint();
- expectedConstraint.setAuthenticate(false);
- expectedConstraint.setDataConstraint(Constraint.DC_NONE);
+
- ConstraintMapping[] expectedMappings = new ConstraintMapping[2];
- expectedMappings[0] = new ConstraintMapping();
- expectedMappings[0].setConstraint(expectedConstraint);
- expectedMappings[0].setPathSpec("/foo/*");
-
- expectedMappings[1] = new ConstraintMapping();
- expectedMappings[1].setConstraint(expectedConstraint);
- expectedMappings[1].setPathSpec("*.foo");
-
+ ConstraintMapping[] expectedMappings = new ConstraintMapping[]{};
+
introspector.introspect(PermitServlet.class);
compareResults (expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java
index b407dc8..e1f5a98 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java
@@ -25,12 +25,14 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
+import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.Test;
@@ -41,6 +43,27 @@
*/
public class TestServletAnnotations
{
+
+ public class TestWebServletAnnotationHandler extends WebServletAnnotationHandler
+ {
+ List<DiscoveredAnnotation> _list = null;
+
+ public TestWebServletAnnotationHandler(WebAppContext context, List<DiscoveredAnnotation> list)
+ {
+ super(context);
+ _list = list;
+ }
+
+ @Override
+ public void addAnnotation(DiscoveredAnnotation a)
+ {
+ super.addAnnotation(a);
+ _list.add(a);
+ }
+
+
+
+ }
@Test
public void testServletAnnotation() throws Exception
{
@@ -49,10 +72,11 @@
AnnotationParser parser = new AnnotationParser();
WebAppContext wac = new WebAppContext();
- WebServletAnnotationHandler handler = new WebServletAnnotationHandler(wac);
- parser.registerAnnotationHandler("javax.servlet.annotation.WebServlet", handler);
-
- parser.parse(classes, new ClassNameResolver ()
+ List<DiscoveredAnnotation> results = new ArrayList<DiscoveredAnnotation>();
+
+ TestWebServletAnnotationHandler handler = new TestWebServletAnnotationHandler(wac, results);
+
+ parser.parse(Collections.singleton(handler), classes, new ClassNameResolver ()
{
public boolean isExcluded(String name)
{
@@ -65,10 +89,11 @@
}
});
- assertEquals(1, handler.getAnnotationList().size());
- assertTrue(handler.getAnnotationList().get(0) instanceof WebServletAnnotation);
+
+ assertEquals(1, results.size());
+ assertTrue(results.get(0) instanceof WebServletAnnotation);
- handler.getAnnotationList().get(0).apply();
+ results.get(0).apply();
ServletHolder[] holders = wac.getServletHandler().getServlets();
assertNotNull(holders);
diff --git a/jetty-ant/pom.xml b/jetty-ant/pom.xml
index c1bd215..4ccc315 100755
--- a/jetty-ant/pom.xml
+++ b/jetty-ant/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-ant</artifactId>
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebAppContext.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebAppContext.java
index e004790..a8752b3 100644
--- a/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebAppContext.java
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebAppContext.java
@@ -22,8 +22,6 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.reflect.Array;
-import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
@@ -50,7 +48,6 @@
import org.eclipse.jetty.ant.utils.TaskLog;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
-import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.Holder;
@@ -432,9 +429,9 @@
/**
- * Default constructor. Takes application name as an argument
+ * Default constructor. Takes project as an argument
*
- * @param name web application name.
+ * @param project the project.
*/
public AntWebAppContext(Project project) throws Exception
{
@@ -669,9 +666,6 @@
}
}
- /**
- * @see WebApplicationProxy#stop()
- */
public void doStop()
{
try
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java
index bb3e322..a8535e9 100644
--- a/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java
@@ -40,12 +40,7 @@
@Override
public void preConfigure(final WebAppContext context) throws Exception
- {
- // Look for a work directory
- File work = findWorkDirectory(context);
- if (work != null)
- makeTempDirectory(work, context, false);
-
+ {
//Make a temp directory for the webapp if one is not already set
resolveTempDirectory(context);
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/JettyRunTask.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/JettyRunTask.java
index e6267d6..626ea4d 100644
--- a/jetty-ant/src/main/java/org/eclipse/jetty/ant/JettyRunTask.java
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/JettyRunTask.java
@@ -136,9 +136,6 @@
this.contextHandlers = handlers;
}
- /**
- * @return
- */
public File getTempDirectory()
{
return tempDirectory;
@@ -152,9 +149,6 @@
this.tempDirectory = tempDirectory;
}
- /**
- * @return
- */
public File getJettyXml()
{
return jettyXml;
@@ -191,9 +185,6 @@
}
}
- /**
- * @return
- */
public String getRequestLog()
{
if (requestLog != null)
@@ -301,9 +292,6 @@
TaskLog.log("Daemon="+daemon);
}
- /**
- * @return
- */
public int getScanIntervalSeconds()
{
return scanIntervalSeconds;
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/utils/ServerProxy.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/utils/ServerProxy.java
index c4b906e..432778e 100644
--- a/jetty-ant/src/main/java/org/eclipse/jetty/ant/utils/ServerProxy.java
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/utils/ServerProxy.java
@@ -27,8 +27,7 @@
/**
* Adds a new web application to this server.
*
- * @param webApp a WebApplicationProxy object.
- * @param scanIntervalSeconds
+ * @param awc a AntWebAppContext object.
*/
public void addWebApplication(AntWebAppContext awc);
diff --git a/jetty-ant/src/test/java/org/eclipse/jetty/ant/JettyAntTaskTest.java b/jetty-ant/src/test/java/org/eclipse/jetty/ant/JettyAntTaskTest.java
index 39960ce..b16bdff 100644
--- a/jetty-ant/src/test/java/org/eclipse/jetty/ant/JettyAntTaskTest.java
+++ b/jetty-ant/src/test/java/org/eclipse/jetty/ant/JettyAntTaskTest.java
@@ -18,15 +18,15 @@
package org.eclipse.jetty.ant;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
import java.net.HttpURLConnection;
import java.net.URI;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.junit.Test;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-
public class JettyAntTaskTest
{
diff --git a/jetty-client/pom.xml b/jetty-client/pom.xml
index 729f83b..59ee0de 100644
--- a/jetty-client/pom.xml
+++ b/jetty-client/pom.xml
@@ -1,138 +1,128 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <parent>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
- </parent>
+ <parent>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-project</artifactId>
+ <version>9.1.1-SNAPSHOT</version>
+ </parent>
- <modelVersion>4.0.0</modelVersion>
- <artifactId>jetty-client</artifactId>
- <name>Jetty :: Asynchronous HTTP Client</name>
- <url>http://www.eclipse.org/jetty</url>
- <properties>
- <bundle-symbolic-name>${project.groupId}.client</bundle-symbolic-name>
- <jetty.test.policy.loc>target/test-policy</jetty.test.policy.loc>
- </properties>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-bundle-plugin</artifactId>
- <extensions>true</extensions>
- <executions>
- <execution>
- <goals>
- <goal>manifest</goal>
- </goals>
- <configuration>
- <instructions>
- <Import-Package>javax.net.*,*</Import-Package>
- </instructions>
- </configuration>
- </execution>
- </executions>
- </plugin>
- <!--
- Required for OSGI
- -->
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- <configuration>
- <archive>
- <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
- </archive>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
- <configuration>
- <onlyAnalyze>org.eclipse.jetty.client.*</onlyAnalyze>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-dependency-plugin</artifactId>
- <executions>
- <execution>
- <id>unpack</id>
- <phase>generate-test-resources</phase>
- <goals>
- <goal>unpack</goal>
- </goals>
- <configuration>
- <artifactItems>
- <artifactItem>
- <groupId>org.eclipse.jetty.toolchain</groupId>
- <artifactId>jetty-test-policy</artifactId>
- <version>${jetty-test-policy-version}</version>
- <type>jar</type>
- <overWrite>true</overWrite>
- <includes>**/*.keystore,**/*.pem</includes>
- <outputDirectory>${jetty.test.policy.loc}</outputDirectory>
- </artifactItem>
- </artifactItems>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>jetty-client</artifactId>
+ <name>Jetty :: Asynchronous HTTP Client</name>
+ <url>http://www.eclipse.org/jetty</url>
+ <properties>
+ <bundle-symbolic-name>${project.groupId}.client</bundle-symbolic-name>
+ <jetty.test.policy.loc>target/test-policy</jetty.test.policy.loc>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <descriptorRef>config</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ <configuration>
+ <instructions>
+ <Import-Package>javax.net.*,*</Import-Package>
+ </instructions>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- Required for OSGI -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <configuration>
+ <onlyAnalyze>org.eclipse.jetty.client.*</onlyAnalyze>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>unpack</id>
+ <phase>generate-test-resources</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-test-policy</artifactId>
+ <version>${jetty-test-policy-version}</version>
+ <type>jar</type>
+ <overWrite>true</overWrite>
+ <includes>**/*.keystore,**/*.pem</includes>
+ <outputDirectory>${jetty.test.policy.loc}</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
- <dependencies>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-http</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-io</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-server</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-security</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-servlet</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>
- <!--
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-websocket</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>-->
- <dependency>
- <groupId>org.eclipse.jetty.toolchain</groupId>
- <artifactId>jetty-test-helper</artifactId>
- <scope>test</scope>
- </dependency>
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-io</artifactId>
+ <version>${project.version}</version>
+ </dependency>
- <dependency>
- <groupId>com.ning</groupId>
- <artifactId>async-http-client</artifactId>
- <version>1.7.5</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpclient</artifactId>
- <version>4.2.1</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-security</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-test-helper</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
</project>
diff --git a/jetty-client/src/main/config/modules/client.mod b/jetty-client/src/main/config/modules/client.mod
new file mode 100644
index 0000000..39b58d4
--- /dev/null
+++ b/jetty-client/src/main/config/modules/client.mod
@@ -0,0 +1,6 @@
+#
+# Client Feature
+#
+
+[lib]
+lib/jetty-client-${jetty.version}.jar
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java
new file mode 100644
index 0000000..fac4bcf
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java
@@ -0,0 +1,164 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.Map;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class AbstractHttpClientTransport extends ContainerLifeCycle implements HttpClientTransport
+{
+ protected static final Logger LOG = Log.getLogger(HttpClientTransport.class);
+
+ private final int selectors;
+ private volatile HttpClient client;
+ private volatile SelectorManager selectorManager;
+
+ protected AbstractHttpClientTransport(int selectors)
+ {
+ this.selectors = selectors;
+ }
+
+ protected HttpClient getHttpClient()
+ {
+ return client;
+ }
+
+ @Override
+ public void setHttpClient(HttpClient client)
+ {
+ this.client = client;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ selectorManager = newSelectorManager(client);
+ selectorManager.setConnectTimeout(client.getConnectTimeout());
+ addBean(selectorManager);
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ removeBean(selectorManager);
+ }
+
+ @Override
+ public void connect(SocketAddress address, Map<String, Object> context)
+ {
+ SocketChannel channel = null;
+ try
+ {
+ channel = SocketChannel.open();
+ HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+ HttpClient client = destination.getHttpClient();
+ SocketAddress bindAddress = client.getBindAddress();
+ if (bindAddress != null)
+ channel.bind(bindAddress);
+ configure(client, channel);
+ channel.configureBlocking(false);
+ channel.connect(address);
+
+ context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, destination.getHost());
+ context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, destination.getPort());
+ selectorManager.connect(channel, context);
+ }
+ // Must catch all exceptions, since some like
+ // UnresolvedAddressException are not IOExceptions.
+ catch (Throwable x)
+ {
+ try
+ {
+ if (channel != null)
+ channel.close();
+ }
+ catch (IOException xx)
+ {
+ LOG.ignore(xx);
+ }
+ finally
+ {
+ @SuppressWarnings("unchecked")
+ Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+ promise.failed(x);
+ }
+ }
+ }
+
+ protected void configure(HttpClient client, SocketChannel channel) throws IOException
+ {
+ channel.socket().setTcpNoDelay(client.isTCPNoDelay());
+ }
+
+ protected SelectorManager newSelectorManager(HttpClient client)
+ {
+ return new ClientSelectorManager(client, selectors);
+ }
+
+ protected class ClientSelectorManager extends SelectorManager
+ {
+ private final HttpClient client;
+
+ protected ClientSelectorManager(HttpClient client, int selectors)
+ {
+ super(client.getExecutor(), client.getScheduler(), selectors);
+ this.client = client;
+ }
+
+ @Override
+ protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key)
+ {
+ return new SelectChannelEndPoint(channel, selector, key, getScheduler(), client.getIdleTimeout());
+ }
+
+ @Override
+ public org.eclipse.jetty.io.Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
+ {
+ @SuppressWarnings("unchecked")
+ Map<String, Object> context = (Map<String, Object>)attachment;
+ HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+ return destination.getClientConnectionFactory().newConnection(endPoint, context);
+ }
+
+ @Override
+ protected void connectionFailed(SocketChannel channel, Throwable x, Object attachment)
+ {
+ @SuppressWarnings("unchecked")
+ Map<String, Object> context = (Map<String, Object>)attachment;
+ @SuppressWarnings("unchecked")
+ Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+ promise.failed(x);
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
index 7adcb80..b19c52c 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
@@ -109,16 +109,19 @@
return;
}
- URI uri = getAuthenticationURI(request);
Authentication authentication = null;
Authentication.HeaderInfo headerInfo = null;
- for (Authentication.HeaderInfo element : headerInfos)
+ URI uri = getAuthenticationURI(request);
+ if (uri != null)
{
- authentication = client.getAuthenticationStore().findAuthentication(element.getType(), uri, element.getRealm());
- if (authentication != null)
+ for (Authentication.HeaderInfo element : headerInfos)
{
- headerInfo = element;
- break;
+ authentication = client.getAuthenticationStore().findAuthentication(element.getType(), uri, element.getRealm());
+ if (authentication != null)
+ {
+ headerInfo = element;
+ break;
+ }
}
}
if (authentication == null)
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java
new file mode 100644
index 0000000..9ed8d93
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java
@@ -0,0 +1,207 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.util.concurrent.BlockingDeque;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Destination;
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class ConnectionPool implements Dumpable
+{
+ private static final Logger LOG = Log.getLogger(ConnectionPool.class);
+
+ private final AtomicInteger connectionCount = new AtomicInteger();
+ private final Destination destination;
+ private final int maxConnections;
+ private final Promise<Connection> connectionPromise;
+ private final BlockingDeque<Connection> idleConnections;
+ private final BlockingQueue<Connection> activeConnections;
+
+ public ConnectionPool(Destination destination, int maxConnections, Promise<Connection> connectionPromise)
+ {
+ this.destination = destination;
+ this.maxConnections = maxConnections;
+ this.connectionPromise = connectionPromise;
+ this.idleConnections = new LinkedBlockingDeque<>(maxConnections);
+ this.activeConnections = new BlockingArrayQueue<>(maxConnections);
+ }
+
+ public BlockingQueue<Connection> getIdleConnections()
+ {
+ return idleConnections;
+ }
+
+ public BlockingQueue<Connection> getActiveConnections()
+ {
+ return activeConnections;
+ }
+
+ public Connection acquire()
+ {
+ Connection result = acquireIdleConnection();
+ if (result != null)
+ return result;
+
+ while (true)
+ {
+ int current = connectionCount.get();
+ final int next = current + 1;
+
+ if (next > maxConnections)
+ {
+ LOG.debug("Max connections {}/{} reached", current, maxConnections);
+ // Try again the idle connections
+ return acquireIdleConnection();
+ }
+
+ if (connectionCount.compareAndSet(current, next))
+ {
+ LOG.debug("Connection {}/{} creation", next, maxConnections);
+
+ destination.newConnection(new Promise<Connection>()
+ {
+ @Override
+ public void succeeded(Connection connection)
+ {
+ LOG.debug("Connection {}/{} creation succeeded {}", next, maxConnections, connection);
+ activate(connection);
+ connectionPromise.succeeded(connection);
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ LOG.debug("Connection " + next + "/" + maxConnections + " creation failed", x);
+ connectionCount.decrementAndGet();
+ connectionPromise.failed(x);
+ }
+ });
+
+ // Try again the idle connections
+ return acquireIdleConnection();
+ }
+ }
+ }
+
+ private Connection acquireIdleConnection()
+ {
+ Connection connection = idleConnections.pollFirst();
+ if (connection != null)
+ activate(connection);
+ return connection;
+ }
+
+ private boolean activate(Connection connection)
+ {
+ if (activeConnections.offer(connection))
+ {
+ LOG.debug("Connection active {}", connection);
+ return true;
+ }
+ else
+ {
+ LOG.debug("Connection active overflow {}", connection);
+ return false;
+ }
+ }
+
+ public boolean release(Connection connection)
+ {
+ if (activeConnections.remove(connection))
+ {
+ // Make sure we use "hot" connections first
+ if (idleConnections.offerFirst(connection))
+ {
+ LOG.debug("Connection idle {}", connection);
+ return true;
+ }
+ else
+ {
+ LOG.debug("Connection idle overflow {}", connection);
+ }
+ }
+ return false;
+ }
+
+ public boolean remove(Connection connection)
+ {
+ boolean removed = activeConnections.remove(connection);
+ removed |= idleConnections.remove(connection);
+ if (removed)
+ {
+ int pooled = connectionCount.decrementAndGet();
+ LOG.debug("Connection removed {} - pooled: {}", connection, pooled);
+ }
+ return removed;
+ }
+
+ public boolean isActive(Connection connection)
+ {
+ return activeConnections.contains(connection);
+ }
+
+ public boolean isIdle(Connection connection)
+ {
+ return idleConnections.contains(connection);
+ }
+
+ public void close()
+ {
+ for (Connection connection : idleConnections)
+ connection.close();
+ idleConnections.clear();
+
+ // A bit drastic, but we cannot wait for all requests to complete
+ for (Connection connection : activeConnections)
+ connection.close();
+ activeConnections.clear();
+
+ connectionCount.set(0);
+ }
+
+ @Override
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ ContainerLifeCycle.dumpObject(out, this);
+ ContainerLifeCycle.dump(out, indent, activeConnections, idleConnections);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s %d/%d", getClass().getSimpleName(), connectionCount.get(), maxConnections);
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java
new file mode 100644
index 0000000..94e28e3
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java
@@ -0,0 +1,82 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class HttpChannel
+{
+ protected static final Logger LOG = Log.getLogger(HttpChannel.class);
+
+ private final AtomicReference<HttpExchange> exchange = new AtomicReference<>();
+ private final HttpDestination destination;
+
+ protected HttpChannel(HttpDestination destination)
+ {
+ this.destination = destination;
+ }
+
+ public HttpDestination getHttpDestination()
+ {
+ return destination;
+ }
+
+ public void associate(HttpExchange exchange)
+ {
+ if (!this.exchange.compareAndSet(null, exchange))
+ throw new UnsupportedOperationException("Pipelined requests not supported");
+ exchange.associate(this);
+ LOG.debug("{} associated to {}", exchange, this);
+ }
+
+ public HttpExchange disassociate()
+ {
+ HttpExchange exchange = this.exchange.getAndSet(null);
+ if (exchange != null)
+ exchange.disassociate(this);
+ LOG.debug("{} disassociated from {}", exchange, this);
+ return exchange;
+ }
+
+ public HttpExchange getHttpExchange()
+ {
+ return exchange.get();
+ }
+
+ public abstract void send();
+
+ public abstract void proceed(HttpExchange exchange, boolean proceed);
+
+ public abstract boolean abort(Throwable cause);
+
+ public void exchangeTerminated(Result result)
+ {
+ disassociate();
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%h", getClass().getSimpleName(), this);
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
index 3fe4028..c422f11 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
@@ -19,51 +19,45 @@
package org.eclipse.jetty.client;
import java.io.IOException;
-import java.net.ConnectException;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.CookieStore;
import java.net.SocketAddress;
-import java.net.SocketException;
import java.net.URI;
-import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.client.api.AuthenticationStore;
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.ProxyConfiguration;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.MappedByteBufferPool;
-import org.eclipse.jetty.io.SelectChannelEndPoint;
-import org.eclipse.jetty.io.SelectorManager;
-import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.SocketAddressResolver;
-import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -110,12 +104,14 @@
{
private static final Logger LOG = Log.getLogger(HttpClient.class);
- private final ConcurrentMap<String, HttpDestination> destinations = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Origin, HttpDestination> destinations = new ConcurrentHashMap<>();
private final ConcurrentMap<Long, HttpConversation> conversations = new ConcurrentHashMap<>();
private final List<ProtocolHandler> handlers = new ArrayList<>();
private final List<Request.Listener> requestListeners = new ArrayList<>();
private final AuthenticationStore authenticationStore = new HttpAuthenticationStore();
private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet();
+ private final ProxyConfiguration proxyConfig = new ProxyConfiguration();
+ private final HttpClientTransport transport;
private final SslContextFactory sslContextFactory;
private volatile CookieManager cookieManager;
private volatile CookieStore cookieStore;
@@ -123,13 +119,12 @@
private volatile ByteBufferPool byteBufferPool;
private volatile Scheduler scheduler;
private volatile SocketAddressResolver resolver;
- private volatile SelectorManager selectorManager;
private volatile HttpField agentField = new HttpField(HttpHeader.USER_AGENT, "Jetty/" + Jetty.VERSION);
private volatile boolean followRedirects = true;
private volatile int maxConnectionsPerDestination = 64;
private volatile int maxRequestsQueuedPerDestination = 1024;
private volatile int requestBufferSize = 4096;
- private volatile int responseBufferSize = 4096;
+ private volatile int responseBufferSize = 16384;
private volatile int maxRedirects = 8;
private volatile SocketAddress bindAddress;
private volatile long connectTimeout = 15000;
@@ -137,7 +132,7 @@
private volatile long idleTimeout;
private volatile boolean tcpNoDelay = true;
private volatile boolean dispatchIO = true;
- private volatile ProxyConfiguration proxyConfig;
+ private volatile boolean strictEventOrdering = false;
private volatile HttpField encodingField;
/**
@@ -160,9 +155,20 @@
*/
public HttpClient(SslContextFactory sslContextFactory)
{
+ this(new HttpClientTransportOverHTTP(), sslContextFactory);
+ }
+
+ public HttpClient(HttpClientTransport transport, SslContextFactory sslContextFactory)
+ {
+ this.transport = transport;
this.sslContextFactory = sslContextFactory;
}
+ public HttpClientTransport getTransport()
+ {
+ return transport;
+ }
+
/**
* @return the {@link SslContextFactory} that manages TLS encryption
* @see #HttpClient(SslContextFactory)
@@ -196,11 +202,10 @@
scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false);
addBean(scheduler);
- resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout());
+ addBean(transport);
+ transport.setHttpClient(this);
- selectorManager = newSelectorManager();
- selectorManager.setConnectTimeout(getConnectTimeout());
- addBean(selectorManager);
+ resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout());
handlers.add(new ContinueProtocolHandler(this));
handlers.add(new RedirectProtocolHandler(this));
@@ -215,11 +220,6 @@
super.doStart();
}
- protected SelectorManager newSelectorManager()
- {
- return new ClientSelectorManager(getExecutor(), getScheduler());
- }
-
private CookieManager newCookieManager()
{
return new CookieManager(getCookieStore(), CookiePolicy.ACCEPT_ALL);
@@ -246,10 +246,10 @@
}
/**
- * Returns a <em>non</em> thread-safe list of {@link Request.Listener}s that can be modified before
+ * Returns a <em>non</em> thread-safe list of {@link org.eclipse.jetty.client.api.Request.Listener}s that can be modified before
* performing requests.
*
- * @return a list of {@link Request.Listener} that can be used to add and remove listeners
+ * @return a list of {@link org.eclipse.jetty.client.api.Request.Listener} that can be used to add and remove listeners
*/
public List<Request.Listener> getRequestListeners()
{
@@ -359,7 +359,7 @@
*/
public Request newRequest(String host, int port)
{
- return newRequest(address("http", host, port));
+ return newRequest(new Origin("http", host, port).asString());
}
/**
@@ -387,9 +387,12 @@
protected Request copyRequest(Request oldRequest, URI newURI)
{
Request newRequest = new HttpRequest(this, oldRequest.getConversationID(), newURI);
- newRequest.method(oldRequest.method())
+ newRequest.method(oldRequest.getMethod())
.version(oldRequest.getVersion())
- .content(oldRequest.getContent());
+ .content(oldRequest.getContent())
+ .idleTimeout(oldRequest.getIdleTimeout(), TimeUnit.MILLISECONDS)
+ .timeout(oldRequest.getTimeout(), TimeUnit.MILLISECONDS)
+ .followRedirects(oldRequest.isFollowRedirects());
for (HttpField header : oldRequest.getHeaders())
{
// We have a new URI, so skip the host header if present
@@ -414,13 +417,6 @@
return newRequest;
}
- protected String address(String scheme, String host, int port)
- {
- StringBuilder result = new StringBuilder();
- URIUtil.appendSchemeHostPort(result, scheme, host, port);
- return result.toString();
- }
-
/**
* Returns a {@link Destination} for the given scheme, host and port.
* Applications may use {@link Destination}s to create {@link Connection}s
@@ -443,20 +439,20 @@
{
port = normalizePort(scheme, port);
- String address = address(scheme, host, port);
- HttpDestination destination = destinations.get(address);
+ Origin origin = new Origin(scheme, host, port);
+ HttpDestination destination = destinations.get(origin);
if (destination == null)
{
- destination = new HttpDestination(this, scheme, host, port);
+ destination = transport.newHttpDestination(origin);
if (isRunning())
{
- HttpDestination existing = destinations.putIfAbsent(address, destination);
+ HttpDestination existing = destinations.putIfAbsent(origin, destination);
if (existing != null)
destination = existing;
else
LOG.debug("Created {}", destination);
if (!isRunning())
- destinations.remove(address);
+ destinations.remove(origin);
}
}
@@ -483,34 +479,16 @@
protected void newConnection(final HttpDestination destination, final Promise<Connection> promise)
{
- Destination.Address address = destination.getConnectAddress();
+ Origin.Address address = destination.getConnectAddress();
resolver.resolve(address.getHost(), address.getPort(), new Promise<SocketAddress>()
{
@Override
public void succeeded(SocketAddress socketAddress)
{
- SocketChannel channel = null;
- try
- {
- channel = SocketChannel.open();
- SocketAddress bindAddress = getBindAddress();
- if (bindAddress != null)
- channel.bind(bindAddress);
- configure(channel);
- channel.configureBlocking(false);
- channel.connect(socketAddress);
-
- ConnectionCallback callback = new ConnectionCallback(destination, promise);
- selectorManager.connect(channel, callback);
- }
- // Must catch all exceptions, since some like
- // UnresolvedAddressException are not IOExceptions.
- catch (Throwable x)
- {
- if (channel != null)
- close(channel);
- promise.failed(x);
- }
+ Map<String, Object> context = new HashMap<>();
+ context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
+ context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
+ transport.connect(socketAddress, context);
}
@Override
@@ -521,23 +499,6 @@
});
}
- protected void configure(SocketChannel channel) throws SocketException
- {
- channel.socket().setTcpNoDelay(isTCPNoDelay());
- }
-
- private void close(SocketChannel channel)
- {
- try
- {
- channel.close();
- }
- catch (IOException x)
- {
- LOG.ignore(x);
- }
- }
-
protected HttpConversation getConversation(long id, boolean create)
{
HttpConversation conversation = conversations.get(id);
@@ -594,7 +555,7 @@
}
/**
- * @return the max time a connection can take to connect to destinations
+ * @return the max time, in milliseconds, a connection can take to connect to destinations
*/
public long getConnectTimeout()
{
@@ -602,7 +563,7 @@
}
/**
- * @param connectTimeout the max time a connection can take to connect to destinations
+ * @param connectTimeout the max time, in milliseconds, a connection can take to connect to destinations
* @see java.net.Socket#connect(SocketAddress, int)
*/
public void setConnectTimeout(long connectTimeout)
@@ -627,7 +588,7 @@
}
/**
- * @return the max time a connection can be idle (that is, without traffic of bytes in either direction)
+ * @return the max time, in milliseconds, a connection can be idle (that is, without traffic of bytes in either direction)
*/
public long getIdleTimeout()
{
@@ -635,7 +596,7 @@
}
/**
- * @param idleTimeout the max time a connection can be idle (that is, without traffic of bytes in either direction)
+ * @param idleTimeout the max time, in milliseconds, a connection can be idle (that is, without traffic of bytes in either direction)
*/
public void setIdleTimeout(long idleTimeout)
{
@@ -729,11 +690,6 @@
this.scheduler = scheduler;
}
- protected SelectorManager getSelectorManager()
- {
- return selectorManager;
- }
-
/**
* @return the max number of connections that this {@link HttpClient} opens to {@link Destination}s
*/
@@ -879,6 +835,41 @@
}
/**
+ * @return whether request events must be strictly ordered
+ */
+ public boolean isStrictEventOrdering()
+ {
+ return strictEventOrdering;
+ }
+
+ /**
+ * Whether request events must be strictly ordered.
+ * <p />
+ * {@link org.eclipse.jetty.client.api.Response.CompleteListener}s may send a second request.
+ * If the second request is for the same destination, there is an inherent race
+ * condition for the use of the connection: the first request may still be associated with the
+ * connection, so the second request cannot use that connection and is forced to open another one.
+ * <p />
+ * From the point of view of connection usage, the connection is reusable just before the "complete"
+ * event, so it would be possible to reuse that connection from {@link org.eclipse.jetty.client.api.Response.CompleteListener}s;
+ * but in this case the second request's events will fire before the "complete" events of the first
+ * request.
+ * <p />
+ * This setting enforces strict event ordering so that a "begin" event of a second request can never
+ * fire before the "complete" event of a first request, but at the expense of an increased usage
+ * of connections.
+ * <p />
+ * When not enforced, a "begin" event of a second request may happen before the "complete" event of
+ * a first request and allow for better usage of connections.
+ *
+ * @param strictEventOrdering whether request events must be strictly ordered
+ */
+ public void setStrictEventOrdering(boolean strictEventOrdering)
+ {
+ this.strictEventOrdering = strictEventOrdering;
+ }
+
+ /**
* @return the forward proxy configuration
*/
public ProxyConfiguration getProxyConfiguration()
@@ -886,14 +877,6 @@
return proxyConfig;
}
- /**
- * @param proxyConfig the forward proxy configuration
- */
- public void setProxyConfiguration(ProxyConfiguration proxyConfig)
- {
- this.proxyConfig = proxyConfig;
- }
-
protected HttpField getAcceptEncodingField()
{
return encodingField;
@@ -916,16 +899,6 @@
return HttpScheme.HTTPS.is(scheme) ? port == 443 : port == 80;
}
- protected HttpConnection newHttpConnection(HttpClient httpClient, EndPoint endPoint, HttpDestination destination)
- {
- return new HttpConnection(httpClient, endPoint, destination);
- }
-
- protected SslConnection newSslConnection(HttpClient httpClient, EndPoint endPoint, SSLEngine engine)
- {
- return new SslConnection(httpClient.getByteBufferPool(), httpClient.getExecutor(), endPoint, engine);
- }
-
@Override
public void dump(Appendable out, String indent) throws IOException
{
@@ -933,113 +906,6 @@
dump(out, indent, getBeans(), destinations.values());
}
- protected Connection tunnel(Connection connection)
- {
- HttpConnection httpConnection = (HttpConnection)connection;
- HttpDestination destination = httpConnection.getDestination();
- SslConnection sslConnection = createSslConnection(destination, httpConnection.getEndPoint());
- Connection result = (Connection)sslConnection.getDecryptedEndPoint().getConnection();
- selectorManager.connectionClosed(httpConnection);
- selectorManager.connectionOpened(sslConnection);
- LOG.debug("Tunnelled {} over {}", connection, result);
- return result;
- }
-
- private SslConnection createSslConnection(HttpDestination destination, EndPoint endPoint)
- {
- SSLEngine engine = sslContextFactory.newSSLEngine(destination.getHost(), destination.getPort());
- engine.setUseClientMode(true);
-
- SslConnection sslConnection = newSslConnection(HttpClient.this, endPoint, engine);
- sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
- endPoint.setConnection(sslConnection);
- EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
- HttpConnection connection = newHttpConnection(this, appEndPoint, destination);
- appEndPoint.setConnection(connection);
-
- return sslConnection;
- }
-
- protected class ClientSelectorManager extends SelectorManager
- {
- public ClientSelectorManager(Executor executor, Scheduler scheduler)
- {
- this(executor, scheduler, 1);
- }
-
- public ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors)
- {
- super(executor, scheduler, selectors);
- }
-
- @Override
- protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key)
- {
- return new SelectChannelEndPoint(channel, selector, key, getScheduler(), getIdleTimeout());
- }
-
- @Override
- public org.eclipse.jetty.io.Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
- {
- ConnectionCallback callback = (ConnectionCallback)attachment;
- HttpDestination destination = callback.destination;
-
- SslContextFactory sslContextFactory = getSslContextFactory();
- if (!destination.isProxied() && HttpScheme.HTTPS.is(destination.getScheme()))
- {
- if (sslContextFactory == null)
- {
- IOException failure = new ConnectException("Missing " + SslContextFactory.class.getSimpleName() + " for " + destination.getScheme() + " requests");
- callback.failed(failure);
- throw failure;
- }
- else
- {
- SslConnection sslConnection = createSslConnection(destination, endPoint);
- callback.succeeded((Connection)sslConnection.getDecryptedEndPoint().getConnection());
- return sslConnection;
- }
- }
- else
- {
- HttpConnection connection = newHttpConnection(HttpClient.this, endPoint, destination);
- callback.succeeded(connection);
- return connection;
- }
- }
-
- @Override
- protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
- {
- ConnectionCallback callback = (ConnectionCallback)attachment;
- callback.failed(ex);
- }
- }
-
- private class ConnectionCallback implements Promise<Connection>
- {
- private final HttpDestination destination;
- private final Promise<Connection> promise;
-
- private ConnectionCallback(HttpDestination destination, Promise<Connection> promise)
- {
- this.destination = destination;
- this.promise = promise;
- }
-
- @Override
- public void succeeded(Connection result)
- {
- promise.succeeded(result);
- }
-
- @Override
- public void failed(Throwable x)
- {
- promise.failed(x);
- }
- }
-
private class ContentDecoderFactorySet implements Set<ContentDecoder.Factory>
{
private final Set<ContentDecoder.Factory> set = new HashSet<>();
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClientTransport.java
new file mode 100644
index 0000000..f6a21ef
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClientTransport.java
@@ -0,0 +1,71 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.SocketAddress;
+import java.util.Map;
+
+import org.eclipse.jetty.io.ClientConnectionFactory;
+
+/**
+ * {@link HttpClientTransport} represents what transport implementations should provide
+ * in order to plug-in a different transport for {@link HttpClient}.
+ * <p/>
+ * While the {@link HttpClient} APIs define the HTTP semantic (request, response, headers, etc.)
+ * <em>how</em> a HTTP exchange is carried over the network depends on implementations of this class.
+ * <p/>
+ * The default implementation uses the HTTP protocol to carry over the network the HTTP exchange,
+ * but the HTTP exchange may also be carried using the SPDY protocol or the FCGI protocol or, in future,
+ * other protocols.
+ */
+public interface HttpClientTransport extends ClientConnectionFactory
+{
+ public static final String HTTP_DESTINATION_CONTEXT_KEY = "http.destination";
+ public static final String HTTP_CONNECTION_PROMISE_CONTEXT_KEY = "http.connection.promise";
+
+ /**
+ * Sets the {@link HttpClient} instance on this transport.
+ * <p />
+ * This is needed because of a chicken-egg problem: in order to create the {@link HttpClient}
+ * a {@link HttpClientTransport} is needed, that therefore cannot have a reference yet to the
+ * {@link HttpClient}.
+ *
+ * @param client the {@link HttpClient} that uses this transport.
+ */
+ public void setHttpClient(HttpClient client);
+
+ /**
+ * Creates a new, transport-specific, {@link HttpDestination} object.
+ * <p />
+ * {@link HttpDestination} controls the destination-connection cardinality: protocols like
+ * HTTP have 1-N cardinality, while multiplexed protocols like SPDY have a 1-1 cardinality.
+ *
+ * @param origin the destination origin
+ * @return a new, transport-specific, {@link HttpDestination} object
+ */
+ public HttpDestination newHttpDestination(Origin origin);
+
+ /**
+ * Establishes a physical connection to the given {@code address}.
+ *
+ * @param address the address to connect to
+ * @param context the context information to establish the connection
+ */
+ public void connect(SocketAddress address, Map<String, Object> context);
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
index dfd11c2..ff0e250 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
@@ -21,10 +21,7 @@
import java.net.HttpCookie;
import java.net.URI;
import java.util.ArrayList;
-import java.util.Enumeration;
import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.Connection;
@@ -37,133 +34,56 @@
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-public class HttpConnection extends AbstractConnection implements Connection
+public abstract class HttpConnection implements Connection
{
- private static final Logger LOG = Log.getLogger(HttpConnection.class);
private static final HttpField CHUNKED_FIELD = new HttpField(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED);
- private final AtomicReference<HttpExchange> exchange = new AtomicReference<>();
- private final HttpClient client;
private final HttpDestination destination;
- private final HttpSender sender;
- private final HttpReceiver receiver;
- private long idleTimeout;
- private boolean closed;
- public HttpConnection(HttpClient client, EndPoint endPoint, HttpDestination destination)
+ protected HttpConnection(HttpDestination destination)
{
- super(endPoint, client.getExecutor(), client.isDispatchIO());
- this.client = client;
this.destination = destination;
- this.sender = new HttpSender(this);
- this.receiver = new HttpReceiver(this);
}
public HttpClient getHttpClient()
{
- return client;
+ return destination.getHttpClient();
}
- public HttpDestination getDestination()
+ public HttpDestination getHttpDestination()
{
return destination;
}
@Override
- public void onOpen()
- {
- super.onOpen();
- fillInterested();
- }
-
- @Override
- public void onClose()
- {
- closed = true;
- super.onClose();
- }
-
- protected boolean isClosed()
- {
- return closed;
- }
-
- @Override
- protected boolean onReadTimeout()
- {
- LOG.debug("{} idle timeout", this);
-
- HttpExchange exchange = getExchange();
- if (exchange != null)
- idleTimeout();
- else
- destination.remove(this);
-
- return true;
- }
-
- protected void idleTimeout()
- {
- receiver.idleTimeout();
- }
-
- @Override
public void send(Request request, Response.CompleteListener listener)
{
ArrayList<Response.ResponseListener> listeners = new ArrayList<>(2);
if (request.getTimeout() > 0)
{
TimeoutCompleteListener timeoutListener = new TimeoutCompleteListener(request);
- timeoutListener.schedule(client.getScheduler());
+ timeoutListener.schedule(getHttpClient().getScheduler());
listeners.add(timeoutListener);
}
if (listener != null)
listeners.add(listener);
- HttpConversation conversation = client.getConversation(request.getConversationID(), true);
- HttpExchange exchange = new HttpExchange(conversation, getDestination(), request, listeners);
+ HttpConversation conversation = getHttpClient().getConversation(request.getConversationID(), true);
+ HttpExchange exchange = new HttpExchange(conversation, getHttpDestination(), request, listeners);
+
send(exchange);
}
- public void send(HttpExchange exchange)
+ protected abstract void send(HttpExchange exchange);
+
+ protected void normalizeRequest(Request request)
{
- Request request = exchange.getRequest();
- normalizeRequest(request);
-
- // Save the old idle timeout to restore it
- EndPoint endPoint = getEndPoint();
- idleTimeout = endPoint.getIdleTimeout();
- endPoint.setIdleTimeout(request.getIdleTimeout());
-
- // Associate the exchange to the connection
- associate(exchange);
-
- sender.send(exchange);
- }
-
- private void normalizeRequest(Request request)
- {
- if (request.method() == null)
- request.method(HttpMethod.GET);
-
- if (request.getVersion() == null)
- request.version(HttpVersion.HTTP_1_1);
-
- if (request.getIdleTimeout() <= 0)
- request.idleTimeout(client.getIdleTimeout(), TimeUnit.MILLISECONDS);
-
- String method = request.method();
+ String method = request.getMethod();
HttpVersion version = request.getVersion();
HttpFields headers = request.getHeaders();
ContentProvider content = request.getContent();
-
- if (request.getAgent() == null)
- headers.put(client.getUserAgentField());
+ ProxyConfiguration.Proxy proxy = destination.getProxy();
// Make sure the path is there
String path = request.getPath();
@@ -172,7 +92,7 @@
path = "/";
request.path(path);
}
- if (destination.isProxied() && !HttpMethod.CONNECT.is(method))
+ if (proxy != null && !HttpMethod.CONNECT.is(method))
{
path = request.getURI().toString();
request.path(path);
@@ -182,9 +102,12 @@
if (version.getVersion() > 10)
{
if (!headers.containsKey(HttpHeader.HOST.asString()))
- headers.put(getDestination().getHostField());
+ headers.put(getHttpDestination().getHostField());
}
+ if (request.getAgent() == null)
+ headers.put(getHttpClient().getUserAgentField());
+
// Add content headers
if (content != null)
{
@@ -202,7 +125,7 @@
}
// Cookies
- List<HttpCookie> cookies = client.getCookieStore().get(request.getURI());
+ List<HttpCookie> cookies = getHttpClient().getCookieStore().get(request.getURI());
StringBuilder cookieString = null;
for (int i = 0; i < cookies.size(); ++i)
{
@@ -217,141 +140,18 @@
request.header(HttpHeader.COOKIE.asString(), cookieString.toString());
// Authorization
- URI authenticationURI = destination.isProxied() ? destination.getProxyURI() : request.getURI();
- Authentication.Result authnResult = client.getAuthenticationStore().findAuthenticationResult(authenticationURI);
- if (authnResult != null)
- authnResult.apply(request);
-
- if (!headers.containsKey(HttpHeader.ACCEPT_ENCODING.asString()))
+ URI authenticationURI = proxy != null ? proxy.getURI() : request.getURI();
+ if (authenticationURI != null)
{
- HttpField acceptEncodingField = client.getAcceptEncodingField();
- if (acceptEncodingField != null)
- headers.put(acceptEncodingField);
+ Authentication.Result authnResult = getHttpClient().getAuthenticationStore().findAuthenticationResult(authenticationURI);
+ if (authnResult != null)
+ authnResult.apply(request);
}
}
- public HttpExchange getExchange()
- {
- return exchange.get();
- }
-
- protected void associate(HttpExchange exchange)
- {
- if (!this.exchange.compareAndSet(null, exchange))
- throw new UnsupportedOperationException("Pipelined requests not supported");
- exchange.setConnection(this);
- LOG.debug("{} associated to {}", exchange, this);
- }
-
- protected HttpExchange disassociate()
- {
- HttpExchange exchange = this.exchange.getAndSet(null);
- if (exchange != null)
- exchange.setConnection(null);
- LOG.debug("{} disassociated from {}", exchange, this);
- return exchange;
- }
-
- @Override
- public void onFillable()
- {
- HttpExchange exchange = getExchange();
- if (exchange != null)
- {
- receive();
- }
- else
- {
- // If there is no exchange, then could be either a remote close,
- // or garbage bytes; in both cases we close the connection
- close();
- }
- }
-
- protected void receive()
- {
- receiver.receive();
- }
-
- public void complete(HttpExchange exchange, boolean success)
- {
- HttpExchange existing = disassociate();
- if (existing == exchange)
- {
- exchange.awaitTermination();
-
- // Restore idle timeout
- getEndPoint().setIdleTimeout(idleTimeout);
-
- LOG.debug("{} disassociated from {}", exchange, this);
- if (success)
- {
- HttpFields responseHeaders = exchange.getResponse().getHeaders();
- Enumeration<String> values = responseHeaders.getValues(HttpHeader.CONNECTION.asString(), ",");
- if (values != null)
- {
- while (values.hasMoreElements())
- {
- if ("close".equalsIgnoreCase(values.nextElement()))
- {
- close();
- return;
- }
- }
- }
- destination.release(this);
- }
- else
- {
- close();
- }
- }
- else if (existing == null)
- {
- // It is possible that the exchange has already been disassociated,
- // for example if the connection idle timeouts: this will fail
- // the response, but the request may still be under processing.
- // Eventually the request will also fail as the connection is closed
- // and will arrive here without an exchange being present.
- // We just ignore this fact, as the exchange has already been processed
- }
- else
- {
- throw new IllegalStateException();
- }
- }
-
- public boolean abort(Throwable cause)
- {
- // We want the return value to be that of the response
- // because if the response has already successfully
- // arrived then we failed to abort the exchange
- sender.abort(cause);
- return receiver.abort(cause);
- }
-
- public void proceed(boolean proceed)
- {
- sender.proceed(proceed);
- }
-
- @Override
- public void close()
- {
- destination.remove(this);
- getEndPoint().shutdownOutput();
- LOG.debug("{} oshut", this);
- getEndPoint().close();
- LOG.debug("{} closed", this);
- }
-
@Override
public String toString()
{
- return String.format("%s@%x(l:%s <-> r:%s)",
- HttpConnection.class.getSimpleName(),
- hashCode(),
- getEndPoint().getLocalAddress(),
- getEndPoint().getRemoteAddress());
+ return String.format("%s@%h", getClass().getSimpleName(), this);
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java
new file mode 100644
index 0000000..989e7f2
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java
@@ -0,0 +1,156 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.util.BufferUtil;
+
+/**
+ * {@link HttpContent} is a stateful, linear representation of the request content provided
+ * by a {@link ContentProvider} that can be traversed one-way to obtain content buffers to
+ * send to a HTTP server.
+ * <p />
+ * {@link HttpContent} offers the notion of a one-way cursor to traverse the content.
+ * The cursor starts in a virtual "before" position and can be advanced using {@link #advance()}
+ * until it reaches a virtual "after" position where the content is fully consumed.
+ * <pre>
+ * +---+ +---+ +---+ +---+ +---+
+ * | | | | | | | | | |
+ * +---+ +---+ +---+ +---+ +---+
+ * ^ ^ ^ ^
+ * | | --> advance() | |
+ * | | last |
+ * | | |
+ * before | after
+ * |
+ * current
+ * </pre>
+ * At each valid (non-before and non-after) cursor position, {@link HttpContent} provides the following state:
+ * <ul>
+ * <li>the buffer containing the content to send, via {@link #getByteBuffer()}</li>
+ * <li>a copy of the content buffer that can be used for notifications, via {@link #getContent()}</li>
+ * <li>whether the buffer to write is the last one, via {@link #isLast()}</li>
+ * </ul>
+ * {@link HttpContent} may not have content, if the related {@link ContentProvider} is {@code null}, and this
+ * is reflected by {@link #hasContent()}.
+ * <p />
+ * {@link HttpContent} may have {@link DeferredContentProvider deferred content}, in which case {@link #advance()}
+ * moves the cursor to a position that provides {@code null} {@link #getByteBuffer() buffer} and
+ * {@link #getContent() content}. When the deferred content is available, a further call to {@link #advance()}
+ * will move the cursor to a position that provides non {@code null} buffer and content.
+ */
+public class HttpContent
+{
+ private static final ByteBuffer AFTER = ByteBuffer.allocate(0);
+
+ private final ContentProvider provider;
+ private final Iterator<ByteBuffer> iterator;
+ private ByteBuffer buffer;
+ private volatile ByteBuffer content;
+
+ public HttpContent(ContentProvider provider)
+ {
+ this.provider = provider;
+ this.iterator = provider == null ? Collections.<ByteBuffer>emptyIterator() : provider.iterator();
+ }
+
+ /**
+ * @return whether there is any content at all
+ */
+ public boolean hasContent()
+ {
+ return provider != null;
+ }
+
+ /**
+ * @return whether the cursor points to the last content
+ */
+ public boolean isLast()
+ {
+ return !iterator.hasNext();
+ }
+
+ /**
+ * @return the {@link ByteBuffer} containing the content at the cursor's position
+ */
+ public ByteBuffer getByteBuffer()
+ {
+ return buffer;
+ }
+
+ /**
+ * @return a {@link ByteBuffer#slice()} of {@link #getByteBuffer()} at the cursor's position
+ */
+ public ByteBuffer getContent()
+ {
+ return content;
+ }
+
+ /**
+ * Advances the cursor to the next block of content.
+ * <p />
+ * The next block of content may be valid (which yields a non-null buffer
+ * returned by {@link #getByteBuffer()}), but may also be deferred
+ * (which yields a null buffer returned by {@link #getByteBuffer()}).
+ * <p />
+ * If the block of content pointed by the new cursor position is valid, this method returns true.
+ *
+ * @return true if there is content at the new cursor's position, false otherwise.
+ */
+ public boolean advance()
+ {
+ if (isLast())
+ {
+ if (content != AFTER)
+ content = buffer = AFTER;
+ return false;
+ }
+ else
+ {
+ ByteBuffer buffer = this.buffer = iterator.next();
+ content = buffer == null ? null : buffer.slice();
+ return buffer != null;
+ }
+ }
+
+ /**
+ * @return whether the cursor has been advanced past the {@link #isLast() last} position.
+ */
+ public boolean isConsumed()
+ {
+ return content == AFTER;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x - has=%b,last=%b,consumed=%b,buffer=%s",
+ getClass().getSimpleName(),
+ hashCode(),
+ hasContent(),
+ isLast(),
+ isConsumed(),
+ BufferUtil.toDetailString(getContent()));
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java
index 6ab9a3a..596e239 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java
@@ -131,8 +131,7 @@
// will notify a listener that may send a new request and trigger
// another call to this method which will build different listeners
// which may be iterated over when the iteration continues.
- listeners = new ArrayList<>();
-
+ List<Response.ResponseListener> listeners = new ArrayList<>();
HttpExchange firstExchange = exchanges.peekFirst();
HttpExchange lastExchange = exchanges.peekLast();
if (firstExchange == lastExchange)
@@ -151,6 +150,7 @@
else
listeners.addAll(firstExchange.getResponseListeners());
}
+ this.listeners = listeners;
}
public void complete()
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
index 9b2caf8..99ed8a9 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
@@ -20,87 +20,89 @@
import java.io.Closeable;
import java.io.IOException;
-import java.net.URI;
import java.nio.channels.AsynchronousCloseException;
-import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
-import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Destination;
-import org.eclipse.jetty.client.api.ProxyConfiguration;
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.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-public class HttpDestination implements Destination, Closeable, Dumpable
+public abstract class HttpDestination implements Destination, Closeable, Dumpable
{
- private static final Logger LOG = Log.getLogger(HttpDestination.class);
+ protected static final Logger LOG = Log.getLogger(HttpDestination.class);
- private final AtomicInteger connectionCount = new AtomicInteger();
private final HttpClient client;
- private final String scheme;
- private final String host;
- private final Address address;
+ private final Origin origin;
private final Queue<HttpExchange> exchanges;
- private final BlockingQueue<Connection> idleConnections;
- private final BlockingQueue<Connection> activeConnections;
private final RequestNotifier requestNotifier;
private final ResponseNotifier responseNotifier;
- private final Address proxyAddress;
+ private final ProxyConfiguration.Proxy proxy;
+ private final ClientConnectionFactory connectionFactory;
private final HttpField hostField;
- public HttpDestination(HttpClient client, String scheme, String host, int port)
+ public HttpDestination(HttpClient client, Origin origin)
{
this.client = client;
- this.scheme = scheme;
- this.host = host;
- this.address = new Address(host, port);
+ this.origin = origin;
- int maxRequestsQueued = client.getMaxRequestsQueuedPerDestination();
- int capacity = Math.min(32, maxRequestsQueued);
- this.exchanges = new BlockingArrayQueue<>(capacity, capacity, maxRequestsQueued);
-
- int maxConnections = client.getMaxConnectionsPerDestination();
- capacity = Math.min(8, maxConnections);
- this.idleConnections = new BlockingArrayQueue<>(capacity, capacity, maxConnections);
- this.activeConnections = new BlockingArrayQueue<>(capacity, capacity, maxConnections);
+ this.exchanges = new BlockingArrayQueue<>(client.getMaxRequestsQueuedPerDestination());
this.requestNotifier = new RequestNotifier(client);
this.responseNotifier = new ResponseNotifier(client);
ProxyConfiguration proxyConfig = client.getProxyConfiguration();
- proxyAddress = proxyConfig != null && proxyConfig.matches(host, port) ?
- new Address(proxyConfig.getHost(), proxyConfig.getPort()) : null;
+ proxy = proxyConfig.match(origin);
+ ClientConnectionFactory connectionFactory = client.getTransport();
+ if (proxy != null)
+ {
+ connectionFactory = proxy.newClientConnectionFactory(connectionFactory);
+ }
+ else
+ {
+ if (HttpScheme.HTTPS.is(getScheme()))
+ connectionFactory = newSslClientConnectionFactory(connectionFactory);
+ }
+ this.connectionFactory = connectionFactory;
- if (!client.isDefaultPort(scheme, port))
- host += ":" + port;
+ String host = getHost();
+ if (!client.isDefaultPort(getScheme(), getPort()))
+ host += ":" + getPort();
hostField = new HttpField(HttpHeader.HOST, host);
}
- protected BlockingQueue<Connection> getIdleConnections()
+ protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
{
- return idleConnections;
+ return new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
}
- protected BlockingQueue<Connection> getActiveConnections()
+ public HttpClient getHttpClient()
{
- return activeConnections;
+ return client;
+ }
+
+ public Origin getOrigin()
+ {
+ return origin;
+ }
+
+ public Queue<HttpExchange> getHttpExchanges()
+ {
+ return exchanges;
}
public RequestNotifier getRequestNotifier()
@@ -113,10 +115,20 @@
return responseNotifier;
}
+ public ProxyConfiguration.Proxy getProxy()
+ {
+ return proxy;
+ }
+
+ public ClientConnectionFactory getClientConnectionFactory()
+ {
+ return connectionFactory;
+ }
+
@Override
public String getScheme()
{
- return scheme;
+ return origin.getScheme();
}
@Override
@@ -124,32 +136,18 @@
{
// InetSocketAddress.getHostString() transforms the host string
// in case of IPv6 addresses, so we return the original host string
- return host;
+ return origin.getAddress().getHost();
}
@Override
public int getPort()
{
- return address.getPort();
+ return origin.getAddress().getPort();
}
- public Address getConnectAddress()
+ public Origin.Address getConnectAddress()
{
- return isProxied() ? proxyAddress : address;
- }
-
- public boolean isProxied()
- {
- return proxyAddress != null;
- }
-
- public URI getProxyURI()
- {
- ProxyConfiguration proxyConfiguration = client.getProxyConfiguration();
- String uri = getScheme() + "://" + proxyConfiguration.getHost();
- if (!client.isDefaultPort(getScheme(), proxyConfiguration.getPort()))
- uri += ":" + proxyConfiguration.getPort();
- return URI.create(uri);
+ return proxy == null ? origin.getAddress() : proxy.getAddress();
}
public HttpField getHostField()
@@ -157,9 +155,9 @@
return hostField;
}
- public void send(Request request, List<Response.ResponseListener> listeners)
+ protected void send(Request request, List<Response.ResponseListener> listeners)
{
- if (!scheme.equals(request.getScheme()))
+ if (!getScheme().equals(request.getScheme()))
throw new IllegalArgumentException("Invalid request scheme " + request.getScheme() + " for destination " + this);
if (!getHost().equals(request.getHost()))
throw new IllegalArgumentException("Invalid request host " + request.getHost() + " for destination " + this);
@@ -182,9 +180,7 @@
{
LOG.debug("Queued {}", request);
requestNotifier.notifyQueued(request);
- Connection connection = acquire();
- if (connection != null)
- process(connection, false);
+ send();
}
}
else
@@ -199,9 +195,11 @@
}
}
+ protected abstract void send();
+
public void newConnection(Promise<Connection> promise)
{
- createConnection(new ProxyPromise(promise));
+ createConnection(promise);
}
protected void createConnection(Promise<Connection> promise)
@@ -209,80 +207,28 @@
client.newConnection(this, promise);
}
- protected Connection acquire()
+ public boolean remove(HttpExchange exchange)
{
- Connection result = idleConnections.poll();
- if (result != null)
- return result;
-
- final int maxConnections = client.getMaxConnectionsPerDestination();
- while (true)
- {
- int current = connectionCount.get();
- final int next = current + 1;
-
- if (next > maxConnections)
- {
- LOG.debug("Max connections per destination {} exceeded for {}", current, this);
- // Try again the idle connections
- return idleConnections.poll();
- }
-
- if (connectionCount.compareAndSet(current, next))
- {
- LOG.debug("Creating connection {}/{} for {}", next, maxConnections, this);
-
- // This is the promise that is being called when a connection (eventually proxied) succeeds or fails.
- Promise<Connection> promise = new Promise<Connection>()
- {
- @Override
- public void succeeded(Connection connection)
- {
- process(connection, true);
- }
-
- @Override
- public void failed(final Throwable x)
- {
- client.getExecutor().execute(new Runnable()
- {
- @Override
- public void run()
- {
- abort(x);
- }
- });
- }
- };
-
- // Create a new connection, and pass a ProxyPromise to establish a proxy tunnel, if needed.
- // Differently from the case where the connection is created explicitly by applications, here
- // we need to do a bit more logging and keep track of the connection count in case of failures.
- createConnection(new ProxyPromise(promise)
- {
- @Override
- public void succeeded(Connection connection)
- {
- LOG.debug("Created connection {}/{} {} for {}", next, maxConnections, connection, HttpDestination.this);
- super.succeeded(connection);
- }
-
- @Override
- public void failed(Throwable x)
- {
- LOG.debug("Connection failed {} for {}", x, HttpDestination.this);
- connectionCount.decrementAndGet();
- super.failed(x);
- }
- });
-
- // Try again the idle connections
- return idleConnections.poll();
- }
- }
+ return exchanges.remove(exchange);
}
- private void abort(Throwable cause)
+ public void close()
+ {
+ abort(new AsynchronousCloseException());
+ LOG.debug("Closed {}", this);
+ }
+
+ public void close(Connection connection)
+ {
+ }
+
+ /**
+ * Aborts all the {@link HttpExchange}s queued in this destination.
+ *
+ * @param cause the abort cause
+ * @see #abort(HttpExchange, Throwable)
+ */
+ public void abort(Throwable cause)
{
HttpExchange exchange;
while ((exchange = exchanges.poll()) != null)
@@ -290,134 +236,11 @@
}
/**
- * <p>Processes a new connection making it idle or active depending on whether requests are waiting to be sent.</p>
- * <p>A new connection is created when a request needs to be executed; it is possible that the request that
- * triggered the request creation is executed by another connection that was just released, so the new connection
- * may become idle.</p>
- * <p>If a request is waiting to be executed, it will be dequeued and executed by the new connection.</p>
+ * Aborts the given {@code exchange}, notifies listeners of the failure, and completes the exchange.
*
- * @param connection the new connection
- * @param dispatch whether to dispatch the processing to another thread
+ * @param exchange the {@link HttpExchange} to abort
+ * @param cause the abort cause
*/
- protected void process(Connection connection, boolean dispatch)
- {
- // Ugly cast, but lack of generic reification forces it
- final HttpConnection httpConnection = (HttpConnection)connection;
-
- final HttpExchange exchange = exchanges.poll();
- if (exchange == null)
- {
- LOG.debug("{} idle", httpConnection);
- if (!idleConnections.offer(httpConnection))
- {
- LOG.debug("{} idle overflow");
- httpConnection.close();
- }
- if (!client.isRunning())
- {
- LOG.debug("{} is stopping", client);
- remove(httpConnection);
- httpConnection.close();
- }
- }
- else
- {
- final Request request = exchange.getRequest();
- Throwable cause = request.getAbortCause();
- if (cause != null)
- {
- abort(exchange, cause);
- LOG.debug("Aborted before processing {}: {}", exchange, cause);
- }
- else
- {
- LOG.debug("{} active", httpConnection);
- if (!activeConnections.offer(httpConnection))
- {
- LOG.warn("{} active overflow");
- }
- if (dispatch)
- {
- client.getExecutor().execute(new Runnable()
- {
- @Override
- public void run()
- {
- httpConnection.send(exchange);
- }
- });
- }
- else
- {
- httpConnection.send(exchange);
- }
- }
- }
- }
-
- public void release(Connection connection)
- {
- LOG.debug("{} released", connection);
- if (client.isRunning())
- {
- boolean removed = activeConnections.remove(connection);
- if (removed)
- process(connection, false);
- else
- LOG.debug("{} explicit", connection);
- }
- else
- {
- LOG.debug("{} is stopped", client);
- remove(connection);
- connection.close();
- }
- }
-
- public void remove(Connection connection)
- {
- boolean removed = activeConnections.remove(connection);
- removed |= idleConnections.remove(connection);
- if (removed)
- {
- int open = connectionCount.decrementAndGet();
- LOG.debug("Removed connection {} for {} - open: {}", connection, this, open);
- }
-
- // We need to execute queued requests even if this connection failed.
- // We may create a connection that is not needed, but it will eventually
- // idle timeout, so no worries
- if (!exchanges.isEmpty())
- {
- connection = acquire();
- if (connection != null)
- process(connection, false);
- }
- }
-
- public void close()
- {
- for (Connection connection : idleConnections)
- connection.close();
- idleConnections.clear();
-
- // A bit drastic, but we cannot wait for all requests to complete
- for (Connection connection : activeConnections)
- connection.close();
- activeConnections.clear();
-
- abort(new AsynchronousCloseException());
-
- connectionCount.set(0);
-
- LOG.debug("Closed {}", this);
- }
-
- public boolean remove(HttpExchange exchange)
- {
- return exchanges.remove(exchange);
- }
-
protected void abort(HttpExchange exchange, Throwable cause)
{
Request request = exchange.getRequest();
@@ -428,19 +251,6 @@
getResponseNotifier().notifyComplete(listeners, new Result(request, cause, response, cause));
}
- protected void tunnelSucceeded(Connection connection, Promise<Connection> promise)
- {
- // Wrap the connection with TLS
- Connection tunnel = client.tunnel(connection);
- promise.succeeded(tunnel);
- }
-
- protected void tunnelFailed(Connection connection, Promise<Connection> promise, Throwable failure)
- {
- promise.failed(failure);
- connection.close();
- }
-
@Override
public String dump()
{
@@ -451,100 +261,19 @@
public void dump(Appendable out, String indent) throws IOException
{
ContainerLifeCycle.dumpObject(out, this + " - requests queued: " + exchanges.size());
- List<String> connections = new ArrayList<>();
- for (Connection connection : idleConnections)
- connections.add(connection + " - IDLE");
- for (Connection connection : activeConnections)
- connections.add(connection + " - ACTIVE");
- ContainerLifeCycle.dump(out, indent, connections);
+ }
+
+ public String asString()
+ {
+ return origin.asString();
}
@Override
public String toString()
{
- return String.format("%s(%s://%s:%d)%s",
+ return String.format("%s(%s)%s",
HttpDestination.class.getSimpleName(),
- getScheme(),
- getHost(),
- getPort(),
- proxyAddress == null ? "" : " via " + proxyAddress.getHost() + ":" + proxyAddress.getPort());
- }
-
- /**
- * Decides whether to establish a proxy tunnel using HTTP CONNECT.
- * It is implemented as a promise because it needs to establish the tunnel
- * when the TCP connection is succeeded, and needs to notify another
- * promise when the tunnel is established (or failed).
- */
- private class ProxyPromise implements Promise<Connection>
- {
- private final Promise<Connection> delegate;
-
- private ProxyPromise(Promise<Connection> delegate)
- {
- this.delegate = delegate;
- }
-
- @Override
- public void succeeded(Connection connection)
- {
- if (isProxied() && HttpScheme.HTTPS.is(getScheme()))
- {
- if (client.getSslContextFactory() != null)
- {
- tunnel(connection);
- }
- else
- {
- String message = String.format("Cannot perform requests over SSL, no %s in %s",
- SslContextFactory.class.getSimpleName(), HttpClient.class.getSimpleName());
- delegate.failed(new IllegalStateException(message));
- }
- }
- else
- {
- delegate.succeeded(connection);
- }
- }
-
- @Override
- public void failed(Throwable x)
- {
- delegate.failed(x);
- }
-
- private void tunnel(final Connection connection)
- {
- String target = address.getHost() + ":" + address.getPort();
- Request connect = client.newRequest(proxyAddress.getHost(), proxyAddress.getPort())
- .scheme(HttpScheme.HTTP.asString())
- .method(HttpMethod.CONNECT)
- .path(target)
- .header(HttpHeader.HOST, target)
- .timeout(client.getConnectTimeout(), TimeUnit.MILLISECONDS);
- connection.send(connect, new Response.CompleteListener()
- {
- @Override
- public void onComplete(Result result)
- {
- if (result.isFailed())
- {
- tunnelFailed(connection, delegate, result.getFailure());
- }
- else
- {
- Response response = result.getResponse();
- if (response.getStatus() == 200)
- {
- tunnelSucceeded(connection, delegate);
- }
- else
- {
- tunnelFailed(connection, delegate, new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
- }
- }
- }
- });
- }
+ asString(),
+ proxy == null ? "" : " via " + proxy);
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
index de04377..b370524 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
@@ -19,9 +19,9 @@
package org.eclipse.jetty.client;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicMarkableReference;
+import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
@@ -33,14 +33,15 @@
{
private static final Logger LOG = Log.getLogger(HttpExchange.class);
+ private final AtomicBoolean requestComplete = new AtomicBoolean();
+ private final AtomicBoolean responseComplete = new AtomicBoolean();
private final AtomicInteger complete = new AtomicInteger();
- private final CountDownLatch terminate = new CountDownLatch(2);
+ private final AtomicReference<HttpChannel> channel = new AtomicReference<>();
private final HttpConversation conversation;
private final HttpDestination destination;
private final Request request;
private final List<Response.ResponseListener> listeners;
private final HttpResponse response;
- private volatile HttpConnection connection;
private volatile Throwable requestFailure;
private volatile Throwable responseFailure;
@@ -86,30 +87,47 @@
return responseFailure;
}
- public void setConnection(HttpConnection connection)
+ public void associate(HttpChannel channel)
{
- this.connection = connection;
+ if (!this.channel.compareAndSet(null, channel))
+ throw new IllegalStateException();
}
- public AtomicMarkableReference<Result> requestComplete(Throwable failure)
+ public void disassociate(HttpChannel channel)
+ {
+ if (!this.channel.compareAndSet(channel, null))
+ throw new IllegalStateException();
+ }
+
+ public boolean requestComplete()
+ {
+ return requestComplete.compareAndSet(false, true);
+ }
+
+ public boolean responseComplete()
+ {
+ return responseComplete.compareAndSet(false, true);
+ }
+
+ public Result terminateRequest(Throwable failure)
{
int requestSuccess = 0b0011;
int requestFailure = 0b0001;
- return complete(failure == null ? requestSuccess : requestFailure, failure);
+ return terminate(failure == null ? requestSuccess : requestFailure, failure);
}
- public AtomicMarkableReference<Result> responseComplete(Throwable failure)
+ public Result terminateResponse(Throwable failure)
{
if (failure == null)
{
int responseSuccess = 0b1100;
- return complete(responseSuccess, failure);
+ return terminate(responseSuccess, failure);
}
else
{
proceed(false);
int responseFailure = 0b0100;
- return complete(responseFailure, failure);
+ return terminate(responseFailure, failure);
}
}
@@ -126,16 +144,10 @@
* By using {@link AtomicInteger} to atomically sum these codes we can know
* whether the exchange is completed and whether is successful.
*
- * @param code the bits representing the status code for either the request or the response
- * @param failure the failure - if any - associated with the status code for either the request or the response
- * @return an AtomicMarkableReference holding whether the operation modified the
- * completion status and the {@link Result} - if any - associated with the status
+ * @return the {@link Result} - if any - associated with the status
*/
- private AtomicMarkableReference<Result> complete(int code, Throwable failure)
+ private Result terminate(int code, Throwable failure)
{
- Result result = null;
- boolean modified = false;
-
int current;
while (true)
{
@@ -147,7 +159,6 @@
if (!complete.compareAndSet(current, candidate))
continue;
current = candidate;
- modified = true;
if ((code & 0b01) == 0b01)
requestFailure = failure;
else
@@ -157,19 +168,16 @@
break;
}
- int completed = 0b0101;
- if ((current & completed) == completed)
+ int terminated = 0b0101;
+ if ((current & terminated) == terminated)
{
- if (modified)
- {
- // Request and response completed
- LOG.debug("{} complete", this);
- conversation.complete();
- }
- result = new Result(getRequest(), getRequestFailure(), getResponse(), getResponseFailure());
+ // Request and response terminated
+ LOG.debug("{} terminated", this);
+ conversation.complete();
+ return new Result(getRequest(), getRequestFailure(), getResponse(), getResponseFailure());
}
- return new AtomicMarkableReference<>(result, modified);
+ return null;
}
public boolean abort(Throwable cause)
@@ -182,12 +190,12 @@
}
else
{
- HttpConnection connection = this.connection;
- // If there is no connection, this exchange is already completed
- if (connection == null)
+ HttpChannel channel = this.channel.get();
+ // If there is no channel, this exchange is already completed
+ if (channel == null)
return false;
- boolean aborted = connection.abort(cause);
+ boolean aborted = channel.abort(cause);
LOG.debug("Aborted while active ({}) {}: {}", aborted, this, cause);
return aborted;
}
@@ -195,6 +203,7 @@
public void resetResponse(boolean success)
{
+ responseComplete.set(false);
int responseSuccess = 0b1100;
int responseFailure = 0b0100;
int code = success ? responseSuccess : responseFailure;
@@ -203,42 +212,25 @@
public void proceed(boolean proceed)
{
- HttpConnection connection = this.connection;
- if (connection != null)
- connection.proceed(proceed);
+ HttpChannel channel = this.channel.get();
+ if (channel != null)
+ channel.proceed(this, proceed);
}
- public void terminateRequest()
- {
- terminate.countDown();
- }
-
- public void terminateResponse()
- {
- terminate.countDown();
- }
-
- public void awaitTermination()
- {
- try
- {
- terminate.await();
- }
- catch (InterruptedException x)
- {
- LOG.ignore(x);
- }
- }
-
- @Override
- public String toString()
+ private String toString(int code)
{
String padding = "0000";
- String status = Integer.toBinaryString(complete.get());
+ String status = Integer.toBinaryString(code);
return String.format("%s@%x status=%s%s",
HttpExchange.class.getSimpleName(),
hashCode(),
padding.substring(status.length()),
status);
}
+
+ @Override
+ public String toString()
+ {
+ return toString(complete.get());
+ }
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
new file mode 100644
index 0000000..23cc2d2
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
@@ -0,0 +1,200 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class HttpProxy extends ProxyConfiguration.Proxy
+{
+ public HttpProxy(String host, int port)
+ {
+ this(new Origin.Address(host, port), false);
+ }
+
+ public HttpProxy(Origin.Address address, boolean secure)
+ {
+ super(address, secure);
+ }
+
+ @Override
+ public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
+ {
+ return new HttpProxyClientConnectionFactory(connectionFactory);
+ }
+
+ @Override
+ public URI getURI()
+ {
+ String scheme = isSecure() ? HttpScheme.HTTPS.asString() : HttpScheme.HTTP.asString();
+ return URI.create(new Origin(scheme, getAddress()).asString());
+ }
+
+ public static class HttpProxyClientConnectionFactory implements ClientConnectionFactory
+ {
+ private static final Logger LOG = Log.getLogger(HttpProxyClientConnectionFactory.class);
+ private final ClientConnectionFactory connectionFactory;
+
+ public HttpProxyClientConnectionFactory(ClientConnectionFactory connectionFactory)
+ {
+ this.connectionFactory = connectionFactory;
+ }
+
+ @Override
+ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+ {
+ @SuppressWarnings("unchecked")
+ Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+ final ProxyPromise proxyPromise = new ProxyPromise(endPoint, promise, context);
+ // Replace the promise with the proxy one
+ context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, proxyPromise);
+ return connectionFactory.newConnection(endPoint, context);
+ }
+
+ /**
+ * Decides whether to establish a proxy tunnel using HTTP CONNECT.
+ * It is implemented as a promise because it needs to establish the
+ * tunnel after the TCP connection is succeeded, and needs to notify
+ * the nested promise when the tunnel is established (or failed).
+ */
+ private class ProxyPromise implements Promise<Connection>
+ {
+ private final EndPoint endPoint;
+ private final Promise<Connection> promise;
+ private final Map<String, Object> context;
+
+ private ProxyPromise(EndPoint endPoint, Promise<Connection> promise, Map<String, Object> context)
+ {
+ this.endPoint = endPoint;
+ this.promise = promise;
+ this.context = context;
+ }
+
+ @Override
+ public void succeeded(Connection connection)
+ {
+ HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+ if (HttpScheme.HTTPS.is(destination.getScheme()))
+ {
+ SslContextFactory sslContextFactory = destination.getHttpClient().getSslContextFactory();
+ if (sslContextFactory != null)
+ {
+ tunnel(destination, connection);
+ }
+ else
+ {
+ String message = String.format("Cannot perform requests over SSL, no %s in %s",
+ SslContextFactory.class.getSimpleName(), HttpClient.class.getSimpleName());
+ promise.failed(new IllegalStateException(message));
+ }
+ }
+ else
+ {
+ promise.succeeded(connection);
+ }
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ promise.failed(x);
+ }
+
+ private void tunnel(HttpDestination destination, final Connection connection)
+ {
+ String target = destination.getOrigin().getAddress().asString();
+ Origin.Address proxyAddress = destination.getConnectAddress();
+ HttpClient httpClient = destination.getHttpClient();
+ Request connect = httpClient.newRequest(proxyAddress.getHost(), proxyAddress.getPort())
+ .scheme(HttpScheme.HTTP.asString())
+ .method(HttpMethod.CONNECT)
+ .path(target)
+ .header(HttpHeader.HOST, target)
+ .timeout(httpClient.getConnectTimeout(), TimeUnit.MILLISECONDS);
+
+ connection.send(connect, new Response.CompleteListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ if (result.isFailed())
+ {
+ tunnelFailed(result.getFailure());
+ }
+ else
+ {
+ Response response = result.getResponse();
+ if (response.getStatus() == 200)
+ {
+ tunnelSucceeded();
+ }
+ else
+ {
+ tunnelFailed(new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
+ }
+ }
+ }
+ });
+ }
+
+ private void tunnelSucceeded()
+ {
+ try
+ {
+ // Replace the promise back with the original
+ context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
+ HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+ HttpClient client = destination.getHttpClient();
+ ClientConnectionFactory sslConnectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
+ org.eclipse.jetty.io.Connection oldConnection = endPoint.getConnection();
+ org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context);
+ Helper.replaceConnection(oldConnection, newConnection);
+ LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection);
+ }
+ catch (Throwable x)
+ {
+ tunnelFailed(x);
+ }
+ }
+
+ private void tunnelFailed(Throwable failure)
+ {
+ endPoint.close();
+ failed(failure);
+ }
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
index 8bb6c6d..4ee0e9c 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.client;
-import java.io.EOFException;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
@@ -27,198 +26,169 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.HttpMethod;
-import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
+/**
+ * {@link HttpReceiver} provides the abstract code to implement the various steps of the receive of HTTP responses.
+ * <p />
+ * {@link HttpReceiver} maintains a state machine that is updated when the steps of receiving a response are executed.
+ * <p />
+ * Subclasses must handle the transport-specific details, for example how to read from the raw socket and how to parse
+ * the bytes read from the socket. Then they have to call the methods defined in this class in the following order:
+ * <ol>
+ * <li>{@link #responseBegin(HttpExchange)}, when the HTTP response data containing the HTTP status code
+ * is available</li>
+ * <li>{@link #responseHeader(HttpExchange, HttpField)}, when a HTTP field is available</li>
+ * <li>{@link #responseHeaders(HttpExchange)}, when all HTTP headers are available</li>
+ * <li>{@link #responseContent(HttpExchange, ByteBuffer)}, when HTTP content is available; this is the only method
+ * that may be invoked multiple times with different buffers containing different content</li>
+ * <li>{@link #responseSuccess(HttpExchange)}, when the response is complete</li>
+ * </ol>
+ * At any time, subclasses may invoke {@link #responseFailure(Throwable)} to indicate that the response has failed
+ * (for example, because of I/O exceptions).
+ * At any time, user threads may abort the response which will cause {@link #responseFailure(Throwable)} to be
+ * invoked.
+ * <p />
+ * The state machine maintained by this class ensures that the response steps are not executed by an I/O thread
+ * if the response has already been failed.
+ *
+ * @see HttpSender
+ */
+public abstract class HttpReceiver
{
- private static final Logger LOG = Log.getLogger(HttpReceiver.class);
+ protected static final Logger LOG = Log.getLogger(HttpReceiver.class);
- private final AtomicReference<State> state = new AtomicReference<>(State.IDLE);
- private final HttpParser parser = new HttpParser(this);
- private final HttpConnection connection;
- private ContentDecoder decoder;
+ private final AtomicReference<ResponseState> responseState = new AtomicReference<>(ResponseState.IDLE);
+ private final HttpChannel channel;
+ private volatile ContentDecoder decoder;
- public HttpReceiver(HttpConnection connection)
+ protected HttpReceiver(HttpChannel channel)
{
- this.connection = connection;
+ this.channel = channel;
}
- public void receive()
+ protected HttpChannel getHttpChannel()
{
- EndPoint endPoint = connection.getEndPoint();
- HttpClient client = connection.getHttpClient();
- ByteBufferPool bufferPool = client.getByteBufferPool();
- ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
- try
+ return channel;
+ }
+
+ protected HttpExchange getHttpExchange()
+ {
+ return channel.getHttpExchange();
+ }
+
+ protected HttpDestination getHttpDestination()
+ {
+ return channel.getHttpDestination();
+ }
+
+ /**
+ * Method to be invoked when the response status code is available.
+ * <p />
+ * Subclasses must have set the response status code on the {@link Response} object of the {@link HttpExchange}
+ * prior invoking this method.
+ * <p />
+ * This method takes case of notifying {@link org.eclipse.jetty.client.api.Response.BeginListener}s.
+ *
+ * @param exchange the HTTP exchange
+ * @return whether the processing should continue
+ */
+ protected boolean responseBegin(HttpExchange exchange)
+ {
+ if (!updateResponseState(ResponseState.IDLE, ResponseState.BEGIN))
+ return false;
+
+ HttpConversation conversation = exchange.getConversation();
+ HttpResponse response = exchange.getResponse();
+ // Probe the protocol handlers
+ HttpDestination destination = getHttpDestination();
+ HttpClient client = destination.getHttpClient();
+ ProtocolHandler protocolHandler = client.findProtocolHandler(exchange.getRequest(), response);
+ Response.Listener handlerListener = null;
+ if (protocolHandler != null)
{
- while (true)
+ handlerListener = protocolHandler.getResponseListener();
+ LOG.debug("Found protocol handler {}", protocolHandler);
+ }
+ exchange.getConversation().updateResponseListeners(handlerListener);
+
+ LOG.debug("Response begin {}", response);
+ ResponseNotifier notifier = destination.getResponseNotifier();
+ notifier.notifyBegin(conversation.getResponseListeners(), response);
+
+ return true;
+ }
+
+ /**
+ * Method to be invoked when a response HTTP header is available.
+ * <p />
+ * Subclasses must not have added the header to the {@link Response} object of the {@link HttpExchange}
+ * prior invoking this method.
+ * <p />
+ * This method takes case of notifying {@link org.eclipse.jetty.client.api.Response.HeaderListener}s and storing cookies.
+ *
+ * @param exchange the HTTP exchange
+ * @param field the response HTTP field
+ * @return whether the processing should continue
+ */
+ protected boolean responseHeader(HttpExchange exchange, HttpField field)
+ {
+ out: while (true)
+ {
+ ResponseState current = responseState.get();
+ switch (current)
{
- // Connection may be closed in a parser callback
- if (connection.isClosed())
+ case BEGIN:
+ case HEADER:
{
- LOG.debug("{} closed", connection);
+ if (updateResponseState(current, ResponseState.HEADER))
+ break out;
break;
}
- else
+ default:
{
- int read = endPoint.fill(buffer);
- LOG.debug("Read {} bytes from {}", read, connection);
- if (read > 0)
+ return false;
+ }
+ }
+ }
+
+ HttpResponse response = exchange.getResponse();
+ ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
+ boolean process = notifier.notifyHeader(exchange.getConversation().getResponseListeners(), response, field);
+ if (process)
+ {
+ response.getHeaders().add(field);
+ HttpHeader fieldHeader = field.getHeader();
+ if (fieldHeader != null)
+ {
+ switch (fieldHeader)
+ {
+ case SET_COOKIE:
+ case SET_COOKIE2:
{
- parse(buffer);
- }
- else if (read == 0)
- {
- fillInterested();
+ storeCookie(exchange.getRequest().getURI(), field);
break;
}
- else
+ default:
{
- shutdown();
break;
}
}
}
}
- catch (EofException x)
- {
- LOG.ignore(x);
- failAndClose(x);
- }
- catch (Exception x)
- {
- LOG.debug(x);
- failAndClose(x);
- }
- finally
- {
- bufferPool.release(buffer);
- }
+
+ return true;
}
- private void parse(ByteBuffer buffer)
- {
- while (buffer.hasRemaining())
- parser.parseNext(buffer);
- }
-
- private void fillInterested()
- {
- State state = this.state.get();
- if (state == State.IDLE || state == State.RECEIVE)
- connection.fillInterested();
- }
-
- private void shutdown()
- {
- // Shutting down the parser may invoke messageComplete() or fail()
- parser.shutdownInput();
- State state = this.state.get();
- if (state == State.IDLE || state == State.RECEIVE)
- {
- if (!fail(new EOFException()))
- connection.close();
- }
- }
-
-
- @Override
- public int getHeaderCacheSize()
- {
- // TODO get from configuration
- return 256;
- }
-
- @Override
- public boolean startResponse(HttpVersion version, int status, String reason)
- {
- if (updateState(State.IDLE, State.RECEIVE))
- {
- HttpExchange exchange = connection.getExchange();
- // The exchange may be null if it failed concurrently
- if (exchange != null)
- {
- HttpConversation conversation = exchange.getConversation();
- HttpResponse response = exchange.getResponse();
-
- String method = exchange.getRequest().method();
- parser.setHeadResponse(HttpMethod.HEAD.is(method) || HttpMethod.CONNECT.is(method));
- response.version(version).status(status).reason(reason);
-
- // Probe the protocol handlers
- HttpClient client = connection.getHttpClient();
- ProtocolHandler protocolHandler = client.findProtocolHandler(exchange.getRequest(), response);
- Response.Listener handlerListener = null;
- if (protocolHandler != null)
- {
- handlerListener = protocolHandler.getResponseListener();
- LOG.debug("Found protocol handler {}", protocolHandler);
- }
- exchange.getConversation().updateResponseListeners(handlerListener);
-
- LOG.debug("Receiving {}", response);
- ResponseNotifier notifier = connection.getDestination().getResponseNotifier();
- notifier.notifyBegin(conversation.getResponseListeners(), response);
- }
- }
- return false;
- }
-
- @Override
- public boolean parsedHeader(HttpField field)
- {
- if (updateState(State.RECEIVE, State.RECEIVE))
- {
- HttpExchange exchange = connection.getExchange();
- // The exchange may be null if it failed concurrently
- if (exchange != null)
- {
- HttpConversation conversation = exchange.getConversation();
- HttpResponse response = exchange.getResponse();
- ResponseNotifier notifier = connection.getDestination().getResponseNotifier();
- boolean process = notifier.notifyHeader(conversation.getResponseListeners(), response, field);
- if (process)
- {
- response.getHeaders().add(field);
- HttpHeader fieldHeader = field.getHeader();
- if (fieldHeader != null)
- {
- switch (fieldHeader)
- {
- case SET_COOKIE:
- case SET_COOKIE2:
- {
- storeCookie(exchange.getRequest().getURI(), field);
- break;
- }
- default:
- {
- break;
- }
- }
- }
- }
- }
- }
- return false;
- }
-
- private void storeCookie(URI uri, HttpField field)
+ protected void storeCookie(URI uri, HttpField field)
{
try
{
@@ -227,7 +197,7 @@
{
Map<String, List<String>> header = new HashMap<>(1);
header.put(field.getHeader().asString(), Collections.singletonList(value));
- connection.getHttpClient().getCookieManager().put(uri, header);
+ getHttpDestination().getHttpClient().getCookieManager().put(uri, header);
}
}
catch (IOException x)
@@ -236,113 +206,166 @@
}
}
- @Override
- public boolean headerComplete()
+ /**
+ * Method to be invoked after all response HTTP headers are available.
+ * <p />
+ * This method takes case of notifying {@link org.eclipse.jetty.client.api.Response.HeadersListener}s.
+ *
+ * @param exchange the HTTP exchange
+ * @return whether the processing should continue
+ */
+ protected boolean responseHeaders(HttpExchange exchange)
{
- if (updateState(State.RECEIVE, State.RECEIVE))
+ out: while (true)
{
- HttpExchange exchange = connection.getExchange();
- // The exchange may be null if it failed concurrently
- if (exchange != null)
+ ResponseState current = responseState.get();
+ switch (current)
{
- HttpConversation conversation = exchange.getConversation();
- HttpResponse response = exchange.getResponse();
- LOG.debug("Headers {}", response);
- ResponseNotifier notifier = connection.getDestination().getResponseNotifier();
- notifier.notifyHeaders(conversation.getResponseListeners(), response);
-
- Enumeration<String> contentEncodings = response.getHeaders().getValues(HttpHeader.CONTENT_ENCODING.asString(), ",");
- if (contentEncodings != null)
+ case BEGIN:
+ case HEADER:
{
- for (ContentDecoder.Factory factory : connection.getHttpClient().getContentDecoderFactories())
+ if (updateResponseState(current, ResponseState.HEADERS))
+ break out;
+ break;
+ }
+ default:
+ {
+ return false;
+ }
+ }
+ }
+
+ HttpResponse response = exchange.getResponse();
+ if (LOG.isDebugEnabled())
+ LOG.debug("Response headers {}{}{}", response, System.getProperty("line.separator"), response.getHeaders().toString().trim());
+ ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
+ notifier.notifyHeaders(exchange.getConversation().getResponseListeners(), response);
+
+ Enumeration<String> contentEncodings = response.getHeaders().getValues(HttpHeader.CONTENT_ENCODING.asString(), ",");
+ if (contentEncodings != null)
+ {
+ for (ContentDecoder.Factory factory : getHttpDestination().getHttpClient().getContentDecoderFactories())
+ {
+ while (contentEncodings.hasMoreElements())
+ {
+ if (factory.getEncoding().equalsIgnoreCase(contentEncodings.nextElement()))
{
- while (contentEncodings.hasMoreElements())
- {
- if (factory.getEncoding().equalsIgnoreCase(contentEncodings.nextElement()))
- {
- this.decoder = factory.newContentDecoder();
- break;
- }
- }
+ this.decoder = factory.newContentDecoder();
+ break;
}
}
}
}
- return false;
+
+ return true;
}
- @Override
- public boolean content(ByteBuffer buffer)
+ /**
+ * Method to be invoked when response HTTP content is available.
+ * <p />
+ * This method takes case of decoding the content, if necessary, and notifying {@link org.eclipse.jetty.client.api.Response.ContentListener}s.
+ *
+ * @param exchange the HTTP exchange
+ * @param buffer the response HTTP content buffer
+ * @return whether the processing should continue
+ */
+ protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer)
{
- if (updateState(State.RECEIVE, State.RECEIVE))
+ out: while (true)
{
- HttpExchange exchange = connection.getExchange();
- // The exchange may be null if it failed concurrently
- if (exchange != null)
+ ResponseState current = responseState.get();
+ switch (current)
{
- HttpConversation conversation = exchange.getConversation();
- HttpResponse response = exchange.getResponse();
- LOG.debug("Content {}: {} bytes", response, buffer.remaining());
-
- ContentDecoder decoder = this.decoder;
- if (decoder != null)
+ case HEADERS:
+ case CONTENT:
{
- buffer = decoder.decode(buffer);
- LOG.debug("{} {}: {} bytes", decoder, response, buffer.remaining());
+ if (updateResponseState(current, ResponseState.CONTENT))
+ break out;
+ break;
}
-
- ResponseNotifier notifier = connection.getDestination().getResponseNotifier();
- notifier.notifyContent(conversation.getResponseListeners(), response, buffer);
+ default:
+ {
+ return false;
+ }
}
}
- return false;
- }
- @Override
- public boolean messageComplete()
- {
- if (updateState(State.RECEIVE, State.RECEIVE))
- success();
+ HttpResponse response = exchange.getResponse();
+ if (LOG.isDebugEnabled())
+ LOG.debug("Response content {}{}{}", response, System.getProperty("line.separator"), BufferUtil.toDetailString(buffer));
+
+ ContentDecoder decoder = this.decoder;
+ if (decoder != null)
+ {
+ buffer = decoder.decode(buffer);
+ if (LOG.isDebugEnabled())
+ LOG.debug("Response content decoded ({}) {}{}{}", decoder, response, System.getProperty("line.separator"), BufferUtil.toDetailString(buffer));
+ }
+
+ ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
+ notifier.notifyContent(exchange.getConversation().getResponseListeners(), response, buffer);
+
return true;
}
- protected boolean success()
+ /**
+ * Method to be invoked when the response is successful.
+ * <p />
+ * This method takes case of notifying {@link org.eclipse.jetty.client.api.Response.SuccessListener}s and possibly
+ * {@link org.eclipse.jetty.client.api.Response.CompleteListener}s (if the exchange is completed).
+ *
+ * @param exchange the HTTP exchange
+ * @return whether the response was processed as successful
+ */
+ protected boolean responseSuccess(HttpExchange exchange)
{
- HttpExchange exchange = connection.getExchange();
- if (exchange == null)
+ // Mark atomically the response as completed, with respect
+ // to concurrency between response success and response failure.
+ boolean completed = exchange.responseComplete();
+ if (!completed)
return false;
- AtomicMarkableReference<Result> completion = exchange.responseComplete(null);
- if (!completion.isMarked())
- return false;
+ // Reset to be ready for another response
+ reset();
- parser.reset();
- decoder = null;
-
- if (!updateState(State.RECEIVE, State.IDLE))
- throw new IllegalStateException();
-
- exchange.terminateResponse();
+ // Mark atomically the response as terminated and succeeded,
+ // with respect to concurrency between request and response.
+ // If there is a non-null result, then both sender and
+ // receiver are reset and ready to be reused, and the
+ // connection closed/pooled (depending on the transport).
+ Result result = exchange.terminateResponse(null);
HttpResponse response = exchange.getResponse();
+ LOG.debug("Response success {}", response);
List<Response.ResponseListener> listeners = exchange.getConversation().getResponseListeners();
- ResponseNotifier notifier = connection.getDestination().getResponseNotifier();
+ ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
notifier.notifySuccess(listeners, response);
- LOG.debug("Received {}", response);
- Result result = completion.getReference();
if (result != null)
{
- connection.complete(exchange, !result.isFailed());
+ boolean ordered = getHttpDestination().getHttpClient().isStrictEventOrdering();
+ if (!ordered)
+ channel.exchangeTerminated(result);
+ LOG.debug("Request/Response complete {}", response);
notifier.notifyComplete(listeners, result);
+ if (ordered)
+ channel.exchangeTerminated(result);
}
return true;
}
- protected boolean fail(Throwable failure)
+ /**
+ * Method to be invoked when the response is failed.
+ * <p />
+ * This method takes care of notifying {@link org.eclipse.jetty.client.api.Response.FailureListener}s.
+ *
+ * @param failure the response failure
+ * @return whether the response was processed as failed
+ */
+ protected boolean responseFailure(Throwable failure)
{
- HttpExchange exchange = connection.getExchange();
+ HttpExchange exchange = getHttpExchange();
// In case of a response error, the failure has already been notified
// and it is possible that a further attempt to read in the receive
// loop throws an exception that reenters here but without exchange;
@@ -350,88 +373,105 @@
if (exchange == null)
return false;
- AtomicMarkableReference<Result> completion = exchange.responseComplete(failure);
- if (!completion.isMarked())
+ // Mark atomically the response as completed, with respect
+ // to concurrency between response success and response failure.
+ boolean completed = exchange.responseComplete();
+ if (!completed)
return false;
- parser.close();
- decoder = null;
+ // Dispose to avoid further responses
+ dispose();
- while (true)
- {
- State current = state.get();
- if (updateState(current, State.FAILURE))
- break;
- }
-
- exchange.terminateResponse();
+ // Mark atomically the response as terminated and failed,
+ // with respect to concurrency between request and response.
+ Result result = exchange.terminateResponse(failure);
HttpResponse response = exchange.getResponse();
- HttpConversation conversation = exchange.getConversation();
- ResponseNotifier notifier = connection.getDestination().getResponseNotifier();
- notifier.notifyFailure(conversation.getResponseListeners(), response, failure);
- LOG.debug("Failed {} {}", response, failure);
+ LOG.debug("Response failure {} {}", response, failure);
+ List<Response.ResponseListener> listeners = exchange.getConversation().getResponseListeners();
+ ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
+ notifier.notifyFailure(listeners, response, failure);
- Result result = completion.getReference();
if (result != null)
{
- connection.complete(exchange, false);
-
- notifier.notifyComplete(conversation.getResponseListeners(), result);
+ boolean ordered = getHttpDestination().getHttpClient().isStrictEventOrdering();
+ if (!ordered)
+ channel.exchangeTerminated(result);
+ notifier.notifyComplete(listeners, result);
+ if (ordered)
+ channel.exchangeTerminated(result);
}
return true;
}
- @Override
- public void earlyEOF()
+ /**
+ * Resets this {@link HttpReceiver} state.
+ * <p />
+ * Subclasses should override (but remember to call {@code super}) to reset their own state.
+ * <p />
+ * Either this method or {@link #dispose()} is called.
+ */
+ protected void reset()
{
- failAndClose(new EOFException());
+ decoder = null;
+ responseState.set(ResponseState.IDLE);
}
- private void failAndClose(Throwable failure)
+ /**
+ * Disposes this {@link HttpReceiver} state.
+ * <p />
+ * Subclasses should override (but remember to call {@code super}) to dispose their own state.
+ * <p />
+ * Either this method or {@link #reset()} is called.
+ */
+ protected void dispose()
{
- fail(failure);
- connection.close();
- }
-
- @Override
- public void badMessage(int status, String reason)
- {
- HttpExchange exchange = connection.getExchange();
- HttpResponse response = exchange.getResponse();
- response.status(status).reason(reason);
- failAndClose(new HttpResponseException("HTTP protocol violation: bad response", response));
- }
-
- public void idleTimeout()
- {
- // If we cannot fail, it means a response arrived
- // just when we were timeout idling, so we don't close
- fail(new TimeoutException());
+ decoder = null;
+ responseState.set(ResponseState.FAILURE);
}
public boolean abort(Throwable cause)
{
- return fail(cause);
+ return responseFailure(cause);
}
- private boolean updateState(State from, State to)
+ private boolean updateResponseState(ResponseState from, ResponseState to)
{
- boolean updated = state.compareAndSet(from, to);
+ boolean updated = responseState.compareAndSet(from, to);
if (!updated)
- LOG.debug("State update failed: {} -> {}: {}", from, to, state.get());
+ LOG.debug("State update failed: {} -> {}: {}", from, to, responseState.get());
return updated;
}
- @Override
- public String toString()
+ /**
+ * The request states {@link HttpReceiver} goes through when receiving a response.
+ */
+ private enum ResponseState
{
- return String.format("%s@%x on %s", getClass().getSimpleName(), hashCode(), connection);
- }
-
- private enum State
- {
- IDLE, RECEIVE, FAILURE
+ /**
+ * The response is not yet received, the initial state
+ */
+ IDLE,
+ /**
+ * The response status code has been received
+ */
+ BEGIN,
+ /**
+ * The response headers are being received
+ */
+ HEADER,
+ /**
+ * All the response headers have been received
+ */
+ HEADERS,
+ /**
+ * The response content is being received
+ */
+ CONTENT,
+ /**
+ * The response is failed
+ */
+ FAILURE
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java
new file mode 100644
index 0000000..cd7e186
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java
@@ -0,0 +1,320 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Utility class that handles HTTP redirects.
+ * <p />
+ * Applications can disable redirection via {@link Request#followRedirects(boolean)}
+ * and then rely on this class to perform the redirect in a simpler way, for example:
+ * <pre>
+ * HttpRedirector redirector = new HttpRedirector(httpClient);
+ *
+ * Request request = httpClient.newRequest("http://host/path").followRedirects(false);
+ * ContentResponse response = request.send();
+ * while (redirector.isRedirect(response))
+ * {
+ * // Validate the redirect URI
+ * if (!validate(redirector.extractRedirectURI(response)))
+ * break;
+ *
+ * Result result = redirector.redirect(request, response);
+ * request = result.getRequest();
+ * response = result.getResponse();
+ * }
+ * </pre>
+ */
+public class HttpRedirector
+{
+ private static final Logger LOG = Log.getLogger(HttpRedirector.class);
+ private static final String SCHEME_REGEXP = "(^https?)";
+ private static final String AUTHORITY_REGEXP = "([^/\\?#]+)";
+ // The location may be relative so the scheme://authority part may be missing
+ private static final String DESTINATION_REGEXP = "(" + SCHEME_REGEXP + "://" + AUTHORITY_REGEXP + ")?";
+ private static final String PATH_REGEXP = "([^\\?#]*)";
+ private static final String QUERY_REGEXP = "([^#]*)";
+ private static final String FRAGMENT_REGEXP = "(.*)";
+ private static final Pattern URI_PATTERN = Pattern.compile(DESTINATION_REGEXP + PATH_REGEXP + QUERY_REGEXP + FRAGMENT_REGEXP);
+ private static final String ATTRIBUTE = HttpRedirector.class.getName() + ".redirects";
+
+ private final HttpClient client;
+ private final ResponseNotifier notifier;
+
+ public HttpRedirector(HttpClient client)
+ {
+ this.client = client;
+ this.notifier = new ResponseNotifier(client);
+ }
+
+ /**
+ * @param response the response to check for redirects
+ * @return whether the response code is a HTTP redirect code
+ */
+ public boolean isRedirect(Response response)
+ {
+ switch (response.getStatus())
+ {
+ case 301:
+ case 302:
+ case 303:
+ case 307:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Redirects the given {@code response}, blocking until the redirect is complete.
+ *
+ * @param request the original request that triggered the redirect
+ * @param response the response to the original request
+ * @return a {@link Result} object containing the request to the redirected location and its response
+ * @throws InterruptedException if the thread is interrupted while waiting for the redirect to complete
+ * @throws ExecutionException if the redirect failed
+ * @see #redirect(Request, Response, Response.CompleteListener)
+ */
+ public Result redirect(Request request, Response response) throws InterruptedException, ExecutionException
+ {
+ final AtomicReference<Result> resultRef = new AtomicReference<>();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Request redirect = redirect(request, response, new BufferingResponseListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ resultRef.set(new Result(result.getRequest(),
+ result.getRequestFailure(),
+ new HttpContentResponse(result.getResponse(), getContent(), getEncoding()),
+ result.getResponseFailure()));
+ latch.countDown();
+ }
+ });
+
+ try
+ {
+ latch.await();
+ Result result = resultRef.get();
+ if (result.isFailed())
+ throw new ExecutionException(result.getFailure());
+ return result;
+ }
+ catch (InterruptedException x)
+ {
+ // If the application interrupts, we need to abort the redirect
+ redirect.abort(x);
+ throw x;
+ }
+ }
+
+ /**
+ * Redirects the given {@code response} asynchronously.
+ *
+ * @param request the original request that triggered the redirect
+ * @param response the response to the original request
+ * @param listener the listener that receives response events
+ * @return the request to the redirected location
+ */
+ public Request redirect(Request request, Response response, Response.CompleteListener listener)
+ {
+ if (isRedirect(response))
+ {
+ String location = response.getHeaders().get("Location");
+ URI newURI = extractRedirectURI(response);
+ if (newURI != null)
+ {
+ LOG.debug("Redirecting to {} (Location: {})", newURI, location);
+ return redirect(request, response, listener, newURI);
+ }
+ else
+ {
+ fail(request, response, new HttpResponseException("Invalid 'Location' header: " + location, response));
+ return null;
+ }
+ }
+ else
+ {
+ fail(request, response, new HttpResponseException("Cannot redirect: " + response, response));
+ return null;
+ }
+ }
+
+ /**
+ * Extracts and sanitizes (by making it absolute and escaping paths and query parameters)
+ * the redirect URI of the given {@code response}.
+ *
+ * @param response the response to extract the redirect URI from
+ * @return the absolute redirect URI, or null if the response does not contain a valid redirect location
+ */
+ public URI extractRedirectURI(Response response)
+ {
+ String location = response.getHeaders().get("location");
+ if (location != null)
+ return sanitize(location);
+ return null;
+ }
+
+ private URI sanitize(String location)
+ {
+ // Redirects should be valid, absolute, URIs, with properly escaped paths and encoded
+ // query parameters. However, shit happens, and here we try our best to recover.
+
+ try
+ {
+ // Direct hit first: if passes, we're good
+ return new URI(location);
+ }
+ catch (URISyntaxException x)
+ {
+ Matcher matcher = URI_PATTERN.matcher(location);
+ if (matcher.matches())
+ {
+ String scheme = matcher.group(2);
+ String authority = matcher.group(3);
+ String path = matcher.group(4);
+ String query = matcher.group(5);
+ if (query.length() == 0)
+ query = null;
+ String fragment = matcher.group(6);
+ if (fragment.length() == 0)
+ fragment = null;
+ try
+ {
+ return new URI(scheme, authority, path, query, fragment);
+ }
+ catch (URISyntaxException xx)
+ {
+ // Give up
+ }
+ }
+ return null;
+ }
+ }
+
+ private Request redirect(Request request, Response response, Response.CompleteListener listener, URI newURI)
+ {
+ if (!newURI.isAbsolute())
+ newURI = request.getURI().resolve(newURI);
+
+ int status = response.getStatus();
+ switch (status)
+ {
+ case 301:
+ {
+ String method = request.getMethod();
+ if (HttpMethod.GET.is(method) || HttpMethod.HEAD.is(method) || HttpMethod.PUT.is(method))
+ return redirect(request, response, listener, newURI, method);
+ else if (HttpMethod.POST.is(method))
+ return redirect(request, response, listener, newURI, HttpMethod.GET.asString());
+ fail(request, response, new HttpResponseException("HTTP protocol violation: received 301 for non GET/HEAD/POST/PUT request", response));
+ return null;
+ }
+ case 302:
+ {
+ String method = request.getMethod();
+ if (HttpMethod.HEAD.is(method) || HttpMethod.PUT.is(method))
+ return redirect(request, response, listener, newURI, method);
+ else
+ return redirect(request, response, listener, newURI, HttpMethod.GET.asString());
+ }
+ case 303:
+ {
+ String method = request.getMethod();
+ if (HttpMethod.HEAD.is(method))
+ return redirect(request, response, listener, newURI, method);
+ else
+ return redirect(request, response, listener, newURI, HttpMethod.GET.asString());
+ }
+ case 307:
+ {
+ // Keep same method
+ return redirect(request, response, listener, newURI, request.getMethod());
+ }
+ default:
+ {
+ fail(request, response, new HttpResponseException("Unhandled HTTP status code " + status, response));
+ return null;
+ }
+ }
+ }
+
+ private Request redirect(final Request request, Response response, Response.CompleteListener listener, URI location, String method)
+ {
+ HttpConversation conversation = client.getConversation(request.getConversationID(), false);
+ Integer redirects = conversation == null ? Integer.valueOf(0) : (Integer)conversation.getAttribute(ATTRIBUTE);
+ if (redirects == null)
+ redirects = 0;
+ if (redirects < client.getMaxRedirects())
+ {
+ ++redirects;
+ if (conversation != null)
+ conversation.setAttribute(ATTRIBUTE, redirects);
+
+ Request redirect = client.copyRequest(request, location);
+
+ // Use given method
+ redirect.method(method);
+
+ redirect.onRequestBegin(new Request.BeginListener()
+ {
+ @Override
+ public void onBegin(Request redirect)
+ {
+ Throwable cause = request.getAbortCause();
+ if (cause != null)
+ redirect.abort(cause);
+ }
+ });
+
+ redirect.send(listener);
+ return redirect;
+ }
+ else
+ {
+ fail(request, response, new HttpResponseException("Max redirects exceeded " + redirects, response));
+ return null;
+ }
+ }
+
+ protected void fail(Request request, Response response, Throwable failure)
+ {
+ HttpConversation conversation = client.getConversation(request.getConversationID(), false);
+ conversation.updateResponseListeners(null);
+ List<Response.ResponseListener> listeners = conversation.getResponseListeners();
+ notifier.notifyFailure(listeners, response, failure);
+ notifier.notifyComplete(listeners, new Result(request, response, failure));
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
index 0986a6a..301b3c4 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
@@ -37,11 +37,13 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.client.util.PathContentProvider;
import org.eclipse.jetty.http.HttpField;
@@ -60,6 +62,7 @@
private final Map<String, Object> attributes = new HashMap<>();
private final List<RequestListener> requestListeners = new ArrayList<>();
private final List<Response.ResponseListener> responseListeners = new ArrayList<>();
+ private final AtomicReference<Throwable> aborted = new AtomicReference<>();
private final HttpClient client;
private final long conversation;
private final String host;
@@ -68,13 +71,12 @@
private String scheme;
private String path;
private String query;
- private String method;
- private HttpVersion version;
+ private String method = HttpMethod.GET.asString();
+ private HttpVersion version = HttpVersion.HTTP_1_1;
private long idleTimeout;
private long timeout;
private ContentProvider content;
private boolean followRedirects;
- private volatile Throwable aborted;
public HttpRequest(HttpClient client, URI uri)
{
@@ -91,8 +93,11 @@
path = uri.getRawPath();
query = uri.getRawQuery();
extractParams(query);
- this.uri = buildURI(true);
followRedirects(client.isFollowRedirects());
+ idleTimeout = client.getIdleTimeout();
+ HttpField acceptEncodingField = client.getAcceptEncodingField();
+ if (acceptEncodingField != null)
+ headers.put(acceptEncodingField);
}
@Override
@@ -111,7 +116,7 @@
public Request scheme(String scheme)
{
this.scheme = scheme;
- this.uri = buildURI(true);
+ this.uri = null;
return this;
}
@@ -128,13 +133,7 @@
}
@Override
- public HttpMethod getMethod()
- {
- return HttpMethod.fromString(method);
- }
-
- @Override
- public String method()
+ public String getMethod()
{
return method;
}
@@ -142,8 +141,7 @@
@Override
public Request method(HttpMethod method)
{
- this.method = method.asString();
- return this;
+ return method(method.asString());
}
@Override
@@ -176,9 +174,9 @@
params.clear();
extractParams(query);
}
- this.uri = buildURI(true);
if (uri.isAbsolute())
this.path = buildURI(false).toString();
+ this.uri = null;
return this;
}
@@ -191,7 +189,9 @@
@Override
public URI getURI()
{
- return uri;
+ if (uri != null)
+ return uri;
+ return uri = buildURI(true);
}
@Override
@@ -203,7 +203,7 @@
@Override
public Request version(HttpVersion version)
{
- this.version = version;
+ this.version = Objects.requireNonNull(version);
return this;
}
@@ -212,7 +212,7 @@
{
params.add(name, value);
this.query = buildQuery();
- this.uri = buildURI(true);
+ this.uri = null;
return this;
}
@@ -275,6 +275,7 @@
}
@Override
+ @SuppressWarnings("unchecked")
public <T extends RequestListener> List<T> getRequestListeners(Class<T> type)
{
// This method is invoked often in a request/response conversation,
@@ -479,6 +480,20 @@
}
@Override
+ public Request onComplete(final Response.CompleteListener listener)
+ {
+ this.responseListeners.add(new Response.CompleteListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ listener.onComplete(result);
+ }
+ });
+ return this;
+ }
+
+ @Override
public ContentProvider getContent()
{
return content;
@@ -558,12 +573,12 @@
FutureResponseListener listener = new FutureResponseListener(this);
send(this, listener);
- long timeout = getTimeout();
- if (timeout <= 0)
- return listener.get();
-
try
{
+ long timeout = getTimeout();
+ if (timeout <= 0)
+ return listener.get();
+
return listener.get(timeout, TimeUnit.MILLISECONDS);
}
catch (InterruptedException | TimeoutException x)
@@ -597,16 +612,19 @@
@Override
public boolean abort(Throwable cause)
{
- aborted = Objects.requireNonNull(cause);
- // The conversation may be null if it is already completed
- HttpConversation conversation = client.getConversation(getConversationID(), false);
- return conversation != null && conversation.abort(cause);
+ if (aborted.compareAndSet(null, Objects.requireNonNull(cause)))
+ {
+ // The conversation may be null if it is already completed
+ HttpConversation conversation = client.getConversation(getConversationID(), false);
+ return conversation != null && conversation.abort(cause);
+ }
+ return false;
}
@Override
public Throwable getAbortCause()
{
- return aborted;
+ return aborted.get();
}
private String buildQuery()
@@ -615,13 +633,13 @@
for (Iterator<Fields.Field> iterator = params.iterator(); iterator.hasNext();)
{
Fields.Field field = iterator.next();
- String[] values = field.values();
- for (int i = 0; i < values.length; ++i)
+ List<String> values = field.getValues();
+ for (int i = 0; i < values.size(); ++i)
{
if (i > 0)
result.append("&");
- result.append(field.name()).append("=");
- result.append(urlEncode(values[i]));
+ result.append(field.getName()).append("=");
+ result.append(urlEncode(values.get(i)));
}
if (iterator.hasNext())
result.append("&");
@@ -681,13 +699,13 @@
path += "?" + query;
URI result = URI.create(path);
if (!result.isAbsolute() && !result.isOpaque())
- result = URI.create(client.address(getScheme(), getHost(), getPort()) + path);
+ result = URI.create(new Origin(getScheme(), getHost(), getPort()).asString() + path);
return result;
}
@Override
public String toString()
{
- return String.format("%s[%s %s %s]@%x", HttpRequest.class.getSimpleName(), method(), getPath(), getVersion(), hashCode());
+ return String.format("%s[%s %s %s]@%x", HttpRequest.class.getSimpleName(), getMethod(), getPath(), getVersion(), hashCode());
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java
index 907f05d..ed126f9 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java
@@ -106,6 +106,6 @@
@Override
public String toString()
{
- return String.format("%s[%s %d %s]", HttpResponse.class.getSimpleName(), getVersion(), getStatus(), getReason());
+ return String.format("%s[%s %d %s]@%x", HttpResponse.class.getSimpleName(), getVersion(), getStatus(), getReason(), hashCode());
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
index 4d607b8..53e7584 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
@@ -19,73 +19,119 @@
package org.eclipse.jetty.client;
import java.nio.ByteBuffer;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Result;
-import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
-import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-public class HttpSender implements AsyncContentProvider.Listener
+/**
+ * {@link HttpSender} abstracts the algorithm to send HTTP requests, so that subclasses only implement
+ * the transport-specific code to send requests over the wire, implementing
+ * {@link #sendHeaders(HttpExchange, HttpContent, Callback)} and
+ * {@link #sendContent(HttpExchange, HttpContent, Callback)}.
+ * <p />
+ * {@link HttpSender} governs two state machines.
+ * <p />
+ * The request state machine is updated by {@link HttpSender} as the various steps of sending a request
+ * are executed, see {@link RequestState}.
+ * At any point in time, a user thread may abort the request, which may (if the request has not been
+ * completely sent yet) move the request state machine to {@link RequestState#FAILURE}.
+ * The request state machine guarantees that the request steps are executed (by I/O threads) only if
+ * the request has not been failed already.
+ * <p />
+ * The sender state machine is updated by {@link HttpSender} from three sources: deferred content notifications
+ * (via {@link #onContent()}), 100-continue notifications (via {@link #proceed(HttpExchange, boolean)})
+ * and normal request send (via {@link #sendContent(HttpExchange, HttpContent, Callback)}).
+ * This state machine must guarantee that the request sending is never executed concurrently: only one of
+ * those sources may trigger the call to {@link #sendContent(HttpExchange, HttpContent, Callback)}.
+ *
+ * @see HttpReceiver
+ */
+public abstract class HttpSender implements AsyncContentProvider.Listener
{
- private static final Logger LOG = Log.getLogger(HttpSender.class);
- private static final String EXPECT_100_ATTRIBUTE = HttpSender.class.getName() + ".expect100";
+ protected static final Logger LOG = Log.getLogger(HttpSender.class);
- private final AtomicReference<State> state = new AtomicReference<>(State.IDLE);
- private final AtomicReference<SendState> sendState = new AtomicReference<>(SendState.IDLE);
- private final HttpGenerator generator = new HttpGenerator();
- private final HttpConnection connection;
- private Iterator<ByteBuffer> contentIterator;
- private ContinueContentChunk continueContentChunk;
+ private final AtomicReference<RequestState> requestState = new AtomicReference<>(RequestState.QUEUED);
+ private final AtomicReference<SenderState> senderState = new AtomicReference<>(SenderState.IDLE);
+ private final Callback commitCallback = new CommitCallback();
+ private final Callback contentCallback = new ContentCallback();
+ private final Callback lastCallback = new LastContentCallback();
+ private final HttpChannel channel;
+ private volatile HttpContent content;
- public HttpSender(HttpConnection connection)
+ protected HttpSender(HttpChannel channel)
{
- this.connection = connection;
+ this.channel = channel;
+ }
+
+ protected HttpChannel getHttpChannel()
+ {
+ return channel;
+ }
+
+ protected HttpExchange getHttpExchange()
+ {
+ return channel.getHttpExchange();
}
@Override
public void onContent()
{
+ HttpExchange exchange = getHttpExchange();
+ if (exchange == null)
+ return;
+
while (true)
{
- SendState current = sendState.get();
+ SenderState current = senderState.get();
switch (current)
{
case IDLE:
{
- if (updateSendState(current, SendState.EXECUTE))
+ if (updateSenderState(current, SenderState.SENDING))
{
- LOG.debug("Deferred content available, sending");
- send();
+ LOG.debug("Deferred content available, idle -> sending");
+ HttpContent content = this.content;
+ content.advance();
+ sendContent(exchange, content, contentCallback);
return;
}
break;
}
- case EXECUTE:
+ case SENDING:
{
- if (updateSendState(current, SendState.SCHEDULE))
+ if (updateSenderState(current, SenderState.SCHEDULED))
{
- LOG.debug("Deferred content available, scheduling");
+ LOG.debug("Deferred content available, sending -> scheduled");
return;
}
break;
}
- case SCHEDULE:
+ case EXPECTING:
{
- LOG.debug("Deferred content available, queueing");
+ if (updateSenderState(current, SenderState.SCHEDULED))
+ {
+ LOG.debug("Deferred content available, expecting -> scheduled");
+ return;
+ }
+ break;
+ }
+ case WAITING:
+ {
+ LOG.debug("Deferred content available, waiting");
+ return;
+ }
+ case SCHEDULED:
+ {
+ LOG.debug("Deferred content available, scheduled");
return;
}
default:
@@ -98,9 +144,6 @@
public void send(HttpExchange exchange)
{
- if (!updateState(State.IDLE, State.BEGIN))
- throw new IllegalStateException();
-
Request request = exchange.getRequest();
Throwable cause = request.getAbortCause();
if (cause != null)
@@ -109,638 +152,613 @@
}
else
{
- LOG.debug("Sending {}", request);
- RequestNotifier notifier = connection.getDestination().getRequestNotifier();
- notifier.notifyBegin(request);
+ if (!queuedToBegin(request))
+ throw new IllegalStateException();
- ContentProvider content = request.getContent();
- this.contentIterator = content == null ? Collections.<ByteBuffer>emptyIterator() : content.iterator();
+ if (!updateSenderState(SenderState.IDLE, expects100Continue(request) ? SenderState.EXPECTING : SenderState.SENDING))
+ throw new IllegalStateException();
- boolean updated = updateSendState(SendState.IDLE, SendState.EXECUTE);
- assert updated;
+ ContentProvider contentProvider = request.getContent();
+ HttpContent content = this.content = new HttpContent(contentProvider);
// Setting the listener may trigger calls to onContent() by other
- // threads so we must set it only after the state has been updated
- if (content instanceof AsyncContentProvider)
- ((AsyncContentProvider)content).setListener(this);
+ // threads so we must set it only after the sender state has been updated
+ if (contentProvider instanceof AsyncContentProvider)
+ ((AsyncContentProvider)contentProvider).setListener(this);
- send();
+ if (!beginToHeaders(request))
+ return;
+
+ sendHeaders(exchange, content, commitCallback);
}
}
- private void send()
+ protected boolean expects100Continue(Request request)
{
- SendState currentSendState = sendState.get();
- assert currentSendState != SendState.IDLE : currentSendState;
+ return request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
+ }
- HttpClient client = connection.getHttpClient();
- ByteBufferPool bufferPool = client.getByteBufferPool();
- ByteBuffer header = null;
- ByteBuffer chunk = null;
- try
+ protected boolean queuedToBegin(Request request)
+ {
+ if (!updateRequestState(RequestState.QUEUED, RequestState.BEGIN))
+ return false;
+ LOG.debug("Request begin {}", request);
+ RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
+ notifier.notifyBegin(request);
+ return true;
+ }
+
+ protected boolean beginToHeaders(Request request)
+ {
+ if (!updateRequestState(RequestState.BEGIN, RequestState.HEADERS))
+ return false;
+ if (LOG.isDebugEnabled())
+ LOG.debug("Request headers {}{}{}", request, System.getProperty("line.separator"), request.getHeaders().toString().trim());
+ RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
+ notifier.notifyHeaders(request);
+ return true;
+ }
+
+ protected boolean headersToCommit(Request request)
+ {
+ if (!updateRequestState(RequestState.HEADERS, RequestState.COMMIT))
+ return false;
+ LOG.debug("Request committed {}", request);
+ RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
+ notifier.notifyCommit(request);
+ return true;
+ }
+
+ protected boolean someToContent(Request request, ByteBuffer content)
+ {
+ RequestState current = requestState.get();
+ switch (current)
{
- HttpExchange exchange = connection.getExchange();
- // The exchange may be null if it failed concurrently
- if (exchange == null)
- return;
+ case COMMIT:
+ case CONTENT:
+ {
+ if (!updateRequestState(current, RequestState.CONTENT))
+ return false;
+ if (LOG.isDebugEnabled())
+ LOG.debug("Request content {}{}{}", request, System.getProperty("line.separator"), BufferUtil.toDetailString(content));
+ RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
+ notifier.notifyContent(request, content);
+ return true;
+ }
+ case FAILURE:
+ {
+ return false;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
- final Request request = exchange.getRequest();
+ protected boolean someToSuccess(HttpExchange exchange)
+ {
+ RequestState current = requestState.get();
+ switch (current)
+ {
+ case COMMIT:
+ case CONTENT:
+ {
+ // Mark atomically the request as completed, with respect
+ // to concurrency between request success and request failure.
+ boolean completed = exchange.requestComplete();
+ if (!completed)
+ return false;
+
+ // Reset to be ready for another request
+ reset();
+
+ // Mark atomically the request as terminated and succeeded,
+ // with respect to concurrency between request and response.
+ Result result = exchange.terminateRequest(null);
+
+ // It is important to notify completion *after* we reset because
+ // the notification may trigger another request/response
+ Request request = exchange.getRequest();
+ LOG.debug("Request success {}", request);
+ HttpDestination destination = getHttpChannel().getHttpDestination();
+ destination.getRequestNotifier().notifySuccess(exchange.getRequest());
+
+ if (result != null)
+ {
+ boolean ordered = destination.getHttpClient().isStrictEventOrdering();
+ if (!ordered)
+ channel.exchangeTerminated(result);
+ LOG.debug("Request/Response succeded {}", request);
+ HttpConversation conversation = exchange.getConversation();
+ destination.getResponseNotifier().notifyComplete(conversation.getResponseListeners(), result);
+ if (ordered)
+ channel.exchangeTerminated(result);
+ }
+
+ return true;
+ }
+ case FAILURE:
+ {
+ return false;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ protected boolean anyToFailure(Throwable failure)
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange == null)
+ return false;
+
+ // Mark atomically the request as completed, with respect
+ // to concurrency between request success and request failure.
+ boolean completed = exchange.requestComplete();
+ if (!completed)
+ return false;
+
+ // Dispose to avoid further requests
+ RequestState requestState = dispose();
+
+ // Mark atomically the request as terminated and failed,
+ // with respect to concurrency between request and response.
+ Result result = exchange.terminateRequest(failure);
+
+ Request request = exchange.getRequest();
+ LOG.debug("Request failure {} {}", exchange, failure);
+ HttpDestination destination = getHttpChannel().getHttpDestination();
+ destination.getRequestNotifier().notifyFailure(request, failure);
+
+ boolean notCommitted = isBeforeCommit(requestState);
+ if (result == null && notCommitted && request.getAbortCause() == null)
+ {
+ // Complete the response from here
+ if (exchange.responseComplete())
+ {
+ result = exchange.terminateResponse(failure);
+ LOG.debug("Failed response from request {}", exchange);
+ }
+ }
+
+ if (result != null)
+ {
+ boolean ordered = destination.getHttpClient().isStrictEventOrdering();
+ if (!ordered)
+ channel.exchangeTerminated(result);
+ LOG.debug("Request/Response failed {}", request);
HttpConversation conversation = exchange.getConversation();
- HttpGenerator.RequestInfo requestInfo = null;
+ destination.getResponseNotifier().notifyComplete(conversation.getResponseListeners(), result);
+ if (ordered)
+ channel.exchangeTerminated(result);
+ }
- // Determine whether we have already received the 100 Continue response or not
- // If it was not received yet, we need to save the content and wait for it
- boolean expect100HeaderPresent = request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
- final boolean expecting100ContinueResponse = expect100HeaderPresent && conversation.getAttribute(EXPECT_100_ATTRIBUTE) == null;
- if (expecting100ContinueResponse)
- conversation.setAttribute(EXPECT_100_ATTRIBUTE, Boolean.TRUE);
+ return true;
+ }
- ContentChunk contentChunk = continueContentChunk;
- continueContentChunk = null;
- if (contentChunk == null)
- contentChunk = new ContentChunk(contentIterator);
+ /**
+ * Implementations should send the HTTP headers over the wire, possibly with some content,
+ * in a single write, and notify the given {@code callback} of the result of this operation.
+ * <p />
+ * If there is more content to send, then {@link #sendContent(HttpExchange, HttpContent, Callback)}
+ * will be invoked.
+ *
+ * @param exchange the exchange to send
+ * @param content the content to send
+ * @param callback the callback to notify
+ */
+ protected abstract void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback);
+ /**
+ * Implementations should send the content at the {@link HttpContent} cursor position over the wire.
+ * <p />
+ * The {@link HttpContent} cursor is advanced by {@link HttpSender} at the right time, and if more
+ * content needs to be sent, this method is invoked again; subclasses need only to send the content
+ * at the {@link HttpContent} cursor position.
+ * <p />
+ * This method is invoked one last time when {@link HttpContent#isConsumed()} is true and therefore
+ * there is no actual content to send.
+ * This is done to allow subclasses to write "terminal" bytes (such as the terminal chunk when the
+ * transfer encoding is chunked) if their protocol needs to.
+ *
+ * @param exchange the exchange to send
+ * @param content the content to send
+ * @param callback the callback to notify
+ */
+ protected abstract void sendContent(HttpExchange exchange, HttpContent content, Callback callback);
+
+ protected void reset()
+ {
+ content = null;
+ requestState.set(RequestState.QUEUED);
+ senderState.set(SenderState.IDLE);
+ }
+
+ protected RequestState dispose()
+ {
+ while (true)
+ {
+ RequestState current = requestState.get();
+ if (updateRequestState(current, RequestState.FAILURE))
+ return current;
+ }
+ }
+
+ public void proceed(HttpExchange exchange, boolean proceed)
+ {
+ if (!expects100Continue(exchange.getRequest()))
+ return;
+
+ if (proceed)
+ {
while (true)
{
- ByteBuffer content = contentChunk.content;
- final ByteBuffer contentBuffer = content == null ? null : content.slice();
-
- HttpGenerator.Result result = generator.generateRequest(requestInfo, header, chunk, content, contentChunk.lastContent);
- switch (result)
+ SenderState current = senderState.get();
+ switch (current)
{
- case NEED_INFO:
+ case EXPECTING:
{
- ContentProvider requestContent = request.getContent();
- long contentLength = requestContent == null ? -1 : requestContent.getLength();
- String path = request.getPath();
- String query = request.getQuery();
- if (query != null)
- path += "?" + query;
- requestInfo = new HttpGenerator.RequestInfo(request.getVersion(), request.getHeaders(), contentLength, request.method(), path);
- break;
+ // We are still sending the headers, but we already got the 100 Continue.
+ // Move to SEND so that the commit callback can send the content.
+ if (!updateSenderState(current, SenderState.SENDING))
+ break;
+ LOG.debug("Proceed while expecting");
+ return;
}
- case NEED_HEADER:
+ case WAITING:
{
- header = bufferPool.acquire(client.getRequestBufferSize(), false);
- break;
- }
- case NEED_CHUNK:
- {
- chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
- break;
- }
- case FLUSH:
- {
- out:
- while (true)
+ // We received the 100 Continue, send the content if any.
+ // First update the sender state to be sure to be the one
+ // to call sendContent() since we race with onContent().
+ if (!updateSenderState(current, SenderState.SENDING))
+ break;
+ HttpContent content = this.content;
+ if (content.advance())
{
- State currentState = state.get();
- switch (currentState)
- {
- case BEGIN:
- {
- if (!updateState(currentState, State.HEADERS))
- continue;
- RequestNotifier notifier = connection.getDestination().getRequestNotifier();
- notifier.notifyHeaders(request);
- break out;
- }
- case HEADERS:
- case COMMIT:
- {
- // State update is performed after the write in commit()
- break out;
- }
- case FAILURE:
- {
- // Failed concurrently, avoid the write since
- // the connection is probably already closed
- return;
- }
- default:
- {
- throw new IllegalStateException();
- }
- }
+ // There is content to send
+ LOG.debug("Proceed while waiting");
+ sendContent(exchange, content, contentCallback);
}
-
- StatefulExecutorCallback callback = new StatefulExecutorCallback(client.getExecutor())
+ else
{
- @Override
- protected void onSucceeded()
- {
- LOG.debug("Write succeeded for {}", request);
-
- if (!processWrite(request, contentBuffer, expecting100ContinueResponse))
- return;
-
- send();
- }
-
- @Override
- protected void onFailed(Throwable x)
- {
- fail(x);
- }
- };
-
- if (expecting100ContinueResponse)
- {
- // Save the content waiting for the 100 Continue response
- continueContentChunk = new ContinueContentChunk(contentChunk);
- }
-
- write(callback, header, chunk, expecting100ContinueResponse ? null : content);
-
- if (callback.process())
- {
- LOG.debug("Write pending for {}", request);
- return;
- }
-
- if (callback.isSucceeded())
- {
- if (!processWrite(request, contentBuffer, expecting100ContinueResponse))
- return;
-
- // Send further content
- contentChunk = new ContentChunk(contentIterator);
-
- if (contentChunk.isDeferred())
- {
- out:
- while (true)
- {
- currentSendState = sendState.get();
- switch (currentSendState)
- {
- case EXECUTE:
- {
- if (updateSendState(currentSendState, SendState.IDLE))
- {
- LOG.debug("Waiting for deferred content for {}", request);
- return;
- }
- break;
- }
- case SCHEDULE:
- {
- if (updateSendState(currentSendState, SendState.EXECUTE))
- {
- LOG.debug("Deferred content available for {}", request);
- break out;
- }
- break;
- }
- default:
- {
- throw new IllegalStateException();
- }
- }
- }
- }
- }
- break;
- }
- case SHUTDOWN_OUT:
- {
- shutdownOutput();
- break;
- }
- case CONTINUE:
- {
- break;
- }
- case DONE:
- {
- if (generator.isEnd())
- {
- out: while (true)
- {
- currentSendState = sendState.get();
- switch (currentSendState)
- {
- case EXECUTE:
- case SCHEDULE:
- {
- if (!updateSendState(currentSendState, SendState.IDLE))
- throw new IllegalStateException();
- break out;
- }
- default:
- {
- throw new IllegalStateException();
- }
- }
- }
- success();
+ // No content to send yet - it's deferred.
+ // We may fail the update as onContent() moved to SCHEDULE.
+ if (!updateSenderState(SenderState.SENDING, SenderState.IDLE))
+ break;
+ LOG.debug("Proceed deferred");
}
return;
}
+ case SCHEDULED:
+ {
+ // We lost the race with onContent() to update the state, try again
+ if (!updateSenderState(current, SenderState.WAITING))
+ throw new IllegalStateException();
+ LOG.debug("Proceed while scheduled");
+ break;
+ }
default:
{
- throw new IllegalStateException("Unknown result " + result);
+ throw new IllegalStateException();
}
}
}
}
- catch (Exception x)
+ else
{
- LOG.debug(x);
- fail(x);
- }
- finally
- {
- releaseBuffers(bufferPool, header, chunk);
+ anyToFailure(new HttpRequestException("Expectation failed", exchange.getRequest()));
}
}
- private boolean processWrite(Request request, ByteBuffer content, boolean expecting100ContinueResponse)
+ public boolean abort(Throwable failure)
{
- if (!commit(request))
- return false;
-
- if (content != null && content.hasRemaining())
- {
- RequestNotifier notifier = connection.getDestination().getRequestNotifier();
- notifier.notifyContent(request, content);
- }
-
- if (expecting100ContinueResponse)
- {
- LOG.debug("Expecting 100 Continue for {}", request);
- continueContentChunk.signal();
- return false;
- }
-
- return true;
- }
-
- public void proceed(boolean proceed)
- {
- ContinueContentChunk contentChunk = continueContentChunk;
- if (contentChunk != null)
- {
- if (proceed)
- {
- // Method send() must not be executed concurrently.
- // The write in send() may arrive to the server and the server reply with 100 Continue
- // before send() exits; the processing of the 100 Continue will invoke this method
- // which in turn invokes send(), with the risk of a concurrent invocation of send().
- // Therefore we wait here on the ContinueContentChunk to send, and send() will signal
- // when it is ok to proceed.
- LOG.debug("Proceeding {}", connection.getExchange());
- contentChunk.await();
- send();
- }
- else
- {
- HttpExchange exchange = connection.getExchange();
- if (exchange != null)
- fail(new HttpRequestException("Expectation failed", exchange.getRequest()));
- }
- }
- }
-
- private void write(Callback callback, ByteBuffer header, ByteBuffer chunk, ByteBuffer content)
- {
- int mask = 0;
- if (header != null)
- mask += 1;
- if (chunk != null)
- mask += 2;
- if (content != null)
- mask += 4;
-
- EndPoint endPoint = connection.getEndPoint();
- switch (mask)
- {
- case 0:
- endPoint.write(callback, BufferUtil.EMPTY_BUFFER);
- break;
- case 1:
- endPoint.write(callback, header);
- break;
- case 2:
- endPoint.write(callback, chunk);
- break;
- case 3:
- endPoint.write(callback, header, chunk);
- break;
- case 4:
- endPoint.write(callback, content);
- break;
- case 5:
- endPoint.write(callback, header, content);
- break;
- case 6:
- endPoint.write(callback, chunk, content);
- break;
- case 7:
- endPoint.write(callback, header, chunk, content);
- break;
- default:
- throw new IllegalStateException();
- }
- }
-
- protected boolean commit(Request request)
- {
- while (true)
- {
- State current = state.get();
- switch (current)
- {
- case HEADERS:
- if (!updateState(current, State.COMMIT))
- continue;
- LOG.debug("Committed {}", request);
- RequestNotifier notifier = connection.getDestination().getRequestNotifier();
- notifier.notifyCommit(request);
- return true;
- case COMMIT:
- if (!updateState(current, State.COMMIT))
- continue;
- return true;
- case FAILURE:
- return false;
- default:
- throw new IllegalStateException();
- }
- }
- }
-
- protected boolean success()
- {
- HttpExchange exchange = connection.getExchange();
- if (exchange == null)
- return false;
-
- AtomicMarkableReference<Result> completion = exchange.requestComplete(null);
- if (!completion.isMarked())
- return false;
-
- generator.reset();
-
- if (!updateState(State.COMMIT, State.IDLE))
- throw new IllegalStateException();
-
- exchange.terminateRequest();
-
- // It is important to notify completion *after* we reset because
- // the notification may trigger another request/response
-
- HttpDestination destination = connection.getDestination();
- Request request = exchange.getRequest();
- destination.getRequestNotifier().notifySuccess(request);
- LOG.debug("Sent {}", request);
-
- Result result = completion.getReference();
- if (result != null)
- {
- connection.complete(exchange, !result.isFailed());
-
- HttpConversation conversation = exchange.getConversation();
- destination.getResponseNotifier().notifyComplete(conversation.getResponseListeners(), result);
- }
-
- return true;
- }
-
- protected boolean fail(Throwable failure)
- {
- HttpExchange exchange = connection.getExchange();
- if (exchange == null)
- return false;
-
- AtomicMarkableReference<Result> completion = exchange.requestComplete(failure);
- if (!completion.isMarked())
- return false;
-
- generator.abort();
-
- State current;
- while (true)
- {
- current = state.get();
- if (updateState(current, State.FAILURE))
- break;
- }
-
- shutdownOutput();
-
- exchange.terminateRequest();
-
- HttpDestination destination = connection.getDestination();
- Request request = exchange.getRequest();
- destination.getRequestNotifier().notifyFailure(request, failure);
- LOG.debug("Failed {} {}", request, failure);
-
- Result result = completion.getReference();
- boolean notCommitted = isBeforeCommit(current);
- if (result == null && notCommitted && request.getAbortCause() == null)
- {
- completion = exchange.responseComplete(failure);
- if (completion.isMarked())
- {
- result = completion.getReference();
- exchange.terminateResponse();
- LOG.debug("Failed on behalf {}", exchange);
- }
- }
-
- if (result != null)
- {
- connection.complete(exchange, false);
-
- HttpConversation conversation = exchange.getConversation();
- destination.getResponseNotifier().notifyComplete(conversation.getResponseListeners(), result);
- }
-
- return true;
- }
-
- private void shutdownOutput()
- {
- connection.getEndPoint().shutdownOutput();
- }
-
- public boolean abort(Throwable cause)
- {
- State current = state.get();
+ RequestState current = requestState.get();
boolean abortable = isBeforeCommit(current) ||
- current == State.COMMIT && contentIterator.hasNext();
- return abortable && fail(cause);
+ isSending(current) && !content.isLast();
+ return abortable && anyToFailure(failure);
}
- private boolean isBeforeCommit(State state)
+ protected boolean updateRequestState(RequestState from, RequestState to)
{
- return state == State.IDLE || state == State.BEGIN || state == State.HEADERS;
- }
-
- private void releaseBuffers(ByteBufferPool bufferPool, ByteBuffer header, ByteBuffer chunk)
- {
- if (!BufferUtil.hasContent(header))
- bufferPool.release(header);
- if (!BufferUtil.hasContent(chunk))
- bufferPool.release(chunk);
- }
-
- private boolean updateState(State from, State to)
- {
- boolean updated = state.compareAndSet(from, to);
+ boolean updated = requestState.compareAndSet(from, to);
if (!updated)
- LOG.debug("State update failed: {} -> {}: {}", from, to, state.get());
+ LOG.debug("RequestState update failed: {} -> {}: {}", from, to, requestState.get());
return updated;
}
- private boolean updateSendState(SendState from, SendState to)
+ private boolean updateSenderState(SenderState from, SenderState to)
{
- boolean updated = sendState.compareAndSet(from, to);
+ boolean updated = senderState.compareAndSet(from, to);
if (!updated)
- LOG.debug("Send state update failed: {} -> {}: {}", from, to, sendState.get());
+ LOG.debug("SenderState update failed: {} -> {}: {}", from, to, senderState.get());
return updated;
}
- private enum State
+ private boolean isBeforeCommit(RequestState requestState)
{
- IDLE, BEGIN, HEADERS, COMMIT, FAILURE
- }
-
- private enum SendState
- {
- IDLE, EXECUTE, SCHEDULE
- }
-
- private static abstract class StatefulExecutorCallback implements Callback, Runnable
- {
- private final AtomicReference<State> state = new AtomicReference<>(State.INCOMPLETE);
- private final Executor executor;
-
- private StatefulExecutorCallback(Executor executor)
+ switch (requestState)
{
- this.executor = executor;
+ case QUEUED:
+ case BEGIN:
+ case HEADERS:
+ return true;
+ default:
+ return false;
}
+ }
+ private boolean isSending(RequestState requestState)
+ {
+ switch (requestState)
+ {
+ case COMMIT:
+ case CONTENT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * The request states {@link HttpSender} goes through when sending a request.
+ */
+ protected enum RequestState
+ {
+ /**
+ * The request is queued, the initial state
+ */
+ QUEUED,
+ /**
+ * The request has been dequeued
+ */
+ BEGIN,
+ /**
+ * The request headers (and possibly some content) is about to be sent
+ */
+ HEADERS,
+ /**
+ * The request headers (and possibly some content) have been sent
+ */
+ COMMIT,
+ /**
+ * The request content is being sent
+ */
+ CONTENT,
+ /**
+ * The request is failed
+ */
+ FAILURE
+ }
+
+ /**
+ * The sender states {@link HttpSender} goes through when sending a request.
+ */
+ private enum SenderState
+ {
+ /**
+ * {@link HttpSender} is not sending the request
+ */
+ IDLE,
+ /**
+ * {@link HttpSender} is sending the request
+ */
+ SENDING,
+ /**
+ * {@link HttpSender} is sending the headers but will wait for 100-Continue before sending the content
+ */
+ EXPECTING,
+ /**
+ * {@link HttpSender} is waiting for 100-Continue
+ */
+ WAITING,
+ /**
+ * {@link HttpSender} is currently sending the request, and deferred content is available to be sent
+ */
+ SCHEDULED
+ }
+
+ private class CommitCallback implements Callback
+ {
@Override
- public final void succeeded()
- {
- State previous = state.get();
- while (true)
- {
- if (state.compareAndSet(previous, State.SUCCEEDED))
- break;
- previous = state.get();
- }
- if (previous == State.PENDING)
- executor.execute(this);
- }
-
- @Override
- public final void run()
- {
- onSucceeded();
- }
-
- protected abstract void onSucceeded();
-
- @Override
- public final void failed(final Throwable x)
- {
- State previous = state.get();
- while (true)
- {
- if (state.compareAndSet(previous, State.FAILED))
- break;
- previous = state.get();
- }
- if (previous == State.PENDING)
- {
- executor.execute(new Runnable()
- {
- @Override
- public void run()
- {
- onFailed(x);
- }
- });
- }
- else
- {
- onFailed(x);
- }
- }
-
- protected abstract void onFailed(Throwable x);
-
- public boolean process()
- {
- return state.compareAndSet(State.INCOMPLETE, State.PENDING);
- }
-
- public boolean isSucceeded()
- {
- return state.get() == State.SUCCEEDED;
- }
-
- public boolean isFailed()
- {
- return state.get() == State.FAILED;
- }
-
- private enum State
- {
- INCOMPLETE, PENDING, SUCCEEDED, FAILED
- }
- }
-
- private class ContentChunk
- {
- private final boolean lastContent;
- private final ByteBuffer content;
-
- private ContentChunk(ContentChunk chunk)
- {
- lastContent = chunk.lastContent;
- content = chunk.content;
- }
-
- private ContentChunk(Iterator<ByteBuffer> contentIterator)
- {
- lastContent = !contentIterator.hasNext();
- content = lastContent ? BufferUtil.EMPTY_BUFFER : contentIterator.next();
- }
-
- private boolean isDeferred()
- {
- return content == null && !lastContent;
- }
- }
-
- private class ContinueContentChunk extends ContentChunk
- {
- private final CountDownLatch latch = new CountDownLatch(1);
-
- private ContinueContentChunk(ContentChunk chunk)
- {
- super(chunk);
- }
-
- private void signal()
- {
- latch.countDown();
- }
-
- private void await()
+ public void succeeded()
{
try
{
- latch.await();
+ process();
}
- catch (InterruptedException x)
+ // Catch-all for runtime exceptions
+ catch (Exception x)
{
- LOG.ignore(x);
+ anyToFailure(x);
}
}
+
+ private void process() throws Exception
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange == null)
+ return;
+
+ Request request = exchange.getRequest();
+ if (!headersToCommit(request))
+ return;
+
+ HttpContent content = HttpSender.this.content;
+
+ if (!content.hasContent())
+ {
+ // No content to send, we are done.
+ someToSuccess(exchange);
+ }
+ else
+ {
+ // Was any content sent while committing ?
+ ByteBuffer contentBuffer = content.getContent();
+ if (contentBuffer != null)
+ {
+ if (!someToContent(request, contentBuffer))
+ return;
+ }
+
+ while (true)
+ {
+ SenderState current = senderState.get();
+ switch (current)
+ {
+ case SENDING:
+ {
+ // We have content to send ?
+ if (content.advance())
+ {
+ sendContent(exchange, content, contentCallback);
+ }
+ else
+ {
+ if (content.isConsumed())
+ {
+ sendContent(exchange, content, lastCallback);
+ }
+ else
+ {
+ if (!updateSenderState(current, SenderState.IDLE))
+ break;
+ LOG.debug("Waiting for deferred content for {}", request);
+ }
+ }
+ return;
+ }
+ case EXPECTING:
+ {
+ // Wait for the 100 Continue response
+ if (!updateSenderState(current, SenderState.WAITING))
+ break;
+ return;
+ }
+ case SCHEDULED:
+ {
+ if (expects100Continue(request))
+ return;
+ // We have deferred content to send.
+ updateSenderState(current, SenderState.SENDING);
+ break;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void failed(Throwable failure)
+ {
+ anyToFailure(failure);
+ }
+ }
+
+ private class ContentCallback extends IteratingCallback
+ {
+ @Override
+ protected boolean process() throws Exception
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange == null)
+ return false;
+
+ Request request = exchange.getRequest();
+ HttpContent content = HttpSender.this.content;
+
+ ByteBuffer contentBuffer = content.getContent();
+ if (contentBuffer != null)
+ {
+ if (!someToContent(request, contentBuffer))
+ return false;
+ }
+
+ if (content.advance())
+ {
+ // There is more content to send
+ sendContent(exchange, content, this);
+ }
+ else
+ {
+ if (content.isConsumed())
+ {
+ sendContent(exchange, content, lastCallback);
+ }
+ else
+ {
+ while (true)
+ {
+ SenderState current = senderState.get();
+ switch (current)
+ {
+ case SENDING:
+ {
+ if (updateSenderState(current, SenderState.IDLE))
+ {
+ LOG.debug("Waiting for deferred content for {}", request);
+ return false;
+ }
+ break;
+ }
+ case SCHEDULED:
+ {
+ if (updateSenderState(current, SenderState.SENDING))
+ {
+ LOG.debug("Deferred content available for {}", request);
+ // TODO: this case is not covered by tests
+ sendContent(exchange, content, this);
+ return false;
+ }
+ break;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void completed()
+ {
+ // Nothing to do, since we always return false from process().
+ // Termination is obtained via LastContentCallback.
+ }
+
+ @Override
+ public void failed(Throwable failure)
+ {
+ super.failed(failure);
+ anyToFailure(failure);
+ }
+ }
+
+ private class LastContentCallback implements Callback
+ {
+ @Override
+ public void succeeded()
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange == null)
+ return;
+ someToSuccess(exchange);
+ }
+
+ @Override
+ public void failed(Throwable failure)
+ {
+ anyToFailure(failure);
+ }
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java
new file mode 100644
index 0000000..ab56360
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java
@@ -0,0 +1,147 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.util.Promise;
+
+public abstract class MultiplexHttpDestination<C extends Connection> extends HttpDestination implements Promise<Connection>
+{
+ private final AtomicReference<ConnectState> connect = new AtomicReference<>(ConnectState.DISCONNECTED);
+ private C connection;
+
+ protected MultiplexHttpDestination(HttpClient client, Origin origin)
+ {
+ super(client, origin);
+ }
+
+ @Override
+ protected void send()
+ {
+ while (true)
+ {
+ ConnectState current = connect.get();
+ switch (current)
+ {
+ case DISCONNECTED:
+ {
+ if (!connect.compareAndSet(current, ConnectState.CONNECTING))
+ break;
+ newConnection(this);
+ return;
+ }
+ case CONNECTING:
+ {
+ // Waiting to connect, just return
+ return;
+ }
+ case CONNECTED:
+ {
+ if (process(connection, false))
+ break;
+ return;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void succeeded(Connection result)
+ {
+ C connection = this.connection = (C)result;
+ if (connect.compareAndSet(ConnectState.CONNECTING, ConnectState.CONNECTED))
+ {
+ process(connection, true);
+ }
+ else
+ {
+ connection.close();
+ failed(new IllegalStateException());
+ }
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ connect.set(ConnectState.DISCONNECTED);
+ }
+
+ protected boolean process(final C connection, boolean dispatch)
+ {
+ HttpClient client = getHttpClient();
+ final HttpExchange exchange = getHttpExchanges().poll();
+ LOG.debug("Processing {} on {}", exchange, connection);
+ if (exchange == null)
+ return false;
+
+ final Request request = exchange.getRequest();
+ Throwable cause = request.getAbortCause();
+ if (cause != null)
+ {
+ LOG.debug("Abort before processing {}: {}", exchange, cause);
+ abort(exchange, cause);
+ }
+ else
+ {
+ if (dispatch)
+ {
+ client.getExecutor().execute(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ send(connection, exchange);
+ }
+ });
+ }
+ else
+ {
+ send(connection, exchange);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void close(Connection connection)
+ {
+ super.close(connection);
+ while (true)
+ {
+ ConnectState current = connect.get();
+ if (connect.compareAndSet(current, ConnectState.DISCONNECTED))
+ break;
+ }
+ }
+
+ protected abstract void send(C connection, HttpExchange exchange);
+
+ private enum ConnectState
+ {
+ DISCONNECTED, CONNECTING, CONNECTED
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java
new file mode 100644
index 0000000..d0a11d3
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java
@@ -0,0 +1,124 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.util.Objects;
+
+import org.eclipse.jetty.util.URIUtil;
+
+public class Origin
+{
+ private final String scheme;
+ private final Address address;
+
+ public Origin(String scheme, String host, int port)
+ {
+ this(scheme, new Address(host, port));
+ }
+
+ public Origin(String scheme, Address address)
+ {
+ this.scheme = Objects.requireNonNull(scheme);
+ this.address = address;
+ }
+
+ public String getScheme()
+ {
+ return scheme;
+ }
+
+ public Address getAddress()
+ {
+ return address;
+ }
+
+ public String asString()
+ {
+ StringBuilder result = new StringBuilder();
+ URIUtil.appendSchemeHostPort(result, scheme, address.host, address.port);
+ return result.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ Origin that = (Origin)obj;
+ return scheme.equals(that.scheme) && address.equals(that.address);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = scheme.hashCode();
+ result = 31 * result + address.hashCode();
+ return result;
+ }
+
+ public static class Address
+ {
+ private final String host;
+ private final int port;
+
+ public Address(String host, int port)
+ {
+ this.host = Objects.requireNonNull(host);
+ this.port = port;
+ }
+
+ public String getHost()
+ {
+ return host;
+ }
+
+ public int getPort()
+ {
+ return port;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ Address that = (Address)obj;
+ return host.equals(that.host) && port == that.port;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = host.hashCode();
+ result = 31 * result + port;
+ return result;
+ }
+
+ public String asString()
+ {
+ return String.format("%s:%d", host, port);
+ }
+
+ @Override
+ public String toString()
+ {
+ return asString();
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java
new file mode 100644
index 0000000..4354b7b
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java
@@ -0,0 +1,189 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+
+public abstract class PoolingHttpDestination<C extends Connection> extends HttpDestination implements Promise<Connection>
+{
+ private final ConnectionPool connectionPool;
+
+ public PoolingHttpDestination(HttpClient client, Origin origin)
+ {
+ super(client, origin);
+ this.connectionPool = newConnectionPool(client);
+ }
+
+ protected ConnectionPool newConnectionPool(HttpClient client)
+ {
+ return new ConnectionPool(this, client.getMaxConnectionsPerDestination(), this);
+ }
+
+ public ConnectionPool getConnectionPool()
+ {
+ return connectionPool;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void succeeded(Connection connection)
+ {
+ process((C)connection, true);
+ }
+
+ @Override
+ public void failed(final Throwable x)
+ {
+ getHttpClient().getExecutor().execute(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ abort(x);
+ }
+ });
+ }
+
+ protected void send()
+ {
+ C connection = acquire();
+ if (connection != null)
+ process(connection, false);
+ }
+
+ @SuppressWarnings("unchecked")
+ public C acquire()
+ {
+ return (C)connectionPool.acquire();
+ }
+
+ /**
+ * <p>Processes a new connection making it idle or active depending on whether requests are waiting to be sent.</p>
+ * <p>A new connection is created when a request needs to be executed; it is possible that the request that
+ * triggered the request creation is executed by another connection that was just released, so the new connection
+ * may become idle.</p>
+ * <p>If a request is waiting to be executed, it will be dequeued and executed by the new connection.</p>
+ *
+ * @param connection the new connection
+ * @param dispatch whether to dispatch the processing to another thread
+ */
+ public void process(final C connection, boolean dispatch)
+ {
+ HttpClient client = getHttpClient();
+ final HttpExchange exchange = getHttpExchanges().poll();
+ LOG.debug("Processing exchange {} on connection {}", exchange, connection);
+ if (exchange == null)
+ {
+ // TODO: review this part... may not be 100% correct
+ // TODO: e.g. is client is not running, there should be no need to close the connection
+
+ if (!connectionPool.release(connection))
+ connection.close();
+
+ if (!client.isRunning())
+ {
+ LOG.debug("{} is stopping", client);
+ connection.close();
+ }
+ }
+ else
+ {
+ final Request request = exchange.getRequest();
+ Throwable cause = request.getAbortCause();
+ if (cause != null)
+ {
+ abort(exchange, cause);
+ LOG.debug("Aborted before processing {}: {}", exchange, cause);
+ }
+ else
+ {
+ if (dispatch)
+ {
+ client.getExecutor().execute(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ send(connection, exchange);
+ }
+ });
+ }
+ else
+ {
+ send(connection, exchange);
+ }
+ }
+ }
+ }
+
+ protected abstract void send(C connection, HttpExchange exchange);
+
+ public void release(C connection)
+ {
+ LOG.debug("{} released", connection);
+ HttpClient client = getHttpClient();
+ if (client.isRunning())
+ {
+ if (connectionPool.isActive(connection))
+ process(connection, false);
+ else
+ LOG.debug("{} explicit", connection);
+ }
+ else
+ {
+ LOG.debug("{} is stopped", client);
+ close(connection);
+ connection.close();
+ }
+ }
+
+ @Override
+ public void close(Connection oldConnection)
+ {
+ super.close(oldConnection);
+ connectionPool.remove(oldConnection);
+
+ // We need to execute queued requests even if this connection failed.
+ // We may create a connection that is not needed, but it will eventually
+ // idle timeout, so no worries
+ if (!getHttpExchanges().isEmpty())
+ {
+ C newConnection = acquire();
+ if (newConnection != null)
+ process(newConnection, false);
+ }
+ }
+
+ public void close()
+ {
+ connectionPool.close();
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ ContainerLifeCycle.dump(out, indent, Arrays.asList(connectionPool));
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java
index 841c661..d7e96f3 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java
@@ -59,6 +59,7 @@
protected URI getAuthenticationURI(Request request)
{
HttpDestination destination = getHttpClient().destinationFor(request.getScheme(), request.getHost(), request.getPort());
- return destination.isProxied() ? destination.getProxyURI() : request.getURI();
+ ProxyConfiguration.Proxy proxy = destination.getProxy();
+ return proxy != null ? proxy.getURI() : request.getURI();
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyConfiguration.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyConfiguration.java
new file mode 100644
index 0000000..5b36482
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyConfiguration.java
@@ -0,0 +1,170 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jetty.io.ClientConnectionFactory;
+
+/**
+ * The configuration of the forward proxy to use with {@link org.eclipse.jetty.client.HttpClient}.
+ * <p />
+ * Applications add subclasses of {@link Proxy} to this configuration via:
+ * <pre>
+ * ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
+ * proxyConfig.getProxies().add(new HttpProxy(proxyHost, 8080));
+ * </pre>
+ *
+ * @see HttpClient#getProxyConfiguration()
+ */
+public class ProxyConfiguration
+{
+ private final List<Proxy> proxies = new ArrayList<>();
+
+ public List<Proxy> getProxies()
+ {
+ return proxies;
+ }
+
+ public Proxy match(Origin origin)
+ {
+ for (Proxy proxy : getProxies())
+ {
+ if (proxy.matches(origin))
+ return proxy;
+ }
+ return null;
+ }
+
+ public static abstract class Proxy
+ {
+ private final Set<String> included = new HashSet<>();
+ private final Set<String> excluded = new HashSet<>();
+ private final Origin.Address address;
+ private final boolean secure;
+
+ protected Proxy(Origin.Address address, boolean secure)
+ {
+ this.address = address;
+ this.secure = secure;
+ }
+
+ /**
+ * @return the address of this proxy
+ */
+ public Origin.Address getAddress()
+ {
+ return address;
+ }
+
+ /**
+ * @return whether the connection to the proxy must be secured via TLS
+ */
+ public boolean isSecure()
+ {
+ return secure;
+ }
+
+ /**
+ * @return the list of origins that must be proxied
+ * @see #matches(Origin)
+ * @see #getExcludedAddresses()
+ */
+ public Set<String> getIncludedAddresses()
+ {
+ return included;
+ }
+
+ /**
+ * @return the list of origins that must not be proxied.
+ * @see #matches(Origin)
+ * @see #getIncludedAddresses()
+ */
+ public Set<String> getExcludedAddresses()
+ {
+ return excluded;
+ }
+
+ /**
+ * @return an URI representing this proxy, or null if no URI can represent this proxy
+ */
+ public URI getURI()
+ {
+ return null;
+ }
+
+ /**
+ * Matches the given {@code origin} with the included and excluded addresses,
+ * returning true if the given {@code origin} is to be proxied.
+ *
+ * @param origin the origin to test for proxying
+ * @return true if the origin must be proxied, false otherwise
+ */
+ public boolean matches(Origin origin)
+ {
+ boolean result = included.isEmpty();
+ Origin.Address address = origin.getAddress();
+ for (String included : this.included)
+ {
+ if (matches(address, included))
+ {
+ result = true;
+ break;
+ }
+ }
+ for (String excluded : this.excluded)
+ {
+ if (matches(address, excluded))
+ {
+ result = false;
+ break;
+ }
+ }
+ return result;
+ }
+
+ private boolean matches(Origin.Address address, String pattern)
+ {
+ // TODO: add support for CIDR notation like 192.168.0.0/24, see DoSFilter
+ int colon = pattern.indexOf(':');
+ if (colon < 0)
+ return pattern.equals(address.getHost());
+ String host = pattern.substring(0, colon);
+ String port = pattern.substring(colon + 1);
+ return host.equals(address.getHost()) && port.equals(String.valueOf(address.getPort()));
+ }
+
+ /**
+ * @param connectionFactory the nested {@link ClientConnectionFactory}
+ * @return a new {@link ClientConnectionFactory} for this {@link Proxy}
+ */
+ public abstract ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory);
+
+ @Override
+ public String toString()
+ {
+ return address.toString();
+ }
+ }
+
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
index 77e8385..c557457 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
@@ -18,53 +18,23 @@
package org.eclipse.jetty.client;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
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.http.HttpMethod;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-public class RedirectProtocolHandler extends Response.Listener.Empty implements ProtocolHandler
+public class RedirectProtocolHandler extends Response.Listener.Adapter implements ProtocolHandler
{
- private static final Logger LOG = Log.getLogger(RedirectProtocolHandler.class);
- private static final String SCHEME_REGEXP = "(^https?)";
- private static final String AUTHORITY_REGEXP = "([^/\\?#]+)";
- // The location may be relative so the scheme://authority part may be missing
- private static final String DESTINATION_REGEXP = "(" + SCHEME_REGEXP + "://" + AUTHORITY_REGEXP + ")?";
- private static final String PATH_REGEXP = "([^\\?#]*)";
- private static final String QUERY_REGEXP = "([^#]*)";
- private static final String FRAGMENT_REGEXP = "(.*)";
- private static final Pattern URI_PATTERN = Pattern.compile(DESTINATION_REGEXP + PATH_REGEXP + QUERY_REGEXP + FRAGMENT_REGEXP);
- private static final String ATTRIBUTE = RedirectProtocolHandler.class.getName() + ".redirects";
-
- private final HttpClient client;
- private final ResponseNotifier notifier;
+ private final HttpRedirector redirector;
public RedirectProtocolHandler(HttpClient client)
{
- this.client = client;
- this.notifier = new ResponseNotifier(client);
+ redirector = new HttpRedirector(client);
}
@Override
public boolean accept(Request request, Response response)
{
- switch (response.getStatus())
- {
- case 301:
- case 302:
- case 303:
- case 307:
- return request.isFollowRedirects();
- }
- return false;
+ return redirector.isRedirect(response) && request.isFollowRedirects();
}
@Override
@@ -76,163 +46,11 @@
@Override
public void onComplete(Result result)
{
- if (!result.isFailed())
- {
- Request request = result.getRequest();
- Response response = result.getResponse();
- String location = response.getHeaders().get("location");
- if (location != null)
- {
- URI newURI = sanitize(location);
- LOG.debug("Redirecting to {} (Location: {})", newURI, location);
- if (newURI != null)
- {
- if (!newURI.isAbsolute())
- newURI = request.getURI().resolve(newURI);
-
- int status = response.getStatus();
- switch (status)
- {
- case 301:
- {
- String method = request.method();
- if (HttpMethod.GET.is(method) || HttpMethod.HEAD.is(method) || HttpMethod.PUT.is(method))
- redirect(result, method, newURI);
- else if (HttpMethod.POST.is(method))
- redirect(result, HttpMethod.GET.asString(), newURI);
- else
- fail(result, new HttpResponseException("HTTP protocol violation: received 301 for non GET/HEAD/POST/PUT request", response));
- break;
- }
- case 302:
- {
- String method = request.method();
- if (HttpMethod.HEAD.is(method) || HttpMethod.PUT.is(method))
- redirect(result, method, newURI);
- else
- redirect(result, HttpMethod.GET.asString(), newURI);
- break;
- }
- case 303:
- {
- String method = request.method();
- if (HttpMethod.HEAD.is(method))
- redirect(result, method, newURI);
- else
- redirect(result, HttpMethod.GET.asString(), newURI);
- break;
- }
- case 307:
- {
- // Keep same method
- redirect(result, request.method(), newURI);
- break;
- }
- default:
- {
- fail(result, new HttpResponseException("Unhandled HTTP status code " + status, response));
- break;
- }
- }
- }
- else
- {
- fail(result, new HttpResponseException("Malformed Location header " + location, response));
- }
- }
- else
- {
- fail(result, new HttpResponseException("Missing Location header " + location, response));
- }
- }
- else
- {
- fail(result, result.getFailure());
- }
- }
-
- private URI sanitize(String location)
- {
- // Redirects should be valid, absolute, URIs, with properly escaped paths and encoded
- // query parameters. However, shit happens, and here we try our best to recover.
-
- try
- {
- // Direct hit first: if passes, we're good
- return new URI(location);
- }
- catch (URISyntaxException x)
- {
- Matcher matcher = URI_PATTERN.matcher(location);
- if (matcher.matches())
- {
- String scheme = matcher.group(2);
- String authority = matcher.group(3);
- String path = matcher.group(4);
- String query = matcher.group(5);
- if (query.length() == 0)
- query = null;
- String fragment = matcher.group(6);
- if (fragment.length() == 0)
- fragment = null;
- try
- {
- return new URI(scheme, authority, path, query, fragment);
- }
- catch (URISyntaxException xx)
- {
- // Give up
- }
- }
- return null;
- }
- }
-
- private void redirect(Result result, String method, URI location)
- {
- final Request request = result.getRequest();
- HttpConversation conversation = client.getConversation(request.getConversationID(), false);
- Integer redirects = (Integer)conversation.getAttribute(ATTRIBUTE);
- if (redirects == null)
- redirects = 0;
-
- if (redirects < client.getMaxRedirects())
- {
- ++redirects;
- conversation.setAttribute(ATTRIBUTE, redirects);
-
- Request redirect = client.copyRequest(request, location);
-
- // Use given method
- redirect.method(method);
-
- redirect.onRequestBegin(new Request.BeginListener()
- {
- @Override
- public void onBegin(Request redirect)
- {
- Throwable cause = request.getAbortCause();
- if (cause != null)
- redirect.abort(cause);
- }
- });
-
- redirect.send(null);
- }
- else
- {
- fail(result, new HttpResponseException("Max redirects exceeded " + redirects, result.getResponse()));
- }
- }
-
- private void fail(Result result, Throwable failure)
- {
Request request = result.getRequest();
Response response = result.getResponse();
- HttpConversation conversation = client.getConversation(request.getConversationID(), false);
- conversation.updateResponseListeners(null);
- List<Response.ResponseListener> listeners = conversation.getResponseListeners();
- notifier.notifyFailure(listeners, response, failure);
- notifier.notifyComplete(listeners, new Result(request, response, failure));
+ if (result.isSucceeded())
+ redirector.redirect(request, response, null);
+ else
+ redirector.fail(request, response, result.getFailure());
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java
index 5c0c479..746a66e 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java
@@ -36,6 +36,7 @@
this.client = client;
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyQueued(Request request)
{
// Optimized to avoid allocations of iterator instances
@@ -66,6 +67,7 @@
}
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyBegin(Request request)
{
// Optimized to avoid allocations of iterator instances
@@ -96,6 +98,7 @@
}
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyHeaders(Request request)
{
// Optimized to avoid allocations of iterator instances
@@ -126,6 +129,7 @@
}
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyCommit(Request request)
{
// Optimized to avoid allocations of iterator instances
@@ -156,20 +160,31 @@
}
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyContent(Request request, ByteBuffer content)
{
- // Optimized to avoid allocations of iterator instances
+ // Slice the buffer to avoid that listeners peek into data they should not look at.
+ content = content.slice();
+ // Optimized to avoid allocations of iterator instances.
List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
for (int i = 0; i < requestListeners.size(); ++i)
{
Request.RequestListener listener = requestListeners.get(i);
if (listener instanceof Request.ContentListener)
+ {
+ // The buffer was sliced, so we always clear it (position=0, limit=capacity)
+ // before passing it to the listener that may consume it.
+ content.clear();
notifyContent((Request.ContentListener)listener, request, content);
+ }
}
List<Request.Listener> listeners = client.getRequestListeners();
for (int i = 0; i < listeners.size(); ++i)
{
Request.Listener listener = listeners.get(i);
+ // The buffer was sliced, so we always clear it (position=0, limit=capacity)
+ // before passing it to the listener that may consume it.
+ content.clear();
notifyContent(listener, request, content);
}
}
@@ -186,6 +201,7 @@
}
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public void notifySuccess(Request request)
{
// Optimized to avoid allocations of iterator instances
@@ -216,6 +232,7 @@
}
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyFailure(Request request, Throwable failure)
{
// Optimized to avoid allocations of iterator instances
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
index a98a1c9..d8aa74c 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
@@ -40,6 +40,7 @@
this.client = client;
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyBegin(List<Response.ResponseListener> listeners, Response response)
{
// Optimized to avoid allocations of iterator instances
@@ -63,6 +64,7 @@
}
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public boolean notifyHeader(List<Response.ResponseListener> listeners, Response response, HttpField field)
{
boolean result = true;
@@ -89,6 +91,7 @@
}
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyHeaders(List<Response.ResponseListener> listeners, Response response)
{
// Optimized to avoid allocations of iterator instances
@@ -112,14 +115,22 @@
}
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyContent(List<Response.ResponseListener> listeners, Response response, ByteBuffer buffer)
{
+ // Slice the buffer to avoid that listeners peek into data they should not look at.
+ buffer = buffer.slice();
// Optimized to avoid allocations of iterator instances
for (int i = 0; i < listeners.size(); ++i)
{
Response.ResponseListener listener = listeners.get(i);
if (listener instanceof Response.ContentListener)
+ {
+ // The buffer was sliced, so we always clear it (position=0, limit=capacity)
+ // before passing it to the listener that may consume it.
+ buffer.clear();
notifyContent((Response.ContentListener)listener, response, buffer);
+ }
}
}
@@ -135,6 +146,7 @@
}
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public void notifySuccess(List<Response.ResponseListener> listeners, Response response)
{
// Optimized to avoid allocations of iterator instances
@@ -158,6 +170,7 @@
}
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyFailure(List<Response.ResponseListener> listeners, Response response, Throwable failure)
{
// Optimized to avoid allocations of iterator instances
@@ -181,6 +194,7 @@
}
}
+ @SuppressWarnings("ForLoopReplaceableByForEach")
public void notifyComplete(List<Response.ResponseListener> listeners, Result result)
{
// Optimized to avoid allocations of iterator instances
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java
new file mode 100644
index 0000000..c5b25d3
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java
@@ -0,0 +1,190 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class Socks4Proxy extends ProxyConfiguration.Proxy
+{
+ public Socks4Proxy(String host, int port)
+ {
+ this(new Origin.Address(host, port), false);
+ }
+
+ public Socks4Proxy(Origin.Address address, boolean secure)
+ {
+ super(address, secure);
+ }
+
+ @Override
+ public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
+ {
+ return new Socks4ProxyClientConnectionFactory(connectionFactory);
+ }
+
+ public static class Socks4ProxyClientConnectionFactory implements ClientConnectionFactory
+ {
+ private final ClientConnectionFactory connectionFactory;
+
+ public Socks4ProxyClientConnectionFactory(ClientConnectionFactory connectionFactory)
+ {
+ this.connectionFactory = connectionFactory;
+ }
+
+ @Override
+ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+ {
+ HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+ Executor executor = destination.getHttpClient().getExecutor();
+ return new Socks4ProxyConnection(endPoint, executor, connectionFactory, context);
+ }
+ }
+
+ private static class Socks4ProxyConnection extends AbstractConnection implements Callback
+ {
+ private static final Pattern IPv4_PATTERN = Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})");
+ private static final Logger LOG = Log.getLogger(Socks4ProxyConnection.class);
+
+ private final ClientConnectionFactory connectionFactory;
+ private final Map<String, Object> context;
+
+ public Socks4ProxyConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, Map<String, Object> context)
+ {
+ super(endPoint, executor);
+ this.connectionFactory = connectionFactory;
+ this.context = context;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ writeSocks4Connect();
+ }
+
+ /**
+ * Writes the SOCKS "connect" bytes, differentiating between SOCKS 4 and 4A;
+ * the former sends an IPv4 address, the latter the full domain name.
+ */
+ private void writeSocks4Connect()
+ {
+ HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+ String host = destination.getHost();
+ short port = (short)destination.getPort();
+ Matcher matcher = IPv4_PATTERN.matcher(host);
+ if (matcher.matches())
+ {
+ // SOCKS 4
+ ByteBuffer buffer = ByteBuffer.allocate(9);
+ buffer.put((byte)4).put((byte)1).putShort(port);
+ for (int i = 1; i <= 4; ++i)
+ buffer.put((byte)Integer.parseInt(matcher.group(i)));
+ buffer.put((byte)0);
+ buffer.flip();
+ getEndPoint().write(this, buffer);
+ }
+ else
+ {
+ // SOCKS 4A
+ byte[] hostBytes = host.getBytes(StandardCharsets.UTF_8);
+ ByteBuffer buffer = ByteBuffer.allocate(9 + hostBytes.length + 1);
+ buffer.put((byte)4).put((byte)1).putShort(port);
+ buffer.put((byte)0).put((byte)0).put((byte)0).put((byte)1).put((byte)0);
+ buffer.put(hostBytes).put((byte)0);
+ buffer.flip();
+ getEndPoint().write(this, buffer);
+ }
+ }
+
+ @Override
+ public void succeeded()
+ {
+ LOG.debug("Written SOCKS4 connect request");
+ fillInterested();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ close();
+ @SuppressWarnings("unchecked")
+ Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+ promise.failed(x);
+ }
+
+ @Override
+ public void onFillable()
+ {
+ try
+ {
+ ByteBuffer buffer = BufferUtil.allocate(8);
+ int filled = getEndPoint().fill(buffer);
+ LOG.debug("Read SOCKS4 connect response, {} bytes", filled);
+ if (filled != 8)
+ throw new IOException("Invalid response from SOCKS4 proxy");
+ int result = buffer.get(1);
+ if (result == 0x5A)
+ tunnel();
+ else
+ throw new IOException("SOCKS4 tunnel failed with code " + result);
+ }
+ catch (Throwable x)
+ {
+ failed(x);
+ }
+ }
+
+ private void tunnel()
+ {
+ try
+ {
+ HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+ HttpClient client = destination.getHttpClient();
+ ClientConnectionFactory connectionFactory = this.connectionFactory;
+ if (HttpScheme.HTTPS.is(destination.getScheme()))
+ connectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
+ org.eclipse.jetty.io.Connection connection = connectionFactory.newConnection(getEndPoint(), context);
+ ClientConnectionFactory.Helper.replaceConnection(this, connection);
+ LOG.debug("SOCKS4 tunnel established: {} over {}", this, connection);
+ }
+ catch (Throwable x)
+ {
+ failed(x);
+ }
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java
index 4cd716f..621a3d5 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java
@@ -35,7 +35,7 @@
/**
* Sends a request with an associated response listener.
* <p />
- * {@link Request#send(Response.Listener)} will eventually call this method to send the request.
+ * {@link Request#send(Response.CompleteListener)} will eventually call this method to send the request.
* It is exposed to allow applications to send requests via unpooled connections.
*
* @param request the request to send
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java
index 03cece3..736a14b 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java
@@ -63,43 +63,4 @@
* @param promise the promise of a new, unpooled, {@link Connection}
*/
void newConnection(Promise<Connection> promise);
-
- public static class Address
- {
- private final String host;
- private final int port;
-
- public Address(String host, int port)
- {
- this.host = host;
- this.port = port;
- }
-
- public String getHost()
- {
- return host;
- }
-
- public int getPort()
- {
- return port;
- }
-
- @Override
- public boolean equals(Object obj)
- {
- if (this == obj) return true;
- if (obj == null || getClass() != obj.getClass()) return false;
- Address that = (Address)obj;
- return host.equals(that.host) && port == that.port;
- }
-
- @Override
- public int hashCode()
- {
- int result = host.hashCode();
- result = 31 * result + port;
- return result;
- }
- }
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ProxyConfiguration.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ProxyConfiguration.java
deleted file mode 100644
index 4b84557..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ProxyConfiguration.java
+++ /dev/null
@@ -1,81 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.client.api;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * The configuration of the forward proxy to use with {@link org.eclipse.jetty.client.HttpClient}.
- * <p />
- * Configuration parameters include the host and port of the forward proxy, and a list of
- * {@link #getExcludedOrigins() origins} that are excluded from being proxied.
- *
- * @see org.eclipse.jetty.client.HttpClient#setProxyConfiguration(ProxyConfiguration)
- */
-public class ProxyConfiguration
-{
- private final Set<String> excluded = new HashSet<>();
- private final String host;
- private final int port;
-
- public ProxyConfiguration(String host, int port)
- {
- this.host = host;
- this.port = port;
- }
-
- /**
- * @return the host name of the forward proxy
- */
- public String getHost()
- {
- return host;
- }
-
- /**
- * @return the port of the forward proxy
- */
- public int getPort()
- {
- return port;
- }
-
- /**
- * Matches the given {@code host} and {@code port} with the list of excluded origins,
- * returning true if the origin is to be proxied, false if it is excluded from proxying.
- * @param host the host to match
- * @param port the port to match
- * @return true if the origin made of {@code host} and {@code port} is to be proxied,
- * false if it is excluded from proxying.
- */
- public boolean matches(String host, int port)
- {
- String hostPort = host + ":" + port;
- return !getExcludedOrigins().contains(hostPort);
- }
-
- /**
- * @return the list of origins to exclude from proxying, in the form "host:port".
- */
- public Set<String> getExcludedOrigins()
- {
- return excluded;
- }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
index 225137a..74b4b2a 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
@@ -76,16 +76,9 @@
int getPort();
/**
- * @return the method of this request, such as GET or POST, or null if the method is not a standard HTTP method
- * @deprecated use {@link #method()} instead
- */
- @Deprecated
- HttpMethod getMethod();
-
- /**
* @return the method of this request, such as GET or POST, as a String
*/
- String method();
+ String getMethod();
/**
* @param method the method of this request, such as GET or POST
@@ -361,6 +354,12 @@
Request onResponseFailure(Response.FailureListener listener);
/**
+ * @param listener a listener for complete event
+ * @return this request object
+ */
+ Request onComplete(Response.CompleteListener listener);
+
+ /**
* Sends this request and returns the response.
* <p />
* This method should be used when a simple blocking semantic is needed, and when it is known
@@ -510,7 +509,7 @@
/**
* An empty implementation of {@link Listener}
*/
- public static class Empty implements Listener
+ public static class Adapter implements Listener
{
@Override
public void onQueued(Request request)
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
index 2fc14010..6a43820 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
@@ -202,7 +202,7 @@
/**
* An empty implementation of {@link Listener}
*/
- public static class Empty implements Listener
+ public static class Adapter implements Listener
{
@Override
public void onBegin(Response response)
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java
new file mode 100644
index 0000000..7f1e56a
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java
@@ -0,0 +1,111 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import java.util.Enumeration;
+
+import org.eclipse.jetty.client.HttpChannel;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+
+public class HttpChannelOverHTTP extends HttpChannel
+{
+ private final HttpConnectionOverHTTP connection;
+ private final HttpSenderOverHTTP sender;
+ private final HttpReceiverOverHTTP receiver;
+
+ public HttpChannelOverHTTP(HttpConnectionOverHTTP connection)
+ {
+ super(connection.getHttpDestination());
+ this.connection = connection;
+ this.sender = new HttpSenderOverHTTP(this);
+ this.receiver = new HttpReceiverOverHTTP(this);
+ }
+
+ public HttpConnectionOverHTTP getHttpConnection()
+ {
+ return connection;
+ }
+
+ @Override
+ public void send()
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange != null)
+ sender.send(exchange);
+ }
+
+ @Override
+ public void proceed(HttpExchange exchange, boolean proceed)
+ {
+ sender.proceed(exchange, proceed);
+ }
+
+ @Override
+ public boolean abort(Throwable cause)
+ {
+ // We want the return value to be that of the response
+ // because if the response has already successfully
+ // arrived then we failed to abort the exchange
+ sender.abort(cause);
+ return receiver.abort(cause);
+ }
+
+ public void receive()
+ {
+ receiver.receive();
+ }
+
+ @Override
+ public void exchangeTerminated(Result result)
+ {
+ super.exchangeTerminated(result);
+
+ if (result.isSucceeded())
+ {
+ HttpFields responseHeaders = result.getResponse().getHeaders();
+ Enumeration<String> values = responseHeaders.getValues(HttpHeader.CONNECTION.asString(), ",");
+ if (values != null)
+ {
+ while (values.hasMoreElements())
+ {
+ if (HttpHeaderValue.CLOSE.asString().equalsIgnoreCase(values.nextElement()))
+ {
+ connection.close();
+ return;
+ }
+ }
+ }
+ connection.release();
+ }
+ else
+ {
+ connection.close();
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x", getClass().getSimpleName(), hashCode());
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java
new file mode 100644
index 0000000..d2383b0
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java
@@ -0,0 +1,60 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jetty.client.AbstractHttpClientTransport;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.Promise;
+
+public class HttpClientTransportOverHTTP extends AbstractHttpClientTransport
+{
+ public HttpClientTransportOverHTTP()
+ {
+ this(Math.max(1, Runtime.getRuntime().availableProcessors() / 2));
+ }
+
+ public HttpClientTransportOverHTTP(int selectors)
+ {
+ super(selectors);
+ }
+
+ @Override
+ public HttpDestination newHttpDestination(Origin origin)
+ {
+ return new HttpDestinationOverHTTP(getHttpClient(), origin);
+ }
+
+ @Override
+ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+ {
+
+ HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+ HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination);
+ @SuppressWarnings("unchecked")
+ Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+ promise.succeeded(connection);
+ return connection;
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java
new file mode 100644
index 0000000..e825e23
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java
@@ -0,0 +1,181 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.client.HttpConnection;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HttpConnectionOverHTTP extends AbstractConnection implements Connection
+{
+ private static final Logger LOG = Log.getLogger(HttpConnectionOverHTTP.class);
+
+ private final Delegate delegate;
+ private final HttpChannelOverHTTP channel;
+ private boolean closed;
+ private long idleTimeout;
+
+ public HttpConnectionOverHTTP(EndPoint endPoint, HttpDestination destination)
+ {
+ super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO());
+ this.delegate = new Delegate(destination);
+ this.channel = new HttpChannelOverHTTP(this);
+ }
+
+ public HttpChannelOverHTTP getHttpChannel()
+ {
+ return channel;
+ }
+
+ public HttpDestinationOverHTTP getHttpDestination()
+ {
+ return (HttpDestinationOverHTTP)delegate.getHttpDestination();
+ }
+
+ @Override
+ public void send(Request request, Response.CompleteListener listener)
+ {
+ delegate.send(request, listener);
+ }
+
+ protected void send(HttpExchange exchange)
+ {
+ delegate.send(exchange);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ fillInterested();
+ }
+
+ @Override
+ public void onClose()
+ {
+ closed = true;
+ super.onClose();
+ }
+
+ protected boolean isClosed()
+ {
+ return closed;
+ }
+
+ @Override
+ protected boolean onReadTimeout()
+ {
+ LOG.debug("{} idle timeout", this);
+
+ HttpExchange exchange = channel.getHttpExchange();
+ if (exchange != null)
+ return exchange.getRequest().abort(new TimeoutException());
+
+ getHttpDestination().close(this);
+ return true;
+ }
+
+ @Override
+ public void onFillable()
+ {
+ HttpExchange exchange = channel.getHttpExchange();
+ if (exchange != null)
+ {
+ channel.receive();
+ }
+ else
+ {
+ // If there is no exchange, then could be either a remote close,
+ // or garbage bytes; in both cases we close the connection
+ close();
+ }
+ }
+
+ public void release()
+ {
+ // Restore idle timeout
+ getEndPoint().setIdleTimeout(idleTimeout);
+ getHttpDestination().release(this);
+ }
+
+ @Override
+ public void close()
+ {
+ getHttpDestination().close(this);
+ getEndPoint().shutdownOutput();
+ LOG.debug("{} oshut", this);
+ getEndPoint().close();
+ LOG.debug("{} closed", this);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%h(l:%s <-> r:%s)",
+ getClass().getSimpleName(),
+ this,
+ getEndPoint().getLocalAddress(),
+ getEndPoint().getRemoteAddress());
+ }
+
+ private class Delegate extends HttpConnection
+ {
+ private Delegate(HttpDestination destination)
+ {
+ super(destination);
+ }
+
+ @Override
+ protected void send(HttpExchange exchange)
+ {
+ Request request = exchange.getRequest();
+ normalizeRequest(request);
+
+ // Save the old idle timeout to restore it
+ EndPoint endPoint = getEndPoint();
+ idleTimeout = endPoint.getIdleTimeout();
+ endPoint.setIdleTimeout(request.getIdleTimeout());
+
+ // One channel per connection, just delegate the send
+ channel.associate(exchange);
+ channel.send();
+ }
+
+ @Override
+ public void close()
+ {
+ HttpConnectionOverHTTP.this.close();
+ }
+
+ @Override
+ public String toString()
+ {
+ return HttpConnectionOverHTTP.this.toString();
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java
new file mode 100644
index 0000000..6e639f5
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.PoolingHttpDestination;
+
+public class HttpDestinationOverHTTP extends PoolingHttpDestination<HttpConnectionOverHTTP>
+{
+ public HttpDestinationOverHTTP(HttpClient client, Origin origin)
+ {
+ super(client, origin);
+ }
+
+ @Override
+ protected void send(HttpConnectionOverHTTP connection, HttpExchange exchange)
+ {
+ connection.send(exchange);
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java
new file mode 100644
index 0000000..efeb2c4
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java
@@ -0,0 +1,243 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import java.io.EOFException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.HttpReceiver;
+import org.eclipse.jetty.client.HttpResponse;
+import org.eclipse.jetty.client.HttpResponseException;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.BufferUtil;
+
+public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
+{
+ private final HttpParser parser = new HttpParser(this);
+
+ public HttpReceiverOverHTTP(HttpChannelOverHTTP channel)
+ {
+ super(channel);
+ }
+
+ @Override
+ public HttpChannelOverHTTP getHttpChannel()
+ {
+ return (HttpChannelOverHTTP)super.getHttpChannel();
+ }
+
+ private HttpConnectionOverHTTP getHttpConnection()
+ {
+ return getHttpChannel().getHttpConnection();
+ }
+
+ public void receive()
+ {
+ HttpConnectionOverHTTP connection = getHttpConnection();
+ EndPoint endPoint = connection.getEndPoint();
+ HttpClient client = getHttpDestination().getHttpClient();
+ ByteBufferPool bufferPool = client.getByteBufferPool();
+ ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
+ try
+ {
+ while (true)
+ {
+ // Connection may be closed in a parser callback
+ if (connection.isClosed())
+ {
+ LOG.debug("{} closed", connection);
+ break;
+ }
+ else
+ {
+ int read = endPoint.fill(buffer);
+ if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'
+ LOG.debug("Read {} bytes from {}", read, endPoint);
+ if (read > 0)
+ {
+ parse(buffer);
+ }
+ else if (read == 0)
+ {
+ fillInterested();
+ break;
+ }
+ else
+ {
+ shutdown();
+ break;
+ }
+ }
+ }
+ }
+ catch (EofException x)
+ {
+ LOG.ignore(x);
+ failAndClose(x);
+ }
+ catch (Exception x)
+ {
+ LOG.debug(x);
+ failAndClose(x);
+ }
+ finally
+ {
+ bufferPool.release(buffer);
+ }
+ }
+
+ private void parse(ByteBuffer buffer)
+ {
+ while (buffer.hasRemaining())
+ parser.parseNext(buffer);
+ }
+
+ private void fillInterested()
+ {
+ // TODO: do we need to call fillInterested() only if we are not failed (or we have an exchange) ?
+ getHttpChannel().getHttpConnection().fillInterested();
+ }
+
+ private void shutdown()
+ {
+ // Shutting down the parser may invoke messageComplete() or earlyEOF()
+ parser.atEOF();
+ parser.parseNext(BufferUtil.EMPTY_BUFFER);
+ if (!responseFailure(new EOFException()))
+ getHttpChannel().getHttpConnection().close();
+ }
+
+ @Override
+ public int getHeaderCacheSize()
+ {
+ // TODO get from configuration
+ return 256;
+ }
+
+ @Override
+ public boolean startResponse(HttpVersion version, int status, String reason)
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange == null)
+ return false;
+
+ String method = exchange.getRequest().getMethod();
+ parser.setHeadResponse(HttpMethod.HEAD.is(method) || HttpMethod.CONNECT.is(method));
+ exchange.getResponse().version(version).status(status).reason(reason);
+
+ responseBegin(exchange);
+ return false;
+ }
+
+ @Override
+ public boolean parsedHeader(HttpField field)
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange == null)
+ return false;
+
+ responseHeader(exchange, field);
+ return false;
+ }
+
+ @Override
+ public boolean headerComplete()
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange == null)
+ return false;
+
+ responseHeaders(exchange);
+ return false;
+ }
+
+ @Override
+ public boolean content(ByteBuffer buffer)
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange == null)
+ return false;
+
+ responseContent(exchange, buffer);
+ return false;
+ }
+
+ @Override
+ public boolean messageComplete()
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange == null)
+ return false;
+
+ responseSuccess(exchange);
+ return true;
+ }
+
+ @Override
+ public void earlyEOF()
+ {
+ failAndClose(new EOFException());
+ }
+
+ @Override
+ public void badMessage(int status, String reason)
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange != null)
+ {
+ HttpResponse response = exchange.getResponse();
+ response.status(status).reason(reason);
+ failAndClose(new HttpResponseException("HTTP protocol violation: bad response", response));
+ }
+ }
+
+ @Override
+ protected void reset()
+ {
+ super.reset();
+ parser.reset();
+ }
+
+ @Override
+ protected void dispose()
+ {
+ super.dispose();
+ parser.close();
+ }
+
+ private void failAndClose(Throwable failure)
+ {
+ if (responseFailure(failure))
+ getHttpChannel().getHttpConnection().close();
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x on %s", getClass().getSimpleName(), hashCode(), getHttpConnection());
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java
new file mode 100644
index 0000000..c18a92a
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java
@@ -0,0 +1,235 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpContent;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.HttpSender;
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.Callback;
+
+public class HttpSenderOverHTTP extends HttpSender
+{
+ private final HttpGenerator generator = new HttpGenerator();
+
+ public HttpSenderOverHTTP(HttpChannelOverHTTP channel)
+ {
+ super(channel);
+ }
+
+ @Override
+ public HttpChannelOverHTTP getHttpChannel()
+ {
+ return (HttpChannelOverHTTP)super.getHttpChannel();
+ }
+
+ @Override
+ protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback)
+ {
+ Request request = exchange.getRequest();
+ ContentProvider requestContent = request.getContent();
+ long contentLength = requestContent == null ? -1 : requestContent.getLength();
+ String path = request.getPath();
+ String query = request.getQuery();
+ if (query != null)
+ path += "?" + query;
+ HttpGenerator.RequestInfo requestInfo = new HttpGenerator.RequestInfo(request.getVersion(), request.getHeaders(), contentLength, request.getMethod(), path);
+
+ try
+ {
+ HttpClient client = getHttpChannel().getHttpDestination().getHttpClient();
+ ByteBufferPool bufferPool = client.getByteBufferPool();
+ ByteBuffer header = bufferPool.acquire(client.getRequestBufferSize(), false);
+ ByteBuffer chunk = null;
+
+ ByteBuffer contentBuffer = null;
+ boolean lastContent = false;
+ if (!expects100Continue(request))
+ {
+ content.advance();
+ contentBuffer = content.getByteBuffer();
+ lastContent = content.isLast();
+ }
+ while (true)
+ {
+ HttpGenerator.Result result = generator.generateRequest(requestInfo, header, chunk, contentBuffer, lastContent);
+ switch (result)
+ {
+ case NEED_CHUNK:
+ {
+ chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
+ break;
+ }
+ case FLUSH:
+ {
+ int size = 1;
+ boolean hasChunk = chunk != null;
+ if (hasChunk)
+ ++size;
+ boolean hasContent = contentBuffer != null;
+ if (hasContent)
+ ++size;
+ ByteBuffer[] toWrite = new ByteBuffer[size];
+ ByteBuffer[] toRecycle = new ByteBuffer[hasChunk ? 2 : 1];
+ toWrite[0] = header;
+ toRecycle[0] = header;
+ if (hasChunk)
+ {
+ toWrite[1] = chunk;
+ toRecycle[1] = chunk;
+ }
+ if (hasContent)
+ toWrite[toWrite.length - 1] = contentBuffer;
+ EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint();
+ endPoint.write(new ByteBufferRecyclerCallback(callback, bufferPool, toRecycle), toWrite);
+ return;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ }
+ catch (Exception x)
+ {
+ LOG.debug(x);
+ callback.failed(x);
+ }
+ }
+
+ @Override
+ protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback)
+ {
+ try
+ {
+ HttpClient client = getHttpChannel().getHttpDestination().getHttpClient();
+ ByteBufferPool bufferPool = client.getByteBufferPool();
+ ByteBuffer chunk = null;
+ while (true)
+ {
+ ByteBuffer contentBuffer = content.getByteBuffer();
+ boolean lastContent = content.isLast();
+ HttpGenerator.Result result = generator.generateRequest(null, null, chunk, contentBuffer, lastContent);
+ switch (result)
+ {
+ case NEED_CHUNK:
+ {
+ chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
+ break;
+ }
+ case FLUSH:
+ {
+ EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint();
+ if (chunk != null)
+ endPoint.write(new ByteBufferRecyclerCallback(callback, bufferPool, chunk), chunk, contentBuffer);
+ else
+ endPoint.write(callback, contentBuffer);
+ return;
+ }
+ case SHUTDOWN_OUT:
+ {
+ shutdownOutput();
+ break;
+ }
+ case CONTINUE:
+ {
+ break;
+ }
+ case DONE:
+ {
+ assert generator.isEnd();
+ callback.succeeded();
+ return;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ }
+ catch (Exception x)
+ {
+ LOG.debug(x);
+ callback.failed(x);
+ }
+ }
+
+ @Override
+ protected void reset()
+ {
+ generator.reset();
+ super.reset();
+ }
+
+ @Override
+ protected RequestState dispose()
+ {
+ generator.abort();
+ RequestState result = super.dispose();
+ shutdownOutput();
+ return result;
+ }
+
+ private void shutdownOutput()
+ {
+ getHttpChannel().getHttpConnection().getEndPoint().shutdownOutput();
+ }
+
+ private class ByteBufferRecyclerCallback implements Callback
+ {
+ private final Callback callback;
+ private final ByteBufferPool pool;
+ private final ByteBuffer[] buffers;
+
+ private ByteBufferRecyclerCallback(Callback callback, ByteBufferPool pool, ByteBuffer... buffers)
+ {
+ this.callback = callback;
+ this.pool = pool;
+ this.buffers = buffers;
+ }
+
+ @Override
+ public void succeeded()
+ {
+ for (ByteBuffer buffer : buffers)
+ {
+ assert !buffer.hasRemaining();
+ pool.release(buffer);
+ }
+ callback.succeeded();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ for (ByteBuffer buffer : buffers)
+ pool.release(buffer);
+ callback.failed(x);
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java b/jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java
index 1c9b859..9ef5dc8 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java
@@ -21,30 +21,30 @@
*
* This package provides APIs, utility classes and an implementation of an asynchronous HTTP client.
* <p />
- * The core class is {@link HttpClient}, which acts as a central configuration object (for example
- * for {@link HttpClient#setIdleTimeout(long) idle timeouts}, {@link HttpClient#setMaxConnectionsPerDestination(int)
+ * The core class is {@link org.eclipse.jetty.client.api.HttpClient}, which acts as a central configuration object (for example
+ * for {@link org.eclipse.jetty.client.api.HttpClient#setIdleTimeout(long) idle timeouts}, {@link org.eclipse.jetty.client.api.HttpClient#setMaxConnectionsPerDestination(int)
* max connections per destination}, etc.) and as a factory for {@link Request} objects.
* <p />
* The HTTP protocol is based on the request/response paradigm, a unit that in this implementation is called
- * <em>exchange</em> and is represented by {@link HttpExchange}.
+ * <em>exchange</em> and is represented by {@link org.eclipse.jetty.client.api.HttpExchange}.
* An initial request may trigger a sequence of exchanges with one or more servers, called a <em>conversation</em>
- * and represented by {@link HttpConversation}. A typical example of a conversation is a redirect, where
+ * and represented by {@link org.eclipse.jetty.client.api.HttpConversation}. A typical example of a conversation is a redirect, where
* upon a request for a resource URI, the server replies with a redirect (for example with the 303 status code)
* to another URI. This conversation is made of a first exchange made of the original request and its 303 response,
* and of a second exchange made of the request for the new URI and its 200 response.
* <p />
- * {@link HttpClient} holds a number of {@link HttpDestination destinations}, which in turn hold a number of
- * pooled {@link HttpConnection connections}.
+ * {@link org.eclipse.jetty.client.api.HttpClient} holds a number of {@link org.eclipse.jetty.client.api.HttpDestination destinations}, which in turn hold a number of
+ * pooled {@link org.eclipse.jetty.client.api.HttpConnection connections}.
* <p />
* When a request is sent, its exchange is associated to a connection, either taken from an idle queue or created
* anew, and when both the request and response are completed, the exchange is disassociated from the connection.
* Conversations may span multiple connections on different destinations, and therefore are maintained at the
- * {@link HttpClient} level.
+ * {@link org.eclipse.jetty.client.api.HttpClient} level.
* <p />
* Applications may decide to send the request and wait for the response in a blocking way, using
- * {@link Request#send()}.
+ * {@link org.eclipse.jetty.client.api.Request#send()}.
* Alternatively, application may ask to be notified of response events asynchronously, using
- * {@link Request#send(Response.Listener)}.
+ * {@link org.eclipse.jetty.client.api.Request#send(Response.Listener)}.
*/
package org.eclipse.jetty.client;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java
index d50d6d9..d144782 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java
@@ -29,7 +29,6 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.StringUtil;
/**
* Implementation of the HTTP "Basic" authentication defined in RFC 2617.
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java
index 50ddbd1..0b7202f 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java
@@ -26,17 +26,18 @@
import java.util.Locale;
import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Response.Listener;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
/**
- * <p>Implementation of {@link Response.Listener} that buffers the content up to a maximum length
+ * <p>Implementation of {@link Listener} that buffers the content up to a maximum length
* specified to the constructors.</p>
* <p>The content may be retrieved from {@link #onSuccess(Response)} or {@link #onComplete(Result)}
* via {@link #getContent()} or {@link #getContentAsString()}.</p>
*/
-public abstract class BufferingResponseListener extends Response.Listener.Empty
+public abstract class BufferingResponseListener extends Listener.Adapter
{
private final int maxLength;
private volatile byte[] buffer = new byte[0];
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java
index d80db07..d5ef779 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java
@@ -19,7 +19,6 @@
package org.eclipse.jetty.client.util;
import java.net.URI;
-import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -221,7 +220,7 @@
String A1 = user + ":" + realm + ":" + password;
String hashA1 = toHexString(digester.digest(A1.getBytes(StandardCharsets.ISO_8859_1)));
- String A2 = request.method() + ":" + request.getURI();
+ String A2 = request.getMethod() + ":" + request.getURI();
if ("auth-int".equals(qop))
A2 += ":" + toHexString(digester.digest(content));
String hashA2 = toHexString(digester.digest(A2.getBytes(StandardCharsets.ISO_8859_1)));
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java
index 2382ef6..0c2e6b8 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java
@@ -104,80 +104,108 @@
@Override
public Iterator<ByteBuffer> iterator()
{
- return new Iterator<ByteBuffer>()
- {
- private final byte[] bytes = new byte[bufferSize];
- private Exception failure;
- private ByteBuffer buffer;
+ return new InputStreamIterator();
+ }
- @Override
- public boolean hasNext()
+ /**
+ * Iterating over an {@link InputStream} is tricky, because {@link #hasNext()} must return false
+ * if the stream reads -1. However, we don't know what to return until we read the stream, which
+ * means that stream reading must be performed by {@link #hasNext()}, which introduces a side-effect
+ * on what is supposed to be a simple query method (with respect to the Query Command Separation
+ * Principle).
+ * <p />
+ * Alternatively, we could return {@code true} from {@link #hasNext()} even if we don't know that
+ * we will read -1, but then when {@link #next()} reads -1 it must return an empty buffer.
+ * However this is problematic, since GETs with no content indication would become GET with chunked
+ * content, and not understood by servers.
+ * <p />
+ * Therefore we need to make sure that {@link #hasNext()} does not perform any side effect (so that
+ * it can be called multiple times) until {@link #next()} is called.
+ */
+ private class InputStreamIterator implements Iterator<ByteBuffer>
+ {
+ private Throwable failure;
+ private ByteBuffer buffer;
+ private Boolean hasNext;
+
+ @Override
+ public boolean hasNext()
+ {
+ try
+ {
+ if (hasNext != null)
+ return hasNext;
+
+ byte[] bytes = new byte[bufferSize];
+ int read = stream.read(bytes);
+ LOG.debug("Read {} bytes from {}", read, stream);
+ if (read > 0)
+ {
+ buffer = onRead(bytes, 0, read);
+ hasNext = Boolean.TRUE;
+ return true;
+ }
+ else if (read < 0)
+ {
+ hasNext = Boolean.FALSE;
+ close();
+ return false;
+ }
+ else
+ {
+ buffer = BufferUtil.EMPTY_BUFFER;
+ hasNext = Boolean.TRUE;
+ return true;
+ }
+ }
+ catch (Throwable x)
+ {
+ LOG.debug(x);
+ if (failure == null)
+ {
+ failure = x;
+ // Signal we have more content to cause a call to
+ // next() which will throw NoSuchElementException.
+ hasNext = Boolean.TRUE;
+ close();
+ return true;
+ }
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public ByteBuffer next()
+ {
+ if (failure != null)
+ throw (NoSuchElementException)new NoSuchElementException().initCause(failure);
+ ByteBuffer result = buffer;
+ if (result == null)
+ throw new NoSuchElementException();
+ buffer = null;
+ hasNext = null;
+ return result;
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ private void close()
+ {
+ if (autoClose)
{
try
{
- int read = stream.read(bytes);
- if (read > 0)
- {
- buffer = onRead(bytes, 0, read);
- return true;
- }
- else if (read < 0)
- {
- close();
- return false;
- }
- else
- {
- buffer = BufferUtil.EMPTY_BUFFER;
- return true;
- }
+ stream.close();
}
- catch (Exception x)
+ catch (IOException x)
{
- if (failure == null)
- {
- failure = x;
- // Signal we have more content to cause a call to
- // next() which will throw NoSuchElementException.
- close();
- return true;
- }
- return false;
+ LOG.ignore(x);
}
}
-
- @Override
- public ByteBuffer next()
- {
- ByteBuffer result = buffer;
- buffer = null;
- if (failure != null)
- throw (NoSuchElementException)new NoSuchElementException().initCause(failure);
- if (result == null)
- throw new NoSuchElementException();
- return result;
- }
-
- @Override
- public void remove()
- {
- throw new UnsupportedOperationException();
- }
-
- private void close()
- {
- if (autoClose)
- {
- try
- {
- stream.close();
- }
- catch (IOException x)
- {
- LOG.ignore(x);
- }
- }
- }
- };
+ }
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
index d59943a..3984408 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
@@ -34,13 +34,14 @@
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Response.Listener;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
- * Implementation of {@link Response.Listener} that produces an {@link InputStream}
+ * Implementation of {@link Listener} that produces an {@link InputStream}
* that allows applications to read the response content.
* <p />
* Typical usage is:
@@ -70,7 +71,7 @@
* If the consumer is slower than the producer, then the producer will block
* until the client consumes.
*/
-public class InputStreamResponseListener extends Response.Listener.Empty
+public class InputStreamResponseListener extends Listener.Adapter
{
private static final Logger LOG = Log.getLogger(InputStreamResponseListener.class);
private static final byte[] EOF = new byte[0];
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java
index 503ad74..c4433ab 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java
@@ -160,7 +160,7 @@
latch.countDown();
}
})
- .send(new Response.Listener.Empty()
+ .send(new Response.Listener.Adapter()
{
@Override
public void onFailure(Response response, Throwable failure)
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/GZIPContentDecoderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/GZIPContentDecoderTest.java
index 8c0a44d..2a6a464 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/GZIPContentDecoderTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/GZIPContentDecoderTest.java
@@ -25,7 +25,6 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java
index 47096e2..3f2ce52 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java
@@ -18,10 +18,15 @@
package org.eclipse.jetty.client;
+import static junit.framework.Assert.fail;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.security.cert.CertificateException;
import java.util.concurrent.ExecutionException;
+
import javax.net.ssl.SSLHandshakeException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -37,11 +42,6 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import org.junit.Ignore;
-
-import static junit.framework.Assert.fail;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.junit.Assert.assertThat;
/**
* This test class runs tests to make sure that hostname verification (http://www.ietf.org/rfc/rfc2818.txt
@@ -100,7 +100,6 @@
* @throws Exception
*/
@Test
- @Ignore
public void simpleGetWithHostnameVerificationEnabledTest() throws Exception
{
clientSslContextFactory.setEndpointIdentificationAlgorithm("HTTPS");
@@ -118,8 +117,8 @@
// Therefore this test may catch a SSLHandshakeException, or a ClosedChannelException.
// If it is the former, we verify that its cause is a CertificateException.
- // ExecutionException wraps an EofException that wraps the SSLHandshakeException
- Throwable cause = x.getCause().getCause();
+ // ExecutionException wraps an SSLHandshakeException
+ Throwable cause = x.getCause();
if (cause instanceof SSLHandshakeException)
assertThat(cause.getCause().getCause(), instanceOf(CertificateException.class));
else
@@ -134,7 +133,6 @@
* @throws Exception
*/
@Test
- @Ignore
public void simpleGetWithHostnameVerificationDisabledTest() throws Exception
{
clientSslContextFactory.setEndpointIdentificationAlgorithm(null);
@@ -156,7 +154,6 @@
* @throws Exception
*/
@Test
- @Ignore
public void trustAllDisablesHostnameVerificationTest() throws Exception
{
clientSslContextFactory.setTrustAll(true);
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
index 23b6926..95c7d99 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
@@ -25,6 +25,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -81,7 +82,7 @@
Constraint constraint = new Constraint();
constraint.setAuthenticate(true);
- constraint.setRoles(new String[]{"*"});
+ constraint.setRoles(new String[]{"**"}); //allow any authenticated user
ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec("/secure");
mapping.setConstraint(constraint);
@@ -89,7 +90,6 @@
securityHandler.addConstraintMapping(mapping);
securityHandler.setAuthenticator(authenticator);
securityHandler.setLoginService(loginService);
- securityHandler.setStrict(false);
securityHandler.setHandler(handler);
start(securityHandler);
@@ -116,7 +116,7 @@
AuthenticationStore authenticationStore = client.getAuthenticationStore();
final AtomicReference<CountDownLatch> requests = new AtomicReference<>(new CountDownLatch(1));
- Request.Listener.Empty requestListener = new Request.Listener.Empty()
+ Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
@@ -138,7 +138,7 @@
authenticationStore.addAuthentication(authentication);
requests.set(new CountDownLatch(2));
- requestListener = new Request.Listener.Empty()
+ requestListener = new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
@@ -157,7 +157,7 @@
client.getRequestListeners().remove(requestListener);
requests.set(new CountDownLatch(1));
- requestListener = new Request.Listener.Empty()
+ requestListener = new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
@@ -197,7 +197,7 @@
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic"));
final CountDownLatch requests = new CountDownLatch(3);
- Request.Listener.Empty requestListener = new Request.Listener.Empty()
+ Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
@@ -236,7 +236,7 @@
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic"));
final CountDownLatch requests = new CountDownLatch(3);
- Request.Listener.Empty requestListener = new Request.Listener.Empty()
+ Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
@@ -263,7 +263,7 @@
startBasic(new EmptyServerHandler());
final AtomicReference<CountDownLatch> requests = new AtomicReference<>(new CountDownLatch(2));
- Request.Listener.Empty requestListener = new Request.Listener.Empty()
+ Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientContinueTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientContinueTest.java
index 50b7a9e..552378e 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientContinueTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientContinueTest.java
@@ -26,6 +26,7 @@
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
@@ -408,7 +409,7 @@
public Response.Listener getResponseListener()
{
final Response.Listener listener = super.getResponseListener();
- return new Response.Listener.Empty()
+ return new Response.Listener.Adapter()
{
@Override
public void onBegin(Response response)
@@ -565,40 +566,7 @@
});
final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
- final DeferredContentProvider content = new DeferredContentProvider()
- {
- @Override
- public Iterator<ByteBuffer> iterator()
- {
- final Iterator<ByteBuffer> delegate = super.iterator();
- return new Iterator<ByteBuffer>()
- {
- private int count;
-
- @Override
- public boolean hasNext()
- {
- return delegate.hasNext();
- }
-
- @Override
- public ByteBuffer next()
- {
- // Fake that it returns null for two times,
- // to trigger particular branches in HttpSender
- if (++count <= 2)
- return null;
- return delegate.next();
- }
-
- @Override
- public void remove()
- {
- delegate.remove();
- }
- };
- }
- };
+ final DeferredContentProvider content = new DeferredContentProvider();
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java
new file mode 100644
index 0000000..8779620
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java
@@ -0,0 +1,246 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientCustomProxyTest
+{
+ public static final byte[] CAFE_BABE = new byte[]{(byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE};
+
+ private Server server;
+ private ServerConnector connector;
+ private HttpClient client;
+
+ public void prepare(Handler handler) throws Exception
+ {
+ server = new Server();
+ connector = new ServerConnector(server, new CAFEBABEServerConnectionFactory(new HttpConnectionFactory()));
+ server.addConnector(connector);
+ server.setHandler(handler);
+ server.start();
+
+ QueuedThreadPool executor = new QueuedThreadPool();
+ executor.setName(executor.getName() + "-client");
+ client = new HttpClient();
+ client.setExecutor(executor);
+ client.start();
+ }
+
+ @After
+ public void dispose() throws Exception
+ {
+ if (client != null)
+ client.stop();
+ if (server != null)
+ server.stop();
+ }
+
+ @Test
+ public void testCustomProxy() throws Exception
+ {
+ final String serverHost = "server";
+ final int status = HttpStatus.NO_CONTENT_204;
+ prepare(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ if (!URI.create(baseRequest.getUri().toString()).isAbsolute())
+ response.setStatus(HttpServletResponse.SC_USE_PROXY);
+ else if (serverHost.equals(request.getServerName()))
+ response.setStatus(status);
+ else
+ response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
+ }
+ });
+
+ // Setup the custom proxy
+ int proxyPort = connector.getLocalPort();
+ int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
+ client.getProxyConfiguration().getProxies().add(new CAFEBABEProxy(new Origin.Address("localhost", proxyPort), false));
+
+ ContentResponse response = client.newRequest(serverHost, serverPort)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertEquals(status, response.getStatus());
+ }
+
+ private class CAFEBABEProxy extends ProxyConfiguration.Proxy
+ {
+ private CAFEBABEProxy(Origin.Address address, boolean secure)
+ {
+ super(address, secure);
+ }
+
+ @Override
+ public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
+ {
+ return new CAFEBABEClientConnectionFactory(connectionFactory);
+ }
+ }
+
+ private class CAFEBABEClientConnectionFactory implements ClientConnectionFactory
+ {
+ private final ClientConnectionFactory connectionFactory;
+
+ private CAFEBABEClientConnectionFactory(ClientConnectionFactory connectionFactory)
+ {
+ this.connectionFactory = connectionFactory;
+ }
+
+ @Override
+ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+ {
+ HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+ Executor executor = destination.getHttpClient().getExecutor();
+ return new CAFEBABEConnection(endPoint, executor, connectionFactory, context);
+ }
+ }
+
+ private class CAFEBABEConnection extends AbstractConnection
+ {
+ private final ClientConnectionFactory connectionFactory;
+ private final Map<String, Object> context;
+
+ public CAFEBABEConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, Map<String, Object> context)
+ {
+ super(endPoint, executor);
+ this.connectionFactory = connectionFactory;
+ this.context = context;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ fillInterested();
+ getEndPoint().write(new Callback.Adapter(), ByteBuffer.wrap(CAFE_BABE));
+ }
+
+ @Override
+ public void onFillable()
+ {
+ try
+ {
+ ByteBuffer buffer = BufferUtil.allocate(4);
+ int filled = getEndPoint().fill(buffer);
+ Assert.assertEquals(4, filled);
+ Assert.assertArrayEquals(CAFE_BABE, buffer.array());
+
+ // We are good, upgrade the connection
+ ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(getEndPoint(), context));
+ }
+ catch (Throwable x)
+ {
+ close();
+ @SuppressWarnings("unchecked")
+ Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+ promise.failed(x);
+ }
+ }
+ }
+
+ private class CAFEBABEServerConnectionFactory extends AbstractConnectionFactory
+ {
+ private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
+
+ private CAFEBABEServerConnectionFactory(org.eclipse.jetty.server.ConnectionFactory connectionFactory)
+ {
+ super("cafebabe");
+ this.connectionFactory = connectionFactory;
+ }
+
+ @Override
+ public org.eclipse.jetty.io.Connection newConnection(Connector connector, EndPoint endPoint)
+ {
+ return new CAFEBABEServerConnection(connector, endPoint, connectionFactory);
+ }
+ }
+
+ private class CAFEBABEServerConnection extends AbstractConnection
+ {
+ private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
+
+ public CAFEBABEServerConnection(Connector connector, EndPoint endPoint, org.eclipse.jetty.server.ConnectionFactory connectionFactory)
+ {
+ super(endPoint, connector.getExecutor());
+ this.connectionFactory = connectionFactory;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ try
+ {
+ ByteBuffer buffer = BufferUtil.allocate(4);
+ int filled = getEndPoint().fill(buffer);
+ Assert.assertEquals(4, filled);
+ Assert.assertArrayEquals(CAFE_BABE, buffer.array());
+ getEndPoint().write(new Callback.Adapter(), buffer);
+
+ // We are good, upgrade the connection
+ ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(connector, getEndPoint()));
+ }
+ catch (Throwable x)
+ {
+ close();
+ }
+ }
+ }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java
index c49b37b..7260ae6 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java
@@ -24,6 +24,8 @@
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.client.http.HttpConnectionOverHTTP;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.FuturePromise;
@@ -56,9 +58,10 @@
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
- HttpDestination httpDestination = (HttpDestination)destination;
- Assert.assertTrue(httpDestination.getActiveConnections().isEmpty());
- Assert.assertTrue(httpDestination.getIdleConnections().isEmpty());
+ HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
+ ConnectionPool connectionPool = httpDestination.getConnectionPool();
+ Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
+ Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
}
}
@@ -84,11 +87,12 @@
// Give the connection some time to process the remote close
TimeUnit.SECONDS.sleep(1);
- HttpConnection httpConnection = (HttpConnection)connection;
+ HttpConnectionOverHTTP httpConnection = (HttpConnectionOverHTTP)connection;
Assert.assertFalse(httpConnection.getEndPoint().isOpen());
- HttpDestination httpDestination = (HttpDestination)destination;
- Assert.assertTrue(httpDestination.getActiveConnections().isEmpty());
- Assert.assertTrue(httpDestination.getIdleConnections().isEmpty());
+ HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
+ ConnectionPool connectionPool = httpDestination.getConnectionPool();
+ Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
+ Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
}
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java
index 601fd1c..f5bacf4 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java
@@ -19,7 +19,6 @@
package org.eclipse.jetty.client;
import java.io.IOException;
-import java.net.URI;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -29,6 +28,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -37,6 +37,8 @@
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
@@ -48,6 +50,7 @@
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
import org.junit.Assert;
import org.junit.Test;
@@ -70,9 +73,27 @@
client.setMaxConnectionsPerDestination(32768);
client.setMaxRequestsQueuedPerDestination(1024 * 1024);
client.setDispatchIO(false);
+ client.setStrictEventOrdering(false);
Random random = new Random();
+ // At least 25k requests to warmup properly (use -XX:+PrintCompilation to verify JIT activity)
+ int runs = 1;
int iterations = 500;
+ for (int i = 0; i < runs; ++i)
+ {
+ run(random, iterations);
+ }
+
+ // Re-run after warmup
+ iterations = 5_000;
+ for (int i = 0; i < runs; ++i)
+ {
+ run(random, iterations);
+ }
+ }
+
+ private void run(Random random, int iterations) throws InterruptedException
+ {
CountDownLatch latch = new CountDownLatch(iterations);
List<String> failures = new ArrayList<>();
@@ -81,7 +102,7 @@
// Dumps the state of the client if the test takes too long
final Thread testThread = Thread.currentThread();
- client.getScheduler().schedule(new Runnable()
+ Scheduler.Task task = client.getScheduler().schedule(new Runnable()
{
@Override
public void run()
@@ -89,11 +110,12 @@
logger.warn("Interrupting test, it is taking too long");
for (String host : Arrays.asList("localhost", "127.0.0.1"))
{
- HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, connector.getLocalPort());
- for (Connection connection : new ArrayList<>(destination.getActiveConnections()))
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, connector.getLocalPort());
+ ConnectionPool connectionPool = destination.getConnectionPool();
+ for (Connection connection : new ArrayList<>(connectionPool.getActiveConnections()))
{
- HttpConnection active = (HttpConnection)connection;
- logger.warn(active.getEndPoint() + " exchange " + active.getExchange());
+ HttpConnectionOverHTTP active = (HttpConnectionOverHTTP)connection;
+ logger.warn(active.getEndPoint() + " exchange " + active.getHttpChannel().getHttpExchange());
}
}
testThread.interrupt();
@@ -104,9 +126,11 @@
for (int i = 0; i < iterations; ++i)
{
test(random, latch, failures);
+// test("http", "localhost", "GET", false, false, 64 * 1024, false, latch, failures);
}
Assert.assertTrue(latch.await(iterations, TimeUnit.SECONDS));
long end = System.nanoTime();
+ task.cancel();
long elapsed = TimeUnit.NANOSECONDS.toMillis(end - begin);
logger.info("{} requests in {} ms, {} req/s", iterations, elapsed, elapsed > 0 ? iterations * 1000 / elapsed : -1);
@@ -118,56 +142,70 @@
private void test(Random random, final CountDownLatch latch, final List<String> failures) throws InterruptedException
{
- int maxContentLength = 64 * 1024;
-
// Choose a random destination
String host = random.nextBoolean() ? "localhost" : "127.0.0.1";
- URI uri = URI.create(scheme + "://" + host + ":" + connector.getLocalPort());
- Request request = client.newRequest(uri);
-
// Choose a random method
HttpMethod method = random.nextBoolean() ? HttpMethod.GET : HttpMethod.POST;
- request.method(method);
boolean ssl = HttpScheme.HTTPS.is(scheme);
// Choose randomly whether to close the connection on the client or on the server
+ boolean clientClose = false;
if (!ssl && random.nextBoolean())
+ clientClose = true;
+ boolean serverClose = false;
+ if (!ssl && random.nextBoolean())
+ serverClose = true;
+
+ int maxContentLength = 64 * 1024;
+ int contentLength = random.nextInt(maxContentLength) + 1;
+
+ test(scheme, host, method.asString(), clientClose, serverClose, contentLength, true, latch, failures);
+ }
+
+ private void test(String scheme, String host, String method, boolean clientClose, boolean serverClose, int contentLength, final boolean checkContentLength, final CountDownLatch latch, final List<String> failures) throws InterruptedException
+ {
+ Request request = client.newRequest(host, connector.getLocalPort())
+ .scheme(scheme)
+ .method(method);
+
+ if (clientClose)
request.header(HttpHeader.CONNECTION, "close");
- else if (!ssl && random.nextBoolean())
+ else if (serverClose)
request.header("X-Close", "true");
- int contentLength = random.nextInt(maxContentLength) + 1;
switch (method)
{
- case GET:
- // Randomly ask the server to download data upon this GET request
- if (random.nextBoolean())
- request.header("X-Download", String.valueOf(contentLength));
+ case "GET":
+ request.header("X-Download", String.valueOf(contentLength));
break;
- case POST:
+ case "POST":
request.header("X-Upload", String.valueOf(contentLength));
request.content(new BytesContentProvider(new byte[contentLength]));
break;
}
final CountDownLatch requestLatch = new CountDownLatch(1);
- request.send(new Response.Listener.Empty()
+ request.send(new Response.Listener.Adapter()
{
private final AtomicInteger contentLength = new AtomicInteger();
@Override
public void onHeaders(Response response)
{
- String content = response.getHeaders().get("X-Content");
- if (content != null)
- contentLength.set(Integer.parseInt(content));
+ if (checkContentLength)
+ {
+ String content = response.getHeaders().get("X-Content");
+ if (content != null)
+ contentLength.set(Integer.parseInt(content));
+ }
}
@Override
public void onContent(Response response, ByteBuffer content)
{
- contentLength.addAndGet(-content.remaining());
+ if (checkContentLength)
+ contentLength.addAndGet(-content.remaining());
}
@Override
@@ -178,8 +216,10 @@
result.getFailure().printStackTrace();
failures.add("Result failed " + result);
}
- if (contentLength.get() != 0)
+
+ if (checkContentLength && contentLength.get() != 0)
failures.add("Content length mismatch " + contentLength);
+
requestLatch.countDown();
latch.countDown();
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java
index c44acc5..4102d4a 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java
@@ -23,12 +23,12 @@
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
-import org.eclipse.jetty.client.api.ProxyConfiguration;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.http.HttpHeader;
@@ -69,7 +69,7 @@
int proxyPort = connector.getLocalPort();
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
- client.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort));
+ client.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
ContentResponse response = client.newRequest(serverHost, serverPort)
.scheme(scheme)
@@ -116,7 +116,7 @@
String proxyHost = "localhost";
int proxyPort = connector.getLocalPort();
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
- client.setProxyConfiguration(new ProxyConfiguration(proxyHost, proxyPort));
+ client.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
ContentResponse response1 = client.newRequest(serverHost, serverPort)
.scheme(scheme)
@@ -130,7 +130,7 @@
URI uri = URI.create(scheme + "://" + proxyHost + ":" + proxyPort);
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, user, password));
final AtomicInteger requests = new AtomicInteger();
- client.getRequestListeners().add(new Request.Listener.Empty()
+ client.getRequestListeners().add(new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java
index 58e4400..79b8b0f 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java
@@ -22,15 +22,18 @@
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.channels.UnresolvedAddressException;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
@@ -44,8 +47,6 @@
import org.junit.Before;
import org.junit.Test;
-import static org.junit.Assert.fail;
-
public class HttpClientRedirectTest extends AbstractHttpClientServerTest
{
public HttpClientRedirectTest(SslContextFactory sslContextFactory)
@@ -123,7 +124,7 @@
.path("/301/localhost/done")
.timeout(5, TimeUnit.SECONDS)
.send();
- fail();
+ Assert.fail();
}
catch (ExecutionException x)
{
@@ -164,7 +165,7 @@
.path("/303/localhost/302/localhost/done")
.timeout(5, TimeUnit.SECONDS)
.send();
- fail();
+ Assert.fail();
}
catch (ExecutionException x)
{
@@ -331,6 +332,43 @@
testSameMethodRedirect(HttpMethod.PUT, HttpStatus.TEMPORARY_REDIRECT_307);
}
+ @Test
+ public void testHttpRedirector() throws Exception
+ {
+ final HttpRedirector redirector = new HttpRedirector(client);
+
+ org.eclipse.jetty.client.api.Request request1 = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .path("/303/localhost/302/localhost/done")
+ .timeout(5, TimeUnit.SECONDS)
+ .followRedirects(false);
+ ContentResponse response1 = request1.send();
+
+ Assert.assertEquals(303, response1.getStatus());
+ Assert.assertTrue(redirector.isRedirect(response1));
+
+ Result result = redirector.redirect(request1, response1);
+ org.eclipse.jetty.client.api.Request request2 = result.getRequest();
+ Response response2 = result.getResponse();
+
+ Assert.assertEquals(302, response2.getStatus());
+ Assert.assertTrue(redirector.isRedirect(response2));
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ redirector.redirect(request2, response2, new Response.CompleteListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ Response response3 = result.getResponse();
+ Assert.assertEquals(200, response3.getStatus());
+ Assert.assertFalse(redirector.isRedirect(response3));
+ latch.countDown();
+ }
+ });
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+ }
+
private void testSameMethodRedirect(final HttpMethod method, int redirectCode) throws Exception
{
testMethodRedirect(method, method, redirectCode);
@@ -344,7 +382,7 @@
private void testMethodRedirect(final HttpMethod requestMethod, final HttpMethod redirectMethod, int redirectCode) throws Exception
{
final AtomicInteger passes = new AtomicInteger();
- client.getRequestListeners().add(new org.eclipse.jetty.client.api.Request.Listener.Empty()
+ client.getRequestListeners().add(new org.eclipse.jetty.client.api.Request.Listener.Adapter()
{
@Override
public void onBegin(org.eclipse.jetty.client.api.Request request)
@@ -352,12 +390,12 @@
int pass = passes.incrementAndGet();
if (pass == 1)
{
- if (!requestMethod.is(request.method()))
+ if (!requestMethod.is(request.getMethod()))
request.abort(new Exception());
}
else if (pass == 2)
{
- if (!redirectMethod.is(request.method()))
+ if (!redirectMethod.is(request.getMethod()))
request.abort(new Exception());
}
else
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java
index 7c1d7b4..7c33340 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java
@@ -18,6 +18,10 @@
package org.eclipse.jetty.client;
+import static java.nio.file.StandardOpenOption.CREATE;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -37,6 +41,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
+
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
@@ -61,10 +66,6 @@
import org.junit.Assert;
import org.junit.Test;
-import static java.nio.file.StandardOpenOption.CREATE;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
public class HttpClientStreamTest extends AbstractHttpClientServerTest
{
public HttpClientStreamTest(SslContextFactory sslContextFactory)
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java
index 2beca50..0f717d8 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java
@@ -56,7 +56,7 @@
synchronized (this)
{
- request.send(new Response.Listener.Empty()
+ request.send(new Response.Listener.Adapter()
{
@Override
public void onFailure(Response response, Throwable failure)
@@ -88,7 +88,7 @@
synchronized (this)
{
- request.send(new Response.Listener.Empty()
+ request.send(new Response.Listener.Adapter()
{
@Override
public void onComplete(Result result)
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
index ce3a19f..be4a2b3 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
@@ -35,6 +35,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
+import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@@ -53,22 +54,29 @@
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
+import org.junit.Rule;
import org.junit.Test;
import static java.nio.file.StandardOpenOption.CREATE;
public class HttpClientTest extends AbstractHttpClientServerTest
{
+ @Rule
+ public TestingDir testdir = new TestingDir();
+
public HttpClientTest(SslContextFactory sslContextFactory)
{
super(sslContextFactory);
@@ -85,13 +93,14 @@
Response response = client.GET(scheme + "://" + host + ":" + port + path);
Assert.assertEquals(200, response.getStatus());
- HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+ ConnectionPool connectionPool = destination.getConnectionPool();
long start = System.nanoTime();
- HttpConnection connection = null;
+ HttpConnectionOverHTTP connection = null;
while (connection == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
{
- connection = (HttpConnection)destination.getIdleConnections().peek();
+ connection = (HttpConnectionOverHTTP)connectionPool.getIdleConnections().peek();
TimeUnit.MILLISECONDS.sleep(10);
}
Assert.assertNotNull(connection);
@@ -102,8 +111,8 @@
client.stop();
Assert.assertEquals(0, client.getDestinations().size());
- Assert.assertEquals(0, destination.getIdleConnections().size());
- Assert.assertEquals(0, destination.getActiveConnections().size());
+ Assert.assertEquals(0, connectionPool.getIdleConnections().size());
+ Assert.assertEquals(0, connectionPool.getActiveConnections().size());
Assert.assertFalse(connection.getEndPoint().isOpen());
}
@@ -402,7 +411,7 @@
}
}
})
- .send(new Response.Listener.Empty()
+ .send(new Response.Listener.Adapter()
{
@Override
public void onSuccess(Response response)
@@ -422,7 +431,7 @@
latch.countDown();
}
})
- .send(new Response.Listener.Empty()
+ .send(new Response.Listener.Adapter()
{
@Override
public void onSuccess(Response response)
@@ -449,7 +458,7 @@
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/one")
- .listener(new Request.Listener.Empty()
+ .listener(new Request.Listener.Adapter()
{
@Override
public void onBegin(Request request)
@@ -504,7 +513,7 @@
start(new EmptyServerHandler());
// Prepare a big file to upload
- Path targetTestsDir = MavenTestingUtils.getTargetTestingDir().toPath();
+ Path targetTestsDir = testdir.getEmptyDir().toPath();
Files.createDirectories(targetTestsDir);
Path file = Paths.get(targetTestsDir.toString(), "http_client_conversation.big");
try (OutputStream output = Files.newOutputStream(file, CREATE))
@@ -530,7 +539,7 @@
latch.countDown();
}
})
- .send(new Response.Listener.Empty()
+ .send(new Response.Listener.Adapter()
{
@Override
public void onSuccess(Response response)
@@ -609,7 +618,7 @@
};
}
})
- .send(new Response.Listener.Empty()
+ .send(new Response.Listener.Adapter()
{
@Override
public void onComplete(Result result)
@@ -636,11 +645,11 @@
@Override
public void onBegin(Request request)
{
- HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
- destination.getActiveConnections().peek().close();
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+ destination.getConnectionPool().getActiveConnections().peek().close();
}
})
- .send(new Response.Listener.Empty()
+ .send(new Response.Listener.Adapter()
{
@Override
public void onComplete(Result result)
@@ -1005,4 +1014,41 @@
int expected = expectedEventsTriggeredByOnResponseXXXListeners + expectedEventsTriggeredByCompletionListener;
Assert.assertEquals(expected, counter.get());
}
+
+ @Test
+ public void setOnCompleteCallbackWithBlockingSend() throws Exception
+ {
+ final byte[] content = new byte[512];
+ new Random().nextBytes(content);
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ response.getOutputStream().write(content);
+ }
+ });
+
+ final AtomicInteger complete = new AtomicInteger();
+ BufferingResponseListener listener = new BufferingResponseListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ complete.incrementAndGet();
+ }
+ };
+
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .onResponseContent(listener)
+ .onComplete(listener)
+ .send();
+
+ Assert.assertEquals(200, response.getStatus());
+ Assert.assertEquals(1, complete.get());
+ Assert.assertArrayEquals(content, listener.getContent());
+ Assert.assertArrayEquals(content, response.getContent());
+ }
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
index 3297b80..25e4de9 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
@@ -21,9 +21,11 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
+
import javax.net.ssl.SSLEngine;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -34,9 +36,14 @@
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.InputStreamContentProvider;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
@@ -240,22 +247,37 @@
start(new TimeoutHandler(2 * timeout));
client.stop();
final AtomicBoolean sslIdle = new AtomicBoolean();
- client = new HttpClient(sslContextFactory)
+ client = new HttpClient(new HttpClientTransportOverHTTP()
{
@Override
- protected SslConnection newSslConnection(HttpClient httpClient, EndPoint endPoint, SSLEngine engine)
+ public HttpDestination newHttpDestination(Origin origin)
{
- return new SslConnection(httpClient.getByteBufferPool(), httpClient.getExecutor(), endPoint, engine)
+ return new HttpDestinationOverHTTP(getHttpClient(), origin)
{
@Override
- protected boolean onReadTimeout()
+ protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
{
- sslIdle.set(true);
- return super.onReadTimeout();
+ HttpClient client = getHttpClient();
+ return new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory)
+ {
+ @Override
+ protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
+ {
+ return new SslConnection(byteBufferPool, executor, endPoint, engine)
+ {
+ @Override
+ protected boolean onReadTimeout()
+ {
+ sslIdle.set(true);
+ return super.onReadTimeout();
+ }
+ };
+ }
+ };
}
};
}
- };
+ }, sslContextFactory);
client.setIdleTimeout(timeout);
client.start();
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java
index 3c88ea1..4112d57 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.net.URLEncoder;
import java.util.concurrent.TimeUnit;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -82,7 +83,7 @@
Assert.assertEquals(path, request.getPath());
Assert.assertNull(request.getQuery());
Fields params = request.getParams();
- Assert.assertEquals(0, params.size());
+ Assert.assertEquals(0, params.getSize());
Assert.assertTrue(request.getURI().toString().endsWith(path));
ContentResponse response = request.send();
@@ -118,8 +119,8 @@
Assert.assertEquals(query, request.getQuery());
Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
Fields params = request.getParams();
- Assert.assertEquals(1, params.size());
- Assert.assertEquals(value, params.get(name).value());
+ Assert.assertEquals(1, params.getSize());
+ Assert.assertEquals(value, params.get(name).getValue());
ContentResponse response = request.send();
@@ -155,8 +156,8 @@
Assert.assertEquals(query, request.getQuery());
Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
Fields params = request.getParams();
- Assert.assertEquals(1, params.size());
- Assert.assertEquals(value, params.get(name).value());
+ Assert.assertEquals(1, params.getSize());
+ Assert.assertEquals(value, params.get(name).getValue());
ContentResponse response = request.send();
@@ -194,9 +195,9 @@
Assert.assertEquals(query, request.getQuery());
Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
Fields params = request.getParams();
- Assert.assertEquals(2, params.size());
- Assert.assertEquals(value1, params.get(name1).value());
- Assert.assertEquals(value2, params.get(name2).value());
+ Assert.assertEquals(2, params.getSize());
+ Assert.assertEquals(value1, params.get(name1).getValue());
+ Assert.assertEquals(value2, params.get(name2).getValue());
ContentResponse response = request.send();
@@ -238,9 +239,9 @@
Assert.assertEquals(query, request.getQuery());
Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
Fields params = request.getParams();
- Assert.assertEquals(2, params.size());
- Assert.assertEquals(value1, params.get(name1).value());
- Assert.assertEquals(value2, params.get(name2).value());
+ Assert.assertEquals(2, params.getSize());
+ Assert.assertEquals(value1, params.get(name1).getValue());
+ Assert.assertEquals(value2, params.get(name2).getValue());
ContentResponse response = request.send();
@@ -273,7 +274,7 @@
Assert.assertEquals(query, request.getQuery());
Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
Fields params = request.getParams();
- Assert.assertEquals(0, params.size());
+ Assert.assertEquals(0, params.getSize());
ContentResponse response = request.send();
@@ -306,7 +307,7 @@
Assert.assertEquals(query, request.getQuery());
Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
Fields params = request.getParams();
- Assert.assertEquals(0, params.size());
+ Assert.assertEquals(0, params.getSize());
ContentResponse response = request.send();
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
index 4d2aa2f..1f8494e 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
@@ -24,6 +24,7 @@
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -33,8 +34,10 @@
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.log.Log;
@@ -50,6 +53,13 @@
super(sslContextFactory);
}
+ @Override
+ public void start(Handler handler) throws Exception
+ {
+ super.start(handler);
+ client.setStrictEventOrdering(false);
+ }
+
@Test
public void test_SuccessfulRequest_ReturnsConnection() throws Exception
{
@@ -57,12 +67,13 @@
String host = "localhost";
int port = connector.getLocalPort();
- HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+ ConnectionPool connectionPool = destination.getConnectionPool();
- final BlockingQueue<Connection> idleConnections = destination.getIdleConnections();
+ final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final BlockingQueue<Connection> activeConnections = destination.getActiveConnections();
+ final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
final CountDownLatch headersLatch = new CountDownLatch(1);
@@ -87,7 +98,7 @@
headersLatch.countDown();
}
})
- .send(new Response.Listener.Empty()
+ .send(new Response.Listener.Adapter()
{
@Override
public void onSuccess(Response response)
@@ -117,17 +128,18 @@
String host = "localhost";
int port = connector.getLocalPort();
- HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+ ConnectionPool connectionPool = destination.getConnectionPool();
- final BlockingQueue<Connection> idleConnections = destination.getIdleConnections();
+ final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final BlockingQueue<Connection> activeConnections = destination.getActiveConnections();
+ final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
final CountDownLatch beginLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(2);
- client.newRequest(host, port).scheme(scheme).listener(new Request.Listener.Empty()
+ client.newRequest(host, port).scheme(scheme).listener(new Request.Listener.Adapter()
{
@Override
public void onBegin(Request request)
@@ -141,7 +153,7 @@
{
failureLatch.countDown();
}
- }).send(new Response.Listener.Empty()
+ }).send(new Response.Listener.Adapter()
{
@Override
public void onComplete(Result result)
@@ -167,18 +179,19 @@
String host = "localhost";
int port = connector.getLocalPort();
- HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+ ConnectionPool connectionPool = destination.getConnectionPool();
- final BlockingQueue<Connection> idleConnections = destination.getIdleConnections();
+ final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final BlockingQueue<Connection> activeConnections = destination.getActiveConnections();
+ final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
final CountDownLatch successLatch = new CountDownLatch(3);
client.newRequest(host, port)
.scheme(scheme)
- .listener(new Request.Listener.Empty()
+ .listener(new Request.Listener.Adapter()
{
@Override
public void onBegin(Request request)
@@ -193,7 +206,7 @@
successLatch.countDown();
}
})
- .send(new Response.Listener.Empty()
+ .send(new Response.Listener.Adapter()
{
@Override
public void onSuccess(Response response)
@@ -226,19 +239,20 @@
String host = "localhost";
int port = connector.getLocalPort();
- HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+ ConnectionPool connectionPool = destination.getConnectionPool();
- final BlockingQueue<Connection> idleConnections = destination.getIdleConnections();
+ final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final BlockingQueue<Connection> activeConnections = destination.getActiveConnections();
+ final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
final long delay = 1000;
final CountDownLatch successLatch = new CountDownLatch(3);
client.newRequest(host, port)
.scheme(scheme)
- .listener(new Request.Listener.Empty()
+ .listener(new Request.Listener.Adapter()
{
@Override
public void onBegin(Request request)
@@ -266,7 +280,7 @@
successLatch.countDown();
}
})
- .send(new Response.Listener.Empty()
+ .send(new Response.Listener.Adapter()
{
@Override
public void onSuccess(Response response)
@@ -298,12 +312,13 @@
String host = "localhost";
int port = connector.getLocalPort();
- HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+ ConnectionPool connectionPool = destination.getConnectionPool();
- final BlockingQueue<Connection> idleConnections = destination.getIdleConnections();
+ final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final BlockingQueue<Connection> activeConnections = destination.getActiveConnections();
+ final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
server.stop();
@@ -319,7 +334,7 @@
failureLatch.countDown();
}
})
- .send(new Response.Listener.Empty()
+ .send(new Response.Listener.Adapter()
{
@Override
public void onComplete(Result result)
@@ -350,18 +365,19 @@
String host = "localhost";
int port = connector.getLocalPort();
- HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+ ConnectionPool connectionPool = destination.getConnectionPool();
- final BlockingQueue<Connection> idleConnections = destination.getIdleConnections();
+ final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final BlockingQueue<Connection> activeConnections = destination.getActiveConnections();
+ final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest(host, port)
.scheme(scheme)
- .send(new Response.Listener.Empty()
+ .send(new Response.Listener.Adapter()
{
@Override
public void onComplete(Result result)
@@ -399,12 +415,13 @@
String host = "localhost";
int port = connector.getLocalPort();
- HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+ ConnectionPool connectionPool = destination.getConnectionPool();
- final BlockingQueue<Connection> idleConnections = destination.getIdleConnections();
+ final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final BlockingQueue<Connection> activeConnections = destination.getActiveConnections();
+ final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
Log.getLogger(HttpConnection.class).info("Expecting java.lang.IllegalStateException: HttpParser{s=CLOSED,...");
@@ -415,11 +432,12 @@
client.newRequest(host, port)
.scheme(scheme)
.content(new ByteBufferContentProvider(buffer))
- .send(new Response.Listener.Empty()
+ .send(new Response.Listener.Adapter()
{
@Override
public void onComplete(Result result)
{
+ Assert.assertEquals(1, latch.getCount());
Assert.assertEquals(0, idleConnections.size());
Assert.assertEquals(0, activeConnections.size());
latch.countDown();
@@ -430,6 +448,7 @@
Assert.assertEquals(0, idleConnections.size());
Assert.assertEquals(0, activeConnections.size());
+
server.stop();
}
finally
@@ -446,12 +465,13 @@
String host = "localhost";
int port = connector.getLocalPort();
- HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+ ConnectionPool connectionPool = destination.getConnectionPool();
- final BlockingQueue<Connection> idleConnections = destination.getIdleConnections();
+ final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final BlockingQueue<Connection> activeConnections = destination.getActiveConnections();
+ final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
ContentResponse response = client.newRequest(host, port)
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java
index 6567b23..6e1ed51 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java
@@ -22,6 +22,7 @@
import java.net.HttpCookie;
import java.net.URI;
import java.util.List;
+
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpDestinationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpDestinationTest.java
deleted file mode 100644
index 3be2eae..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpDestinationTest.java
+++ /dev/null
@@ -1,216 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.client.api.Connection;
-import org.eclipse.jetty.client.api.ContentResponse;
-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.toolchain.test.annotation.Slow;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.hamcrest.Matchers;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-public class HttpDestinationTest extends AbstractHttpClientServerTest
-{
- public HttpDestinationTest(SslContextFactory sslContextFactory)
- {
- super(sslContextFactory);
- }
-
- @Before
- public void init() throws Exception
- {
- start(new EmptyServerHandler());
- }
-
- @Test
- public void test_FirstAcquire_WithEmptyQueue() throws Exception
- {
- HttpDestination destination = new HttpDestination(client, "http", "localhost", connector.getLocalPort());
- Connection connection = destination.acquire();
- if (connection == null)
- {
- // There are no queued requests, so the newly created connection will be idle
- connection = destination.getIdleConnections().poll(5, TimeUnit.SECONDS);
- }
- Assert.assertNotNull(connection);
- }
-
- @Test
- public void test_SecondAcquire_AfterFirstAcquire_WithEmptyQueue_ReturnsSameConnection() throws Exception
- {
- HttpDestination destination = new HttpDestination(client, "http", "localhost", connector.getLocalPort());
- Connection connection1 = destination.acquire();
- if (connection1 == null)
- {
- // There are no queued requests, so the newly created connection will be idle
- long start = System.nanoTime();
- while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
- {
- connection1 = destination.getIdleConnections().peek();
- TimeUnit.MILLISECONDS.sleep(50);
- }
- Assert.assertNotNull(connection1);
-
- Connection connection2 = destination.acquire();
- Assert.assertSame(connection1, connection2);
- }
- }
-
- @Test
- public void test_SecondAcquire_ConcurrentWithFirstAcquire_WithEmptyQueue_CreatesTwoConnections() throws Exception
- {
- final CountDownLatch latch = new CountDownLatch(1);
- HttpDestination destination = new HttpDestination(client, "http", "localhost", connector.getLocalPort())
- {
- @Override
- protected void process(Connection connection, boolean dispatch)
- {
- try
- {
- latch.await(5, TimeUnit.SECONDS);
- super.process(connection, dispatch);
- }
- catch (InterruptedException x)
- {
- x.printStackTrace();
- }
- }
- };
- Connection connection1 = destination.acquire();
-
- // There are no available existing connections, so acquire()
- // returns null because we delayed process() above
- Assert.assertNull(connection1);
-
- Connection connection2 = destination.acquire();
- Assert.assertNull(connection2);
-
- latch.countDown();
-
- // There must be 2 idle connections
- Connection connection = destination.getIdleConnections().poll(5, TimeUnit.SECONDS);
- Assert.assertNotNull(connection);
- connection = destination.getIdleConnections().poll(5, TimeUnit.SECONDS);
- Assert.assertNotNull(connection);
- }
-
- @Test
- public void test_Acquire_Process_Release_Acquire_ReturnsSameConnection() throws Exception
- {
- HttpDestination destination = new HttpDestination(client, "http", "localhost", connector.getLocalPort());
- Connection connection1 = destination.acquire();
- if (connection1 == null)
- connection1 = destination.getIdleConnections().poll(5, TimeUnit.SECONDS);
- Assert.assertNotNull(connection1);
-
- destination.process(connection1, false);
- destination.release(connection1);
-
- Connection connection2 = destination.acquire();
- Assert.assertSame(connection1, connection2);
- }
-
- @Slow
- @Test
- public void test_IdleConnection_IdleTimeout() throws Exception
- {
- long idleTimeout = 1000;
- client.setIdleTimeout(idleTimeout);
-
- HttpDestination destination = new HttpDestination(client, "http", "localhost", connector.getLocalPort());
- Connection connection1 = destination.acquire();
- if (connection1 == null)
- {
- // There are no queued requests, so the newly created connection will be idle
- long start = System.nanoTime();
- while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
- {
- connection1 = destination.getIdleConnections().peek();
- TimeUnit.MILLISECONDS.sleep(50);
- }
- Assert.assertNotNull(connection1);
-
- TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
-
- connection1 = destination.getIdleConnections().poll();
- Assert.assertNull(connection1);
- }
- }
-
- @Test
- public void test_Request_Failed_If_MaxRequestsQueuedPerDestination_Exceeded() throws Exception
- {
- int maxQueued = 1;
- client.setMaxRequestsQueuedPerDestination(maxQueued);
- client.setMaxConnectionsPerDestination(1);
-
- // Make one request to open the connection and be sure everything is setup properly
- ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
- .scheme(scheme)
- .send();
- Assert.assertEquals(200, response.getStatus());
-
- // Send another request that is sent immediately
- final CountDownLatch successLatch = new CountDownLatch(1);
- final CountDownLatch failureLatch = new CountDownLatch(1);
- client.newRequest("localhost", connector.getLocalPort())
- .scheme(scheme)
- .onRequestQueued(new Request.QueuedListener()
- {
- @Override
- public void onQueued(Request request)
- {
- // This request exceeds the maximum queued, should fail
- client.newRequest("localhost", connector.getLocalPort())
- .scheme(scheme)
- .send(new Response.CompleteListener()
- {
- @Override
- public void onComplete(Result result)
- {
- Assert.assertTrue(result.isFailed());
- Assert.assertThat(result.getRequestFailure(), Matchers.instanceOf(RejectedExecutionException.class));
- failureLatch.countDown();
- }
- });
- }
- })
- .send(new Response.CompleteListener()
- {
- @Override
- public void onComplete(Result result)
- {
- if (result.isSucceeded())
- successLatch.countDown();
- }
- });
-
- Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
- Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
- }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java
index e7a55a5..109a3f8 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java
@@ -18,228 +18,203 @@
package org.eclipse.jetty.client;
-import java.io.ByteArrayOutputStream;
-import java.io.EOFException;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.zip.GZIPOutputStream;
-
-import org.eclipse.jetty.client.api.ContentResponse;
-import org.eclipse.jetty.client.api.Response;
-import org.eclipse.jetty.client.util.FutureResponseListener;
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.toolchain.test.TestTracker;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
public class HttpReceiverTest
{
- @Rule
- public final TestTracker tracker = new TestTracker();
-
- private HttpClient client;
- private HttpDestination destination;
- private ByteArrayEndPoint endPoint;
- private HttpConnection connection;
- private HttpConversation conversation;
-
- @Before
- public void init() throws Exception
- {
- client = new HttpClient();
- client.start();
- destination = new HttpDestination(client, "http", "localhost", 8080);
- endPoint = new ByteArrayEndPoint();
- connection = new HttpConnection(client, endPoint, destination);
- conversation = new HttpConversation(client, 1);
- }
-
- @After
- public void destroy() throws Exception
- {
- client.stop();
- }
-
- protected HttpExchange newExchange()
- {
- HttpRequest request = new HttpRequest(client, URI.create("http://localhost"));
- FutureResponseListener listener = new FutureResponseListener(request);
- HttpExchange exchange = new HttpExchange(conversation, destination, request, Collections.<Response.ResponseListener>singletonList(listener));
- conversation.getExchanges().offer(exchange);
- connection.associate(exchange);
- exchange.requestComplete(null);
- exchange.terminateRequest();
- return exchange;
- }
-
- @Test
- public void test_Receive_NoResponseContent() throws Exception
- {
- endPoint.setInput("" +
- "HTTP/1.1 200 OK\r\n" +
- "Content-length: 0\r\n" +
- "\r\n");
- HttpExchange exchange = newExchange();
- FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
- connection.receive();
-
- Response response = listener.get(5, TimeUnit.SECONDS);
- Assert.assertNotNull(response);
- Assert.assertEquals(200, response.getStatus());
- Assert.assertEquals("OK", response.getReason());
- Assert.assertSame(HttpVersion.HTTP_1_1, response.getVersion());
- HttpFields headers = response.getHeaders();
- Assert.assertNotNull(headers);
- Assert.assertEquals(1, headers.size());
- Assert.assertEquals("0", headers.get(HttpHeader.CONTENT_LENGTH));
- }
-
- @Test
- public void test_Receive_ResponseContent() throws Exception
- {
- String content = "0123456789ABCDEF";
- endPoint.setInput("" +
- "HTTP/1.1 200 OK\r\n" +
- "Content-length: " + content.length() + "\r\n" +
- "\r\n" +
- content);
- HttpExchange exchange = newExchange();
- FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
- connection.receive();
-
- Response response = listener.get(5, TimeUnit.SECONDS);
- Assert.assertNotNull(response);
- Assert.assertEquals(200, response.getStatus());
- Assert.assertEquals("OK", response.getReason());
- Assert.assertSame(HttpVersion.HTTP_1_1, response.getVersion());
- HttpFields headers = response.getHeaders();
- Assert.assertNotNull(headers);
- Assert.assertEquals(1, headers.size());
- Assert.assertEquals(String.valueOf(content.length()), headers.get(HttpHeader.CONTENT_LENGTH));
- String received = listener.getContentAsString(StandardCharsets.UTF_8);
- Assert.assertEquals(content, received);
- }
-
- @Test
- public void test_Receive_ResponseContent_EarlyEOF() throws Exception
- {
- String content1 = "0123456789";
- String content2 = "ABCDEF";
- endPoint.setInput("" +
- "HTTP/1.1 200 OK\r\n" +
- "Content-length: " + (content1.length() + content2.length()) + "\r\n" +
- "\r\n" +
- content1);
- HttpExchange exchange = newExchange();
- FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
- connection.receive();
- endPoint.setInputEOF();
- connection.receive();
-
- try
- {
- listener.get(5, TimeUnit.SECONDS);
- Assert.fail();
- }
- catch (ExecutionException e)
- {
- Assert.assertTrue(e.getCause() instanceof EOFException);
- }
- }
-
- @Test
- public void test_Receive_ResponseContent_IdleTimeout() throws Exception
- {
- endPoint.setInput("" +
- "HTTP/1.1 200 OK\r\n" +
- "Content-length: 1\r\n" +
- "\r\n");
- HttpExchange exchange = newExchange();
- FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
- connection.receive();
- // Simulate an idle timeout
- connection.idleTimeout();
-
- try
- {
- listener.get(5, TimeUnit.SECONDS);
- Assert.fail();
- }
- catch (ExecutionException e)
- {
- Assert.assertTrue(e.getCause() instanceof TimeoutException);
- }
- }
-
- @Test
- public void test_Receive_BadResponse() throws Exception
- {
- endPoint.setInput("" +
- "HTTP/1.1 200 OK\r\n" +
- "Content-length: A\r\n" +
- "\r\n");
- HttpExchange exchange = newExchange();
- FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
- connection.receive();
-
- try
- {
- listener.get(5, TimeUnit.SECONDS);
- Assert.fail();
- }
- catch (ExecutionException e)
- {
- Assert.assertTrue(e.getCause() instanceof HttpResponseException);
- }
- }
-
- @Test
- public void test_Receive_GZIPResponseContent_Fragmented() throws Exception
- {
- byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try (GZIPOutputStream gzipOutput = new GZIPOutputStream(baos))
- {
- gzipOutput.write(data);
- }
- byte[] gzip = baos.toByteArray();
-
- endPoint.setInput("" +
- "HTTP/1.1 200 OK\r\n" +
- "Content-Length: " + gzip.length + "\r\n" +
- "Content-Encoding: gzip\r\n" +
- "\r\n");
- HttpExchange exchange = newExchange();
- FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
- connection.receive();
- endPoint.reset();
-
- ByteBuffer buffer = ByteBuffer.wrap(gzip);
- int fragment = buffer.limit() - 1;
- buffer.limit(fragment);
- endPoint.setInput(buffer);
- connection.receive();
- endPoint.reset();
-
- buffer.limit(gzip.length);
- buffer.position(fragment);
- endPoint.setInput(buffer);
- connection.receive();
-
- ContentResponse response = listener.get(5, TimeUnit.SECONDS);
- Assert.assertNotNull(response);
- Assert.assertEquals(200, response.getStatus());
- Assert.assertArrayEquals(data, response.getContent());
- }
+// @Rule
+// public final TestTracker tracker = new TestTracker();
+//
+// private HttpClient client;
+// private HttpDestination destination;
+// private ByteArrayEndPoint endPoint;
+// private HttpConnection connection;
+// private HttpConversation conversation;
+//
+// @Before
+// public void init() throws Exception
+// {
+// client = new HttpClient();
+// client.start();
+// destination = new HttpDestination(client, "http", "localhost", 8080);
+// endPoint = new ByteArrayEndPoint();
+// connection = new HttpConnection(client, endPoint, destination);
+// conversation = new HttpConversation(client, 1);
+// }
+//
+// @After
+// public void destroy() throws Exception
+// {
+// client.stop();
+// }
+//
+// protected HttpExchange newExchange()
+// {
+// HttpRequest request = new HttpRequest(client, URI.create("http://localhost"));
+// FutureResponseListener listener = new FutureResponseListener(request);
+// HttpExchange exchange = new HttpExchange(conversation, destination, request, Collections.<Response.ResponseListener>singletonList(listener));
+// conversation.getExchanges().offer(exchange);
+// connection.associate(exchange);
+// exchange.requestComplete();
+// exchange.terminateRequest();
+// return exchange;
+// }
+//
+// @Test
+// public void test_Receive_NoResponseContent() throws Exception
+// {
+// endPoint.setInput("" +
+// "HTTP/1.1 200 OK\r\n" +
+// "Content-length: 0\r\n" +
+// "\r\n");
+// HttpExchange exchange = newExchange();
+// FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
+// connection.receive();
+//
+// Response response = listener.get(5, TimeUnit.SECONDS);
+// Assert.assertNotNull(response);
+// Assert.assertEquals(200, response.getStatus());
+// Assert.assertEquals("OK", response.getReason());
+// Assert.assertSame(HttpVersion.HTTP_1_1, response.getVersion());
+// HttpFields headers = response.getHeaders();
+// Assert.assertNotNull(headers);
+// Assert.assertEquals(1, headers.size());
+// Assert.assertEquals("0", headers.get(HttpHeader.CONTENT_LENGTH));
+// }
+//
+// @Test
+// public void test_Receive_ResponseContent() throws Exception
+// {
+// String content = "0123456789ABCDEF";
+// endPoint.setInput("" +
+// "HTTP/1.1 200 OK\r\n" +
+// "Content-length: " + content.length() + "\r\n" +
+// "\r\n" +
+// content);
+// HttpExchange exchange = newExchange();
+// FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
+// connection.receive();
+//
+// Response response = listener.get(5, TimeUnit.SECONDS);
+// Assert.assertNotNull(response);
+// Assert.assertEquals(200, response.getStatus());
+// Assert.assertEquals("OK", response.getReason());
+// Assert.assertSame(HttpVersion.HTTP_1_1, response.getVersion());
+// HttpFields headers = response.getHeaders();
+// Assert.assertNotNull(headers);
+// Assert.assertEquals(1, headers.size());
+// Assert.assertEquals(String.valueOf(content.length()), headers.get(HttpHeader.CONTENT_LENGTH));
+// String received = listener.getContentAsString(StandardCharsets.UTF_8);
+// Assert.assertEquals(content, received);
+// }
+//
+// @Test
+// public void test_Receive_ResponseContent_EarlyEOF() throws Exception
+// {
+// String content1 = "0123456789";
+// String content2 = "ABCDEF";
+// endPoint.setInput("" +
+// "HTTP/1.1 200 OK\r\n" +
+// "Content-length: " + (content1.length() + content2.length()) + "\r\n" +
+// "\r\n" +
+// content1);
+// HttpExchange exchange = newExchange();
+// FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
+// connection.receive();
+// endPoint.setInputEOF();
+// connection.receive();
+//
+// try
+// {
+// listener.get(5, TimeUnit.SECONDS);
+// Assert.fail();
+// }
+// catch (ExecutionException e)
+// {
+// Assert.assertTrue(e.getCause() instanceof EOFException);
+// }
+// }
+//
+// @Test
+// public void test_Receive_ResponseContent_IdleTimeout() throws Exception
+// {
+// endPoint.setInput("" +
+// "HTTP/1.1 200 OK\r\n" +
+// "Content-length: 1\r\n" +
+// "\r\n");
+// HttpExchange exchange = newExchange();
+// FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
+// connection.receive();
+// // Simulate an idle timeout
+// connection.idleTimeout();
+//
+// try
+// {
+// listener.get(5, TimeUnit.SECONDS);
+// Assert.fail();
+// }
+// catch (ExecutionException e)
+// {
+// Assert.assertTrue(e.getCause() instanceof TimeoutException);
+// }
+// }
+//
+// @Test
+// public void test_Receive_BadResponse() throws Exception
+// {
+// endPoint.setInput("" +
+// "HTTP/1.1 200 OK\r\n" +
+// "Content-length: A\r\n" +
+// "\r\n");
+// HttpExchange exchange = newExchange();
+// FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
+// connection.receive();
+//
+// try
+// {
+// listener.get(5, TimeUnit.SECONDS);
+// Assert.fail();
+// }
+// catch (ExecutionException e)
+// {
+// Assert.assertTrue(e.getCause() instanceof HttpResponseException);
+// }
+// }
+//
+// @Test
+// public void test_Receive_GZIPResponseContent_Fragmented() throws Exception
+// {
+// byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+// ByteArrayOutputStream baos = new ByteArrayOutputStream();
+// try (GZIPOutputStream gzipOutput = new GZIPOutputStream(baos))
+// {
+// gzipOutput.write(data);
+// }
+// byte[] gzip = baos.toByteArray();
+//
+// endPoint.setInput("" +
+// "HTTP/1.1 200 OK\r\n" +
+// "Content-Length: " + gzip.length + "\r\n" +
+// "Content-Encoding: gzip\r\n" +
+// "\r\n");
+// HttpExchange exchange = newExchange();
+// FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
+// connection.receive();
+// endPoint.reset();
+//
+// ByteBuffer buffer = ByteBuffer.wrap(gzip);
+// int fragment = buffer.limit() - 1;
+// buffer.limit(fragment);
+// endPoint.setInput(buffer);
+// connection.receive();
+// endPoint.reset();
+//
+// buffer.limit(gzip.length);
+// buffer.position(fragment);
+// endPoint.setInput(buffer);
+// connection.receive();
+//
+// ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+// Assert.assertNotNull(response);
+// Assert.assertEquals(200, response.getStatus());
+// Assert.assertArrayEquals(data, response.getContent());
+// }
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
index 5d1a2b1..9648698 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
@@ -25,6 +25,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -59,7 +60,7 @@
{
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .listener(new Request.Listener.Empty()
+ .listener(new Request.Listener.Adapter()
{
@Override
public void onQueued(Request request)
@@ -97,7 +98,7 @@
{
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .listener(new Request.Listener.Empty()
+ .listener(new Request.Listener.Adapter()
{
@Override
public void onBegin(Request request)
@@ -137,7 +138,7 @@
{
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .listener(new Request.Listener.Empty()
+ .listener(new Request.Listener.Adapter()
{
@Override
public void onHeaders(Request request)
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpSenderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpSenderTest.java
index f63b532..722dfe5 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpSenderTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpSenderTest.java
@@ -18,283 +18,263 @@
package org.eclipse.jetty.client;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Locale;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.client.api.Response;
-import org.eclipse.jetty.client.api.Result;
-import org.eclipse.jetty.client.util.ByteBufferContentProvider;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.toolchain.test.TestTracker;
-import org.eclipse.jetty.toolchain.test.annotation.Slow;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
public class HttpSenderTest
{
- @Rule
- public final TestTracker tracker = new TestTracker();
-
- private HttpClient client;
-
- @Before
- public void init() throws Exception
- {
- client = new HttpClient();
- client.start();
- }
-
- @After
- public void destroy() throws Exception
- {
- client.stop();
- }
-
- @Test
- public void test_Send_NoRequestContent() throws Exception
- {
- ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
- HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
- HttpConnection connection = new HttpConnection(client, endPoint, destination);
- Request request = client.newRequest(URI.create("http://localhost/"));
- final CountDownLatch headersLatch = new CountDownLatch(1);
- final CountDownLatch successLatch = new CountDownLatch(1);
- request.listener(new Request.Listener.Empty()
- {
- @Override
- public void onHeaders(Request request)
- {
- headersLatch.countDown();
- }
-
- @Override
- public void onSuccess(Request request)
- {
- successLatch.countDown();
- }
- });
- connection.send(request, (Response.CompleteListener)null);
-
- String requestString = endPoint.takeOutputString();
- Assert.assertTrue(requestString.startsWith("GET "));
- Assert.assertTrue(requestString.endsWith("\r\n\r\n"));
- Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
- Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
- }
-
- @Slow
- @Test
- public void test_Send_NoRequestContent_IncompleteFlush() throws Exception
- {
- ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
- HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
- HttpConnection connection = new HttpConnection(client, endPoint, destination);
- Request request = client.newRequest(URI.create("http://localhost/"));
- connection.send(request, (Response.CompleteListener)null);
-
- // This take will free space in the buffer and allow for the write to complete
- StringBuilder builder = new StringBuilder(endPoint.takeOutputString());
-
- // Wait for the write to complete
- TimeUnit.SECONDS.sleep(1);
-
- String chunk = endPoint.takeOutputString();
- while (chunk.length() > 0)
- {
- builder.append(chunk);
- chunk = endPoint.takeOutputString();
- }
-
- String requestString = builder.toString();
- Assert.assertTrue(requestString.startsWith("GET "));
- Assert.assertTrue(requestString.endsWith("\r\n\r\n"));
- }
-
- @Test
- public void test_Send_NoRequestContent_Exception() throws Exception
- {
- ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
- // Shutdown output to trigger the exception on write
- endPoint.shutdownOutput();
- HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
- HttpConnection connection = new HttpConnection(client, endPoint, destination);
- Request request = client.newRequest(URI.create("http://localhost/"));
- final CountDownLatch failureLatch = new CountDownLatch(2);
- request.listener(new Request.Listener.Empty()
- {
- @Override
- public void onFailure(Request request, Throwable x)
- {
- failureLatch.countDown();
- }
- });
- connection.send(request, new Response.Listener.Empty()
- {
- @Override
- public void onComplete(Result result)
- {
- Assert.assertTrue(result.isFailed());
- failureLatch.countDown();
- }
- });
-
- Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
- }
-
- @Test
- public void test_Send_NoRequestContent_IncompleteFlush_Exception() throws Exception
- {
- ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
- HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
- HttpConnection connection = new HttpConnection(client, endPoint, destination);
- Request request = client.newRequest(URI.create("http://localhost/"));
- final CountDownLatch failureLatch = new CountDownLatch(2);
- request.listener(new Request.Listener.Empty()
- {
- @Override
- public void onFailure(Request request, Throwable x)
- {
- failureLatch.countDown();
- }
- });
- connection.send(request, new Response.Listener.Empty()
- {
- @Override
- public void onComplete(Result result)
- {
- Assert.assertTrue(result.isFailed());
- failureLatch.countDown();
- }
- });
-
- // Shutdown output to trigger the exception on write
- endPoint.shutdownOutput();
- // This take will free space in the buffer and allow for the write to complete
- // although it will fail because we shut down the output
- endPoint.takeOutputString();
-
- Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
- }
-
- @Test
- public void test_Send_SmallRequestContent_InOneBuffer() throws Exception
- {
- ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
- HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
- HttpConnection connection = new HttpConnection(client, endPoint, destination);
- Request request = client.newRequest(URI.create("http://localhost/"));
- String content = "abcdef";
- request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8))));
- final CountDownLatch headersLatch = new CountDownLatch(1);
- final CountDownLatch successLatch = new CountDownLatch(1);
- request.listener(new Request.Listener.Empty()
- {
- @Override
- public void onHeaders(Request request)
- {
- headersLatch.countDown();
- }
-
- @Override
- public void onSuccess(Request request)
- {
- successLatch.countDown();
- }
- });
- connection.send(request, (Response.CompleteListener)null);
-
- String requestString = endPoint.takeOutputString();
- Assert.assertTrue(requestString.startsWith("GET "));
- Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content));
- Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
- Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
- }
-
- @Test
- public void test_Send_SmallRequestContent_InTwoBuffers() throws Exception
- {
- ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
- HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
- HttpConnection connection = new HttpConnection(client, endPoint, destination);
- Request request = client.newRequest(URI.create("http://localhost/"));
- String content1 = "0123456789";
- String content2 = "abcdef";
- request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8))));
- final CountDownLatch headersLatch = new CountDownLatch(1);
- final CountDownLatch successLatch = new CountDownLatch(1);
- request.listener(new Request.Listener.Empty()
- {
- @Override
- public void onHeaders(Request request)
- {
- headersLatch.countDown();
- }
-
- @Override
- public void onSuccess(Request request)
- {
- successLatch.countDown();
- }
- });
- connection.send(request, (Response.CompleteListener)null);
-
- String requestString = endPoint.takeOutputString();
- Assert.assertTrue(requestString.startsWith("GET "));
- Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content1 + content2));
- Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
- Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
- }
-
- @Test
- public void test_Send_SmallRequestContent_Chunked_InTwoChunks() throws Exception
- {
- ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
- HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
- HttpConnection connection = new HttpConnection(client, endPoint, destination);
- Request request = client.newRequest(URI.create("http://localhost/"));
- String content1 = "0123456789";
- String content2 = "ABCDEF";
- request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8)))
- {
- @Override
- public long getLength()
- {
- return -1;
- }
- });
- final CountDownLatch headersLatch = new CountDownLatch(1);
- final CountDownLatch successLatch = new CountDownLatch(1);
- request.listener(new Request.Listener.Empty()
- {
- @Override
- public void onHeaders(Request request)
- {
- headersLatch.countDown();
- }
-
- @Override
- public void onSuccess(Request request)
- {
- successLatch.countDown();
- }
- });
- connection.send(request, (Response.CompleteListener)null);
-
- String requestString = endPoint.takeOutputString();
- Assert.assertTrue(requestString.startsWith("GET "));
- String content = Integer.toHexString(content1.length()).toUpperCase(Locale.ENGLISH) + "\r\n" + content1 + "\r\n";
- content += Integer.toHexString(content2.length()).toUpperCase(Locale.ENGLISH) + "\r\n" + content2 + "\r\n";
- content += "0\r\n\r\n";
- Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content));
- Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
- Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
- }
+// @Rule
+// public final TestTracker tracker = new TestTracker();
+//
+// private HttpClient client;
+//
+// @Before
+// public void init() throws Exception
+// {
+// client = new HttpClient();
+// client.start();
+// }
+//
+// @After
+// public void destroy() throws Exception
+// {
+// client.stop();
+// }
+//
+// @Test
+// public void test_Send_NoRequestContent() throws Exception
+// {
+// ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
+// HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
+// HttpConnection connection = new HttpConnection(client, endPoint, destination);
+// Request request = client.newRequest(URI.create("http://localhost/"));
+// final CountDownLatch headersLatch = new CountDownLatch(1);
+// final CountDownLatch successLatch = new CountDownLatch(1);
+// request.listener(new Request.Listener.Adapter()
+// {
+// @Override
+// public void onHeaders(Request request)
+// {
+// headersLatch.countDown();
+// }
+//
+// @Override
+// public void onSuccess(Request request)
+// {
+// successLatch.countDown();
+// }
+// });
+// connection.send(request, (Response.CompleteListener)null);
+//
+// String requestString = endPoint.takeOutputString();
+// Assert.assertTrue(requestString.startsWith("GET "));
+// Assert.assertTrue(requestString.endsWith("\r\n\r\n"));
+// Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
+// Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
+// }
+//
+// @Slow
+// @Test
+// public void test_Send_NoRequestContent_IncompleteFlush() throws Exception
+// {
+// ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
+// HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
+// HttpConnection connection = new HttpConnection(client, endPoint, destination);
+// Request request = client.newRequest(URI.create("http://localhost/"));
+// connection.send(request, (Response.CompleteListener)null);
+//
+// // This take will free space in the buffer and allow for the write to complete
+// StringBuilder builder = new StringBuilder(endPoint.takeOutputString());
+//
+// // Wait for the write to complete
+// TimeUnit.SECONDS.sleep(1);
+//
+// String chunk = endPoint.takeOutputString();
+// while (chunk.length() > 0)
+// {
+// builder.append(chunk);
+// chunk = endPoint.takeOutputString();
+// }
+//
+// String requestString = builder.toString();
+// Assert.assertTrue(requestString.startsWith("GET "));
+// Assert.assertTrue(requestString.endsWith("\r\n\r\n"));
+// }
+//
+// @Test
+// public void test_Send_NoRequestContent_Exception() throws Exception
+// {
+// ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
+// // Shutdown output to trigger the exception on write
+// endPoint.shutdownOutput();
+// HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
+// HttpConnection connection = new HttpConnection(client, endPoint, destination);
+// Request request = client.newRequest(URI.create("http://localhost/"));
+// final CountDownLatch failureLatch = new CountDownLatch(2);
+// request.listener(new Request.Listener.Adapter()
+// {
+// @Override
+// public void onFailure(Request request, Throwable x)
+// {
+// failureLatch.countDown();
+// }
+// });
+// connection.send(request, new Response.Listener.Adapter()
+// {
+// @Override
+// public void onComplete(Result result)
+// {
+// Assert.assertTrue(result.isFailed());
+// failureLatch.countDown();
+// }
+// });
+//
+// Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
+// }
+//
+// @Test
+// public void test_Send_NoRequestContent_IncompleteFlush_Exception() throws Exception
+// {
+// ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
+// HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
+// HttpConnection connection = new HttpConnection(client, endPoint, destination);
+// Request request = client.newRequest(URI.create("http://localhost/"));
+// final CountDownLatch failureLatch = new CountDownLatch(2);
+// request.listener(new Request.Listener.Adapter()
+// {
+// @Override
+// public void onFailure(Request request, Throwable x)
+// {
+// failureLatch.countDown();
+// }
+// });
+// connection.send(request, new Response.Listener.Adapter()
+// {
+// @Override
+// public void onComplete(Result result)
+// {
+// Assert.assertTrue(result.isFailed());
+// failureLatch.countDown();
+// }
+// });
+//
+// // Shutdown output to trigger the exception on write
+// endPoint.shutdownOutput();
+// // This take will free space in the buffer and allow for the write to complete
+// // although it will fail because we shut down the output
+// endPoint.takeOutputString();
+//
+// Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
+// }
+//
+// @Test
+// public void test_Send_SmallRequestContent_InOneBuffer() throws Exception
+// {
+// ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
+// HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
+// HttpConnection connection = new HttpConnection(client, endPoint, destination);
+// Request request = client.newRequest(URI.create("http://localhost/"));
+// String content = "abcdef";
+// request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8))));
+// final CountDownLatch headersLatch = new CountDownLatch(1);
+// final CountDownLatch successLatch = new CountDownLatch(1);
+// request.listener(new Request.Listener.Adapter()
+// {
+// @Override
+// public void onHeaders(Request request)
+// {
+// headersLatch.countDown();
+// }
+//
+// @Override
+// public void onSuccess(Request request)
+// {
+// successLatch.countDown();
+// }
+// });
+// connection.send(request, (Response.CompleteListener)null);
+//
+// String requestString = endPoint.takeOutputString();
+// Assert.assertTrue(requestString.startsWith("GET "));
+// Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content));
+// Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
+// Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
+// }
+//
+// @Test
+// public void test_Send_SmallRequestContent_InTwoBuffers() throws Exception
+// {
+// ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
+// HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
+// HttpConnection connection = new HttpConnection(client, endPoint, destination);
+// Request request = client.newRequest(URI.create("http://localhost/"));
+// String content1 = "0123456789";
+// String content2 = "abcdef";
+// request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8))));
+// final CountDownLatch headersLatch = new CountDownLatch(1);
+// final CountDownLatch successLatch = new CountDownLatch(1);
+// request.listener(new Request.Listener.Adapter()
+// {
+// @Override
+// public void onHeaders(Request request)
+// {
+// headersLatch.countDown();
+// }
+//
+// @Override
+// public void onSuccess(Request request)
+// {
+// successLatch.countDown();
+// }
+// });
+// connection.send(request, (Response.CompleteListener)null);
+//
+// String requestString = endPoint.takeOutputString();
+// Assert.assertTrue(requestString.startsWith("GET "));
+// Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content1 + content2));
+// Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
+// Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
+// }
+//
+// @Test
+// public void test_Send_SmallRequestContent_Chunked_InTwoChunks() throws Exception
+// {
+// ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
+// HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
+// HttpConnection connection = new HttpConnection(client, endPoint, destination);
+// Request request = client.newRequest(URI.create("http://localhost/"));
+// String content1 = "0123456789";
+// String content2 = "ABCDEF";
+// request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8)))
+// {
+// @Override
+// public long getLength()
+// {
+// return -1;
+// }
+// });
+// final CountDownLatch headersLatch = new CountDownLatch(1);
+// final CountDownLatch successLatch = new CountDownLatch(1);
+// request.listener(new Request.Listener.Adapter()
+// {
+// @Override
+// public void onHeaders(Request request)
+// {
+// headersLatch.countDown();
+// }
+//
+// @Override
+// public void onSuccess(Request request)
+// {
+// successLatch.countDown();
+// }
+// });
+// connection.send(request, (Response.CompleteListener)null);
+//
+// String requestString = endPoint.takeOutputString();
+// Assert.assertTrue(requestString.startsWith("GET "));
+// String content = Integer.toHexString(content1.length()).toUpperCase(Locale.ENGLISH) + "\r\n" + content1 + "\r\n";
+// content += Integer.toHexString(content2.length()).toUpperCase(Locale.ENGLISH) + "\r\n" + content2 + "\r\n";
+// content += "0\r\n\r\n";
+// Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content));
+// Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
+// Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
+// }
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyConfigurationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyConfigurationTest.java
new file mode 100644
index 0000000..c565e1c
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyConfigurationTest.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ProxyConfigurationTest
+{
+ @Test
+ public void testProxyMatchesWithoutIncludesWithoutExcludes() throws Exception
+ {
+ HttpProxy proxy = new HttpProxy("host", 0);
+ Assert.assertTrue(proxy.matches(new Origin("http", "any", 0)));
+ }
+
+ @Test
+ public void testProxyMatchesWithOnlyExcludes() throws Exception
+ {
+ HttpProxy proxy = new HttpProxy("host", 0);
+ proxy.getExcludedAddresses().add("1.2.3.4:5");
+
+ Assert.assertTrue(proxy.matches(new Origin("http", "any", 0)));
+ Assert.assertTrue(proxy.matches(new Origin("http", "1.2.3.4", 0)));
+ Assert.assertFalse(proxy.matches(new Origin("http", "1.2.3.4", 5)));
+ }
+
+ @Test
+ public void testProxyMatchesWithOnlyIncludes() throws Exception
+ {
+ HttpProxy proxy = new HttpProxy("host", 0);
+ proxy.getIncludedAddresses().add("1.2.3.4:5");
+
+ Assert.assertFalse(proxy.matches(new Origin("http", "any", 0)));
+ Assert.assertFalse(proxy.matches(new Origin("http", "1.2.3.4", 0)));
+ Assert.assertTrue(proxy.matches(new Origin("http", "1.2.3.4", 5)));
+ }
+
+ @Test
+ public void testProxyMatchesWithIncludesAndExcludes() throws Exception
+ {
+ HttpProxy proxy = new HttpProxy("host", 0);
+ proxy.getIncludedAddresses().add("1.2.3.4");
+ proxy.getExcludedAddresses().add("1.2.3.4:5");
+
+ Assert.assertFalse(proxy.matches(new Origin("http", "any", 0)));
+ Assert.assertTrue(proxy.matches(new Origin("http", "1.2.3.4", 0)));
+ Assert.assertFalse(proxy.matches(new Origin("http", "1.2.3.4", 5)));
+ }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/api/ApacheUsage.java b/jetty-client/src/test/java/org/eclipse/jetty/client/api/ApacheUsage.java
deleted file mode 100644
index 4f4b040..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/api/ApacheUsage.java
+++ /dev/null
@@ -1,34 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.client.api;
-
-import org.apache.http.client.HttpClient;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.junit.Ignore;
-import org.junit.Test;
-
-@Ignore
-public class ApacheUsage
-{
- @Test
- public void test()
- {
- HttpClient client = new DefaultHttpClient();
- }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/api/NingUsage.java b/jetty-client/src/test/java/org/eclipse/jetty/client/api/NingUsage.java
deleted file mode 100644
index 798c43f..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/api/NingUsage.java
+++ /dev/null
@@ -1,76 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.client.api;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-import com.ning.http.client.AsyncHttpClient;
-import com.ning.http.client.BodyDeferringAsyncHandler;
-import com.ning.http.client.Cookie;
-import com.ning.http.client.Realm;
-import com.ning.http.client.Request;
-import com.ning.http.client.Response;
-
-@Ignore
-public class NingUsage
-{
- @Test
- public void testFileUpload() throws Exception
- {
- AsyncHttpClient client = new AsyncHttpClient();
- Request request = client.prepareGet("http://localhost:8080/foo").setBody(new FileInputStream("")).build();
- client.executeRequest(request);
- }
-
- @Test
- public void testAuthentication() throws Exception
- {
- AsyncHttpClient client = new AsyncHttpClient();
- Response response = client.prepareGet("http://localhost:8080/foo")
- // Not sure what a builder buys me in this case...
- .setRealm(new Realm.RealmBuilder().build()).execute().get();
- }
-
- @Test
- public void testCookies() throws Exception
- {
- AsyncHttpClient client = new AsyncHttpClient();
- // Cookie class too complex
- client.prepareGet("").addCookie(new Cookie("domain", "name", "value", "path", 3600, false)).execute();
- }
-
- @Test
- public void testResponseStream() throws Exception
- {
- AsyncHttpClient client = new AsyncHttpClient();
- ByteArrayOutputStream output = new ByteArrayOutputStream();
- BodyDeferringAsyncHandler handler = new BodyDeferringAsyncHandler(output);
- client.prepareGet("").execute(handler);
- // What I would really like is an InputStream, not an OutputStream
- // so I can read the response content
-
- Response response = handler.getResponse(); // No timeout
-
- // Not sure how I can read the body ONLY if status == 200 ?
- }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java b/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java
index 15d15e2..eddfcb3 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java
@@ -134,7 +134,7 @@
Response response = client.newRequest("localhost", 8080)
// Add a request listener
- .listener(new Request.Listener.Empty()
+ .listener(new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
@@ -320,7 +320,7 @@
DeferredContentProvider async = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0, 1, 2}));
client.newRequest("localhost", 8080)
.content(async)
- .send(new Response.Listener.Empty()
+ .send(new Response.Listener.Adapter()
{
@Override
public void onBegin(Response response)
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java
new file mode 100644
index 0000000..55109b2
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java
@@ -0,0 +1,227 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.AbstractHttpClientServerTest;
+import org.eclipse.jetty.client.EmptyServerHandler;
+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.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
+{
+ public HttpDestinationOverHTTPTest(SslContextFactory sslContextFactory)
+ {
+ super(sslContextFactory);
+ }
+
+ @Before
+ public void init() throws Exception
+ {
+ start(new EmptyServerHandler());
+ }
+
+ @Test
+ public void test_FirstAcquire_WithEmptyQueue() throws Exception
+ {
+ HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
+ Connection connection = destination.acquire();
+ if (connection == null)
+ {
+ // There are no queued requests, so the newly created connection will be idle
+ connection = destination.getConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
+ }
+ Assert.assertNotNull(connection);
+ }
+
+ @Test
+ public void test_SecondAcquire_AfterFirstAcquire_WithEmptyQueue_ReturnsSameConnection() throws Exception
+ {
+ HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
+ Connection connection1 = destination.acquire();
+ if (connection1 == null)
+ {
+ // There are no queued requests, so the newly created connection will be idle
+ long start = System.nanoTime();
+ while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
+ {
+ TimeUnit.MILLISECONDS.sleep(50);
+ connection1 = destination.getConnectionPool().getIdleConnections().peek();
+ }
+ Assert.assertNotNull(connection1);
+
+ Connection connection2 = destination.acquire();
+ Assert.assertSame(connection1, connection2);
+ }
+ }
+
+ @Test
+ public void test_SecondAcquire_ConcurrentWithFirstAcquire_WithEmptyQueue_CreatesTwoConnections() throws Exception
+ {
+ final CountDownLatch latch = new CountDownLatch(1);
+ HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()))
+ {
+ @Override
+ public void process(HttpConnectionOverHTTP connection, boolean dispatch)
+ {
+ try
+ {
+ latch.await(5, TimeUnit.SECONDS);
+ super.process(connection, dispatch);
+ }
+ catch (InterruptedException x)
+ {
+ x.printStackTrace();
+ }
+ }
+ };
+ Connection connection1 = destination.acquire();
+
+ // There are no available existing connections, so acquire()
+ // returns null because we delayed process() above
+ Assert.assertNull(connection1);
+
+ Connection connection2 = destination.acquire();
+ Assert.assertNull(connection2);
+
+ latch.countDown();
+
+ // There must be 2 idle connections
+ Connection connection = destination.getConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
+ Assert.assertNotNull(connection);
+ connection = destination.getConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
+ Assert.assertNotNull(connection);
+ }
+
+ @Test
+ public void test_Acquire_Process_Release_Acquire_ReturnsSameConnection() throws Exception
+ {
+ HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
+ HttpConnectionOverHTTP connection1 = destination.acquire();
+
+ long start = System.nanoTime();
+ while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
+ {
+ TimeUnit.MILLISECONDS.sleep(50);
+ connection1 = (HttpConnectionOverHTTP)destination.getConnectionPool().getIdleConnections().peek();
+ }
+ Assert.assertNotNull(connection1);
+
+ // Acquire the connection to make it active
+ Assert.assertSame(connection1, destination.acquire());
+
+ destination.process(connection1, false);
+ destination.release(connection1);
+
+ Connection connection2 = destination.acquire();
+ Assert.assertSame(connection1, connection2);
+ }
+
+ @Slow
+ @Test
+ public void test_IdleConnection_IdleTimeout() throws Exception
+ {
+ long idleTimeout = 1000;
+ client.setIdleTimeout(idleTimeout);
+
+ HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
+ Connection connection1 = destination.acquire();
+ if (connection1 == null)
+ {
+ // There are no queued requests, so the newly created connection will be idle
+ long start = System.nanoTime();
+ while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
+ {
+ TimeUnit.MILLISECONDS.sleep(50);
+ connection1 = destination.getConnectionPool().getIdleConnections().peek();
+ }
+ Assert.assertNotNull(connection1);
+
+ TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+
+ connection1 = destination.getConnectionPool().getIdleConnections().poll();
+ Assert.assertNull(connection1);
+ }
+ }
+
+ @Test
+ public void test_Request_Failed_If_MaxRequestsQueuedPerDestination_Exceeded() throws Exception
+ {
+ int maxQueued = 1;
+ client.setMaxRequestsQueuedPerDestination(maxQueued);
+ client.setMaxConnectionsPerDestination(1);
+
+ // Make one request to open the connection and be sure everything is setup properly
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .send();
+ Assert.assertEquals(200, response.getStatus());
+
+ // Send another request that is sent immediately
+ final CountDownLatch successLatch = new CountDownLatch(1);
+ final CountDownLatch failureLatch = new CountDownLatch(1);
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .onRequestQueued(new Request.QueuedListener()
+ {
+ @Override
+ public void onQueued(Request request)
+ {
+ // This request exceeds the maximum queued, should fail
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .send(new Response.CompleteListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ Assert.assertTrue(result.isFailed());
+ Assert.assertThat(result.getRequestFailure(), Matchers.instanceOf(RejectedExecutionException.class));
+ failureLatch.countDown();
+ }
+ });
+ }
+ })
+ .send(new Response.CompleteListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ if (result.isSucceeded())
+ successLatch.countDown();
+ }
+ });
+
+ Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
+ Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
+ }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java
index b2b860d..3173028 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java
@@ -31,6 +31,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
index 9f585ac..424ac6e 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.client.ssl;
+import static org.hamcrest.Matchers.nullValue;
+
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
@@ -40,6 +42,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocket;
@@ -49,6 +52,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.client.ssl.SslBytesTest.TLSRecord.Type;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
@@ -165,7 +169,7 @@
}
};
- ServerConnector connector = new ServerConnector(server, sslFactory, httpFactory)
+ ServerConnector connector = new ServerConnector(server, null,null,null,1,1,sslFactory, httpFactory)
{
@Override
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
@@ -471,7 +475,14 @@
// Socket close
record = proxy.readFromServer();
- Assert.assertNull(String.valueOf(record), record);
+ if (record!=null)
+ {
+ Assert.assertEquals(record.getType(),Type.ALERT);
+
+ // Now should be a raw close
+ record = proxy.readFromServer();
+ Assert.assertNull(String.valueOf(record), record);
+ }
}
@Test
@@ -759,7 +770,15 @@
// Socket close
record = proxy.readFromServer();
- Assert.assertNull(String.valueOf(record), record);
+ // Raw close or alert
+ if (record!=null)
+ {
+ Assert.assertEquals(record.getType(),Type.ALERT);
+
+ // Now should be a raw close
+ record = proxy.readFromServer();
+ Assert.assertNull(String.valueOf(record), record);
+ }
}
@Test
@@ -814,7 +833,14 @@
// Socket close
record = proxy.readFromServer();
- Assert.assertNull(record);
+ if (record!=null)
+ {
+ Assert.assertEquals(record.getType(),Type.ALERT);
+
+ // Now should be a raw close
+ record = proxy.readFromServer();
+ Assert.assertNull(String.valueOf(record), record);
+ }
// Check that we did not spin
TimeUnit.MILLISECONDS.sleep(500);
@@ -882,7 +908,14 @@
// Socket close
record = proxy.readFromServer();
- Assert.assertNull(String.valueOf(record), record);
+ if (record!=null)
+ {
+ Assert.assertEquals(record.getType(),Type.ALERT);
+
+ // Now should be a raw close
+ record = proxy.readFromServer();
+ Assert.assertNull(String.valueOf(record), record);
+ }
// Check that we did not spin
TimeUnit.MILLISECONDS.sleep(500);
@@ -934,9 +967,17 @@
// Close the raw socket, this generates a truncation attack
proxy.flushToServer(null);
- // Expect raw close from server
+ // Expect raw close from server OR ALERT
record = proxy.readFromServer();
- Assert.assertNull(String.valueOf(record), record);
+ // TODO check that this is OK?
+ if (record!=null)
+ {
+ Assert.assertEquals(record.getType(),Type.ALERT);
+
+ // Now should be a raw close
+ record = proxy.readFromServer();
+ Assert.assertNull(String.valueOf(record), record);
+ }
// Check that we did not spin
TimeUnit.MILLISECONDS.sleep(500);
@@ -986,7 +1027,14 @@
// Expect raw close from server
record = proxy.readFromServer();
- Assert.assertNull(String.valueOf(record), record);
+ if (record!=null)
+ {
+ Assert.assertEquals(record.getType(),Type.ALERT);
+
+ // Now should be a raw close
+ record = proxy.readFromServer();
+ Assert.assertNull(String.valueOf(record), record);
+ }
// Check that we did not spin
TimeUnit.MILLISECONDS.sleep(500);
@@ -1000,6 +1048,9 @@
@Test
public void testRequestWithBigContentWriteBlockedThenReset() throws Exception
{
+ // Don't run on Windows (buggy JVM)
+ Assume.assumeTrue(!OS.IS_WINDOWS);
+
final SSLSocket client = newClient();
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
@@ -1058,7 +1109,10 @@
@Test
public void testRequestWithBigContentReadBlockedThenReset() throws Exception
{
- final SSLSocket client = newClient();
+ // Don't run on Windows (buggy JVM)
+ Assume.assumeTrue(!OS.IS_WINDOWS);
+
+ final SSLSocket client = newClient();
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
client.startHandshake();
@@ -1180,7 +1234,14 @@
// Socket close
record = proxy.readFromServer();
- Assert.assertNull(record);
+ if (record!=null)
+ {
+ Assert.assertEquals(record.getType(),Type.ALERT);
+
+ // Now should be a raw close
+ record = proxy.readFromServer();
+ Assert.assertNull(String.valueOf(record), record);
+ }
// Check that we did not spin
TimeUnit.MILLISECONDS.sleep(500);
@@ -1904,6 +1965,11 @@
// Socket close
record = proxy.readFromServer();
- Assert.assertNull(String.valueOf(record), record);
+ if (record!=null)
+ {
+ Assert.assertEquals(record.getType(),Type.ALERT);
+ record = proxy.readFromServer();
+ }
+ Assert.assertThat(record,nullValue());
}
}
diff --git a/jetty-continuation/pom.xml b/jetty-continuation/pom.xml
index 69c4b71..5552c7a 100644
--- a/jetty-continuation/pom.xml
+++ b/jetty-continuation/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-continuation</artifactId>
@@ -54,9 +54,9 @@
</build>
<dependencies>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
- </dependency>
+ </dependency>
</dependencies>
</project>
diff --git a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Servlet3Continuation.java b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Servlet3Continuation.java
index 8c1a66e..d370a34 100644
--- a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Servlet3Continuation.java
+++ b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Servlet3Continuation.java
@@ -34,7 +34,7 @@
/* ------------------------------------------------------------ */
/**
* This implementation of Continuation is used by {@link ContinuationSupport}
- * when it detects that the application has been deployed in a non-jetty Servlet 3
+ * when it detects that the application has been deployed in a Servlet 3
* server.
*/
public class Servlet3Continuation implements Continuation, AsyncListener
diff --git a/jetty-deploy/pom.xml b/jetty-deploy/pom.xml
index 8588d1f..3f3f275 100644
--- a/jetty-deploy/pom.xml
+++ b/jetty-deploy/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-deploy</artifactId>
@@ -25,7 +25,7 @@
</goals>
<configuration>
<instructions>
- <Import-Package>org.eclipse.jetty.jmx.*;version="9.0";resolution:=optional,*</Import-Package>
+ <Import-Package>org.eclipse.jetty.jmx.*;version="9.1";resolution:=optional,*</Import-Package>
<_nouses>true</_nouses>
</instructions>
</configuration>
@@ -92,12 +92,5 @@
<version>${project.version}</version>
<optional>true</optional>
</dependency>
- <!--
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-websocket</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency> -->
</dependencies>
</project>
diff --git a/jetty-deploy/src/main/config/etc/jetty-deploy.xml b/jetty-deploy/src/main/config/etc/jetty-deploy.xml
index 1b46387..16e5253 100644
--- a/jetty-deploy/src/main/config/etc/jetty-deploy.xml
+++ b/jetty-deploy/src/main/config/etc/jetty-deploy.xml
@@ -39,14 +39,14 @@
<Call id="webappprovider" name="addAppProvider">
<Arg>
<New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
- <Set name="monitoredDirName"><Property name="jetty.home" default="." />/webapps</Set>
+ <Set name="monitoredDirName"><Property name="jetty.base" default="." />/webapps</Set>
<Set name="defaultsDescriptor"><Property name="jetty.home" default="." />/etc/webdefault.xml</Set>
<Set name="scanInterval">1</Set>
<Set name="extractWars">true</Set>
<Set name="configurationManager">
<New class="org.eclipse.jetty.deploy.PropertiesConfigurationManager">
<!-- file of context configuration properties
- <Set name="file"><SystemProperty name="jetty.home"/>/etc/some.properties</Set>
+ <Set name="file"><SystemProperty name="jetty.base"/>/etc/some.properties</Set>
-->
<!-- set a context configuration property
<Call name="put"><Arg>name</Arg><Arg>value</Arg></Call>
@@ -56,7 +56,6 @@
</New>
</Arg>
</Call>
-
</New>
</Arg>
</Call>
diff --git a/jetty-deploy/src/main/config/modules/deploy.mod b/jetty-deploy/src/main/config/modules/deploy.mod
new file mode 100644
index 0000000..8cb3697
--- /dev/null
+++ b/jetty-deploy/src/main/config/modules/deploy.mod
@@ -0,0 +1,15 @@
+#
+# Deploy Feature
+#
+
+[depend]
+webapp
+
+[lib]
+lib/jetty-deploy-${jetty.version}.jar
+
+[files]
+webapps/
+
+[xml]
+etc/jetty-deploy.xml
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java
index b9efded..ab6cd39 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java
@@ -282,6 +282,8 @@
};
xmlc.getIdMap().put("Server", getDeploymentManager().getServer());
+ xmlc.getProperties().put("jetty.home",System.getProperty("jetty.home","."));
+ xmlc.getProperties().put("jetty.base",System.getProperty("jetty.base","."));
xmlc.getProperties().put("jetty.webapp",file.getCanonicalPath());
xmlc.getProperties().put("jetty.webapps",file.getParentFile().getCanonicalPath());
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java
index 6f9790e..3f04994 100644
--- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.deploy.test;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.io.File;
import java.io.FileOutputStream;
diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml
index 38897f7..56e1ffd 100644
--- a/jetty-distribution/pom.xml
+++ b/jetty-distribution/pom.xml
@@ -3,14 +3,14 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<artifactId>jetty-distribution</artifactId>
<name>Jetty :: Distribution Assemblies</name>
<url>http://www.eclipse.org/jetty</url>
<packaging>pom</packaging>
<properties>
- <assembly-directory>target/distribution</assembly-directory>
+ <assembly-directory>${basedir}/target/distribution</assembly-directory>
<jetty-setuid-version>1.0.1</jetty-setuid-version>
</properties>
<build>
@@ -101,7 +101,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
- <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
+ <outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
<destFileName>test.war</destFileName>
</artifactItem>
<artifactItem>
@@ -111,7 +111,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
- <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
+ <outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
<destFileName>test-jaas.war</destFileName>
</artifactItem>
<artifactItem>
@@ -121,7 +121,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
- <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
+ <outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
<destFileName>test-jndi.war</destFileName>
</artifactItem>
<artifactItem>
@@ -131,7 +131,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
- <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
+ <outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
<destFileName>test-spec.war</destFileName>
</artifactItem>
<artifactItem>
@@ -141,7 +141,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
- <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
+ <outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
<destFileName>xref-proxy.war</destFileName>
</artifactItem>
<artifactItem>
@@ -151,7 +151,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
- <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
+ <outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
<destFileName>async-rest.war</destFileName>
</artifactItem>
<artifactItem>
@@ -223,6 +223,7 @@
<outputDirectory>${assembly-directory}</outputDirectory>
</artifactItem>
</artifactItems>
+ <excludes>META-INF/**</excludes>
</configuration>
</execution>
@@ -244,6 +245,7 @@
<outputDirectory>${assembly-directory}</outputDirectory>
</artifactItem>
</artifactItems>
+ <excludes>META-INF/**</excludes>
</configuration>
</execution>
@@ -265,6 +267,7 @@
<outputDirectory>${assembly-directory}</outputDirectory>
</artifactItem>
</artifactItems>
+ <excludes>META-INF/**</excludes>
</configuration>
</execution>
@@ -286,6 +289,7 @@
<outputDirectory>${assembly-directory}</outputDirectory>
</artifactItem>
</artifactItems>
+ <excludes>META-INF/**</excludes>
</configuration>
</execution>
@@ -297,8 +301,8 @@
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty</includeGroupIds>
- <excludeGroupIds>org.eclipse.jetty.orbit,org.eclipse.jetty.spdy,org.eclipse.jetty.websocket,org.eclipse.jetty.drafts</excludeGroupIds>
- <excludeArtifactIds>jetty-all,jetty-start,jetty-monitor,jetty-jsp</excludeArtifactIds>
+ <excludeGroupIds>org.eclipse.jetty.orbit,org.eclipse.jetty.spdy,org.eclipse.jetty.websocket,org.eclipse.jetty.toolchain</excludeGroupIds>
+ <excludeArtifactIds>jetty-all,jetty-jsp,jetty-start,jetty-monitor</excludeArtifactIds>
<includeTypes>jar</includeTypes>
<outputDirectory>${assembly-directory}/lib</outputDirectory>
</configuration>
@@ -310,7 +314,8 @@
<goal>copy-dependencies</goal>
</goals>
<configuration>
- <includeGroupIds>org.eclipse.jetty.websocket,org.eclipse.jetty.drafts</includeGroupIds>
+ <includeGroupIds>javax.websocket,org.eclipse.jetty.websocket</includeGroupIds>
+ <excludeArtifactIds>javax.websocket-client-api</excludeArtifactIds>
<includeTypes>jar</includeTypes>
<outputDirectory>${assembly-directory}/lib/websocket</outputDirectory>
</configuration>
@@ -335,7 +340,7 @@
</configuration>
</execution>
<execution>
- <id>copy-orbit-servlet-api-deps</id>
+ <id>copy-servlet-api-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>copy</goal>
@@ -343,12 +348,20 @@
<configuration>
<artifactItems>
<artifactItem>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
- <version>${orbit-servlet-api-version}</version>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>3.1.0</version>
<overWrite>true</overWrite>
<outputDirectory>${assembly-directory}/lib</outputDirectory>
- <destFileName>servlet-api-3.0.jar</destFileName>
+ <destFileName>servlet-api-3.1.jar</destFileName>
+ </artifactItem>
+ <artifactItem>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-schemas</artifactId>
+ <version>3.1.RC0</version>
+ <overWrite>true</overWrite>
+ <outputDirectory>${assembly-directory}/lib</outputDirectory>
+ <destFileName>jetty-schemas-3.1.jar</destFileName>
</artifactItem>
</artifactItems>
</configuration>
@@ -380,66 +393,79 @@
</configuration>
</execution>
<execution>
- <id>copy-orbit-lib-annotations-deps</id>
+ <id>copy-annotations-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
- <includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
- <includeArtifactIds>javax.annotation,org.objectweb.asm</includeArtifactIds>
+ <includeGroupIds>javax.annotation,org.eclipse.jetty.orbit,org.ow2.asm</includeGroupIds>
+ <includeArtifactIds>javax.annotation-api,asm,asm-commons</includeArtifactIds>
<includeTypes>jar</includeTypes>
<outputDirectory>${assembly-directory}/lib/annotations</outputDirectory>
</configuration>
</execution>
<execution>
- <id>copy-orbit-lib-jta-deps</id>
+ <id>copy-jta-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
- <includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
- <includeArtifactIds>javax.transaction</includeArtifactIds>
+ <includeGroupIds>javax.transaction</includeGroupIds>
+ <includeArtifactIds>javax.transaction-api</includeArtifactIds>
<includeTypes>jar</includeTypes>
<outputDirectory>${assembly-directory}/lib/jndi</outputDirectory>
</configuration>
</execution>
<execution>
- <id>copy-orbit-lib-jndi-deps</id>
+ <id>copy-jndi-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
- <includeArtifactIds>javax.mail.glassfish,javax.activation</includeArtifactIds>
+ <includeArtifactIds>javax.activation,javax.mail.glassfish</includeArtifactIds>
<includeTypes>jar</includeTypes>
<outputDirectory>${assembly-directory}/lib/jndi</outputDirectory>
</configuration>
</execution>
<execution>
- <id>copy-orbit-lib-jsp-deps</id>
+ <id>copy-jsp-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
- <includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
- <includeArtifactIds>com.sun.el,javax.el,javax.servlet.jsp,javax.servlet.jsp.jstl,org.apache.jasper.glassfish,org.apache.taglibs.standard.glassfish,org.eclipse.jdt.core</includeArtifactIds>
+ <includeGroupIds>org.eclipse.jetty.orbit,org.glassfish.web, org.glassfish, javax.el, javax.servlet.jsp, org.eclipse.jetty.toolchain</includeGroupIds>
+ <includeArtifactIds>javax.servlet.jsp.jstl,org.apache.taglibs.standard.glassfish,org.eclipse.jdt.core, javax.servlet.jsp-api, javax.servlet.jsp, jetty-jsp-jdt, javax.el-api, javax.el</includeArtifactIds>
<includeTypes>jar</includeTypes>
<outputDirectory>${assembly-directory}/lib/jsp</outputDirectory>
</configuration>
</execution>
<execution>
+ <id>copy-jaspi-deps</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
+ <includeArtifactIds>javax.security.auth.message</includeArtifactIds>
+ <includeTypes>jar</includeTypes>
+ <outputDirectory>${assembly-directory}/lib/jaspi</outputDirectory>
+ </configuration>
+ </execution>
+ <execution>
<id>unpack-config-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
- <includeGroupIds>org.eclipse.jetty</includeGroupIds>
+ <includeGroupIds>org.eclipse.jetty,org.eclipse.jetty.websocket</includeGroupIds>
<classifier>config</classifier>
<failOnMissingClassifierArtifact>false</failOnMissingClassifierArtifact>
<excludes>META-INF/**</excludes>
@@ -449,6 +475,44 @@
</executions>
</plugin>
<plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>setup home</id>
+ <phase>process-classes</phase>
+ <configuration>
+ <mainClass>org.eclipse.jetty.start.Main</mainClass>
+ <arguments>
+ <argument>jetty.home=${assembly-directory}</argument>
+ <argument>jetty.base=${assembly-directory}</argument>
+ <argument>--add-to-start=server,deploy,websocket,jsp,ext,resources</argument>
+ <argument>--add-to-startd=http</argument>
+ </arguments>
+ </configuration>
+ <goals>
+ <goal>java</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>setup demo-base</id>
+ <phase>process-classes</phase>
+ <configuration>
+ <mainClass>org.eclipse.jetty.start.Main</mainClass>
+ <arguments>
+ <argument>jetty.home=${assembly-directory}</argument>
+ <argument>jetty.base=${assembly-directory}/demo-base</argument>
+ <argument>--add-to-start=server,continuation,deploy,jsp,ext,resources,client,annotations,jndi</argument>
+ <argument>--add-to-startd-ini=http,https</argument>
+ </arguments>
+ </configuration>
+ <goals>
+ <goal>java</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
@@ -488,12 +552,8 @@
<dependencies>
<!-- Orbit Deps -->
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.annotation</artifactId>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>org.objectweb.asm</artifactId>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
@@ -504,13 +564,43 @@
<artifactId>javax.mail.glassfish</artifactId>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.transaction</artifactId>
+ <groupId>javax.transaction</groupId>
+ <artifactId>javax.transaction-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.security.auth.message</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.orbit</groupId>
+ <artifactId>org.apache.taglibs.standard.glassfish</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.web</groupId>
+ <artifactId>javax.servlet.jsp</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-jsp-jdt</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet.jsp</groupId>
+ <artifactId>javax.servlet.jsp-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish</groupId>
+ <artifactId>javax.el</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm-commons</artifactId>
+ </dependency>
<!-- jetty deps -->
<dependency>
@@ -552,10 +642,20 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>websocket-servlet</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>javax-websocket-server-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jsp</artifactId>
<version>${project.version}</version>
@@ -580,6 +680,13 @@
<artifactId>jetty-proxy</artifactId>
<version>${project.version}</version>
</dependency>
+<!--
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-overlay-deployer</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+-->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jaas</artifactId>
@@ -622,5 +729,10 @@
<version>${project.version}</version>
<type>war</type>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-jaspi</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
</project>
diff --git a/jetty-distribution/src/main/resources/README.TXT b/jetty-distribution/src/main/resources/README.TXT
new file mode 100644
index 0000000..5b47675
--- /dev/null
+++ b/jetty-distribution/src/main/resources/README.TXT
@@ -0,0 +1,53 @@
+
+JETTY
+=====
+The Jetty project is a 100% Java HTTP Server, HTTP Client
+and Servlet Container from the eclipse foundation
+
+ http://www.eclipse.org/jetty/
+
+Jetty is open source and is dual licensed using the apache 2.0 and
+eclipse public license 1.0. You may choose either license when distributing
+jetty.
+
+
+
+RUNNING JETTY
+=============
+The run directory is either the top-level of a binary release
+or jetty-distribution/target/assembly-prep directory when built from
+source.
+
+To run with the default options:
+
+ java -jar start.jar
+
+To see the available options and the default arguments
+provided by the start.ini file:
+
+ java -jar start.jar --help
+
+
+Many Jetty features can be enabled by using the --ini command to create
+template configurations in the start.d directory. For example:
+
+ java -jar start.jar --ini=https
+
+Will enable HTTPS and its dependencies in the files start.d/http.ini and start.d/ssl.ini
+To list the know modules:
+
+ java -jar start.jar --list-modules
+
+
+
+JETTY BASE
+==========
+
+If the property jetty.base is defined on the command line, then the jetty start.jar
+mechanism will look for start.ini, start.d, webapps and etc files relative to the
+jetty.home directory
+
+ java -jar start.jar jetty.base=/opt/myjettybase/
+
+
+
diff --git a/jetty-distribution/src/main/resources/README.txt b/jetty-distribution/src/main/resources/README.txt
deleted file mode 100644
index 9656bab..0000000
--- a/jetty-distribution/src/main/resources/README.txt
+++ /dev/null
@@ -1,62 +0,0 @@
-
-JETTY
-=====
-The Jetty project is a 100% Java HTTP Server, HTTP Client
-and Servlet Container from the eclipse foundation
-
- http://www.eclipse.org/jetty/
-
-Jetty is open source and is dual licensed using the apache 2.0 and
-eclipse public license 1.0. You may choose either license when distributing
-jetty.
-
-
-
-BUILDING JETTY
-==============
-Jetty uses maven 2 as its build system. Maven will fetch
-the dependancies, build the server and assemble a runnable
-version:
-
- mvn install
-
-
-
-RUNNING JETTY
-=============
-The run directory is either the top-level of a binary release
-or jetty-distribution/target/assembly-prep directory when built from
-source.
-
-To run with the default options:
-
- java -jar start.jar
-
-To see the available options and the default arguments
-provided by the start.ini file:
-
- java -jar start.jar --help
-
-
-Most start options can be configured in the start.ini file or they can be appended to the start line.
-
-To run with extra configuration file(s) appended, eg SSL
-
- java -jar start.jar etc/jetty-https.xml
-
-To run with properties
-
- java -jar start.jar jetty.port=8081
-
-To run with extra configuration file(s) prepended, eg logging & jmx
-
- java -jar start.jar --pre=etc/jetty-logging.xml --pre=etc/jetty-jmx.xml
-
-To run without the args from start.ini
-
- java -jar start.jar --ini OPTIONS=Server,websocket etc/jetty.xml etc/jetty-deploy.xml etc/jetty-ssl.xml
-
-to list the know OPTIONS:
-
- java -jar start.jar --list-options
-
diff --git a/jetty-distribution/src/main/resources/bin/README.jetty-cygwin.txt.txt b/jetty-distribution/src/main/resources/bin/README.jetty-cygwin.txt.txt
deleted file mode 100644
index 6ac050d..0000000
--- a/jetty-distribution/src/main/resources/bin/README.jetty-cygwin.txt.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-Before running jetty-cygwin.sh on cygwin, define the JAVA_HOME and JETTY_HOME first.
-
-$ export JAVA_HOME=/path/to/jvm
-$ export JETTY_HOME=/path/to/jetty
-
-Examples:
-$ export JAVA_HOME=/usr/bin/jvm
-So assuming you installed cygwin on C:\cygwin, the jvm needs to be in C:\cygwin\usr\bin\jvm
-
-$ export JETTY_HOME=/usr/share/jetty6
-So assuming you installed cygwin on C:\cygwin, jetty needs to be in C:\cygwin\usr\share\jetty6
\ No newline at end of file
diff --git a/jetty-distribution/src/main/resources/bin/jetty-cygwin.sh b/jetty-distribution/src/main/resources/bin/jetty-cygwin.sh
deleted file mode 100755
index b1a1cbe..0000000
--- a/jetty-distribution/src/main/resources/bin/jetty-cygwin.sh
+++ /dev/null
@@ -1,677 +0,0 @@
-#!/bin/bash
-#
-# Startup script for jetty under *nix systems (it works under NT/cygwin too).
-
-# To get the service to restart correctly on reboot, uncomment below (3 lines):
-# ========================
-# chkconfig: 3 99 99
-# description: Jetty 9 webserver
-# processname: jetty
-# ========================
-
-# Configuration files
-#
-# /etc/default/jetty
-# If it exists, this is read at the start of script. It may perform any
-# sequence of shell commands, like setting relevant environment variables.
-#
-# $HOME/.jettyrc
-# If it exists, this is read at the start of script. It may perform any
-# sequence of shell commands, like setting relevant environment variables.
-#
-# /etc/jetty.conf
-# If found, and no configurations were given on the command line,
-# the file will be used as this script's configuration.
-# Each line in the file may contain:
-# - A comment denoted by the pound (#) sign as first non-blank character.
-# - The path to a regular file, which will be passed to jetty as a
-# config.xml file.
-# - The path to a directory. Each *.xml file in the directory will be
-# passed to jetty as a config.xml file.
-#
-# The files will be checked for existence before being passed to jetty.
-#
-# $JETTY_HOME/etc/jetty.xml
-# If found, used as this script's configuration file, but only if
-# /etc/jetty.conf was not present. See above.
-#
-# Configuration variables
-#
-# JAVA_HOME
-# Home of Java installation.
-#
-# JAVA
-# Command to invoke Java. If not set, $JAVA_HOME/bin/java will be
-# used.
-#
-# JAVA_OPTIONS
-# Extra options to pass to the JVM
-#
-# JETTY_HOME
-# Where Jetty is installed. If not set, the script will try go
-# guess it by first looking at the invocation path for the script,
-# and then by looking in standard locations as $HOME/opt/jetty
-# and /opt/jetty. The java system property "jetty.home" will be
-# set to this value for use by configure.xml files, f.e.:
-#
-# <Arg><SystemProperty name="jetty.home" default="."/>/webapps/jetty.war</Arg>
-#
-# JETTY_PORT
-# Override the default port for Jetty servers. If not set then the
-# default value in the xml configuration file will be used. The java
-# system property "jetty.port" will be set to this value for use in
-# configure.xml files. For example, the following idiom is widely
-# used in the demo config files to respect this property in Listener
-# configuration elements:
-#
-# <Set name="Port"><SystemProperty name="jetty.port" default="8080"/></Set>
-#
-# Note: that the config file could ignore this property simply by saying:
-#
-# <Set name="Port">8080</Set>
-#
-# JETTY_RUN
-# Where the jetty.pid file should be stored. It defaults to the
-# first available of /var/run, /usr/var/run, and /tmp if not set.
-#
-# JETTY_PID
-# The Jetty PID file, defaults to $JETTY_RUN/jetty.pid
-#
-# JETTY_ARGS
-# The default arguments to pass to jetty.
-#
-# JETTY_USER
-# if set, then used as a username to run the server as
-#
-
-usage()
-{
- echo "Usage: $0 {start|stop|run|restart|check|supervise} [ CONFIGS ... ] "
- exit 1
-}
-
-[ $# -gt 0 ] || usage
-
-
-##################################################
-# Some utility functions
-##################################################
-findDirectory()
-{
- OP=$1
- shift
- for L in $* ; do
- [ $OP $L ] || continue
- echo $L
- break
- done
-}
-
-running()
-{
- [ -f $1 ] || return 1
- PID=$(cat $1)
- ps -p $PID >/dev/null 2>/dev/null || return 1
- return 0
-}
-
-
-
-
-
-
-
-##################################################
-# Get the action & configs
-##################################################
-
-ACTION=$1
-shift
-ARGS="$*"
-CONFIGS=""
-NO_START=0
-
-##################################################
-# See if there's a default configuration file
-##################################################
-if [ -f /etc/default/jetty9 ] ; then
- . /etc/default/jetty9
-elif [ -f /etc/default/jetty ] ; then
- . /etc/default/jetty
-fi
-
-
-##################################################
-# See if there's a user-specific configuration file
-##################################################
-if [ -f $HOME/.jettyrc ] ; then
- . $HOME/.jettyrc
-fi
-
-##################################################
-# Set tmp if not already set.
-##################################################
-
-if [ -z "$TMP" ]
-then
- TMP=/tmp
-fi
-
-##################################################
-# Jetty's hallmark
-##################################################
-JETTY_INSTALL_TRACE_FILE="etc/jetty.xml"
-TMPJ=$TMP/j$$
-
-
-##################################################
-# Try to determine JETTY_HOME if not set
-##################################################
-if [ -z "$JETTY_HOME" ]
-then
- JETTY_HOME_1=`dirname "$0"`
- JETTY_HOME_1=`dirname "$JETTY_HOME_1"`
- if [ -f "${JETTY_HOME_1}/${JETTY_INSTALL_TRACE_FILE}" ] ;
- then
- JETTY_HOME=${JETTY_HOME_1}
- fi
-fi
-
-
-##################################################
-# if no JETTY_HOME, search likely locations.
-##################################################
-if [ "$JETTY_HOME" = "" ] ; then
- STANDARD_LOCATIONS=" \
- /usr/share \
- /usr/share/java \
- $HOME \
- $HOME/src \
- ${HOME}/opt/ \
- /opt \
- /java \
- /usr/local \
- /usr/local/share \
- /usr/local/share/java \
- /home \
- "
- JETTY_DIR_NAMES=" \
- jetty-9 \
- jetty9 \
- jetty-9.* \
- jetty \
- Jetty-9 \
- Jetty9 \
- Jetty-9.* \
- Jetty \
- "
-
- JETTY_HOME=
- for L in $STANDARD_LOCATIONS
- do
- for N in $JETTY_DIR_NAMES
- do
- if [ -d $L/$N ] && [ -f "$L/${N}/${JETTY_INSTALL_TRACE_FILE}" ] ;
- then
- JETTY_HOME="$L/$N"
- fi
- done
- [ ! -z "$JETTY_HOME" ] && break
- done
-fi
-
-
-##################################################
-# No JETTY_HOME yet? We're out of luck!
-##################################################
-if [ -z "$JETTY_HOME" ] ; then
- echo "** ERROR: JETTY_HOME not set, you need to set it or install in a standard location"
- exit 1
-fi
-
-cd $JETTY_HOME
-JETTY_HOME=`pwd`
-
-
-#####################################################
-# Check that jetty is where we think it is
-#####################################################
-if [ ! -r $JETTY_HOME/$JETTY_INSTALL_TRACE_FILE ]
-then
- echo "** ERROR: Oops! Jetty doesn't appear to be installed in $JETTY_HOME"
- echo "** ERROR: $JETTY_HOME/$JETTY_INSTALL_TRACE_FILE is not readable!"
- exit 1
-fi
-
-
-###########################################################
-# Get the list of config.xml files from the command line.
-###########################################################
-if [ ! -z "$ARGS" ]
-then
- for A in $ARGS
- do
- if [ -f $A ]
- then
- CONF="$A"
- elif [ -f $JETTY_HOME/etc/$A ]
- then
- CONF="$JETTY_HOME/etc/$A"
- elif [ -f ${A}.xml ]
- then
- CONF="${A}.xml"
- elif [ -f $JETTY_HOME/etc/${A}.xml ]
- then
- CONF="$JETTY_HOME/etc/${A}.xml"
- else
- echo "** ERROR: Cannot find configuration '$A' specified in the command line."
- exit 1
- fi
- if [ ! -r $CONF ]
- then
- echo "** ERROR: Cannot read configuration '$A' specified in the command line."
- exit 1
- fi
- CONFIGS="$CONFIGS $CONF"
- done
-fi
-
-
-##################################################
-# Try to find this script's configuration file,
-# but only if no configurations were given on the
-# command line.
-##################################################
-if [ -z "$JETTY_CONF" ]
-then
- if [ -f /etc/jetty.conf ]
- then
- JETTY_CONF=/etc/jetty.conf
- elif [ -f "${JETTY_HOME}/etc/jetty.conf" ]
- then
- JETTY_CONF="${JETTY_HOME}/etc/jetty.conf"
- fi
-fi
-
-##################################################
-# Read the configuration file if one exists
-##################################################
-CONFIG_LINES=
-if [ -z "$CONFIGS" ] && [ -f "$JETTY_CONF" ] && [ -r "$JETTY_CONF" ]
-then
- CONFIG_LINES=`cat $JETTY_CONF | grep -v "^[:space:]*#" | tr "\n" " "`
-fi
-
-##################################################
-# Get the list of config.xml files from jetty.conf
-##################################################
-if [ ! -z "${CONFIG_LINES}" ]
-then
- for CONF in ${CONFIG_LINES}
- do
- if [ ! -r "$CONF" ]
- then
- echo "** WARNING: Cannot read '$CONF' specified in '$JETTY_CONF'"
- elif [ -f "$CONF" ]
- then
- # assume it's a configure.xml file
- CONFIGS="$CONFIGS $CONF"
- elif [ -d "$CONF" ]
- then
- # assume it's a directory with configure.xml files
- # for example: /etc/jetty.d/
- # sort the files before adding them to the list of CONFIGS
- XML_FILES=`ls ${CONF}/*.xml | sort | tr "\n" " "`
- for FILE in ${XML_FILES}
- do
- if [ -r "$FILE" ] && [ -f "$FILE" ]
- then
- CONFIGS="$CONFIGS $FILE"
- else
- echo "** WARNING: Cannot read '$FILE' specified in '$JETTY_CONF'"
- fi
- done
- else
- echo "** WARNING: Don''t know what to do with '$CONF' specified in '$JETTY_CONF'"
- fi
- done
-fi
-
-#####################################################
-# Run the standard server if there's nothing else to run
-#####################################################
-if [ -z "$CONFIGS" ]
-then
- CONFIGS="${JETTY_HOME}/etc/jetty-logging.xml ${JETTY_HOME}/etc/jetty.xml"
-fi
-
-
-#####################################################
-# Find a location for the pid file
-#####################################################
-if [ -z "$JETTY_RUN" ]
-then
- JETTY_RUN=`findDirectory -w /var/run /usr/var/run /tmp`
-fi
-
-#####################################################
-# Find a PID for the pid file
-#####################################################
-if [ -z "$JETTY_PID" ]
-then
- JETTY_PID="$JETTY_RUN/jetty.pid"
-fi
-
-
-##################################################
-# Check for JAVA_HOME
-##################################################
-if [ -z "$JAVA_HOME" ]
-then
- # If a java runtime is not defined, search the following
- # directories for a JVM and sort by version. Use the highest
- # version number.
-
- # Java search path
- JAVA_LOCATIONS="\
- /usr/java \
- /usr/bin \
- /usr/local/bin \
- /usr/local/java \
- /usr/local/jdk \
- /usr/local/jre \
- /usr/lib/jvm \
- /opt/java \
- /opt/jdk \
- /opt/jre \
- "
- JAVA_NAMES="java jdk jre"
- for N in $JAVA_NAMES ; do
- for L in $JAVA_LOCATIONS ; do
- [ -d $L ] || continue
- find $L -name "$N" ! -type d | grep -v threads | while read J ; do
- [ -x $J ] || continue
- VERSION=`eval $J -version 2>&1`
- [ $? = 0 ] || continue
- VERSION=`expr "$VERSION" : '.*"\(1.[0-9\.]*\)["_]'`
- [ "$VERSION" = "" ] && continue
- expr $VERSION \< 1.2 >/dev/null && continue
- echo $VERSION:$J
- done
- done
- done | sort | tail -1 > $TMPJ
- JAVA=`cat $TMPJ | cut -d: -f2`
- JVERSION=`cat $TMPJ | cut -d: -f1`
-
- JAVA_HOME=`dirname $JAVA`
- while [ ! -z "$JAVA_HOME" -a "$JAVA_HOME" != "/" -a ! -f "$JAVA_HOME/lib/tools.jar" ] ; do
- JAVA_HOME=`dirname $JAVA_HOME`
- done
- [ "$JAVA_HOME" = "" ] && JAVA_HOME=
-
- echo "Found JAVA=$JAVA in JAVA_HOME=$JAVA_HOME"
-fi
-
-
-##################################################
-# Determine which JVM of version >1.2
-# Try to use JAVA_HOME
-##################################################
-if [ "$JAVA" = "" -a "$JAVA_HOME" != "" ]
-then
- if [ ! -z "$JAVACMD" ]
- then
- JAVA="$JAVACMD"
- else
- [ -x $JAVA_HOME/bin/jre -a ! -d $JAVA_HOME/bin/jre ] && JAVA=$JAVA_HOME/bin/jre
- [ -x $JAVA_HOME/bin/java -a ! -d $JAVA_HOME/bin/java ] && JAVA=$JAVA_HOME/bin/java
- fi
-fi
-
-if [ "$JAVA" = "" ]
-then
- echo "Cannot find a JRE or JDK. Please set JAVA_HOME to a >=1.2 JRE" 2>&2
- exit 1
-fi
-
-JAVA_VERSION=`expr "$($JAVA -version 2>&1 | head -1)" : '.*1\.\([0-9]\)'`
-
-#####################################################
-# See if JETTY_PORT is defined
-#####################################################
-if [ "$JETTY_PORT" != "" ]
-then
- JAVA_OPTIONS="$JAVA_OPTIONS -Djetty.port=$JETTY_PORT"
-fi
-
-#####################################################
-# See if JETTY_LOGS is defined
-#####################################################
-if [ "$JETTY_LOGS" != "" ]
-then
- JAVA_OPTIONS="$JAVA_OPTIONS -Djetty.logs=$JETTY_LOGS"
-fi
-
-#####################################################
-# Are we running on Windows? Could be, with Cygwin/NT.
-#####################################################
-case "`uname`" in
-CYGWIN*) PATH_SEPARATOR=";";;
-*) PATH_SEPARATOR=":";;
-esac
-
-
-#####################################################
-# Add jetty properties to Java VM options.
-#####################################################
-JAVA_OPTIONS="$JAVA_OPTIONS -Djetty.home=$JETTY_HOME -Djava.io.tmpdir=$TMP"
-
-[ -f $JETTY_HOME/etc/start.config ] && JAVA_OPTIONS="-DSTART=$JETTY_HOME/etc/start.config $JAVA_OPTIONS"
-
-#####################################################
-# This is how the Jetty server will be started
-#####################################################
-
-JETTY_START=$JETTY_HOME/start.jar
-[ ! -f $JETTY_START ] && JETTY_START=$JETTY_HOME/lib/start.jar
-
-case "`uname`" in
-CYGWIN*)
-JETTY_START="`cygpath -w $JETTY_START`"
-echo $JETTY_START
-
-CONFIGS="`cygpath -w $CONFIGS`"
-echo $CONFIGS
-;;
-esac
-
-
-RUN_ARGS="$JAVA_OPTIONS -jar $JETTY_START $JETTY_ARGS $CONFIGS"
-RUN_CMD="$JAVA $RUN_ARGS"
-
-#####################################################
-# Comment these out after you're happy with what
-# the script is doing.
-#####################################################
-#echo "JETTY_HOME = $JETTY_HOME"
-#echo "JETTY_CONF = $JETTY_CONF"
-#echo "JETTY_RUN = $JETTY_RUN"
-#echo "JETTY_PID = $JETTY_PID"
-#echo "JETTY_ARGS = $JETTY_ARGS"
-#echo "CONFIGS = $CONFIGS"
-#echo "JAVA_OPTIONS = $JAVA_OPTIONS"
-#echo "JAVA = $JAVA"
-
-
-##################################################
-# Do the action
-##################################################
-case "$ACTION" in
- start)
- echo -n "Starting Jetty: "
-
- if [ "$NO_START" = "1" ]; then
- echo "Not starting jetty - NO_START=1 in /etc/default/jetty9";
- exit 0;
- fi
-
-
- if [ "$START_STOP_DAEMON" = "1" ] && type start-stop-daemon > /dev/null 2>&1
- then
- [ x$JETTY_USER = x ] && JETTY_USER=$(whoami)
- [ $UID = 0 ] && CH_USER="-c $JETTY_USER"
- if start-stop-daemon -S -p$JETTY_PID $CH_USER -d $JETTY_HOME -b -m -a $JAVA -- $RUN_ARGS
- then
- sleep 1
- if running $JETTY_PID
- then
- echo OK
- else
- echo FAILED
- fi
- fi
-
- else
-
- if [ -f $JETTY_PID ]
- then
- if running $JETTY_PID
- then
- echo "Already Running!!"
- exit 1
- else
- # dead pid file - remove
- rm -f $JETTY_PID
- fi
- fi
-
- if [ x$JETTY_USER != x ]
- then
- touch $JETTY_PID
- chown $JETTY_USER $JETTY_PID
- su - $JETTY_USER -c "
- $RUN_CMD &
- PID=\$!
- disown \$PID
- echo \$PID > $JETTY_PID"
- else
- $RUN_CMD &
- PID=$!
- disown $PID
- echo $PID > $JETTY_PID
- fi
-
- echo "STARTED Jetty `date`"
- fi
-
- ;;
-
- stop)
- echo -n "Stopping Jetty: "
- if [ "$START_STOP_DAEMON" = "1" ] && type start-stop-daemon > /dev/null 2>&1; then
- start-stop-daemon -K -p $JETTY_PID -d $JETTY_HOME -a $JAVA -s HUP
- sleep 1
- if running $JETTY_PID
- then
- sleep 3
- if running $JETTY_PID
- then
- sleep 30
- if running $JETTY_PID
- then
- start-stop-daemon -K -p $JETTY_PID -d $JETTY_HOME -a $JAVA -s KILL
- fi
- fi
- fi
-
- rm -f $JETTY_PID
- echo OK
- else
- PID=`cat $JETTY_PID 2>/dev/null`
- TIMEOUT=30
- while running $JETTY_PID && [ $TIMEOUT -gt 0 ]
- do
- kill $PID 2>/dev/null
- sleep 1
- let TIMEOUT=$TIMEOUT-1
- done
-
- [ $TIMEOUT -gt 0 ] || kill -9 $PID 2>/dev/null
-
- rm -f $JETTY_PID
- echo OK
- fi
- ;;
-
- restart)
- JETTY_SH=$0
- if [ ! -f $JETTY_SH ]; then
- if [ ! -f $JETTY_HOME/bin/jetty.sh ]; then
- echo "$JETTY_HOME/bin/jetty.sh does not exist."
- exit 1
- fi
- JETTY_SH=$JETTY_HOME/bin/jetty.sh
- fi
- $JETTY_SH stop $*
- sleep 5
- $JETTY_SH start $*
- ;;
-
- supervise)
- #
- # Under control of daemontools supervise monitor which
- # handles restarts and shutdowns via the svc program.
- #
- exec $RUN_CMD
- ;;
-
- run|demo)
- echo "Running Jetty: "
-
- if [ -f $JETTY_PID ]
- then
- if running $JETTY_PID
- then
- echo "Already Running!!"
- exit 1
- else
- # dead pid file - remove
- rm -f $JETTY_PID
- fi
- fi
-
- exec $RUN_CMD
- ;;
-
- check)
- echo "Checking arguments to Jetty: "
- echo "JETTY_HOME = $JETTY_HOME"
- echo "JETTY_CONF = $JETTY_CONF"
- echo "JETTY_RUN = $JETTY_RUN"
- echo "JETTY_PID = $JETTY_PID"
- echo "JETTY_PORT = $JETTY_PORT"
- echo "JETTY_LOGS = $JETTY_LOGS"
- echo "CONFIGS = $CONFIGS"
- echo "JAVA_OPTIONS = $JAVA_OPTIONS"
- echo "JAVA = $JAVA"
- echo "CLASSPATH = $CLASSPATH"
- echo "RUN_CMD = $RUN_CMD"
- echo
-
- if [ -f $JETTY_RUN/jetty.pid ]
- then
- echo "Jetty running pid="`cat $JETTY_RUN/jetty.pid`
- exit 0
- fi
- exit 1
- ;;
-
-*)
- usage
- ;;
-esac
-
-exit 0
-
-
-
diff --git a/jetty-distribution/src/main/resources/bin/jetty-xinetd.sh b/jetty-distribution/src/main/resources/bin/jetty-xinetd.sh
deleted file mode 100755
index f74b932..0000000
--- a/jetty-distribution/src/main/resources/bin/jetty-xinetd.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/bash
-
-
-# look for JETTY_HOME
-if [ -z "$JETTY_HOME" ]
-then
- JETTY_HOME_1=`dirname "$0"`
- JETTY_HOME_1=`dirname "$JETTY_HOME_1"`
- JETTY_HOME=${JETTY_HOME_1}
-fi
-
-cd $JETTY_HOME
-exec /usr/bin/java -Djetty.port=8088 -jar start.jar etc/jetty.xml etc/jetty-xinetd.xml
-
diff --git a/jetty-distribution/src/main/resources/bin/jetty.sh b/jetty-distribution/src/main/resources/bin/jetty.sh
index c7ab396..a084caf 100755
--- a/jetty-distribution/src/main/resources/bin/jetty.sh
+++ b/jetty-distribution/src/main/resources/bin/jetty.sh
@@ -2,6 +2,12 @@
#
# Startup script for jetty under *nix systems (it works under NT/cygwin too).
+##################################################
+# Set the name which is used by other variables.
+# Defaults to the file name without extension.
+##################################################
+NAME=$(echo $(basename $0) | sed -e 's/^[SK][0-9]+//' -e 's/\.sh$//')
+
# To get the service to restart correctly on reboot, uncomment below (3 lines):
# ========================
# chkconfig: 3 99 99
@@ -11,15 +17,15 @@
# Configuration files
#
-# /etc/default/jetty
+# /etc/default/$NAME
# If it exists, this is read at the start of script. It may perform any
# sequence of shell commands, like setting relevant environment variables.
#
-# $HOME/.jettyrc
+# $HOME/.$NAMErc (e.g. $HOME/.jettyrc)
# If it exists, this is read at the start of script. It may perform any
# sequence of shell commands, like setting relevant environment variables.
#
-# /etc/jetty.conf
+# /etc/$NAME.conf
# If found, and no configurations were given on the command line,
# the file will be used as this script's configuration.
# Each line in the file may contain:
@@ -28,13 +34,10 @@
# config.xml file.
# - The path to a directory. Each *.xml file in the directory will be
# passed to jetty as a config.xml file.
+# - All other lines will be passed, as-is to the start.jar
#
# The files will be checked for existence before being passed to jetty.
#
-# $JETTY_HOME/etc/jetty.xml
-# If found, used as this script's configuration file, but only if
-# /etc/jetty.conf was not present. See above.
-#
# Configuration variables
#
# JAVA
@@ -52,27 +55,17 @@
#
# <Arg><Property name="jetty.home" default="."/>/webapps/jetty.war</Arg>
#
-# JETTY_PORT (Deprecated - use JETTY_ARGS)
-# Override the default port for Jetty servers. If not set then the
-# default value in the xml configuration file will be used. The java
-# system property "jetty.port" will be set to this value for use in
-# configure.xml files. For example, the following idiom is widely
-# used in the demo config files to respect this property in Listener
-# configuration elements:
-#
-# <Set name="Port"><Property name="jetty.port" default="8080"/></Set>
-#
-# Note: that the config file could ignore this property simply by saying:
-#
-# <Set name="Port">8080</Set>
+# JETTY_BASE
+# Where your Jetty base directory is. If not set, the value from
+# $JETTY_HOME will be used.
#
# JETTY_RUN
-# Where the jetty.pid file should be stored. It defaults to the
-# first available of /var/run, /usr/var/run, JETTY_HOME and /tmp
+# Where the $NAME.pid file should be stored. It defaults to the
+# first available of /var/run, /usr/var/run, JETTY_BASE and /tmp
# if not set.
#
# JETTY_PID
-# The Jetty PID file, defaults to $JETTY_RUN/jetty.pid
+# The Jetty PID file, defaults to $JETTY_RUN/$NAME.pid
#
# JETTY_ARGS
# The default arguments to pass to jetty.
@@ -81,7 +74,6 @@
#
# JETTY_USER
# if set, then used as a username to run the server as
-#
usage()
{
@@ -163,7 +155,7 @@
ETC=$HOME/etc
fi
-for CONFIG in $ETC/default/jetty{,9} $HOME/.jettyrc; do
+for CONFIG in $ETC/default/${NAME}{,9} $HOME/.${NAME}rc; do
if [ -f "$CONFIG" ] ; then
readConfig "$CONFIG"
fi
@@ -178,7 +170,7 @@
##################################################
# Jetty's hallmark
##################################################
-JETTY_INSTALL_TRACE_FILE="etc/jetty.xml"
+JETTY_INSTALL_TRACE_FILE="start.jar"
##################################################
@@ -204,64 +196,6 @@
##################################################
-# if no JETTY_HOME, search likely locations.
-##################################################
-if [ -z "$JETTY_HOME" ] ; then
- STANDARD_LOCATIONS=(
- "/usr/share"
- "/usr/share/java"
- "${HOME}"
- "${HOME}/src"
- "${HOME}/opt"
- "/opt"
- "/java"
- "/usr/local"
- "/usr/local/share"
- "/usr/local/share/java"
- "/home"
- )
- JETTY_DIR_NAMES=(
- "jetty-9"
- "jetty9"
- "jetty-9.*"
- "jetty"
- "Jetty-9"
- "Jetty9"
- "Jetty-9.*"
- "Jetty"
- )
-
- for L in "${STANDARD_LOCATIONS[@]}"
- do
- for N in "${JETTY_DIR_NAMES[@]}"
- do
- POSSIBLE_JETTY_HOME=("$L/"$N)
- if [ ! -d "$POSSIBLE_JETTY_HOME" ]
- then
- # Not a directory. skip.
- unset POSSIBLE_JETTY_HOME
- elif [ ! -f "$POSSIBLE_JETTY_HOME/$JETTY_INSTALL_TRACE_FILE" ]
- then
- # Trace file not found. skip.
- unset POSSIBLE_JETTY_HOME
- else
- # Good hit, Use it
- JETTY_HOME=$POSSIBLE_JETTY_HOME
- # Break out of JETTY_DIR_NAMES loop
- break
- fi
- done
- if [ -n "$POSSIBLE_JETTY_HOME" ]
- then
- # We have found our JETTY_HOME
- # Break out of STANDARD_LOCATIONS loop
- break
- fi
- done
-fi
-
-
-##################################################
# No JETTY_HOME yet? We're out of luck!
##################################################
if [ -z "$JETTY_HOME" ]; then
@@ -273,6 +207,17 @@
JETTY_HOME=$PWD
+##################################################
+# Set JETTY_BASE
+##################################################
+if [ -z "$JETTY_BASE" ]; then
+ JETTY_BASE=$JETTY_HOME
+fi
+
+cd "$JETTY_BASE"
+JETTY_BASE=$PWD
+
+
#####################################################
# Check that jetty is where we think it is
#####################################################
@@ -290,9 +235,12 @@
##################################################
if [ -z "$JETTY_CONF" ]
then
- if [ -f $ETC/jetty.conf ]
+ if [ -f $ETC/${NAME}.conf ]
then
- JETTY_CONF=$ETC/jetty.conf
+ JETTY_CONF=$ETC/${NAME}.conf
+ elif [ -f "$JETTY_BASE/etc/jetty.conf" ]
+ then
+ JETTY_CONF=$JETTY_BASE/etc/jetty.conf
elif [ -f "$JETTY_HOME/etc/jetty.conf" ]
then
JETTY_CONF=$JETTY_HOME/etc/jetty.conf
@@ -302,7 +250,7 @@
##################################################
# Get the list of config.xml files from jetty.conf
##################################################
-if [ -z "$CONFIGS" ] && [ -f "$JETTY_CONF" ] && [ -r "$JETTY_CONF" ]
+if [ -f "$JETTY_CONF" ] && [ -r "$JETTY_CONF" ]
then
while read -r CONF
do
@@ -314,19 +262,19 @@
then
# assume it's a directory with configure.xml files
# for example: /etc/jetty.d/
- # sort the files before adding them to the list of CONFIGS
+ # sort the files before adding them to the list of JETTY_ARGS
for XMLFILE in "$CONF/"*.xml
do
if [ -r "$XMLFILE" ] && [ -f "$XMLFILE" ]
then
- CONFIGS+=("$XMLFILE")
+ JETTY_ARGS+=("$XMLFILE")
else
echo "** WARNING: Cannot read '$XMLFILE' specified in '$JETTY_CONF'"
fi
done
else
# assume it's a command line parameter (let start.jar deal with its validity)
- CONFIGS+=("$CONF")
+ JETTY_ARGS+=("$CONF")
fi
done < "$JETTY_CONF"
fi
@@ -336,7 +284,7 @@
#####################################################
if [ -z "$JETTY_RUN" ]
then
- JETTY_RUN=$(findDirectory -w /var/run /usr/var/run $JETTY_HOME /tmp)
+ JETTY_RUN=$(findDirectory -w /var/run /usr/var/run $JETTY_BASE /tmp)
fi
#####################################################
@@ -344,12 +292,12 @@
#####################################################
if [ -z "$JETTY_PID" ]
then
- JETTY_PID="$JETTY_RUN/jetty.pid"
+ JETTY_PID="$JETTY_RUN/${NAME}.pid"
fi
if [ -z "$JETTY_STATE" ]
then
- JETTY_STATE=$JETTY_HOME/jetty.state
+ JETTY_STATE=$JETTY_BASE/${NAME}.state
fi
JAVA_OPTIONS+=("-Djetty.state=$JETTY_STATE")
rm -f $JETTY_STATE
@@ -369,16 +317,16 @@
fi
#####################################################
-# See if JETTY_PORT is defined
-#####################################################
-if [ "$JETTY_PORT" ]
-then
- JETTY_ARGS+=("jetty.port=$JETTY_PORT")
-fi
-
-#####################################################
# See if JETTY_LOGS is defined
#####################################################
+if [ -z "$JETTY_LOGS" ] && [ -d $JETTY_BASE/logs ]
+then
+ JETTY_LOGS=$JETTY_BASE/logs
+fi
+if [ -z "$JETTY_LOGS" ] && [ -d $JETTY_HOME/logs ]
+then
+ JETTY_LOGS=$JETTY_HOME/logs
+fi
if [ "$JETTY_LOGS" ]
then
JAVA_OPTIONS+=("-Djetty.logs=$JETTY_LOGS")
@@ -396,21 +344,21 @@
#####################################################
# Add jetty properties to Java VM options.
#####################################################
-JAVA_OPTIONS+=("-Djetty.home=$JETTY_HOME" "-Djava.io.tmpdir=$TMPDIR")
-
-[ -f "$JETTY_HOME/etc/start.config" ] && JAVA_OPTIONS=("-DSTART=$JETTY_HOME/etc/start.config" "${JAVA_OPTIONS[@]}")
+JAVA_OPTIONS+=("-Djetty.home=$JETTY_HOME" "-Djetty.base=$JETTY_BASE" "-Djava.io.tmpdir=$TMPDIR")
#####################################################
# This is how the Jetty server will be started
#####################################################
JETTY_START=$JETTY_HOME/start.jar
-[ ! -f "$JETTY_START" ] && JETTY_START=$JETTY_HOME/lib/start.jar
+START_INI=$JETTY_BASE/start.ini
+if [ ! -f "$START_INI" ]
+then
+ echo "Cannot find a start.ini in your JETTY_BASE directory: $JETTY_BASE" 2>&2
+ exit 1
+fi
-START_INI=$(dirname $JETTY_START)/start.ini
-[ -r "$START_INI" ] || START_INI=""
-
-RUN_ARGS=(${JAVA_OPTIONS[@]} -jar "$JETTY_START" $JETTY_ARGS "${CONFIGS[@]}")
+RUN_ARGS=(${JAVA_OPTIONS[@]} -jar "$JETTY_START" ${JETTY_ARGS[*]})
RUN_CMD=("$JAVA" ${RUN_ARGS[@]})
#####################################################
@@ -419,12 +367,13 @@
#####################################################
if (( DEBUG ))
then
+ echo "START_INI = $START_INI"
echo "JETTY_HOME = $JETTY_HOME"
+ echo "JETTY_BASE = $JETTY_BASE"
echo "JETTY_CONF = $JETTY_CONF"
echo "JETTY_PID = $JETTY_PID"
echo "JETTY_START = $JETTY_START"
- echo "JETTY_ARGS = $JETTY_ARGS"
- echo "CONFIGS = ${CONFIGS[*]}"
+ echo "JETTY_ARGS = ${JETTY_ARGS[*]}"
echo "JAVA_OPTIONS = ${JAVA_OPTIONS[*]}"
echo "JAVA = $JAVA"
echo "RUN_CMD = ${RUN_CMD}"
@@ -438,7 +387,7 @@
echo -n "Starting Jetty: "
if (( NO_START )); then
- echo "Not starting jetty - NO_START=1";
+ echo "Not starting ${NAME} - NO_START=1";
exit
fi
@@ -483,7 +432,7 @@
fi
- if expr "${CONFIGS[*]}" : '.*etc/jetty-started.xml.*' >/dev/null
+ if expr "${JETTY_ARGS[*]}" : '.*jetty-started.xml.*' >/dev/null
then
if started "$JETTY_STATE" "$JETTY_PID"
then
@@ -578,15 +527,15 @@
echo "Checking arguments to Jetty: "
echo "START_INI = $START_INI"
echo "JETTY_HOME = $JETTY_HOME"
+ echo "JETTY_BASE = $JETTY_BASE"
echo "JETTY_CONF = $JETTY_CONF"
echo "JETTY_PID = $JETTY_PID"
echo "JETTY_START = $JETTY_START"
echo "JETTY_LOGS = $JETTY_LOGS"
- echo "CONFIGS = ${CONFIGS[*]}"
echo "CLASSPATH = $CLASSPATH"
echo "JAVA = $JAVA"
echo "JAVA_OPTIONS = ${JAVA_OPTIONS[*]}"
- echo "JETTY_ARGS = $JETTY_ARGS"
+ echo "JETTY_ARGS = ${JETTY_ARGS[*]}"
echo "RUN_CMD = ${RUN_CMD[*]}"
echo
diff --git a/jetty-distribution/src/main/resources/demo-base/etc/keystore b/jetty-distribution/src/main/resources/demo-base/etc/keystore
new file mode 100644
index 0000000..08f6cda
--- /dev/null
+++ b/jetty-distribution/src/main/resources/demo-base/etc/keystore
Binary files differ
diff --git a/jetty-distribution/src/main/resources/demo-base/webapps/README.TXT b/jetty-distribution/src/main/resources/demo-base/webapps/README.TXT
new file mode 100644
index 0000000..d6fb93b
--- /dev/null
+++ b/jetty-distribution/src/main/resources/demo-base/webapps/README.TXT
@@ -0,0 +1,12 @@
+
+This directory is scanned by the demo WebAppDeployer provider
+created in the etc/jetty-demo.xml file and enabled by the
+start.d/900-demo.ini file.
+
+To disable the demo, either remove the start.d/900-demo.ini or issue the following command:
+
+ java -jar start.jar --disable=demo
+
+
+For normal webapp deployment, use the webapps directory.
+
diff --git a/jetty-distribution/src/main/resources/webapps.demo/ROOT/images/jetty-header.jpg b/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/images/jetty-header.jpg
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps.demo/ROOT/images/jetty-header.jpg
rename to jetty-distribution/src/main/resources/demo-base/webapps/ROOT/images/jetty-header.jpg
Binary files differ
diff --git a/jetty-distribution/src/main/resources/webapps.demo/ROOT/images/webtide_logo.jpg b/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/images/webtide_logo.jpg
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps.demo/ROOT/images/webtide_logo.jpg
rename to jetty-distribution/src/main/resources/demo-base/webapps/ROOT/images/webtide_logo.jpg
Binary files differ
diff --git a/jetty-distribution/src/main/resources/webapps.demo/ROOT/index.html b/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/index.html
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps.demo/ROOT/index.html
rename to jetty-distribution/src/main/resources/demo-base/webapps/ROOT/index.html
diff --git a/jetty-distribution/src/main/resources/webapps.demo/ROOT/jetty.css b/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/jetty.css
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps.demo/ROOT/jetty.css
rename to jetty-distribution/src/main/resources/demo-base/webapps/ROOT/jetty.css
diff --git a/jetty-distribution/src/main/resources/webapps.demo/example-moved.xml b/jetty-distribution/src/main/resources/demo-base/webapps/example-moved.xml
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps.demo/example-moved.xml
rename to jetty-distribution/src/main/resources/demo-base/webapps/example-moved.xml
diff --git a/jetty-distribution/src/main/resources/etc/jetty-demo.xml b/jetty-distribution/src/main/resources/etc/jetty-demo.xml
deleted file mode 100644
index 4dc0aae..0000000
--- a/jetty-distribution/src/main/resources/etc/jetty-demo.xml
+++ /dev/null
@@ -1,124 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
-
-<!-- =============================================================== -->
-<!-- Configure the demos -->
-<!-- =============================================================== -->
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-
- <!-- ============================================================= -->
- <!-- Add webapps.demo to deployment manager scans -->
- <!-- ============================================================= -->
- <Ref refid="DeploymentManager">
- <Call id="webappprovider" name="addAppProvider">
- <Arg>
- <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
- <Set name="monitoredDirName"><Property name="jetty.home" default="." />/webapps.demo</Set>
- <Set name="defaultsDescriptor"><Property name="jetty.home" default="." />/etc/webdefault.xml</Set>
- <Set name="scanInterval">1</Set>
- <Set name="extractWars">true</Set>
- <Set name="configurationManager">
- <New class="org.eclipse.jetty.deploy.PropertiesConfigurationManager"/>
- </Set>
- </New>
- </Arg>
- </Call>
- </Ref>
-
-
- <!-- ============================================================= -->
- <!-- Add rewrite rules -->
- <!-- ============================================================= -->
- <Ref refid="Rewrite">
- <!-- Add rule to protect against IE ssl bug -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.MsieSslRule"/>
- </Arg>
- </Call>
-
- <!-- protect favicon handling -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
- <Set name="pattern">/favicon.ico</Set>
- <Set name="name">Cache-Control</Set>
- <Set name="value">Max-Age=3600,public</Set>
- <Set name="terminating">true</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- redirect from the welcome page to a specific page -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
- <Set name="pattern">/test/rewrite/</Set>
- <Set name="replacement">/test/rewrite/info.html</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- replace the entire request URI -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
- <Set name="pattern">/test/some/old/context</Set>
- <Set name="replacement">/test/rewritten/newcontext</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- replace the beginning of the request URI -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
- <Set name="pattern">/test/rewrite/for/*</Set>
- <Set name="replacement">/test/rewritten/</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- reverse the order of the path sections -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.RewriteRegexRule">
- <Set name="regex">(.*?)/reverse/([^/]*)/(.*)</Set>
- <Set name="replacement">$1/reverse/$3/$2</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- add a cookie to each path visited -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.CookiePatternRule">
- <Set name="pattern">/*</Set>
- <Set name="name">visited</Set>
- <Set name="value">yes</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- actual redirect, instead of internal rewrite -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.RedirectPatternRule">
- <Set name="pattern">/test/redirect/*</Set>
- <Set name="location">/test/redirected</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- add a response rule -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.ResponsePatternRule">
- <Set name="pattern">/400Error</Set>
- <Set name="code">400</Set>
- <Set name="reason">ResponsePatternRule Demo</Set>
- </New>
- </Arg>
- </Call>
- </Ref>
-</Configure>
diff --git a/jetty-distribution/src/main/resources/etc/jetty.conf b/jetty-distribution/src/main/resources/etc/jetty.conf
index 2e2e1aa..2061402 100644
--- a/jetty-distribution/src/main/resources/etc/jetty.conf
+++ b/jetty-distribution/src/main/resources/etc/jetty.conf
@@ -8,5 +8,5 @@
# Each line in this file becomes an arguement to start.jar
# in addition to those found in the start.ini file
# =======================================================
-etc/jetty-logging.xml
-etc/jetty-started.xml
+jetty-logging.xml
+jetty-started.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-distribution/src/main/resources/modules/.donotdelete
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-distribution/src/main/resources/modules/.donotdelete
diff --git a/jetty-distribution/src/main/resources/modules/setuid.mod b/jetty-distribution/src/main/resources/modules/setuid.mod
new file mode 100644
index 0000000..64c9e23
--- /dev/null
+++ b/jetty-distribution/src/main/resources/modules/setuid.mod
@@ -0,0 +1,19 @@
+#
+# Set UID Feature
+#
+
+[depend]
+server
+
+[lib]
+lib/setuid/jetty-setuid-java-1.0.1.jar
+
+[xml]
+etc/jetty-setuid.xml
+
+[ini-template]
+## SetUID Configuration
+# jetty.startServerAsPrivileged=false
+# jetty.username=jetty
+# jetty.groupname=jetty
+# jetty.umask=002
diff --git a/jetty-distribution/src/main/resources/start.d/900-demo.ini b/jetty-distribution/src/main/resources/start.d/900-demo.ini
deleted file mode 100644
index ae04b10..0000000
--- a/jetty-distribution/src/main/resources/start.d/900-demo.ini
+++ /dev/null
@@ -1,59 +0,0 @@
-
-# ===========================================================
-# Enable the demonstration web applications
-#
-# To disable the demos, either delete this file, move it out of
-# the start.d directory or rename it to not end with ".ini"
-# ===========================================================
-
-# ===========================================================
-# Enable rewrite handler
-# -----------------------------------------------------------
-OPTIONS=rewrite
-etc/jetty-rewrite.xml
-
-# ===========================================================
-# Add a deploy app provider to scan the webapps.demo directory
-# -----------------------------------------------------------
-etc/jetty-demo.xml
-
-# ===========================================================
-# Enable the Jetty HTTP client APIs for use by demo webapps
-# -----------------------------------------------------------
-OPTIONS=client
-
-# ===========================================================
-# Enable the test-realm login service for use by authentication
-# demonstrations
-# -----------------------------------------------------------
-etc/test-realm.xml
-
-# ===========================================================
-# Enable JAAS test webapp
-# -----------------------------------------------------------
-OPTIONS=jaas
-jaas.login.conf=webapps.demo/test-jaas.d/login.conf
-etc/jetty-jaas.xml
-
-# ===========================================================
-# Enable JNDI test webapp
-# -----------------------------------------------------------
-OPTIONS=jndi,jndi.demo
-
-# ===========================================================
-# Enable additional webapp environment configurators
-# -----------------------------------------------------------
-OPTIONS=plus
-etc/jetty-plus.xml
-
-# ===========================================================
-# Enable servlet 3.1 annotations
-# -----------------------------------------------------------
-OPTIONS=annotations
-etc/jetty-annotations.xml
-
-# ===========================================================
-# Enable https listener
-# -----------------------------------------------------------
-etc/jetty-ssl.xml
-etc/jetty-https.xml
diff --git a/jetty-distribution/src/main/resources/start.d/README.TXT b/jetty-distribution/src/main/resources/start.d/README.TXT
new file mode 100644
index 0000000..4a6256c
--- /dev/null
+++ b/jetty-distribution/src/main/resources/start.d/README.TXT
@@ -0,0 +1,24 @@
+Jetty start.d ini directory
+===========================
+
+This start.d directory contains *.ini files that are appended to the effective command line
+used to start jetty by the command:
+
+ java -jar start.jar
+
+Each line in these files is prepended to the real command line as arguments, and may be either:
+ + A property like: name=value
+ + A module to enable like: --module=jmx
+ + An XML configuration file like: etc/jetty-feature.xml
+ + A start.jar option like: --exec
+
+If --exec or --dry-run are used, then these files may also contain lines with:
+ + A JVM option like: -Xmx2000m
+ + A System Property like: -Dcom.sun.management.jmxremote
+
+
+A template ini file may be created for known modules by using the --ini option.
+For example to create an ini templates for https use the command
+
+ java -jar start.jar --ini=https
+
diff --git a/jetty-distribution/src/main/resources/start.ini b/jetty-distribution/src/main/resources/start.ini
index d44c4b4..59d94be 100644
--- a/jetty-distribution/src/main/resources/start.ini
+++ b/jetty-distribution/src/main/resources/start.ini
@@ -1,244 +1,35 @@
#===========================================================
# Jetty start.jar arguments
#
-# The contents of this file, together with the start.ini
-# fragments found in start.d directory are used to build
+# The contents of this file, together with the *.ini
+# files found in start.d directory are used to build
# the classpath and command line on a call to
# java -jar start.jar [arg...]
#
# Use the following command to see more options
# java -jar start.jar --help
#
-# Each line in this file is prepended to the command line
-# as arguments, which may be either:
+# Each line in these files is prepended to the command line
+# as arguments and may be either:
# + A property like: name=value
-# + A file of properties like: /etc/myjetty.properties
-# + A classpath option like: OPTION=jmx
+# + A module to enable like: --module=jmx
# + An XML configuration file like: etc/jetty-feature.xml
# + A start.jar option like: --dry-run
#
-# If --exec or --exec-print are used, then this file may also
+# If --exec or --dry-run are used, then this file may also
# contain lines with:
# + A JVM option like: -Xmx2000m
# + A System Property like: -Dcom.sun.management.jmxremote
#
-#-----------------------------------------------------------
+# The --add-to-start=module option can be used to append
+# a configuration template for a module to start.ini
+# The --add-to-startd=module option can be used to create
+# a configuration template for a module in start.d/module.ini
+# For example configure and run with SPDY use
#
-# NOTE: The lines in this file may be uncommented to activate
-# features. Alternately, the lines may be copied to a ini file
-# in the start.d directory to enabled configuration without
-# editing this file. See start.d/900-demo.ini for an example.
+# java -jar start.jar --add-to-startd=spdy
+# $EDITOR start.d/spdy.ini
+# java -jar start.jar
#
-# Future releases will switch start.d style configuration for
-# all features.
#===========================================================
-
-
-#===========================================================
-# Configure JVM arguments.
-# If JVM args are include in an ini file then --exec is needed
-# to start a new JVM from start.jar with the extra args.
-# If you wish to avoid an extra JVM running, place JVM args
-# on the normal command line and do not use --exec
-#-----------------------------------------------------------
-# --exec
-# -Xmx2000m
-# -Xmn512m
-# -XX:+UseConcMarkSweepGC
-# -XX:ParallelCMSThreads=2
-# -XX:+CMSClassUnloadingEnabled
-# -XX:+UseCMSCompactAtFullCollection
-# -XX:CMSInitiatingOccupancyFraction=80
-# -verbose:gc
-# -XX:+PrintGCDateStamps
-# -XX:+PrintGCTimeStamps
-# -XX:+PrintGCDetails
-# -XX:+PrintTenuringDistribution
-# -XX:+PrintCommandLineFlags
-# -XX:+DisableExplicitGC
-
-# -Dorg.apache.jasper.compiler.disablejsr199=true
-
-
-
-#===========================================================
-# Default Server Options
-# Use the core server jars with websocket on the classpath
-# Add the contents of the resources directory to the classpath
-# Add jars discovered in lib/ext to the classpath
-# Include the core jetty configuration file
-#-----------------------------------------------------------
-OPTIONS=Server,websocket,resources,ext
-threads.min=10
-threads.max=200
-threads.timeout=60000
-#jetty.host=myhost.com
-jetty.dump.start=false
-jetty.dump.stop=false
-
-etc/jetty.xml
-
-#===========================================================
-# JMX Management
-# To enable remote JMX access uncomment jmxremote and
-# enable --exec
-#-----------------------------------------------------------
-OPTIONS=jmx
-# jetty.jmxrmihost=localhost
-# jetty.jmxrmiport=1099
-# -Dcom.sun.management.jmxremote
-etc/jetty-jmx.xml
-
-#===========================================================
-# Java Server Pages
-#-----------------------------------------------------------
-OPTIONS=jsp
-
-#===========================================================
-# Request logger
-# Will add a handler to log all HTTP requests to a standard
-# request log format file.
-#-----------------------------------------------------------
-# requestlog.retain=90
-# requestlog.append=true
-# requestlog.extended=true
-# etc/jetty-requestlog.xml
-
-
-#===========================================================
-# stderr/stdout logging.
-# The following configuration will redirect stderr and stdout
-# to file which is rolled over daily.
-#-----------------------------------------------------------
-# jetty.log.retain=90
-# etc/jetty-logging.xml
-
-
-#===========================================================
-# Enable SetUID
-# The default user and group is 'jetty' and if you are
-# starting as root you must change the run privledged to true
-#-----------------------------------------------------------
-# OPTIONS=setuid
-# jetty.startServerAsPrivileged=false
-# jetty.username=jetty
-# jetty.groupname=jetty
-# jetty.umask=002
-# etc/jetty-setuid.xml
-
-
-#===========================================================
-# HTTP Connector
-#-----------------------------------------------------------
-jetty.port=8080
-http.timeout=30000
-etc/jetty-http.xml
-
-
-#===========================================================
-# SSL Context
-# Create the keystore and trust store for use by
-# HTTPS and SPDY
-#-----------------------------------------------------------
-# jetty.keystore=etc/keystore
-# jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
-# jetty.keymanager.password=OBF:1u2u1wml1z7s1z7a1wnl1u2g
-# jetty.truststore=etc/keystore
-# jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
-# jetty.secure.port=8443
-# etc/jetty-ssl.xml
-
-
-#===========================================================
-# HTTPS Connector
-# Must be used with jetty-ssl.xml
-#-----------------------------------------------------------
-# jetty.https.port=8443
-# etc/jetty-https.xml
-
-
-#===========================================================
-# NPN Next Protocol Negotiation
-#
-# The SPDY and HTTP/2.0 connectors require NPN. The jar for
-# NPN cannot be downloaded from eclipse. So the --download
-# option is used to install the NPN jar if it does not already
-# exist. Uncomment the lines appropriate to the version of the
-# jvm you are using.
-#
-#-----------------------------------------------------------
-# --exec
-#
-# Uncomment the following 2 lines if using jvm < 1.7.0_40
-# --download=http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar:lib/npn/npn-boot-1.1.5.v20130313.jar
-# -Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
-#
-# Uncomment the following 2 lines if using jvm >= 1.7.0_40
-# --download=http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar:lib/npn/npn-boot-1.1.6.v20130911.jar
-# -Xbootclasspath/p:lib/npn/npn-boot-1.1.6.v20130911.jar
-
-
-#===========================================================
-# SPDY Connector
-# Requires SSL Context and NPN from above
-#-----------------------------------------------------------
-# OPTIONS=spdy
-# jetty.spdy.port=8443
-# etc/jetty-spdy.xml
-
-
-#===========================================================
-# Webapplication Deployer
-#-----------------------------------------------------------
-etc/jetty-deploy.xml
-
-
-# ===========================================================
-# Enable JAAS
-# -----------------------------------------------------------
-# OPTIONS=jaas
-# jaas.login.conf=etc/login.conf
-# etc/jetty-jaas.xml
-
-# ===========================================================
-# Enable JNDI
-# -----------------------------------------------------------
-# OPTIONS=jndi
-
-# ===========================================================
-# Enable additional webapp environment configurators
-# -----------------------------------------------------------
-# OPTIONS=plus
-# etc/jetty-plus.xml
-
-# ===========================================================
-# Enable servlet 3.1 annotations
-# -----------------------------------------------------------
-# OPTIONS=annotations
-# etc/jetty-annotations.xml
-
-#===========================================================
-# Other server features
-#-----------------------------------------------------------
-# etc/jetty-debug.xml
-# etc/jetty-ipaccess.xml
-# etc/jetty-stats.xml
-
-
-#===========================================================
-# Low resource managment
-#-----------------------------------------------------------
-# lowresources.period=1050
-# lowresources.lowResourcesIdleTimeout=200
-# lowresources.monitorThreads=true
-# lowresources.maxConnections=0
-# lowresources.maxMemory=0
-# lowresources.maxLowResourcesTime=5000
-# etc/jetty-lowresources.xml
-
-
-#===========================================================
-# The start.d directory contains the active start.ini fragments
-start.d/
-
diff --git a/jetty-distribution/src/main/resources/webapps.demo/README.TXT b/jetty-distribution/src/main/resources/webapps.demo/README.TXT
deleted file mode 100644
index ec2bea2..0000000
--- a/jetty-distribution/src/main/resources/webapps.demo/README.TXT
+++ /dev/null
@@ -1,7 +0,0 @@
-
-This directory is scanned by the demo WebAppDeployer provider
-created in the etc/jetty-demo.xml file and enabled by the
-start.d/900-demo.ini file.
-
-For normal deployment, use the webapps directory.
-
diff --git a/jetty-distribution/src/main/resources/webapps.demo/javadoc.xml b/jetty-distribution/src/main/resources/webapps.demo/javadoc.xml
deleted file mode 100644
index df854d2..0000000
--- a/jetty-distribution/src/main/resources/webapps.demo/javadoc.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.eclipse.org/configure.dtd">
-
-<!--
-Configure a custom context for serving javadoc as static resources
--->
-
-<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
- <Set name="contextPath">/javadoc</Set>
- <Set name="resourceBase"><SystemProperty name="jetty.home" default="."/>/javadoc/</Set>
- <Set name="handler">
- <New class="org.eclipse.jetty.server.handler.ResourceHandler">
- <Set name="welcomeFiles">
- <Array type="String">
- <Item>index.html</Item>
- </Array>
- </Set>
- <Set name="cacheControl">max-age=3600,public</Set>
- </New>
- </Set>
-</Configure>
-
diff --git a/jetty-distribution/src/main/resources/webapps/README.TXT b/jetty-distribution/src/main/resources/webapps/README.TXT
index dc2b389..170137a 100644
--- a/jetty-distribution/src/main/resources/webapps/README.TXT
+++ b/jetty-distribution/src/main/resources/webapps/README.TXT
@@ -23,3 +23,11 @@
This directory is scanned for additions, removals and updates
for hot deployment.
+To configure the deployment mechanism, edit the files:
+ start.d/500-deploy.ini
+ etc/jetty-deploy.ini
+
+To disable the auto deploy mechanism use the command:
+
+ java -jar start.jar --disable=deploy
+
diff --git a/jetty-http-spi/pom.xml b/jetty-http-spi/pom.xml
index 6ac12da..08658a2 100644
--- a/jetty-http-spi/pom.xml
+++ b/jetty-http-spi/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-http-spi</artifactId>
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java
index 1a1ce7b..3073568 100644
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java
@@ -64,27 +64,6 @@
_executor.execute(job);
}
-
- /* ------------------------------------------------------------ */
- @Override
- public boolean dispatch(Runnable job)
- {
- final Executor executor=_executor;
- if (executor instanceof ThreadPool)
- return ((ThreadPool)executor).dispatch(job);
-
- try
- {
- _executor.execute(job);
- return true;
- }
- catch(RejectedExecutionException e)
- {
- LOG.warn(e);
- return false;
- }
- }
-
/* ------------------------------------------------------------ */
@Override
public int getIdleThreads()
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java
index d9c1aea..8787626 100644
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java
@@ -32,7 +32,6 @@
import com.sun.net.httpserver.Authenticator;
import com.sun.net.httpserver.Authenticator.Result;
-import com.sun.net.httpserver.BasicAuthenticator;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java
index 22e6b3d..b702f93 100644
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java
Binary files differ
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java
index 410ef2a..be046ba 100644
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java
@@ -23,10 +23,9 @@
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.handler.ContextHandlerCollection;
-import org.eclipse.jetty.util.thread.ExecutorThreadPool;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
diff --git a/jetty-http/pom.xml b/jetty-http/pom.xml
index d47b296..2aca909 100644
--- a/jetty-http/pom.xml
+++ b/jetty-http/pom.xml
@@ -3,7 +3,7 @@
<parent>
<artifactId>jetty-project</artifactId>
<groupId>org.eclipse.jetty</groupId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-http</artifactId>
@@ -37,7 +37,7 @@
</goals>
<configuration>
<instructions>
- <Import-Package>javax.servlet.*;version="2.6.0",javax.net.*,*</Import-Package>
+ <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",javax.net.*,*</Import-Package>
</instructions>
</configuration>
</execution>
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java
new file mode 100644
index 0000000..93017f3
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java
@@ -0,0 +1,164 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.StringUtil;
+
+/**
+ * ThreadLocal Date formatters for HTTP style dates.
+ *
+ */
+public class DateGenerator
+{
+ private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
+ static
+ {
+ __GMT.setID("GMT");
+ }
+
+ static final String[] DAYS =
+ { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+ static final String[] MONTHS =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
+
+
+ private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
+ {
+ @Override
+ protected DateGenerator initialValue()
+ {
+ return new DateGenerator();
+ }
+ };
+
+
+ public final static String __01Jan1970=DateGenerator.formatDate(0);
+
+ /**
+ * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
+ */
+ public static String formatDate(long date)
+ {
+ return __dateGenerator.get().doFormatDate(date);
+ }
+
+ /**
+ * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+ */
+ public static void formatCookieDate(StringBuilder buf, long date)
+ {
+ __dateGenerator.get().doFormatCookieDate(buf,date);
+ }
+
+ /**
+ * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+ */
+ public static String formatCookieDate(long date)
+ {
+ StringBuilder buf = new StringBuilder(28);
+ formatCookieDate(buf, date);
+ return buf.toString();
+ }
+
+ private final StringBuilder buf = new StringBuilder(32);
+ private final GregorianCalendar gc = new GregorianCalendar(__GMT);
+
+ /**
+ * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
+ */
+ public String doFormatDate(long date)
+ {
+ buf.setLength(0);
+ gc.setTimeInMillis(date);
+
+ int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+ int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+ int month = gc.get(Calendar.MONTH);
+ int year = gc.get(Calendar.YEAR);
+ int century = year / 100;
+ year = year % 100;
+
+ int hours = gc.get(Calendar.HOUR_OF_DAY);
+ int minutes = gc.get(Calendar.MINUTE);
+ int seconds = gc.get(Calendar.SECOND);
+
+ buf.append(DAYS[day_of_week]);
+ buf.append(',');
+ buf.append(' ');
+ StringUtil.append2digits(buf, day_of_month);
+
+ buf.append(' ');
+ buf.append(MONTHS[month]);
+ buf.append(' ');
+ StringUtil.append2digits(buf, century);
+ StringUtil.append2digits(buf, year);
+
+ buf.append(' ');
+ StringUtil.append2digits(buf, hours);
+ buf.append(':');
+ StringUtil.append2digits(buf, minutes);
+ buf.append(':');
+ StringUtil.append2digits(buf, seconds);
+ buf.append(" GMT");
+ return buf.toString();
+ }
+
+ /**
+ * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
+ */
+ public void doFormatCookieDate(StringBuilder buf, long date)
+ {
+ gc.setTimeInMillis(date);
+
+ int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+ int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+ int month = gc.get(Calendar.MONTH);
+ int year = gc.get(Calendar.YEAR);
+ year = year % 10000;
+
+ int epoch = (int) ((date / 1000) % (60 * 60 * 24));
+ int seconds = epoch % 60;
+ epoch = epoch / 60;
+ int minutes = epoch % 60;
+ int hours = epoch / 60;
+
+ buf.append(DAYS[day_of_week]);
+ buf.append(',');
+ buf.append(' ');
+ StringUtil.append2digits(buf, day_of_month);
+
+ buf.append('-');
+ buf.append(MONTHS[month]);
+ buf.append('-');
+ StringUtil.append2digits(buf, year/100);
+ StringUtil.append2digits(buf, year%100);
+
+ buf.append(' ');
+ StringUtil.append2digits(buf, hours);
+ buf.append(':');
+ StringUtil.append2digits(buf, minutes);
+ buf.append(':');
+ StringUtil.append2digits(buf, seconds);
+ buf.append(" GMT");
+ }
+}
\ No newline at end of file
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java
new file mode 100644
index 0000000..7e642ef
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java
@@ -0,0 +1,109 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * ThreadLocal data parsers for HTTP style dates
+ *
+ */
+class DateParser
+{
+ private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
+ static
+ {
+ __GMT.setID("GMT");
+ }
+
+ final static String __dateReceiveFmt[] =
+ {
+ "EEE, dd MMM yyyy HH:mm:ss zzz",
+ "EEE, dd-MMM-yy HH:mm:ss",
+ "EEE MMM dd HH:mm:ss yyyy",
+
+ "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz",
+ "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss",
+ "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz",
+ "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz",
+ "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",
+ "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz",
+ "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
+ };
+
+ public static long parseDate(String date)
+ {
+ return __dateParser.get().parse(date);
+ }
+
+ private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
+ {
+ @Override
+ protected DateParser initialValue()
+ {
+ return new DateParser();
+ }
+ };
+
+ final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
+
+ private long parse(final String dateVal)
+ {
+ for (int i = 0; i < _dateReceive.length; i++)
+ {
+ if (_dateReceive[i] == null)
+ {
+ _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
+ _dateReceive[i].setTimeZone(__GMT);
+ }
+
+ try
+ {
+ Date date = (Date) _dateReceive[i].parseObject(dateVal);
+ return date.getTime();
+ }
+ catch (java.lang.Exception e)
+ {
+ // LOG.ignore(e);
+ }
+ }
+
+ if (dateVal.endsWith(" GMT"))
+ {
+ final String val = dateVal.substring(0, dateVal.length() - 4);
+
+ for (SimpleDateFormat element : _dateReceive)
+ {
+ try
+ {
+ Date date = (Date) element.parseObject(val);
+ return date.getTime();
+ }
+ catch (java.lang.Exception e)
+ {
+ // LOG.ignore(e);
+ }
+ }
+ }
+ return -1;
+ }
+}
\ No newline at end of file
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java
index 61775e5..5a4cbf7 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java
@@ -23,6 +23,7 @@
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
+import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.resource.Resource;
/* ------------------------------------------------------------ */
@@ -98,7 +99,16 @@
@Override
public ByteBuffer getDirectBuffer()
{
- return null;
+ if (_resource.length()<=0 || _maxBuffer<_resource.length())
+ return null;
+ try
+ {
+ return BufferUtil.toBuffer(_resource,true);
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException(e);
+ }
}
/* ------------------------------------------------------------ */
@@ -114,24 +124,9 @@
{
if (_resource.length()<=0 || _maxBuffer<_resource.length())
return null;
- int length=(int)_resource.length();
- byte[] array = new byte[length];
-
- int offset=0;
- try (InputStream in=_resource.getInputStream())
+ try
{
- do
- {
- int filled=in.read(array,offset,length);
- if (filled<0)
- break;
- length-=filled;
- offset+=filled;
- }
- while(length>0);
-
- ByteBuffer buffer = ByteBuffer.wrap(array);
- return buffer;
+ return BufferUtil.toBuffer(_resource,false);
}
catch(IOException e)
{
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java
index 81af822..1394091 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java
@@ -18,92 +18,12 @@
package org.eclipse.jetty.http;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.eclipse.jetty.util.ArrayTrie;
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.Trie;
-
/* ------------------------------------------------------------ */
/** A HTTP Field
*/
public class HttpField
{
- /**
- * Cache of common {@link HttpField}s including: <UL>
- * <LI>Common static combinations such as:<UL>
- * <li>Connection: close
- * <li>Accept-Encoding: gzip
- * <li>Content-Length: 0
- * </ul>
- * <li>Combinations of Content-Type header for common mime types by common charsets
- * <li>Most common headers with null values so that a lookup will at least
- * determine the header name even if the name:value combination is not cached
- * </ul>
- */
- public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
- public final static Trie<HttpField> CONTENT_TYPE = new ArrayTrie<>(512);
-
- static
- {
- CACHE.put(new CachedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE));
- CACHE.put(new CachedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE));
- CACHE.put(new CachedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE));
- CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_ENCODING,"gzip"));
- CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate"));
- CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch"));
- CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5"));
- CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6"));
- CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
- CACHE.put(new CachedHttpField(HttpHeader.ACCEPT,"*/*"));
- CACHE.put(new CachedHttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5"));
- CACHE.put(new CachedHttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
- CACHE.put(new CachedHttpField(HttpHeader.PRAGMA,"no-cache"));
- CACHE.put(new CachedHttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
- CACHE.put(new CachedHttpField(HttpHeader.CACHE_CONTROL,"no-cache"));
- CACHE.put(new CachedHttpField(HttpHeader.CONTENT_LENGTH,"0"));
- CACHE.put(new CachedHttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
- CACHE.put(new CachedHttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
- CACHE.put(new CachedHttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
- CACHE.put(new CachedHttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
-
- // Content types
- for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/x-www-form-urlencoded"})
- {
- HttpField field=new CachedHttpField(HttpHeader.CONTENT_TYPE,type);
- CACHE.put(field);
- CONTENT_TYPE.put(type,field);
-
- for (String charset : new String[]{"UTF-8","ISO-8859-1"})
- {
- String type_charset=type+"; charset="+charset;
- field=new CachedHttpField(HttpHeader.CONTENT_TYPE,type_charset);
- CACHE.put(field);
- CACHE.put(new CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
- CONTENT_TYPE.put(type_charset,field);
- CONTENT_TYPE.put(type+";charset="+charset,field);
- }
- }
-
- // Add headers with null values so HttpParser can avoid looking up name again for unknown values
- for (HttpHeader h:HttpHeader.values())
- if (!CACHE.put(new HttpField(h,(String)null)))
- throw new IllegalStateException("CACHE FULL");
- // Add some more common headers
- CACHE.put(new HttpField(HttpHeader.REFERER,(String)null));
- CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null));
- CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null));
- CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
- CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
- }
-
- private final static byte[] __colon_space = new byte[] {':',' '};
-
private final HttpHeader _header;
private final String _name;
private final String _value;
@@ -119,7 +39,6 @@
{
this(header,header.asString(),value);
}
-
public HttpField(HttpHeader header, HttpHeaderValue value)
{
@@ -145,89 +64,7 @@
{
return _value;
}
-
- public boolean contains(String value)
- {
- if (_value==null)
- return false;
-
- if (value.equalsIgnoreCase(_value))
- return true;
-
- String[] split = _value.split("\\s*,\\s*");
- for (String s : split)
- {
- if (value.equalsIgnoreCase(s))
- return true;
- }
-
- return false;
- }
-
-
- public int getIntValue()
- {
- return StringUtil.toInt(_value);
- }
-
- public long getLongValue()
- {
- return StringUtil.toLong(_value);
- }
- private static byte[] toSanitisedName(String s)
- {
- byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1);
- for (int i=bytes.length;i-->0;)
- {
- switch(bytes[i])
- {
- case '\r':
- case '\n':
- case ':' :
- bytes[i]=(byte)'?';
- }
- }
- return bytes;
- }
-
- private static byte[] toSanitisedValue(String s)
- {
- byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1);
- for (int i=bytes.length;i-->0;)
- {
- switch(bytes[i])
- {
- case '\r':
- case '\n':
- bytes[i]=(byte)'?';
- }
- }
- return bytes;
- }
-
- public void putTo(ByteBuffer bufferInFillMode)
- {
- if (_header!=null)
- {
- bufferInFillMode.put(_header.getBytesColonSpace());
- bufferInFillMode.put(toSanitisedValue(_value));
- }
- else
- {
- bufferInFillMode.put(toSanitisedName(_name));
- bufferInFillMode.put(__colon_space);
- bufferInFillMode.put(toSanitisedValue(_value));
- }
-
- BufferUtil.putCRLF(bufferInFillMode);
- }
-
- public void putValueTo(ByteBuffer buffer)
- {
- buffer.put(toSanitisedValue(_value));
- }
-
@Override
public String toString()
{
@@ -249,31 +86,4 @@
}
- /* ------------------------------------------------------------ */
- /** A HTTP Field optimised to be reused.
- */
- public static class CachedHttpField extends HttpField
- {
- final byte[] _bytes;
- public CachedHttpField(HttpHeader header, String value)
- {
- super(header,value);
- _bytes=new byte[header.asString().length()+2+value.length()+2];
- System.arraycopy(header.getBytesColonSpace(),0,_bytes,0,header.asString().length()+2);
- System.arraycopy(toSanitisedValue(value),0,_bytes,header.asString().length()+2,value.length());
- _bytes[_bytes.length-2]='\r';
- _bytes[_bytes.length-1]='\n';
- }
-
- CachedHttpField(HttpHeader header, HttpHeaderValue value)
- {
- this(header,value.asString());
- }
-
- @Override
- public void putTo(ByteBuffer bufferInFillMode)
- {
- bufferInFillMode.put(_bytes);
- }
- }
}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
index 58f9557..8274396 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
@@ -18,36 +18,23 @@
package org.eclipse.jetty.http;
-import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted;
-import static org.eclipse.jetty.util.QuotedStringTokenizer.quoteOnly;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
-import java.util.Date;
import java.util.Enumeration;
-import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
-import java.util.TimeZone;
+import java.util.regex.Pattern;
import org.eclipse.jetty.util.ArrayTernaryTrie;
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.DateCache;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.QuotedStringTokenizer;
-import org.eclipse.jetty.util.StringMap;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.log.Log;
@@ -57,7 +44,7 @@
/**
* HTTP Fields. A collection of HTTP header and or Trailer fields.
*
- * <p>This class is not synchronised as it is expected that modifications will only be performed by a
+ * <p>This class is not synchronized as it is expected that modifications will only be performed by a
* single thread.
*
* <p>The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
@@ -66,222 +53,9 @@
public class HttpFields implements Iterable<HttpField>
{
private static final Logger LOG = Log.getLogger(HttpFields.class);
- public static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
- public static final DateCache __dateCache = new DateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
-
- public static final String __COOKIE_DELIM="\",;\\ \t";
-
- static
- {
- __GMT.setID("GMT");
- __dateCache.setTimeZone(__GMT);
- }
-
+ private final static Pattern __splitter = Pattern.compile("\\s*,\\s*");
public final static String __separators = ", \t";
- private static final String[] DAYS =
- { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
- private static final String[] MONTHS =
- { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
-
- public static class DateGenerator
- {
- private final StringBuilder buf = new StringBuilder(32);
- private final GregorianCalendar gc = new GregorianCalendar(__GMT);
-
- /**
- * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
- */
- public String formatDate(long date)
- {
- buf.setLength(0);
- gc.setTimeInMillis(date);
-
- int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
- int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
- int month = gc.get(Calendar.MONTH);
- int year = gc.get(Calendar.YEAR);
- int century = year / 100;
- year = year % 100;
-
- int hours = gc.get(Calendar.HOUR_OF_DAY);
- int minutes = gc.get(Calendar.MINUTE);
- int seconds = gc.get(Calendar.SECOND);
-
- buf.append(DAYS[day_of_week]);
- buf.append(',');
- buf.append(' ');
- StringUtil.append2digits(buf, day_of_month);
-
- buf.append(' ');
- buf.append(MONTHS[month]);
- buf.append(' ');
- StringUtil.append2digits(buf, century);
- StringUtil.append2digits(buf, year);
-
- buf.append(' ');
- StringUtil.append2digits(buf, hours);
- buf.append(':');
- StringUtil.append2digits(buf, minutes);
- buf.append(':');
- StringUtil.append2digits(buf, seconds);
- buf.append(" GMT");
- return buf.toString();
- }
-
- /**
- * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
- */
- public void formatCookieDate(StringBuilder buf, long date)
- {
- gc.setTimeInMillis(date);
-
- int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
- int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
- int month = gc.get(Calendar.MONTH);
- int year = gc.get(Calendar.YEAR);
- year = year % 10000;
-
- int epoch = (int) ((date / 1000) % (60 * 60 * 24));
- int seconds = epoch % 60;
- epoch = epoch / 60;
- int minutes = epoch % 60;
- int hours = epoch / 60;
-
- buf.append(DAYS[day_of_week]);
- buf.append(',');
- buf.append(' ');
- StringUtil.append2digits(buf, day_of_month);
-
- buf.append('-');
- buf.append(MONTHS[month]);
- buf.append('-');
- StringUtil.append2digits(buf, year/100);
- StringUtil.append2digits(buf, year%100);
-
- buf.append(' ');
- StringUtil.append2digits(buf, hours);
- buf.append(':');
- StringUtil.append2digits(buf, minutes);
- buf.append(':');
- StringUtil.append2digits(buf, seconds);
- buf.append(" GMT");
- }
- }
-
- private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
- {
- @Override
- protected DateGenerator initialValue()
- {
- return new DateGenerator();
- }
- };
-
- /**
- * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
- */
- public static String formatDate(long date)
- {
- return __dateGenerator.get().formatDate(date);
- }
-
- /**
- * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
- */
- public static void formatCookieDate(StringBuilder buf, long date)
- {
- __dateGenerator.get().formatCookieDate(buf,date);
- }
-
- /**
- * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
- */
- public static String formatCookieDate(long date)
- {
- StringBuilder buf = new StringBuilder(28);
- formatCookieDate(buf, date);
- return buf.toString();
- }
-
- private final static String __dateReceiveFmt[] =
- {
- "EEE, dd MMM yyyy HH:mm:ss zzz",
- "EEE, dd-MMM-yy HH:mm:ss",
- "EEE MMM dd HH:mm:ss yyyy",
-
- "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz",
- "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss",
- "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz",
- "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz",
- "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",
- "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz",
- "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
- };
-
- private static class DateParser
- {
- final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
-
- long parse(final String dateVal)
- {
- for (int i = 0; i < _dateReceive.length; i++)
- {
- if (_dateReceive[i] == null)
- {
- _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
- _dateReceive[i].setTimeZone(__GMT);
- }
-
- try
- {
- Date date = (Date) _dateReceive[i].parseObject(dateVal);
- return date.getTime();
- }
- catch (java.lang.Exception e)
- {
- // LOG.ignore(e);
- }
- }
-
- if (dateVal.endsWith(" GMT"))
- {
- final String val = dateVal.substring(0, dateVal.length() - 4);
-
- for (SimpleDateFormat element : _dateReceive)
- {
- try
- {
- Date date = (Date) element.parseObject(val);
- return date.getTime();
- }
- catch (java.lang.Exception e)
- {
- // LOG.ignore(e);
- }
- }
- }
- return -1;
- }
- }
-
- public static long parseDate(String date)
- {
- return __dateParser.get().parse(date);
- }
-
- private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
- {
- @Override
- protected DateParser initialValue()
- {
- return new DateParser();
- }
- };
-
- public final static String __01Jan1970=formatDate(0);
- public final static ByteBuffer __01Jan1970_BUFFER=BufferUtil.toBuffer(__01Jan1970);
- public final static String __01Jan1970_COOKIE = formatCookieDate(0).trim();
private final ArrayList<HttpField> _fields = new ArrayList<>(20);
/**
@@ -291,7 +65,6 @@
{
}
-
/**
* Get Collection of header names.
*/
@@ -357,13 +130,13 @@
}
return null;
}
-
+
public boolean contains(HttpHeader header, String value)
{
for (int i=0;i<_fields.size();i++)
{
HttpField f=_fields.get(i);
- if (f.getHeader()==header && f.contains(value))
+ if (f.getHeader()==header && contains(f,value))
return true;
}
return false;
@@ -374,12 +147,31 @@
for (int i=0;i<_fields.size();i++)
{
HttpField f=_fields.get(i);
- if (f.getName().equalsIgnoreCase(name) && f.contains(value))
+ if (f.getName().equalsIgnoreCase(name) && contains(f,value))
return true;
}
return false;
}
+ private boolean contains(HttpField field,String value)
+ {
+ String v = field.getValue();
+ if (v==null)
+ return false;
+
+ if (value.equalsIgnoreCase(v))
+ return true;
+
+ String[] split = __splitter.split(v);
+ for (int i = 0; split!=null && i < split.length; i++)
+ {
+ if (value.equals(split[i]))
+ return true;
+ }
+
+ return false;
+ }
+
public boolean containsKey(String name)
{
for (int i=0;i<_fields.size();i++)
@@ -417,14 +209,13 @@
return field==null?null:field.getValue();
}
-
/**
* Get multi headers
*
- * @return Enumeration of the values, or null if no such header.
+ * @return List the values
* @param name the case-insensitive field name
*/
- public Collection<String> getValuesCollection(String name)
+ public List<String> getValuesList(String name)
{
final List<String> list = new ArrayList<>();
for (HttpField f : _fields)
@@ -488,7 +279,6 @@
List<String> empty=Collections.emptyList();
return Collections.enumeration(empty);
-
}
/**
@@ -649,14 +439,15 @@
*
* @param name the field to remove
*/
- public void remove(HttpHeader name)
+ public HttpField remove(HttpHeader name)
{
for (int i=_fields.size();i-->0;)
{
HttpField f=_fields.get(i);
if (f.getHeader()==name)
- _fields.remove(i);
+ return _fields.remove(i);
}
+ return null;
}
/**
@@ -664,14 +455,15 @@
*
* @param name the field to remove
*/
- public void remove(String name)
+ public HttpField remove(String name)
{
for (int i=_fields.size();i-->0;)
{
HttpField f=_fields.get(i);
if (f.getName().equalsIgnoreCase(name))
- _fields.remove(i);
+ return _fields.remove(i);
}
+ return null;
}
/**
@@ -684,7 +476,7 @@
public long getLongField(String name) throws NumberFormatException
{
HttpField field = getField(name);
- return field==null?-1L:field.getLongValue();
+ return field==null?-1L:StringUtil.toLong(field.getValue());
}
/**
@@ -703,7 +495,7 @@
if (val == null)
return -1;
- final long date = __dateParser.get().parse(val);
+ final long date = DateParser.parseDate(val);
if (date==-1)
throw new IllegalArgumentException("Cannot convert date: " + val);
return date;
@@ -743,7 +535,7 @@
*/
public void putDateField(HttpHeader name, long date)
{
- String d=formatDate(date);
+ String d=DateGenerator.formatDate(date);
put(name, d);
}
@@ -755,7 +547,7 @@
*/
public void putDateField(String name, long date)
{
- String d=formatDate(date);
+ String d=DateGenerator.formatDate(date);
put(name, d);
}
@@ -767,170 +559,10 @@
*/
public void addDateField(String name, long date)
{
- String d=formatDate(date);
+ String d=DateGenerator.formatDate(date);
add(name,d);
}
- /**
- * Format a set cookie value
- *
- * @param cookie The cookie.
- */
- public void addSetCookie(HttpCookie cookie)
- {
- addSetCookie(
- cookie.getName(),
- cookie.getValue(),
- cookie.getDomain(),
- cookie.getPath(),
- cookie.getMaxAge(),
- cookie.getComment(),
- cookie.isSecure(),
- cookie.isHttpOnly(),
- cookie.getVersion());
- }
-
- /**
- * Format a set cookie value
- *
- * @param name the name
- * @param value the value
- * @param domain the domain
- * @param path the path
- * @param maxAge the maximum age
- * @param comment the comment (only present on versions > 0)
- * @param isSecure true if secure cookie
- * @param isHttpOnly true if for http only
- * @param version version of cookie logic to use (0 == default behavior)
- */
- public void addSetCookie(
- final String name,
- final String value,
- final String domain,
- final String path,
- final long maxAge,
- final String comment,
- final boolean isSecure,
- final boolean isHttpOnly,
- int version)
- {
- // Check arguments
- if (name == null || name.length() == 0)
- throw new IllegalArgumentException("Bad cookie name");
-
- // Format value and params
- StringBuilder buf = new StringBuilder(128);
-
- // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
- boolean quote_name=isQuoteNeededForCookie(name);
- quoteOnlyOrAppend(buf,name,quote_name);
-
- buf.append('=');
-
- // Remember name= part to look for other matching set-cookie
- String name_equals=buf.toString();
-
- // Append the value
- boolean quote_value=isQuoteNeededForCookie(value);
- quoteOnlyOrAppend(buf,value,quote_value);
-
- // Look for domain and path fields and check if they need to be quoted
- boolean has_domain = domain!=null && domain.length()>0;
- boolean quote_domain = has_domain && isQuoteNeededForCookie(domain);
- boolean has_path = path!=null && path.length()>0;
- boolean quote_path = has_path && isQuoteNeededForCookie(path);
-
- // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
- if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path || isQuoted(name) || isQuoted(value) || isQuoted(path) || isQuoted(domain)))
- version=1;
-
- // Append version
- if (version==1)
- buf.append (";Version=1");
- else if (version>1)
- buf.append (";Version=").append(version);
-
- // Append path
- if (has_path)
- {
- buf.append(";Path=");
- quoteOnlyOrAppend(buf,path,quote_path);
- }
-
- // Append domain
- if (has_domain)
- {
- buf.append(";Domain=");
- quoteOnlyOrAppend(buf,domain,quote_domain);
- }
-
- // Handle max-age and/or expires
- if (maxAge >= 0)
- {
- // Always use expires
- // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
- buf.append(";Expires=");
- if (maxAge == 0)
- buf.append(__01Jan1970_COOKIE);
- else
- formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
-
- // for v1 cookies, also send max-age
- if (version>=1)
- {
- buf.append(";Max-Age=");
- buf.append(maxAge);
- }
- }
-
- // add the other fields
- if (isSecure)
- buf.append(";Secure");
- if (isHttpOnly)
- buf.append(";HttpOnly");
- if (comment != null)
- {
- buf.append(";Comment=");
- quoteOnlyOrAppend(buf,comment,isQuoteNeededForCookie(comment));
- }
-
- // remove any existing set-cookie fields of same name
- Iterator<HttpField> i=_fields.iterator();
- while (i.hasNext())
- {
- HttpField field=i.next();
- if (field.getHeader()==HttpHeader.SET_COOKIE)
- {
- String val = field.getValue();
- if (val!=null && val.startsWith(name_equals))
- {
- //existing cookie has same name, does it also match domain and path?
- if (((!has_domain && !val.contains("Domain")) || (has_domain && val.contains(domain))) &&
- ((!has_path && !val.contains("Path")) || (has_path && val.contains(path))))
- {
- i.remove();
- }
- }
- }
- }
-
- // add the set cookie
- add(HttpHeader.SET_COOKIE.toString(), buf.toString());
-
- // Expire responses with set-cookie headers so they do not get cached.
- put(HttpHeader.EXPIRES.toString(), __01Jan1970);
- }
-
- public void putTo(ByteBuffer bufferInFillMode)
- {
- for (HttpField field : _fields)
- {
- if (field != null)
- field.putTo(bufferInFillMode);
- }
- BufferUtil.putCRLF(bufferInFillMode);
- }
-
@Override
public String
toString()
@@ -1149,39 +781,4 @@
- /* ------------------------------------------------------------ */
- /** Does a cookie value need to be quoted?
- * @param s value string
- * @return true if quoted;
- * @throws IllegalArgumentException If there a control characters in the string
- */
- public static boolean isQuoteNeededForCookie(String s)
- {
- if (s==null || s.length()==0)
- return true;
-
- if (QuotedStringTokenizer.isQuoted(s))
- return false;
-
- for (int i=0;i<s.length();i++)
- {
- char c = s.charAt(i);
- if (__COOKIE_DELIM.indexOf(c)>=0)
- return true;
-
- if (c<0x20 || c>=0x7f)
- throw new IllegalArgumentException("Illegal character in cookie value");
- }
-
- return false;
- }
-
-
- private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
- {
- if (quote)
- QuotedStringTokenizer.quoteOnly(buf,s);
- else
- buf.append(s);
- }
}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java
index af09866..9cbeedd 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java
@@ -37,6 +37,7 @@
{
private static final Logger LOG = Log.getLogger(HttpGenerator.class);
+ private final static byte[] __colon_space = new byte[] {':',' '};
public static final ResponseInfo CONTINUE_100_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,100,null,false);
public static final ResponseInfo PROGRESS_102_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,102,null,false);
public final static ResponseInfo RESPONSE_500_INFO =
@@ -495,6 +496,9 @@
case HTTP_1_1:
header.put((byte)' ');
header.put(request.getHttpVersion().toBytes());
+ break;
+ default:
+ throw new IllegalStateException();
}
header.put(HttpTokens.CRLF);
}
@@ -585,7 +589,7 @@
// write the field to the header
content_type=true;
- field.putTo(header);
+ putTo(field,header);
break;
}
@@ -600,7 +604,7 @@
case CONNECTION:
{
if (request!=null)
- field.putTo(header);
+ putTo(field,header);
// Lookup and/or split connection value field
HttpHeaderValue[] values = new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
@@ -672,12 +676,12 @@
case SERVER:
{
send=send&~SEND_SERVER;
- field.putTo(header);
+ putTo(field,header);
break;
}
default:
- field.putTo(header);
+ putTo(field,header);
}
}
}
@@ -776,7 +780,7 @@
{
String c = transfer_encoding.getValue();
if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
- transfer_encoding.putTo(header);
+ putTo(transfer_encoding,header);
else
throw new IllegalArgumentException("BAD TE");
}
@@ -1007,5 +1011,88 @@
{
return String.format("ResponseInfo{%s %s %s,%d,%b}",_httpVersion,_status,_reason,_contentLength,_head);
}
+ }
+
+ private static void putSanitisedName(String s,ByteBuffer buffer)
+ {
+ int l=s.length();
+ for (int i=0;i<l;i++)
+ {
+ char c=s.charAt(i);
+
+ if (c<0 || c>0xff || c=='\r' || c=='\n'|| c==':')
+ buffer.put((byte)'?');
+ else
+ buffer.put((byte)(0xff&c));
+ }
+ }
+
+ private static void putSanitisedValue(String s,ByteBuffer buffer)
+ {
+ int l=s.length();
+ for (int i=0;i<l;i++)
+ {
+ char c=s.charAt(i);
+
+ if (c<0 || c>0xff || c=='\r' || c=='\n')
+ buffer.put((byte)'?');
+ else
+ buffer.put((byte)(0xff&c));
+ }
+ }
+
+ public static void putTo(HttpField field, ByteBuffer bufferInFillMode)
+ {
+ if (field instanceof CachedHttpField)
+ {
+ ((CachedHttpField)field).putTo(bufferInFillMode);
+ }
+ else
+ {
+ HttpHeader header=field.getHeader();
+ if (header!=null)
+ {
+ bufferInFillMode.put(header.getBytesColonSpace());
+ putSanitisedValue(field.getValue(),bufferInFillMode);
+ }
+ else
+ {
+ putSanitisedName(field.getName(),bufferInFillMode);
+ bufferInFillMode.put(__colon_space);
+ putSanitisedValue(field.getValue(),bufferInFillMode);
+ }
+
+ BufferUtil.putCRLF(bufferInFillMode);
+ }
+ }
+
+ public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode)
+ {
+ for (HttpField field : fields)
+ {
+ if (field != null)
+ putTo(field,bufferInFillMode);
+ }
+ BufferUtil.putCRLF(bufferInFillMode);
+ }
+
+ public static class CachedHttpField extends HttpField
+ {
+ private final byte[] _bytes;
+ public CachedHttpField(HttpHeader header,String value)
+ {
+ super(header,value);
+ int cbl=header.getBytesColonSpace().length;
+ _bytes=new byte[cbl+value.length()+2];
+ System.arraycopy(header.getBytesColonSpace(),0,_bytes,0,cbl);
+ System.arraycopy(value.getBytes(StringUtil.__ISO_8859_1_CHARSET),0,_bytes,cbl,value.length());
+ _bytes[_bytes.length-2]=(byte)'\r';
+ _bytes[_bytes.length-1]=(byte)'\n';
+ }
+
+ public void putTo(ByteBuffer bufferInFillMode)
+ {
+ bufferInFillMode.put(_bytes);
+ }
}
}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
index 7810666..7fbfe0f 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
@@ -23,6 +23,7 @@
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
@@ -77,6 +78,21 @@
public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpParser.STRICT");
public final static int INITIAL_URI_LENGTH=256;
+ /**
+ * Cache of common {@link HttpField}s including: <UL>
+ * <LI>Common static combinations such as:<UL>
+ * <li>Connection: close
+ * <li>Accept-Encoding: gzip
+ * <li>Content-Length: 0
+ * </ul>
+ * <li>Combinations of Content-Type header for common mime types by common charsets
+ * <li>Most common headers with null values so that a lookup will at least
+ * determine the header name even if the name:value combination is not cached
+ * </ul>
+ */
+ public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
+ public final static Trie<HttpField> CONTENT_TYPE = new ArrayTrie<>(512);
+
// States
public enum State
{
@@ -93,17 +109,17 @@
HEADER_IN_NAME,
HEADER_VALUE,
HEADER_IN_VALUE,
- END,
- EOF_CONTENT,
CONTENT,
+ EOF_CONTENT,
CHUNKED_CONTENT,
CHUNK_SIZE,
CHUNK_PARAMS,
CHUNK,
+ END,
CLOSED
- };
+ }
- private final boolean DEBUG=LOG.isDebugEnabled();
+ private final boolean DEBUG=LOG.isDebugEnabled(); // Cache debug to help branch prediction
private final HttpHandler<ByteBuffer> _handler;
private final RequestHandler<ByteBuffer> _requestHandler;
private final ResponseHandler<ByteBuffer> _responseHandler;
@@ -120,6 +136,8 @@
/* ------------------------------------------------------------------------------- */
private volatile State _state=State.START;
+ private volatile boolean _eof;
+ private volatile boolean _closed;
private HttpMethod _method;
private String _methodString;
private HttpVersion _version;
@@ -137,6 +155,59 @@
private int _length;
private final StringBuilder _string=new StringBuilder();
+ static
+ {
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE));
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE));
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"*/*"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
+ CACHE.put(new HttpField(HttpHeader.PRAGMA,"no-cache"));
+ CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
+ CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"no-cache"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,"0"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
+ CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
+ CACHE.put(new HttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
+
+ // Content types
+ for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/x-www-form-urlencoded"})
+ {
+ HttpField field=new HttpField(HttpHeader.CONTENT_TYPE,type);
+ CACHE.put(field);
+ CONTENT_TYPE.put(type,field);
+
+ for (String charset : new String[]{"UTF-8","ISO-8859-1"})
+ {
+ String type_charset=type+"; charset="+charset;
+ field=new HttpField(HttpHeader.CONTENT_TYPE,type_charset);
+ CACHE.put(field);
+ CACHE.put(new HttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
+ CONTENT_TYPE.put(type_charset,field);
+ CONTENT_TYPE.put(type+";charset="+charset,field);
+ }
+ }
+
+ // Add headers with null values so HttpParser can avoid looking up name again for unknown values
+ for (HttpHeader h:HttpHeader.values())
+ if (!CACHE.put(new HttpField(h,(String)null)))
+ throw new IllegalStateException("CACHE FULL");
+ // Add some more common headers
+ CACHE.put(new HttpField(HttpHeader.REFERER,(String)null));
+ CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null));
+ CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null));
+ CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
+ CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
+ }
+
/* ------------------------------------------------------------------------------- */
public HttpParser(RequestHandler<ByteBuffer> handler)
{
@@ -203,6 +274,12 @@
}
/* ------------------------------------------------------------------------------- */
+ protected void setResponseStatus(int status)
+ {
+ _responseStatus=status;
+ }
+
+ /* ------------------------------------------------------------------------------- */
public State getState()
{
return _state;
@@ -211,19 +288,13 @@
/* ------------------------------------------------------------------------------- */
public boolean inContentState()
{
- return _state.ordinal() > State.END.ordinal();
+ return _state.ordinal()>=State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal();
}
/* ------------------------------------------------------------------------------- */
public boolean inHeaderState()
{
- return _state.ordinal() < State.END.ordinal();
- }
-
- /* ------------------------------------------------------------------------------- */
- public boolean isInContent()
- {
- return _state.ordinal()>State.END.ordinal() && _state.ordinal()<State.CLOSED.ordinal();
+ return _state.ordinal() < State.CONTENT.ordinal();
}
/* ------------------------------------------------------------------------------- */
@@ -265,6 +336,7 @@
/* ------------------------------------------------------------------------------- */
private static class BadMessage extends Error
{
+ private static final long serialVersionUID = 1L;
private final int _code;
private final String _message;
@@ -292,65 +364,43 @@
}
/* ------------------------------------------------------------------------------- */
- private byte next(ByteBuffer buffer)
+ private byte next(ByteBuffer buffer)
{
- byte ch=buffer.get();
-
- // If not a special character
- if (ch>=HttpTokens.SPACE || ch<0)
- {
- if (_cr)
- throw new BadMessage("Bad EOL");
-
- /*
- if (ch>HttpTokens.SPACE)
- System.err.println("Next "+(char)ch);
- else
- System.err.println("Next ["+ch+"]");*/
- return ch;
- }
-
+ byte ch = buffer.get();
- // Only a LF acceptable after CR
if (_cr)
{
- _cr=false;
- if (ch==HttpTokens.LINE_FEED)
- return ch;
-
- throw new BadMessage("Bad EOL");
- }
-
- // If it is a CR
- if (ch==HttpTokens.CARRIAGE_RETURN)
- {
- // Skip CR and look for a LF
- if (buffer.hasRemaining())
- {
- if(_maxHeaderBytes>0 && _state.ordinal()<State.END.ordinal())
- _headerBytes++;
- ch=buffer.get();
- if (ch==HttpTokens.LINE_FEED)
- return ch;
-
+ if (ch!=HttpTokens.LINE_FEED)
throw new BadMessage("Bad EOL");
- }
+ _cr=false;
+ return ch;
+ }
- // Defer lookup of LF
- _cr=true;
- return 0;
+ if (ch>=0 && ch<HttpTokens.SPACE)
+ {
+ if (ch==HttpTokens.CARRIAGE_RETURN)
+ {
+ if (buffer.hasRemaining())
+ {
+ if(_maxHeaderBytes>0 && _state.ordinal()<State.END.ordinal())
+ _headerBytes++;
+ ch=buffer.get();
+ if (ch!=HttpTokens.LINE_FEED)
+ throw new BadMessage("Bad EOL");
+ }
+ else
+ {
+ _cr=true;
+ // Can return 0 here to indicate the need for more characters,
+ // because a real 0 in the buffer would cause a BadMessage below
+ return 0;
+ }
+ }
+ // Only LF or TAB acceptable special characters
+ else if (!(ch==HttpTokens.LINE_FEED || ch==HttpTokens.TAB))
+ throw new BadMessage("Illegal character");
}
- // Only LF or TAB acceptable special characters
- if (ch!=HttpTokens.LINE_FEED && ch!=HttpTokens.TAB)
- throw new BadMessage("Illegal character");
-
- /*
- if (ch>HttpTokens.SPACE)
- System.err.println("Next "+(char)ch);
- else
- System.err.println("Next ["+ch+"]");
- */
return ch;
}
@@ -360,33 +410,33 @@
*/
private boolean quickStart(ByteBuffer buffer)
{
+ if (_requestHandler!=null)
+ {
+ _method = HttpMethod.lookAheadGet(buffer);
+ if (_method!=null)
+ {
+ _methodString = _method.asString();
+ buffer.position(buffer.position()+_methodString.length()+1);
+ setState(State.SPACE1);
+ return false;
+ }
+ }
+ else if (_responseHandler!=null)
+ {
+ _version = HttpVersion.lookAheadGet(buffer);
+ if (_version!=null)
+ {
+ buffer.position(buffer.position()+_version.asString().length()+1);
+ setState(State.SPACE1);
+ return false;
+ }
+ }
+
// Quick start look
while (_state==State.START && buffer.hasRemaining())
{
- if (_requestHandler!=null)
- {
- _method = HttpMethod.lookAheadGet(buffer);
- if (_method!=null)
- {
- _methodString = _method.asString();
- buffer.position(buffer.position()+_methodString.length()+1);
- setState(State.SPACE1);
- return false;
- }
- }
- else if (_responseHandler!=null)
- {
- _version = HttpVersion.lookAheadGet(buffer);
- if (_version!=null)
- {
- buffer.position(buffer.position()+_version.asString().length()+1);
- setState(State.SPACE1);
- return false;
- }
- }
+ int ch=next(buffer);
- byte ch=next(buffer);
-
if (ch > HttpTokens.SPACE)
{
_string.setLength(0);
@@ -394,6 +444,10 @@
setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION);
return false;
}
+ else if (ch==0)
+ break;
+ else if (ch<0)
+ throw new BadMessage();
}
return false;
}
@@ -421,15 +475,15 @@
*/
private boolean parseLine(ByteBuffer buffer)
{
- boolean return_from_parse=false;
+ boolean handle=false;
// Process headers
- while (_state.ordinal()<State.HEADER.ordinal() && buffer.hasRemaining() && !return_from_parse)
+ while (_state.ordinal()<State.HEADER.ordinal() && buffer.hasRemaining() && !handle)
{
// process each character
byte ch=next(buffer);
if (ch==0)
- continue;
+ break;
if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
{
@@ -460,7 +514,7 @@
_methodString=method.asString();
setState(State.SPACE1);
}
- else if (ch<HttpTokens.SPACE)
+ else if (ch < HttpTokens.SPACE)
throw new BadMessage(ch<0?"Illegal character":"No URI");
else
_string.append((char)ch);
@@ -473,9 +527,7 @@
String version=takeString();
_version=HttpVersion.CACHE.get(version);
if (_version==null)
- {
throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
- }
setState(State.SPACE1);
}
else if (ch < HttpTokens.SPACE)
@@ -485,12 +537,12 @@
break;
case SPACE1:
- if (ch > HttpTokens.SPACE)
+ if (ch > HttpTokens.SPACE || ch<0)
{
if (_responseHandler!=null)
{
setState(State.STATUS);
- _responseStatus=ch-'0';
+ setResponseStatus(ch-'0');
}
else
{
@@ -529,7 +581,9 @@
}
}
else if (ch < HttpTokens.SPACE)
+ {
throw new BadMessage(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status");
+ }
break;
case STATUS:
@@ -543,7 +597,7 @@
}
else if (ch < HttpTokens.SPACE && ch>=0)
{
- return_from_parse=_responseHandler.startResponse(_version, _responseStatus, null)||return_from_parse;
+ handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
setState(State.HEADER);
}
else
@@ -561,11 +615,11 @@
{
// HTTP/0.9
_uri.flip();
- return_from_parse=_requestHandler.startRequest(_method,_methodString,_uri,null)||return_from_parse;
+ handle=_requestHandler.startRequest(_method,_methodString,_uri,null)||handle;
setState(State.END);
BufferUtil.clear(buffer);
- return_from_parse=_handler.headerComplete()||return_from_parse;
- return_from_parse=_handler.messageComplete()||return_from_parse;
+ handle=_handler.headerComplete()||handle;
+ handle=_handler.messageComplete()||handle;
}
else
{
@@ -595,28 +649,29 @@
setState(State.REQUEST_VERSION);
// try quick look ahead for HTTP Version
+ HttpVersion version;
if (buffer.position()>0 && buffer.hasArray())
+ version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
+ else
+ version=HttpVersion.CACHE.getBest(buffer,0,buffer.remaining());
+ if (version!=null)
{
- HttpVersion version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
- if (version!=null)
+ int pos = buffer.position()+version.asString().length()-1;
+ if (pos<buffer.limit())
{
- int pos = buffer.position()+version.asString().length()-1;
- if (pos<buffer.limit())
+ byte n=buffer.get(pos);
+ if (n==HttpTokens.CARRIAGE_RETURN)
{
- byte n=buffer.get(pos);
- if (n==HttpTokens.CARRIAGE_RETURN)
- {
- _cr=true;
- _version=version;
- _string.setLength(0);
- buffer.position(pos+1);
- }
- else if (n==HttpTokens.LINE_FEED)
- {
- _version=version;
- _string.setLength(0);
- buffer.position(pos);
- }
+ _cr=true;
+ _version=version;
+ _string.setLength(0);
+ buffer.position(pos+1);
+ }
+ else if (n==HttpTokens.LINE_FEED)
+ {
+ _version=version;
+ _string.setLength(0);
+ buffer.position(pos);
}
}
}
@@ -626,21 +681,21 @@
{
if (_responseHandler!=null)
{
- return_from_parse=_responseHandler.startResponse(_version, _responseStatus, null)||return_from_parse;
+ handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
setState(State.HEADER);
}
else
{
// HTTP/0.9
_uri.flip();
- return_from_parse=_requestHandler.startRequest(_method,_methodString,_uri, null)||return_from_parse;
+ handle=_requestHandler.startRequest(_method,_methodString,_uri, null)||handle;
setState(State.END);
BufferUtil.clear(buffer);
- return_from_parse=_handler.headerComplete()||return_from_parse;
- return_from_parse=_handler.messageComplete()||return_from_parse;
+ handle=_handler.headerComplete()||handle;
+ handle=_handler.messageComplete()||handle;
}
}
- else if (ch<HttpTokens.SPACE)
+ else if (ch<0)
throw new BadMessage();
break;
@@ -653,9 +708,7 @@
_version=HttpVersion.CACHE.get(takeString());
}
if (_version==null)
- {
throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
- }
// Should we try to cache header fields?
if (_connectionFields==null && _version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
@@ -667,10 +720,10 @@
setState(State.HEADER);
_uri.flip();
- return_from_parse=_requestHandler.startRequest(_method,_methodString,_uri, _version)||return_from_parse;
+ handle=_requestHandler.startRequest(_method,_methodString,_uri, _version)||handle;
continue;
}
- else if (ch>HttpTokens.SPACE)
+ else if (ch>=HttpTokens.SPACE)
_string.append((char)ch);
else
throw new BadMessage();
@@ -683,7 +736,7 @@
String reason=takeString();
setState(State.HEADER);
- return_from_parse=_responseHandler.startResponse(_version, _responseStatus, reason)||return_from_parse;
+ handle=_responseHandler.startResponse(_version, _responseStatus, reason)||handle;
continue;
}
else if (ch>=HttpTokens.SPACE)
@@ -691,7 +744,7 @@
_string.append((char)ch);
if (ch!=' '&&ch!='\t')
_length=_string.length();
- }
+ }
else
throw new BadMessage();
break;
@@ -702,7 +755,7 @@
}
}
- return return_from_parse;
+ return handle;
}
private boolean handleKnownHeaders(ByteBuffer buffer)
@@ -796,7 +849,10 @@
case CONNECTION:
// Don't cache if not persistent
if (_valueString!=null && _valueString.indexOf("close")>=0)
+ {
+ _closed=true;
_connectionFields=null;
+ }
break;
case AUTHORIZATION:
@@ -815,7 +871,7 @@
if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
{
- _field=new HttpField.CachedHttpField(_header,_valueString);
+ _field=new HttpField(_header,_valueString);
_connectionFields.put(_field);
}
@@ -827,17 +883,17 @@
/*
* Parse the message headers and return true if the handler has signaled for a return
*/
- private boolean parseHeaders(ByteBuffer buffer)
+ protected boolean parseHeaders(ByteBuffer buffer)
{
- boolean return_from_parse=false;
+ boolean handle=false;
// Process headers
- while (_state.ordinal()<State.END.ordinal() && buffer.hasRemaining() && !return_from_parse)
+ while (_state.ordinal()<State.CONTENT.ordinal() && buffer.hasRemaining() && !handle)
{
// process each character
byte ch=next(buffer);
if (ch==0)
- continue;
+ break;
if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
{
@@ -885,7 +941,7 @@
_field=null;
return true;
}
- return_from_parse=_handler.parsedHeader(_field!=null?_field:new HttpField(_header,_headerString,_valueString))||return_from_parse;
+ handle=_handler.parsedHeader(_field!=null?_field:new HttpField(_header,_headerString,_valueString))||handle;
}
_headerString=_valueString=null;
_header=null;
@@ -929,23 +985,23 @@
{
case EOF_CONTENT:
setState(State.EOF_CONTENT);
- return_from_parse=_handler.headerComplete()||return_from_parse;
+ handle=_handler.headerComplete()||handle;
break;
case CHUNKED_CONTENT:
setState(State.CHUNKED_CONTENT);
- return_from_parse=_handler.headerComplete()||return_from_parse;
+ handle=_handler.headerComplete()||handle;
break;
case NO_CONTENT:
- return_from_parse=_handler.headerComplete()||return_from_parse;
+ handle=_handler.headerComplete()||handle;
setState(State.END);
- return_from_parse=_handler.messageComplete()||return_from_parse;
+ handle=_handler.messageComplete()||handle;
break;
default:
setState(State.CONTENT);
- return_from_parse=_handler.headerComplete()||return_from_parse;
+ handle=_handler.headerComplete()||handle;
break;
}
}
@@ -958,7 +1014,7 @@
// Try a look ahead for the known header name and value.
HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
if (field==null)
- field=HttpField.CACHE.getBest(buffer,-1,buffer.remaining());
+ field=CACHE.getBest(buffer,-1,buffer.remaining());
if (field!=null)
{
@@ -1129,7 +1185,7 @@
}
}
- return return_from_parse;
+ return handle;
}
/* ------------------------------------------------------------------------------- */
@@ -1139,214 +1195,113 @@
*/
public boolean parseNext(ByteBuffer buffer)
{
+ if (DEBUG)
+ LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer));
try
{
- // handle initial state
- switch(_state)
+ boolean handle=false;
+
+ // Start a request/response
+ if (_state==State.START)
{
- case START:
- _version=null;
- _method=null;
- _methodString=null;
- _endOfContent=EndOfContent.UNKNOWN_CONTENT;
- _header=null;
- if(quickStart(buffer))
- return true;
- break;
-
- case CONTENT:
- if (_contentPosition==_contentLength)
- {
- setState(State.END);
- if(_handler.messageComplete())
- return true;
- }
- break;
-
- case END:
- // eat white space
- while (buffer.remaining()>0 && buffer.get(buffer.position())<=HttpTokens.SPACE)
- buffer.get();
- return false;
-
- case CLOSED:
- if (BufferUtil.hasContent(buffer))
- {
- // Just ignore data when closed
- _headerBytes+=buffer.remaining();
- BufferUtil.clear(buffer);
- if (_headerBytes>_maxHeaderBytes)
- {
- // Don't want to waste time reading data of a closed request
- throw new IllegalStateException("too much data after closed");
- }
- }
- return false;
- default: break;
-
+ _version=null;
+ _method=null;
+ _methodString=null;
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ _header=null;
+ handle=quickStart(buffer);
}
-
+
// Request/response line
- if (_state.ordinal()<State.HEADER.ordinal())
- if (parseLine(buffer))
- return true;
+ if (!handle && _state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal())
+ handle=parseLine(buffer);
- if (_state.ordinal()<State.END.ordinal())
- if (parseHeaders(buffer))
- return true;
-
- // Handle HEAD response
- if (_responseStatus>0 && _headResponse)
+ // parse headers
+ if (!handle && _state.ordinal()>= State.HEADER.ordinal() && _state.ordinal()<State.CONTENT.ordinal())
+ handle=parseHeaders(buffer);
+
+ // parse content
+ if (!handle && _state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal())
{
- setState(State.END);
- if (_handler.messageComplete())
- return true;
- }
-
-
- // Handle _content
- byte ch;
- while (_state.ordinal() > State.END.ordinal() && buffer.hasRemaining())
- {
- switch (_state)
+ // Handle HEAD response
+ if (_responseStatus>0 && _headResponse)
{
- case EOF_CONTENT:
- _contentChunk=buffer.asReadOnlyBuffer();
- _contentPosition += _contentChunk.remaining();
- buffer.position(buffer.position()+_contentChunk.remaining());
- if (_handler.content(_contentChunk))
- return true;
- break;
-
- case CONTENT:
+ setState(State.END);
+ handle=_handler.messageComplete();
+ }
+ else
+ handle=parseContent(buffer);
+ }
+
+ // handle end states
+ if (_state==State.END)
+ {
+ // eat white space
+ while (buffer.remaining()>0 && buffer.get(buffer.position())<=HttpTokens.SPACE)
+ buffer.get();
+ }
+ else if (_state==State.CLOSED)
+ {
+ if (BufferUtil.hasContent(buffer))
+ {
+ // Just ignore data when closed
+ _headerBytes+=buffer.remaining();
+ BufferUtil.clear(buffer);
+ if (_headerBytes>_maxHeaderBytes)
{
- long remaining=_contentLength - _contentPosition;
- if (remaining == 0)
- {
- setState(State.END);
- if (_handler.messageComplete())
- return true;
- }
- else
- {
- _contentChunk=buffer.asReadOnlyBuffer();
-
- // limit content by expected size
- if (_contentChunk.remaining() > remaining)
- {
- // We can cast remaining to an int as we know that it is smaller than
- // or equal to length which is already an int.
- _contentChunk.limit(_contentChunk.position()+(int)remaining);
- }
-
- _contentPosition += _contentChunk.remaining();
- buffer.position(buffer.position()+_contentChunk.remaining());
-
- if (_handler.content(_contentChunk))
- return true;
-
- if(_contentPosition == _contentLength)
- {
- setState(State.END);
- if (_handler.messageComplete())
- return true;
- }
- }
- break;
+ // Don't want to waste time reading data of a closed request
+ throw new IllegalStateException("too much data after closed");
}
-
- case CHUNKED_CONTENT:
- {
- ch=next(buffer);
- if (ch>HttpTokens.SPACE)
- {
- _chunkLength=TypeUtil.convertHexDigit(ch);
- _chunkPosition=0;
- setState(State.CHUNK_SIZE);
- }
-
- break;
- }
-
- case CHUNK_SIZE:
- {
- ch=next(buffer);
- if (ch == HttpTokens.LINE_FEED)
- {
- if (_chunkLength == 0)
- {
- setState(State.END);
- if (_handler.messageComplete())
- return true;
- }
- else
- setState(State.CHUNK);
- }
- else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
- setState(State.CHUNK_PARAMS);
- else
- _chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch);
- break;
- }
-
- case CHUNK_PARAMS:
- {
- ch=next(buffer);
- if (ch == HttpTokens.LINE_FEED)
- {
- if (_chunkLength == 0)
- {
- setState(State.END);
- if (_handler.messageComplete())
- return true;
- }
- else
- setState(State.CHUNK);
- }
- break;
- }
-
- case CHUNK:
- {
- int remaining=_chunkLength - _chunkPosition;
- if (remaining == 0)
- {
- setState(State.CHUNKED_CONTENT);
- }
- else
- {
- _contentChunk=buffer.asReadOnlyBuffer();
-
- if (_contentChunk.remaining() > remaining)
- _contentChunk.limit(_contentChunk.position()+remaining);
- remaining=_contentChunk.remaining();
-
- _contentPosition += remaining;
- _chunkPosition += remaining;
- buffer.position(buffer.position()+remaining);
- if (_handler.content(_contentChunk))
- return true;
- }
- break;
- }
+ }
+ }
+
+ // Handle EOF
+ if (_eof && !buffer.hasRemaining())
+ {
+ switch(_state)
+ {
case CLOSED:
- {
- BufferUtil.clear(buffer);
- return false;
- }
-
- default:
+ break;
+
+ case START:
+ _handler.earlyEOF();
+ setState(State.CLOSED);
+ break;
+
+ case END:
+ setState(State.CLOSED);
+ break;
+
+ case EOF_CONTENT:
+ handle=_handler.messageComplete()||handle;
+ setState(State.CLOSED);
+ break;
+
+ case CONTENT:
+ case CHUNKED_CONTENT:
+ case CHUNK_SIZE:
+ case CHUNK_PARAMS:
+ case CHUNK:
+ _handler.earlyEOF();
+ setState(State.CLOSED);
+ break;
+
+ default:
+ if (DEBUG)
+ LOG.debug("{} EOF in {}",this,_state);
+ _handler.badMessage(400,null);
+ setState(State.CLOSED);
break;
}
}
-
- return false;
+
+ return handle;
}
catch(BadMessage e)
{
BufferUtil.clear(buffer);
- LOG.warn("BadMessage: "+e._code+(e._message!=null?" "+e._message:"")+" for "+_handler);
+ LOG.warn("badMessage: "+e._code+(e._message!=null?" "+e._message:"")+" for "+_handler);
if (DEBUG)
LOG.debug(e);
setState(State.CLOSED);
@@ -1357,8 +1312,8 @@
{
BufferUtil.clear(buffer);
- LOG.warn("Parsing Exception: "+e.toString()+" for "+_handler);
- if (DEBUG)
+ LOG.warn("badMessage: "+e.toString()+" for "+_handler);
+ if (DEBUG)
LOG.debug(e);
if (_state.ordinal()<=State.END.ordinal())
@@ -1376,85 +1331,187 @@
}
}
- /**
- * Notifies this parser that I/O code read a -1 and therefore no more data will arrive to be parsed.
- * Calling this method may result in an invocation to {@link HttpHandler#messageComplete()}, for
- * example when the content is delimited by the close of the connection.
- * If the parser is already in a state that does not need data (for example, it is idle waiting for
- * a request/response to be parsed), then calling this method is a no-operation.
- *
- * @return the result of the invocation to {@link HttpHandler#messageComplete()} if there has been
- * one, or false otherwise.
- */
- public boolean shutdownInput()
+ protected boolean parseContent(ByteBuffer buffer)
{
- if (DEBUG)
- LOG.debug("shutdownInput {}", this);
-
- // was this unexpected?
- switch(_state)
+ // Handle _content
+ byte ch;
+ while (_state.ordinal() < State.END.ordinal() && buffer.hasRemaining())
{
- case START:
- case END:
- break;
+ switch (_state)
+ {
+ case EOF_CONTENT:
+ _contentChunk=buffer.asReadOnlyBuffer();
+ _contentPosition += _contentChunk.remaining();
+ buffer.position(buffer.position()+_contentChunk.remaining());
+ if (_handler.content(_contentChunk))
+ return true;
+ break;
- case EOF_CONTENT:
- setState(State.END);
- return _handler.messageComplete();
+ case CONTENT:
+ {
+ long remaining=_contentLength - _contentPosition;
+ if (remaining == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ {
+ _contentChunk=buffer.asReadOnlyBuffer();
- case CLOSED:
- break;
+ // limit content by expected size
+ if (_contentChunk.remaining() > remaining)
+ {
+ // We can cast remaining to an int as we know that it is smaller than
+ // or equal to length which is already an int.
+ _contentChunk.limit(_contentChunk.position()+(int)remaining);
+ }
- default:
- setState(State.END);
- if (!_headResponse)
- _handler.earlyEOF();
- return _handler.messageComplete();
+ _contentPosition += _contentChunk.remaining();
+ buffer.position(buffer.position()+_contentChunk.remaining());
+
+ boolean handle=_handler.content(_contentChunk);
+ if(_contentPosition == _contentLength)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ if (handle)
+ return true;
+ }
+ break;
+ }
+
+ case CHUNKED_CONTENT:
+ {
+ ch=next(buffer);
+ if (ch>HttpTokens.SPACE)
+ {
+ _chunkLength=TypeUtil.convertHexDigit(ch);
+ _chunkPosition=0;
+ setState(State.CHUNK_SIZE);
+ }
+
+ break;
+ }
+
+ case CHUNK_SIZE:
+ {
+ ch=next(buffer);
+ if (ch==0)
+ break;
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_chunkLength == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ setState(State.CHUNK);
+ }
+ else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
+ setState(State.CHUNK_PARAMS);
+ else
+ _chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch);
+ break;
+ }
+
+ case CHUNK_PARAMS:
+ {
+ ch=next(buffer);
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_chunkLength == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ setState(State.CHUNK);
+ }
+ break;
+ }
+
+ case CHUNK:
+ {
+ int remaining=_chunkLength - _chunkPosition;
+ if (remaining == 0)
+ {
+ setState(State.CHUNKED_CONTENT);
+ }
+ else
+ {
+ _contentChunk=buffer.asReadOnlyBuffer();
+
+ if (_contentChunk.remaining() > remaining)
+ _contentChunk.limit(_contentChunk.position()+remaining);
+ remaining=_contentChunk.remaining();
+
+ _contentPosition += remaining;
+ _chunkPosition += remaining;
+ buffer.position(buffer.position()+remaining);
+ if (_handler.content(_contentChunk))
+ return true;
+ }
+ break;
+ }
+
+ case CLOSED:
+ {
+ BufferUtil.clear(buffer);
+ return false;
+ }
+
+ default:
+ break;
+ }
}
-
return false;
}
/* ------------------------------------------------------------------------------- */
+ public boolean isAtEOF()
+
+ {
+ return _eof;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void atEOF()
+
+ {
+ if (DEBUG)
+ LOG.debug("atEOF {}", this);
+ _eof=true;
+ }
+
+ /* ------------------------------------------------------------------------------- */
public void close()
{
if (DEBUG)
LOG.debug("close {}", this);
- switch(_state)
- {
- case START:
- case CLOSED:
- case END:
- break;
-
- case EOF_CONTENT:
- _handler.messageComplete();
- break;
-
- default:
- if (_state.ordinal()>State.END.ordinal())
- {
- _handler.earlyEOF();
- _handler.messageComplete();
- }
- else
- LOG.warn("Closing {}",this);
- }
setState(State.CLOSED);
- _endOfContent=EndOfContent.UNKNOWN_CONTENT;
- _contentLength=-1;
- _contentPosition=0;
- _responseStatus=0;
- _headerBytes=0;
- _contentChunk=null;
}
-
+
/* ------------------------------------------------------------------------------- */
public void reset()
{
if (DEBUG)
LOG.debug("reset {}", this);
// reset state
+ if (_state==State.CLOSED)
+ return;
+ if (_closed)
+ {
+ setState(State.CLOSED);
+ return;
+ }
+
setState(State.START);
_endOfContent=EndOfContent.UNKNOWN_CONTENT;
_contentLength=-1;
@@ -1466,7 +1523,7 @@
}
/* ------------------------------------------------------------------------------- */
- private void setState(State state)
+ protected void setState(State state)
{
if (DEBUG)
LOG.debug("{} --> {}",_state,state);
@@ -1512,7 +1569,6 @@
/* ------------------------------------------------------------ */
/** Called to signal that an EOF was received unexpectedly
* during the parsing of a HTTP message
- * @return True if the parser should return to its caller
*/
public void earlyEOF();
@@ -1543,7 +1599,7 @@
/**
* This is the method called by the parser after it has parsed the host header (and checked it's format). This is
- * called after the {@link HttpHandler#parsedHeader(HttpField) methods and before
+ * called after the {@link HttpHandler#parsedHeader(HttpField)} methods and before
* HttpHandler#headerComplete();
*/
public abstract boolean parsedHostHeader(String host,int port);
@@ -1561,5 +1617,4 @@
{
return _connectionFields;
}
-
}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java
index 311a688..35fcb32 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java
@@ -136,10 +136,10 @@
try
{
ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime");
- Enumeration i = mime.getKeys();
+ Enumeration<String> i = mime.getKeys();
while(i.hasMoreElements())
{
- String ext = (String)i.nextElement();
+ String ext = i.nextElement();
String m = mime.getString(ext);
__dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m));
}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java
index 00b77a5..20f559c 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java
@@ -26,7 +26,6 @@
import java.util.StringTokenizer;
import org.eclipse.jetty.util.ArrayTernaryTrie;
-import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.URIUtil;
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java
index 13dbd77..4333cfc 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java
@@ -83,7 +83,7 @@
ByteBuffer buffer=BufferUtil.allocate(1024);
BufferUtil.flipToFill(buffer);
- header.putTo(buffer);
+ HttpGenerator.putTo(header,buffer);
BufferUtil.flipToFlush(buffer,0);
String result=BufferUtil.toString(buffer);
@@ -117,7 +117,7 @@
ByteBuffer buffer = BufferUtil.allocate(1024);
BufferUtil.flipToFill(buffer);
- header.putTo(buffer);
+ HttpGenerator.putTo(header,buffer);
BufferUtil.flipToFlush(buffer,0);
String out = BufferUtil.toString(buffer);
assertThat(out,containsString("name0: value??0"));
@@ -136,7 +136,7 @@
ByteBuffer buffer = BufferUtil.allocate(1024);
BufferUtil.flipToFill(buffer);
- header.putTo(buffer);
+ HttpGenerator.putTo(header,buffer);
BufferUtil.flipToFlush(buffer,0);
String out = BufferUtil.toString(buffer).toLowerCase();
@@ -268,123 +268,6 @@
- @Test
- public void testSetCookie() throws Exception
- {
- HttpFields fields = new HttpFields();
-
- fields.addSetCookie("null",null,null,null,-1,null,false,false,-1);
- assertEquals("null=",fields.getStringField("Set-Cookie"));
-
- fields.clear();
-
- fields.addSetCookie("minimal","value",null,null,-1,null,false,false,-1);
- assertEquals("minimal=value",fields.getStringField("Set-Cookie"));
-
- fields.clear();
- //test cookies with same name, domain and path, only 1 allowed
- fields.addSetCookie("everything","wrong","domain","path",0,"to be replaced",true,true,0);
- fields.addSetCookie("everything","value","domain","path",0,"comment",true,true,0);
- assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",fields.getStringField("Set-Cookie"));
- Enumeration<String> e =fields.getValues("Set-Cookie");
- assertTrue(e.hasMoreElements());
- assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
- assertFalse(e.hasMoreElements());
- assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.getStringField("Expires"));
- assertFalse(e.hasMoreElements());
-
- //test cookies with same name, different domain
- fields.clear();
- fields.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
- fields.addSetCookie("everything","value","domain2","path",0,"comment",true,true,0);
- e =fields.getValues("Set-Cookie");
- assertTrue(e.hasMoreElements());
- assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
- assertTrue(e.hasMoreElements());
- assertEquals("everything=value;Version=1;Path=path;Domain=domain2;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
- assertFalse(e.hasMoreElements());
-
- //test cookies with same name, same path, one with domain, one without
- fields.clear();
- fields.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
- fields.addSetCookie("everything","value","","path",0,"comment",true,true,0);
- e =fields.getValues("Set-Cookie");
- assertTrue(e.hasMoreElements());
- assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
- assertTrue(e.hasMoreElements());
- assertEquals("everything=value;Version=1;Path=path;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
- assertFalse(e.hasMoreElements());
-
-
- //test cookies with same name, different path
- fields.clear();
- fields.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
- fields.addSetCookie("everything","value","domain1","path2",0,"comment",true,true,0);
- e =fields.getValues("Set-Cookie");
- assertTrue(e.hasMoreElements());
- assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
- assertTrue(e.hasMoreElements());
- assertEquals("everything=value;Version=1;Path=path2;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
- assertFalse(e.hasMoreElements());
-
- //test cookies with same name, same domain, one with path, one without
- fields.clear();
- fields.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
- fields.addSetCookie("everything","value","domain1","",0,"comment",true,true,0);
- e =fields.getValues("Set-Cookie");
- assertTrue(e.hasMoreElements());
- assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
- assertTrue(e.hasMoreElements());
- assertEquals("everything=value;Version=1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
- assertFalse(e.hasMoreElements());
-
- //test cookies same name only, no path, no domain
- fields.clear();
- fields.addSetCookie("everything","other","","",0,"blah",true,true,0);
- fields.addSetCookie("everything","value","","",0,"comment",true,true,0);
- e =fields.getValues("Set-Cookie");
- assertTrue(e.hasMoreElements());
- assertEquals("everything=value;Version=1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
- assertFalse(e.hasMoreElements());
-
- fields.clear();
- fields.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,1);
- String setCookie=fields.getStringField("Set-Cookie");
- assertThat(setCookie,Matchers.startsWith("\"ev erything\"=\"va lue\";Version=1;Path=\"pa th\";Domain=\"do main\";Expires="));
- assertThat(setCookie,Matchers.endsWith(" GMT;Max-Age=1;Secure;HttpOnly;Comment=\"co mment\""));
-
- fields.clear();
- fields.addSetCookie("name","value",null,null,-1,null,false,false,0);
- setCookie=fields.getStringField("Set-Cookie");
- assertEquals(-1,setCookie.indexOf("Version="));
- fields.clear();
- fields.addSetCookie("name","v a l u e",null,null,-1,null,false,false,0);
- setCookie=fields.getStringField("Set-Cookie");
-
- fields.clear();
- fields.addSetCookie("json","{\"services\":[\"cwa\", \"aa\"]}",null,null,-1,null,false,false,-1);
- assertEquals("json=\"{\\\"services\\\":[\\\"cwa\\\", \\\"aa\\\"]}\"",fields.getStringField("Set-Cookie"));
-
- fields.clear();
- fields.addSetCookie("name","value","domain",null,-1,null,false,false,-1);
- fields.addSetCookie("name","other","domain",null,-1,null,false,false,-1);
- assertEquals("name=other;Domain=domain",fields.getStringField("Set-Cookie"));
- fields.addSetCookie("name","more","domain",null,-1,null,false,false,-1);
- assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie"));
- fields.addSetCookie("foo","bar","domain",null,-1,null,false,false,-1);
- fields.addSetCookie("foo","bob","domain",null,-1,null,false,false,-1);
- assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie"));
-
- e=fields.getValues("Set-Cookie");
- assertEquals("name=more;Domain=domain",e.nextElement());
- assertEquals("foo=bob;Domain=domain",e.nextElement());
-
- fields=new HttpFields();
- fields.addSetCookie("name","value%=",null,null,-1,null,false,false,0);
- setCookie=fields.getStringField("Set-Cookie");
- assertEquals("name=value%=",setCookie);
-
- }
private Set<String> enum2set(Enumeration<String> e)
{
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java
index 026fd2c..518a09b 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java
@@ -34,7 +34,6 @@
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
import org.eclipse.jetty.util.BufferUtil;
-import org.hamcrest.Matchers;
import org.junit.Test;
public class HttpGeneratorServerTest
@@ -372,7 +371,7 @@
assertEquals(HttpGenerator.State.START, gen.getState());
ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), -1, 200, null, false);
- info.getHttpFields().add("Last-Modified", HttpFields.__01Jan1970);
+ info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
result = gen.generateResponse(info, null, null, null, true);
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
@@ -441,7 +440,7 @@
assertEquals(HttpGenerator.State.START, gen.getState());
ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), -1, 200, null, false);
- info.getHttpFields().add("Last-Modified", HttpFields.__01Jan1970);
+ info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
result = gen.generateResponse(info, null, null, content0, false);
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
assertEquals(HttpGenerator.State.START, gen.getState());
@@ -503,7 +502,7 @@
assertEquals(HttpGenerator.State.START, gen.getState());
ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), 59, 200, null, false);
- info.getHttpFields().add("Last-Modified", HttpFields.__01Jan1970);
+ info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
result = gen.generateResponse(info, null, null, content0, false);
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
assertEquals(HttpGenerator.State.START, gen.getState());
@@ -570,7 +569,7 @@
assertEquals(HttpGenerator.State.START, gen.getState());
ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), 59, 200, null, false);
- info.getHttpFields().add("Last-Modified", HttpFields.__01Jan1970);
+ info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
result = gen.generateResponse(info, null, null, content0, false);
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
assertEquals(HttpGenerator.State.START, gen.getState());
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java
index 9f105ca..72a12dd 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java
@@ -30,7 +30,6 @@
import org.eclipse.jetty.http.HttpParser.State;
import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
@@ -77,7 +76,7 @@
assertEquals("POST", _methodOrVersion);
assertEquals("/foo", _uriOrStatus);
assertEquals("HTTP/1.0", _versionOrReason);
- assertEquals(-1, _h);
+ assertEquals(-1, _headers);
}
@Test
@@ -92,7 +91,7 @@
assertEquals("GET", _methodOrVersion);
assertEquals("/999", _uriOrStatus);
assertEquals(null, _versionOrReason);
- assertEquals(-1, _h);
+ assertEquals(-1, _headers);
}
@Test
@@ -107,7 +106,7 @@
assertEquals("POST", _methodOrVersion);
assertEquals("/222", _uriOrStatus);
assertEquals(null, _versionOrReason);
- assertEquals(-1, _h);
+ assertEquals(-1, _headers);
}
@Test
@@ -121,7 +120,7 @@
assertEquals("POST", _methodOrVersion);
assertEquals("/fo\u0690", _uriOrStatus);
assertEquals("HTTP/1.0", _versionOrReason);
- assertEquals(-1, _h);
+ assertEquals(-1, _headers);
}
@Test
@@ -135,7 +134,7 @@
assertEquals("POST", _methodOrVersion);
assertEquals("/foo?param=\u0690", _uriOrStatus);
assertEquals("HTTP/1.0", _versionOrReason);
- assertEquals(-1, _h);
+ assertEquals(-1, _headers);
}
@Test
@@ -149,7 +148,7 @@
assertEquals("POST", _methodOrVersion);
assertEquals("/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/", _uriOrStatus);
assertEquals("HTTP/1.0", _versionOrReason);
- assertEquals(-1, _h);
+ assertEquals(-1, _headers);
}
@Test
@@ -162,7 +161,32 @@
assertEquals("CONNECT", _methodOrVersion);
assertEquals("192.168.1.2:80", _uriOrStatus);
assertEquals("HTTP/1.1", _versionOrReason);
- assertEquals(-1, _h);
+ assertEquals(-1, _headers);
+ }
+
+ @Test
+ public void testSimple() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.0\015\012" +
+ "Host: localhost\015\012" +
+ "Connection: close\015\012" +
+ "\015\012");
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parseAll(parser,buffer);
+
+ assertTrue(_headerCompleted);
+ assertTrue(_messageCompleted);
+ assertEquals("GET", _methodOrVersion);
+ assertEquals("/", _uriOrStatus);
+ assertEquals("HTTP/1.0", _versionOrReason);
+ assertEquals("Host", _hdr[0]);
+ assertEquals("localhost", _val[0]);
+ assertEquals("Connection", _hdr[1]);
+ assertEquals("close", _val[1]);
+ assertEquals(1, _headers);
}
@Test
@@ -215,7 +239,7 @@
assertEquals("gzip, deflated", _val[8]);
assertEquals("Accept", _hdr[9]);
assertEquals("unknown", _val[9]);
- assertEquals(9, _h);
+ assertEquals(9, _headers);
}
@Test
@@ -263,7 +287,7 @@
assertEquals("gzip, deflated", _val[8]);
assertEquals("Accept", _hdr[9]);
assertEquals("unknown", _val[9]);
- assertEquals(9, _h);
+ assertEquals(9, _headers);
}
@@ -313,7 +337,7 @@
assertEquals("gzip, deflated", _val[8]);
assertEquals("Accept", _hdr[9]);
assertEquals("unknown", _val[9]);
- assertEquals(9, _h);
+ assertEquals(9, _headers);
}
@Test
@@ -338,7 +362,7 @@
assertEquals("HTTP/1.0", _versionOrReason);
assertEquals("Header1", _hdr[0]);
assertEquals("\u00e6 \u00e6", _val[0]);
- assertEquals(0, _h);
+ assertEquals(0, _headers);
assertEquals(null,_bad);
}
@@ -400,7 +424,7 @@
assertEquals("localhost", _val[0]);
assertEquals("Connection", _hdr[1]);
assertEquals("close", _val[1]);
- assertEquals(1, _h);
+ assertEquals(1, _headers);
}
@Test
@@ -422,7 +446,7 @@
assertEquals("localhost", _val[0]);
assertEquals("cOnNeCtIoN", _hdr[1]);
assertEquals("ClOsE", _val[1]);
- assertEquals(1, _h);
+ assertEquals(1, _headers);
}
@Test
@@ -477,7 +501,7 @@
assertEquals("value4", _val[4]);
assertEquals("Server5", _hdr[5]);
assertEquals("notServer", _val[5]);
- assertEquals(5, _h);
+ assertEquals(5, _headers);
}
}
@@ -502,13 +526,74 @@
assertEquals("GET", _methodOrVersion);
assertEquals("/chunk", _uriOrStatus);
assertEquals("HTTP/1.0", _versionOrReason);
- assertEquals(1, _h);
+ assertEquals(1, _headers);
assertEquals("Header1", _hdr[0]);
assertEquals("value1", _val[0]);
assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content);
}
@Test
+ public void testStartEOF() throws Exception
+ {
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parser.atEOF();
+ parser.parseNext(BufferUtil.EMPTY_BUFFER);
+
+ assertTrue(_early);
+ assertEquals(null,_bad);
+ }
+
+ @Test
+ public void testEarlyEOF() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET /uri HTTP/1.0\015\012"
+ + "Content-Length: 20\015\012"
+ + "\015\012"
+ + "0123456789");
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parser.atEOF();
+ parseAll(parser,buffer);
+
+ assertEquals("GET", _methodOrVersion);
+ assertEquals("/uri", _uriOrStatus);
+ assertEquals("HTTP/1.0", _versionOrReason);
+ assertEquals("0123456789", _content);
+
+ assertTrue(_early);
+ }
+
+ @Test
+ public void testChunkEarlyEOF() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET /chunk HTTP/1.0\015\012"
+ + "Header1: value1\015\012"
+ + "Transfer-Encoding: chunked\015\012"
+ + "\015\012"
+ + "a;\015\012"
+ + "0123456789\015\012");
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parser.atEOF();
+ parseAll(parser,buffer);
+
+ assertEquals("GET", _methodOrVersion);
+ assertEquals("/chunk", _uriOrStatus);
+ assertEquals("HTTP/1.0", _versionOrReason);
+ assertEquals(1, _headers);
+ assertEquals("Header1", _hdr[0]);
+ assertEquals("value1", _val[0]);
+ assertEquals("0123456789", _content);
+
+ assertTrue(_early);
+
+ }
+
+
+ @Test
public void testMultiParse() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
@@ -544,7 +629,7 @@
assertEquals("GET", _methodOrVersion);
assertEquals("/mp", _uriOrStatus);
assertEquals("HTTP/1.0", _versionOrReason);
- assertEquals(2, _h);
+ assertEquals(2, _headers);
assertEquals("Header1", _hdr[1]);
assertEquals("value1", _val[1]);
assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content);
@@ -555,7 +640,7 @@
assertEquals("POST", _methodOrVersion);
assertEquals("/foo", _uriOrStatus);
assertEquals("HTTP/1.0", _versionOrReason);
- assertEquals(2, _h);
+ assertEquals(2, _headers);
assertEquals("Header2", _hdr[1]);
assertEquals("value2", _val[1]);
assertEquals(null, _content);
@@ -563,17 +648,86 @@
parser.reset();
init();
parser.parseNext(buffer);
- parser.shutdownInput();
+ parser.atEOF();
assertEquals("PUT", _methodOrVersion);
assertEquals("/doodle", _uriOrStatus);
assertEquals("HTTP/1.0", _versionOrReason);
- assertEquals(2, _h);
+ assertEquals(2, _headers);
assertEquals("Header3", _hdr[1]);
assertEquals("value3", _val[1]);
assertEquals("0123456789", _content);
}
+
@Test
+ public void testMultiParseEarlyEOF() throws Exception
+ {
+ ByteBuffer buffer0= BufferUtil.toBuffer(
+ "GET /mp HTTP/1.0\015\012"
+ + "Connection: Keep-Alive\015\012");
+
+ ByteBuffer buffer1= BufferUtil.toBuffer("Header1: value1\015\012"
+ + "Transfer-Encoding: chunked\015\012"
+ + "\015\012"
+ + "a;\015\012"
+ + "0123456789\015\012"
+ + "1a\015\012"
+ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012"
+ + "0\015\012"
+
+ + "\015\012"
+
+ + "POST /foo HTTP/1.0\015\012"
+ + "Connection: Keep-Alive\015\012"
+ + "Header2: value2\015\012"
+ + "Content-Length: 0\015\012"
+ + "\015\012"
+
+ + "PUT /doodle HTTP/1.0\015\012"
+ + "Connection: close\015\012"
+ + "Header3: value3\015\012"
+ + "Content-Length: 10\015\012"
+ + "\015\012"
+ + "0123456789\015\012");
+
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parser.parseNext(buffer0);
+ parser.atEOF();
+ parser.parseNext(buffer1);
+ assertEquals("GET", _methodOrVersion);
+ assertEquals("/mp", _uriOrStatus);
+ assertEquals("HTTP/1.0", _versionOrReason);
+ assertEquals(2, _headers);
+ assertEquals("Header1", _hdr[1]);
+ assertEquals("value1", _val[1]);
+ assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content);
+
+ parser.reset();
+ init();
+ parser.parseNext(buffer1);
+ assertEquals("POST", _methodOrVersion);
+ assertEquals("/foo", _uriOrStatus);
+ assertEquals("HTTP/1.0", _versionOrReason);
+ assertEquals(2, _headers);
+ assertEquals("Header2", _hdr[1]);
+ assertEquals("value2", _val[1]);
+ assertEquals(null, _content);
+
+ parser.reset();
+ init();
+ parser.parseNext(buffer1);
+ assertEquals("PUT", _methodOrVersion);
+ assertEquals("/doodle", _uriOrStatus);
+ assertEquals("HTTP/1.0", _versionOrReason);
+ assertEquals(2, _headers);
+ assertEquals("Header3", _hdr[1]);
+ assertEquals("value3", _val[1]);
+ assertEquals("0123456789", _content);
+ }
+
+ @Test
public void testResponseParse0() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
@@ -639,7 +793,7 @@
init();
parser.parseNext(buffer);
- parser.shutdownInput();
+ parser.atEOF();
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("200", _uriOrStatus);
assertEquals("Correct", _versionOrReason);
@@ -692,6 +846,29 @@
}
@Test
+ public void testResponseEOFContent() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "HTTP/1.1 200 \015\012"
+ + "Content-Type: text/plain\015\012"
+ + "\015\012"
+ + "0123456789\015\012");
+
+ HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parser.atEOF();
+ parser.parseNext(buffer);
+
+ assertEquals("HTTP/1.1", _methodOrVersion);
+ assertEquals("200", _uriOrStatus);
+ assertEquals(null, _versionOrReason);
+ assertEquals(12,_content.length());
+ assertEquals("0123456789\015\012",_content);
+ assertTrue(_headerCompleted);
+ assertTrue(_messageCompleted);
+ }
+
+ @Test
public void testResponse304WithContentLength() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
@@ -738,7 +915,7 @@
+ "Connection: close\015\012"
+ "\015\012"
+ "\015\012" // extra CRLF ignored
- + "HTTP/1.1 400 OK\015\012"); // extra data causes close
+ + "HTTP/1.1 400 OK\015\012"); // extra data causes close ??
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
@@ -752,8 +929,13 @@
assertTrue(_headerCompleted);
assertTrue(_messageCompleted);
-
+ parser.reset();
+ parser.parseNext(buffer);
+ assertFalse(buffer.hasRemaining());
+ assertTrue(parser.isClosed());
}
+
+
@Test
public void testNoURI() throws Exception
@@ -1136,7 +1318,7 @@
_versionOrReason=null;
_hdr=null;
_val=null;
- _h=0;
+ _headers=0;
_headerCompleted=false;
_messageCompleted=false;
}
@@ -1151,15 +1333,15 @@
private List<HttpField> _fields=new ArrayList<>();
private String[] _hdr;
private String[] _val;
- private int _h;
-
+ private int _headers;
+
+ private boolean _early;
private boolean _headerCompleted;
private boolean _messageCompleted;
private class Handler implements HttpParser.RequestHandler<ByteBuffer>, HttpParser.ResponseHandler<ByteBuffer>
{
private HttpFields fields;
- private boolean request;
@Override
public boolean content(ByteBuffer ref)
@@ -1177,8 +1359,7 @@
public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
{
_fields.clear();
- request=true;
- _h= -1;
+ _headers= -1;
_hdr= new String[10];
_val= new String[10];
_methodOrVersion= method;
@@ -1188,6 +1369,7 @@
fields=new HttpFields();
_messageCompleted = false;
_headerCompleted = false;
+ _early=false;
return false;
}
@@ -1196,8 +1378,8 @@
{
_fields.add(field);
//System.err.println("header "+name+": "+value);
- _hdr[++_h]= field.getName();
- _val[_h]= field.getValue();
+ _hdr[++_headers]= field.getName();
+ _val[_headers]= field.getValue();
return false;
}
@@ -1243,7 +1425,6 @@
public boolean startResponse(HttpVersion version, int status, String reason)
{
_fields.clear();
- request=false;
_methodOrVersion = version.asString();
_uriOrStatus = Integer.toString(status);
_versionOrReason = reason;
@@ -1260,6 +1441,7 @@
@Override
public void earlyEOF()
{
+ _early=true;
}
@Override
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java
index f400ad2..6c9452b 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import org.junit.Ignore;
import org.junit.Test;
/**
@@ -30,6 +31,7 @@
public class PathMapTest
{
@Test
+ @Ignore
public void testPathMap() throws Exception
{
PathMap<String> p = new PathMap<>();
diff --git a/jetty-io/pom.xml b/jetty-io/pom.xml
index a90c9cb..9260af0 100644
--- a/jetty-io/pom.xml
+++ b/jetty-io/pom.xml
@@ -2,7 +2,7 @@
<parent>
<artifactId>jetty-project</artifactId>
<groupId>org.eclipse.jetty</groupId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-io</artifactId>
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
index 002472a..3f7cfc7 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
@@ -24,7 +24,6 @@
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
-import org.eclipse.jetty.util.BlockingCallback;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -43,7 +42,7 @@
public static final boolean EXECUTE_ONFILLABLE=true;
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
- private final AtomicReference<State> _state = new AtomicReference<>(State.IDLE);
+ private final AtomicReference<State> _state = new AtomicReference<>(IDLE);
private final long _created=System.currentTimeMillis();
private final EndPoint _endPoint;
private final Executor _executor;
@@ -64,6 +63,7 @@
_executor = executor;
_readCallback = new ReadCallback();
_executeOnfillable=executeOnfillable;
+ _state.set(IDLE);
}
@Override
@@ -95,149 +95,32 @@
*/
public void fillInterested()
{
+ LOG.debug("fillInterested {}",this);
+
+ while(true)
+ {
+ State state=_state.get();
+ if (next(state,state.fillInterested()))
+ break;
+ }
+ }
+
+ public void fillInterested(Callback callback)
+ {
LOG.debug("fillInterested {}",this);
- loop:while(true)
+ while(true)
{
- switch(_state.get())
- {
- case IDLE:
- if (_state.compareAndSet(State.IDLE,State.INTERESTED))
- {
- getEndPoint().fillInterested(_readCallback);
- break loop;
- }
- break;
-
- case FILLING:
- if (_state.compareAndSet(State.FILLING,State.FILLING_INTERESTED))
- break loop;
- break;
-
- case FILLING_BLOCKED:
- if (_state.compareAndSet(State.FILLING_BLOCKED,State.FILLING_BLOCKED_INTERESTED))
- break loop;
- break;
-
- case BLOCKED:
- if (_state.compareAndSet(State.BLOCKED,State.BLOCKED_INTERESTED))
- break loop;
- break;
-
- case FILLING_BLOCKED_INTERESTED:
- case FILLING_INTERESTED:
- case BLOCKED_INTERESTED:
- case INTERESTED:
- break loop;
- }
+ State state=_state.get();
+ // TODO yuck
+ if (state instanceof FillingInterestedCallback && ((FillingInterestedCallback)state)._callback==callback)
+ break;
+ State next=new FillingInterestedCallback(callback,state);
+ if (next(state,next))
+ break;
}
}
-
- private void unblock()
- {
- LOG.debug("unblock {}",this);
-
- loop:while(true)
- {
- switch(_state.get())
- {
- case FILLING_BLOCKED:
- if (_state.compareAndSet(State.FILLING_BLOCKED,State.FILLING))
- break loop;
- break;
-
- case FILLING_BLOCKED_INTERESTED:
- if (_state.compareAndSet(State.FILLING_BLOCKED_INTERESTED,State.FILLING_INTERESTED))
- break loop;
- break;
-
- case BLOCKED_INTERESTED:
- if (_state.compareAndSet(State.BLOCKED_INTERESTED,State.INTERESTED))
- {
- getEndPoint().fillInterested(_readCallback);
- break loop;
- }
- break;
-
- case BLOCKED:
- if (_state.compareAndSet(State.BLOCKED,State.IDLE))
- break loop;
- break;
-
- case FILLING:
- case IDLE:
- case FILLING_INTERESTED:
- case INTERESTED:
- break loop;
- }
- }
- }
-
-
- /**
- */
- protected void block(final BlockingCallback callback)
- {
- LOG.debug("block {}",this);
-
- final Callback blocked=new Callback()
- {
- @Override
- public void succeeded()
- {
- unblock();
- callback.succeeded();
- }
-
- @Override
- public void failed(Throwable x)
- {
- unblock();
- callback.failed(x);
- }
- };
-
- loop:while(true)
- {
- switch(_state.get())
- {
- case IDLE:
- if (_state.compareAndSet(State.IDLE,State.BLOCKED))
- {
- getEndPoint().fillInterested(blocked);
- break loop;
- }
- break;
-
- case FILLING:
- if (_state.compareAndSet(State.FILLING,State.FILLING_BLOCKED))
- {
- getEndPoint().fillInterested(blocked);
- break loop;
- }
- break;
-
- case FILLING_INTERESTED:
- if (_state.compareAndSet(State.FILLING_INTERESTED,State.FILLING_BLOCKED_INTERESTED))
- {
- getEndPoint().fillInterested(blocked);
- break loop;
- }
- break;
-
- case BLOCKED:
- case BLOCKED_INTERESTED:
- case FILLING_BLOCKED:
- case FILLING_BLOCKED_INTERESTED:
- throw new IllegalStateException("Already Blocked");
-
- case INTERESTED:
- throw new IllegalStateException();
- }
- }
- }
-
/**
* <p>Callback method invoked when the endpoint is ready to be read.</p>
* @see #fillInterested()
@@ -264,6 +147,9 @@
_endPoint.shutdownOutput();
}
}
+
+ if (_endPoint.isOpen())
+ fillInterested();
}
/**
@@ -340,73 +226,308 @@
{
return String.format("%s@%x{%s}", getClass().getSimpleName(), hashCode(), _state.get());
}
-
- private enum State
+
+ public boolean next(State state, State next)
{
- IDLE, INTERESTED, FILLING, FILLING_INTERESTED, FILLING_BLOCKED, BLOCKED, FILLING_BLOCKED_INTERESTED, BLOCKED_INTERESTED
+ if (next==null)
+ return true;
+ if(_state.compareAndSet(state,next))
+ {
+ LOG.debug("{}-->{} {}",state,next,this);
+ if (next!=state)
+ next.onEnter(AbstractConnection.this);
+ return true;
+ }
+ return false;
}
- private class ReadCallback implements Callback, Runnable
+ private static final class IdleState extends State
+ {
+ private IdleState()
+ {
+ super("IDLE");
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return FILL_INTERESTED;
+ }
+ }
+
+
+ private static final class FillInterestedState extends State
+ {
+ private FillInterestedState()
+ {
+ super("FILL_INTERESTED");
+ }
+
+ @Override
+ public void onEnter(AbstractConnection connection)
+ {
+ connection.getEndPoint().fillInterested(connection._readCallback);
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return this;
+ }
+
+ @Override
+ public State onFillable()
+ {
+ return FILLING;
+ }
+
+ @Override
+ State onFailed()
+ {
+ return IDLE;
+ }
+ }
+
+
+ private static final class RefillingState extends State
+ {
+ private RefillingState()
+ {
+ super("REFILLING");
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return FILLING_FILL_INTERESTED;
+ }
+
+ @Override
+ public State onFilled()
+ {
+ return IDLE;
+ }
+ }
+
+
+ private static final class FillingFillInterestedState extends State
+ {
+ private FillingFillInterestedState(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return this;
+ }
+
+ State onFilled()
+ {
+ return FILL_INTERESTED;
+ }
+ }
+
+
+ private static final class FillingState extends State
+ {
+ private FillingState()
+ {
+ super("FILLING");
+ }
+
+ @Override
+ public void onEnter(AbstractConnection connection)
+ {
+ if (connection._executeOnfillable)
+ connection.getExecutor().execute(connection._runOnFillable);
+ else
+ connection._runOnFillable.run();
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return FILLING_FILL_INTERESTED;
+ }
+
+ @Override
+ public State onFilled()
+ {
+ return IDLE;
+ }
+ }
+
+
+ public static class State
+ {
+ private final String _name;
+ State(String name)
+ {
+ _name=name;
+ }
+
+ @Override
+ public String toString()
+ {
+ return _name;
+ }
+
+ void onEnter(AbstractConnection connection)
+ {
+ }
+
+ State fillInterested()
+ {
+ throw new IllegalStateException(this.toString());
+ }
+
+ State onFillable()
+ {
+ throw new IllegalStateException(this.toString());
+ }
+
+ State onFilled()
+ {
+ throw new IllegalStateException(this.toString());
+ }
+
+ State onFailed()
+ {
+ throw new IllegalStateException(this.toString());
+ }
+ }
+
+
+ public static final State IDLE=new IdleState();
+
+ public static final State FILL_INTERESTED=new FillInterestedState();
+
+ public static final State FILLING=new FillingState();
+
+ public static final State REFILLING=new RefillingState();
+
+ public static final State FILLING_FILL_INTERESTED=new FillingFillInterestedState("FILLING_FILL_INTERESTED");
+
+ public class NestedState extends State
+ {
+ private final State _nested;
+
+ NestedState(State nested)
+ {
+ super("NESTED("+nested+")");
+ _nested=nested;
+ }
+ NestedState(String name,State nested)
+ {
+ super(name+"("+nested+")");
+ _nested=nested;
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return new NestedState(_nested.fillInterested());
+ }
+
+ @Override
+ State onFillable()
+ {
+ return new NestedState(_nested.onFillable());
+ }
+
+ @Override
+ State onFilled()
+ {
+ return new NestedState(_nested.onFilled());
+ }
+ }
+
+
+ public class FillingInterestedCallback extends NestedState
+ {
+ private final Callback _callback;
+
+ FillingInterestedCallback(Callback callback,State nested)
+ {
+ super("FILLING_INTERESTED_CALLBACK",nested==FILLING?REFILLING:nested);
+ _callback=callback;
+ }
+
+ @Override
+ void onEnter(final AbstractConnection connection)
+ {
+ Callback callback=new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ while(true)
+ {
+ State state = connection._state.get();
+ if (!(state instanceof NestedState))
+ break;
+ State nested=((NestedState)state)._nested;
+ if (connection.next(state,nested))
+ break;
+ }
+ _callback.succeeded();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ while(true)
+ {
+ State state = connection._state.get();
+ if (!(state instanceof NestedState))
+ break;
+ State nested=((NestedState)state)._nested;
+ if (connection.next(state,nested))
+ break;
+ }
+ _callback.failed(x);
+ }
+ };
+
+ connection.getEndPoint().fillInterested(callback);
+ }
+ }
+
+ private final Runnable _runOnFillable = new Runnable()
{
@Override
public void run()
{
- if (_state.compareAndSet(State.INTERESTED,State.FILLING))
+ try
{
- try
+ onFillable();
+ }
+ finally
+ {
+ while(true)
{
- onFillable();
- }
- finally
- {
- loop:while(true)
- {
- switch(_state.get())
- {
- case IDLE:
- case INTERESTED:
- case BLOCKED:
- case BLOCKED_INTERESTED:
- LOG.warn(new IllegalStateException());
- return;
-
- case FILLING:
- if (_state.compareAndSet(State.FILLING,State.IDLE))
- break loop;
- break;
-
- case FILLING_BLOCKED:
- if (_state.compareAndSet(State.FILLING_BLOCKED,State.BLOCKED))
- break loop;
- break;
-
- case FILLING_BLOCKED_INTERESTED:
- if (_state.compareAndSet(State.FILLING_BLOCKED_INTERESTED,State.BLOCKED_INTERESTED))
- break loop;
- break;
-
- case FILLING_INTERESTED:
- if (_state.compareAndSet(State.FILLING_INTERESTED,State.INTERESTED))
- {
- getEndPoint().fillInterested(_readCallback);
- break loop;
- }
- break;
- }
- }
+ State state=_state.get();
+ if (next(state,state.onFilled()))
+ break;
}
}
- else
- LOG.warn(new IllegalStateException());
}
-
+ };
+
+
+ private class ReadCallback implements Callback
+ {
@Override
public void succeeded()
{
- if (_executeOnfillable)
- _executor.execute(this);
- else
- run();
+ while(true)
+ {
+ State state=_state.get();
+ if (next(state,state.onFillable()))
+ break;
+ }
}
@Override
@@ -417,6 +538,12 @@
@Override
public void run()
{
+ while(true)
+ {
+ State state=_state.get();
+ if (next(state,state.onFailed()))
+ break;
+ }
onFillInterestedFailed(x);
}
});
@@ -425,7 +552,7 @@
@Override
public String toString()
{
- return String.format("AC.ExReadCB@%x", AbstractConnection.this.hashCode());
+ return String.format("AC.ReadCB@%x{%s}", AbstractConnection.this.hashCode(),AbstractConnection.this);
}
};
}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
index 4ec1d84..9ec9e60 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
@@ -152,17 +152,17 @@
@Override
public String toString()
{
- return String.format("%s@%x{%s<r-l>%s,o=%b,is=%b,os=%b,fi=%s,wf=%s,it=%d}{%s}",
+ return String.format("%s@%x{%s<->%d,%s,%s,%s,%s,%s,%d,%s}",
getClass().getSimpleName(),
hashCode(),
getRemoteAddress(),
- getLocalAddress(),
- isOpen(),
- isInputShutdown(),
- isOutputShutdown(),
- _fillInterest,
- _writeFlusher,
+ getLocalAddress().getPort(),
+ isOpen()?"Open":"CLOSED",
+ isInputShutdown()?"ISHUT":"in",
+ isOutputShutdown()?"OSHUT":"out",
+ _fillInterest.isInterested()?"R":"-",
+ _writeFlusher.isInProgress()?"W":"-",
getIdleTimeout(),
- getConnection());
+ getConnection()==null?null:getConnection().getClass().getSimpleName());
}
}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java
index e515e03..263e247 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java
@@ -33,7 +33,7 @@
public ArrayByteBufferPool()
{
- this(64,2048,64*1024);
+ this(0,1024,64*1024);
}
public ArrayByteBufferPool(int minSize, int increment, int maxSize)
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java
index 460b298..6dea1f9 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java
@@ -26,7 +26,6 @@
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java
index 95480a5..f69af7f 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java
@@ -35,7 +35,7 @@
* Channel End Point.
* <p>Holds the channel and socket for an NIO endpoint.
*/
-public class ChannelEndPoint extends AbstractEndPoint implements SocketBased
+public class ChannelEndPoint extends AbstractEndPoint
{
private static final Logger LOG = Log.getLogger(ChannelEndPoint.class);
@@ -134,7 +134,8 @@
try
{
int filled = _channel.read(buffer);
- LOG.debug("filled {} {}", filled, this);
+ if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+ LOG.debug("filled {} {}", filled, this);
if (filled>0)
notIdle();
@@ -185,14 +186,14 @@
{
throw new EofException(e);
}
-
+
if (flushed>0)
notIdle();
for (ByteBuffer b : buffers)
if (!BufferUtil.isEmpty(b))
return false;
-
+
return true;
}
@@ -207,7 +208,6 @@
return _channel;
}
- @Override
public Socket getSocket()
{
return _socket;
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
new file mode 100644
index 0000000..075e261
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
@@ -0,0 +1,89 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Factory for client-side {@link Connection} instances.
+ */
+public interface ClientConnectionFactory
+{
+ /**
+ *
+ * @param endPoint the {@link org.eclipse.jetty.io.EndPoint} to link the newly created connection to
+ * @param context the context data to create the connection
+ * @return a new {@link Connection}
+ * @throws IOException if the connection cannot be created
+ */
+ public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException;
+
+ public static class Helper
+ {
+ private static Logger LOG = Log.getLogger(Helper.class);
+
+ private Helper()
+ {
+ }
+
+ /**
+ * Replaces the given {@code oldConnection} with the given {@code newConnection} on the
+ * {@link EndPoint} associated with {@code oldConnection}, performing connection lifecycle management.
+ * <p />
+ * The {@code oldConnection} will be closed by invoking {@link org.eclipse.jetty.io.Connection#onClose()}
+ * and the {@code newConnection} will be opened by invoking {@link org.eclipse.jetty.io.Connection#onOpen()}.
+ * @param oldConnection the old connection to replace
+ * @param newConnection the new connection replacement
+ */
+ public static void replaceConnection(Connection oldConnection, Connection newConnection)
+ {
+ close(oldConnection);
+ oldConnection.getEndPoint().setConnection(newConnection);
+ open(newConnection);
+ }
+
+ private static void open(Connection connection)
+ {
+ try
+ {
+ connection.onOpen();
+ }
+ catch (Throwable x)
+ {
+ LOG.debug(x);
+ }
+ }
+
+ private static void close(Connection connection)
+ {
+ try
+ {
+ connection.onClose();
+ }
+ catch (Throwable x)
+ {
+ LOG.debug(x);
+ }
+ }
+ }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java
index 5ac5c72..912dc20 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java
@@ -73,7 +73,7 @@
public void onClosed(Connection connection);
- public static class Empty implements Listener
+ public static class Adapter implements Listener
{
@Override
public void onOpened(Connection connection)
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java
index ff416b8..c6ab8ba 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java
@@ -26,11 +26,9 @@
import java.nio.channels.WritePendingException;
import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.util.ExecutorCallback;
import org.eclipse.jetty.util.FutureCallback;
-
/**
*
* A transport EndPoint
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java
index 6af94f5..41707b2 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java
@@ -21,10 +21,11 @@
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadPendingException;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
@@ -35,6 +36,7 @@
*/
public abstract class FillInterest
{
+ private final static Logger LOG = Log.getLogger(FillInterest.class);
private final AtomicReference<Callback> _interested = new AtomicReference<>(null);
/* ------------------------------------------------------------ */
@@ -46,7 +48,6 @@
/** Call to register interest in a callback when a read is possible.
* The callback will be called either immediately if {@link #needsFill()}
* returns true or eventually once {@link #fillable()} is called.
- * @param context
* @param callback
* @throws ReadPendingException
*/
@@ -56,7 +57,10 @@
throw new IllegalArgumentException();
if (!_interested.compareAndSet(null,callback))
+ {
+ LOG.warn("Read pending for "+_interested.get()+" pervented "+callback);
throw new ReadPendingException();
+ }
try
{
if (needsFill())
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java
index bfbf39d..d0de44d 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java
@@ -79,7 +79,7 @@
/**
* <p>A commodity class that implements {@link NetworkTrafficListener} with empty methods.</p>
*/
- public static class Empty implements NetworkTrafficListener
+ public static class Adapter implements NetworkTrafficListener
{
public void opened(Socket socket)
{
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectChannelEndPoint.java
index 7f012d9..b3bf8e8 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectChannelEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectChannelEndPoint.java
@@ -133,7 +133,7 @@
if (_interestOps.compareAndSet(oldInterestOps, newInterestOps))
{
LOG.debug("Local interests updated {} -> {} for {}", oldInterestOps, newInterestOps, this);
- _selector.submit(_updateTask);
+ _selector.updateKey(_updateTask);
}
else
{
@@ -152,7 +152,6 @@
private void setKeyInterests(int oldInterestOps, int newInterestOps)
{
- assert _selector.isSelectorThread();
LOG.debug("Key interests updated {} -> {}", oldInterestOps, newInterestOps);
_key.interestOps(newInterestOps);
}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
index 4e0a932..8ab87da 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
@@ -37,6 +37,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.ConcurrentArrayQueue;
import org.eclipse.jetty.util.TypeUtil;
@@ -55,12 +56,11 @@
*/
public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
{
- protected static final Logger LOG = Log.getLogger(SelectorManager.class);
- /**
- * The default connect timeout, in milliseconds
- */
+ public static final String SUBMIT_KEY_UPDATES = "org.eclipse.jetty.io.SelectorManager.submitKeyUpdates";
public static final int DEFAULT_CONNECT_TIMEOUT = 15000;
-
+ protected static final Logger LOG = Log.getLogger(SelectorManager.class);
+ private final static boolean __submitKeyUpdates = Boolean.valueOf(System.getProperty(SUBMIT_KEY_UPDATES, "false"));
+
private final Executor executor;
private final Scheduler scheduler;
private final ManagedSelector[] _selectors;
@@ -74,6 +74,8 @@
protected SelectorManager(Executor executor, Scheduler scheduler, int selectors)
{
+ if (selectors<=0)
+ throw new IllegalArgumentException("No selectors");
this.executor = executor;
this.scheduler = scheduler;
_selectors = new ManagedSelector[selectors];
@@ -163,6 +165,34 @@
final ManagedSelector selector = chooseSelector();
selector.submit(selector.new Accept(channel));
}
+
+ /**
+ * <p>Registers a server channel for accept operations.
+ * When a {@link SocketChannel} is accepted from the given {@link ServerSocketChannel}
+ * then the {@link #accepted(SocketChannel)} method is called, which must be
+ * overridden by a derivation of this class to handle the accepted channel
+ *
+ * @param server the server channel to register
+ */
+ public void acceptor(final ServerSocketChannel server)
+ {
+ final ManagedSelector selector = chooseSelector();
+ selector.submit(selector.new Acceptor(server));
+ }
+
+ /**
+ * Callback method when a channel is accepted from the {@link ServerSocketChannel}
+ * passed to {@link #acceptor(ServerSocketChannel)}.
+ * The default impl throws an {@link UnsupportedOperationException}, so it must
+ * be overridden by subclasses if a server channel is provided.
+ *
+ * @param channel the
+ * @throws IOException
+ */
+ protected void accepted(SocketChannel channel) throws IOException
+ {
+ throw new UnsupportedOperationException();
+ }
@Override
protected void doStart() throws Exception
@@ -310,6 +340,11 @@
ContainerLifeCycle.dump(out, indent, TypeUtil.asList(_selectors));
}
+ private enum State
+ {
+ CHANGES, MORE_CHANGES, SELECT, WAKEUP, PROCESS
+ }
+
/**
* <p>{@link ManagedSelector} wraps a {@link Selector} simplifying non-blocking operations on channels.</p>
* <p>{@link ManagedSelector} runs the select loop, which waits on {@link Selector#select()} until events
@@ -318,13 +353,11 @@
*/
public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
{
+ private final AtomicReference<State> _state= new AtomicReference<>(State.PROCESS);
private final Queue<Runnable> _changes = new ConcurrentArrayQueue<>();
-
private final int _id;
private Selector _selector;
private volatile Thread _thread;
- private boolean _needsWakeup = true;
- private boolean _runningChanges = false;
public ManagedSelector(int id)
{
@@ -337,6 +370,7 @@
{
super.doStart();
_selector = Selector.open();
+ _state.set(State.PROCESS);
}
@Override
@@ -350,6 +384,26 @@
}
/**
+ * Submit a task to update a selector key. If the System property {@link SelectorManager#SUBMIT_KEY_UPDATES}
+ * is set true (default is false), the task is passed to {@link #submit(Runnable)}. Otherwise it is run immediately and the selector
+ * woken up if need be.
+ * @param update the update to a key
+ */
+ public void updateKey(Runnable update)
+ {
+ if (__submitKeyUpdates)
+ {
+ submit(update);
+ }
+ else
+ {
+ runChange(update);
+ if (_state.compareAndSet(State.SELECT, State.WAKEUP))
+ wakeup();
+ }
+ }
+
+ /**
* <p>Submits a change to be executed in the selector thread.</p>
* <p>Changes may be submitted from any thread, and the selector thread woken up
* (if necessary) to execute the change.</p>
@@ -358,48 +412,50 @@
*/
public void submit(Runnable change)
{
- // if we have been called by the selector thread we can directly run the change
- if (_thread==Thread.currentThread())
+ // This method may be called from the selector thread, and therefore
+ // we could directly run the change without queueing, but this may
+ // lead to stack overflows on a busy server, so we always offer the
+ // change to the queue and process the state.
+
+ _changes.offer(change);
+ LOG.debug("Queued change {}", change);
+
+ out: while (true)
{
- // If we are already iterating over the changes, just add this change to the list.
- // No race here because it is this thread that is iterating over the changes.
- if (_runningChanges)
- _changes.offer(change);
- else
+ switch (_state.get())
{
- // Otherwise we run the queued changes
- runChanges();
- // and then directly run the passed change
- runChange(change);
+ case SELECT:
+ // Avoid multiple wakeup() calls if we the CAS fails
+ if (!_state.compareAndSet(State.SELECT, State.WAKEUP))
+ continue;
+ wakeup();
+ break out;
+ case CHANGES:
+ // Tell the selector thread that we have more changes.
+ // If we fail to CAS, we possibly need to wakeup(), so loop.
+ if (_state.compareAndSet(State.CHANGES, State.MORE_CHANGES))
+ break out;
+ continue;
+ case WAKEUP:
+ // Do nothing, we have already a wakeup scheduled
+ break out;
+ case MORE_CHANGES:
+ // Do nothing, we already notified the selector thread of more changes
+ break out;
+ case PROCESS:
+ // Do nothing, the changes will be run after the processing
+ break out;
+ default:
+ throw new IllegalStateException();
}
}
- else
- {
- // otherwise we have to queue the change and wakeup the selector
- _changes.offer(change);
- LOG.debug("Queued change {}", change);
- boolean wakeup = _needsWakeup;
- if (wakeup)
- wakeup();
- }
}
private void runChanges()
{
- try
- {
- if (_runningChanges)
- throw new IllegalStateException();
- _runningChanges=true;
-
- Runnable change;
- while ((change = _changes.poll()) != null)
- runChange(change);
- }
- finally
- {
- _runningChanges=false;
- }
+ Runnable change;
+ while ((change = _changes.poll()) != null)
+ runChange(change);
}
protected void runChange(Runnable change)
@@ -422,11 +478,11 @@
String name = _thread.getName();
try
{
- _thread.setName(name + "-selector-" + _id);
+ _thread.setName(name + "-selector-" + SelectorManager.this.getClass().getSimpleName()+"@"+Integer.toHexString(SelectorManager.this.hashCode())+"/"+_id);
LOG.debug("Starting {} on {}", _thread, this);
while (isRunning())
select();
- processChanges();
+ runChanges();
}
finally
{
@@ -445,7 +501,30 @@
boolean debug = LOG.isDebugEnabled();
try
{
- processChanges();
+ _state.set(State.CHANGES);
+
+ // Run the changes, and only exit if we ran all changes
+ out: while(true)
+ {
+ switch (_state.get())
+ {
+ case CHANGES:
+ runChanges();
+ if (_state.compareAndSet(State.CHANGES, State.SELECT))
+ break out;
+ continue;
+ case MORE_CHANGES:
+ runChanges();
+ _state.set(State.CHANGES);
+ continue;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+ // Must check first for SELECT and *then* for WAKEUP
+ // because we read the state twice in the assert, and
+ // it could change from SELECT to WAKEUP in between.
+ assert _state.get() == State.SELECT || _state.get() == State.WAKEUP;
if (debug)
LOG.debug("Selector loop waiting on select");
@@ -453,7 +532,7 @@
if (debug)
LOG.debug("Selector loop woken up from select, {}/{} selected", selected, _selector.keys().size());
- _needsWakeup = false;
+ _state.set(State.PROCESS);
Set<SelectionKey> selectedKeys = _selector.selectedKeys();
for (SelectionKey key : selectedKeys)
@@ -482,20 +561,6 @@
}
}
- private void processChanges()
- {
- runChanges();
-
- // If tasks are submitted between these 2 statements, they will not
- // wakeup the selector, therefore below we run again the tasks
-
- _needsWakeup = true;
-
- // Run again the tasks to avoid the race condition where a task is
- // submitted but will not wake up the selector
- runChanges();
- }
-
private void processKey(SelectionKey key)
{
Object attachment = key.attachment();
@@ -509,6 +574,10 @@
{
processConnect(key, (Connect)attachment);
}
+ else if (key.isAcceptable())
+ {
+ processAccept(key);
+ }
else
{
throw new IllegalStateException();
@@ -518,13 +587,13 @@
{
LOG.debug("Ignoring cancelled key for channel {}", key.channel());
if (attachment instanceof EndPoint)
- ((EndPoint)attachment).close();
+ closeNoExceptions((EndPoint)attachment);
}
catch (Throwable x)
{
LOG.warn("Could not process key for channel " + key.channel(), x);
if (attachment instanceof EndPoint)
- ((EndPoint)attachment).close();
+ closeNoExceptions((EndPoint)attachment);
}
}
@@ -552,12 +621,31 @@
connect.failed(x);
}
}
+
+ private void processAccept(SelectionKey key)
+ {
+ ServerSocketChannel server = (ServerSocketChannel)key.channel();
+ SocketChannel channel = null;
+ try
+ {
+ while ((channel = server.accept()) != null)
+ {
+ accepted(channel);
+ }
+ }
+ catch (Throwable x)
+ {
+ closeNoExceptions(channel);
+ LOG.warn("Accept failed for channel " + channel, x);
+ }
+ }
private void closeNoExceptions(Closeable closeable)
{
try
{
- closeable.close();
+ if (closeable != null)
+ closeable.close();
}
catch (Throwable x)
{
@@ -688,6 +776,31 @@
}
}
+ private class Acceptor implements Runnable
+ {
+ private final ServerSocketChannel _channel;
+
+ public Acceptor(ServerSocketChannel channel)
+ {
+ this._channel = channel;
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ SelectionKey key = _channel.register(_selector, SelectionKey.OP_ACCEPT, null);
+ LOG.debug("{} acceptor={}", this, key);
+ }
+ catch (Throwable x)
+ {
+ closeNoExceptions(_channel);
+ LOG.warn(x);
+ }
+ }
+ }
+
private class Accept implements Runnable
{
private final SocketChannel _channel;
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SocketBased.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SocketBased.java
deleted file mode 100644
index f6e17eb..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/SocketBased.java
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import java.net.Socket;
-
-/**
- * Interface for Socket based I/O
- */
-public interface SocketBased
-{
- public Socket getSocket();
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java
index 8b3def6..6d3c587 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java
@@ -314,7 +314,7 @@
* Tries to switch state to WRITING. If successful it writes the given buffers to the EndPoint. If state transition
* fails it'll fail the callback.
*
- * If not all buffers can be written in one go it creates a new {@link PendingState} object to preserve the state
+ * If not all buffers can be written in one go it creates a new <code>PendingState</code> object to preserve the state
* and then calls {@link #onIncompleteFlushed()}. The remaining buffers will be written in {@link #completeWrite()}.
*
* If all buffers have been written it calls callback.complete().
@@ -432,9 +432,6 @@
public void onFail(Throwable cause)
{
- if (DEBUG)
- LOG.debug("failed: {} {}", this, cause);
-
// Keep trying to handle the failure until we get to IDLE or FAILED state
while(true)
{
@@ -443,9 +440,14 @@
{
case IDLE:
case FAILED:
+ if (DEBUG)
+ LOG.debug("ignored: {} {}", this, cause);
return;
case PENDING:
+ if (DEBUG)
+ LOG.debug("failed: {} {}", this, cause);
+
PendingState pending = (PendingState)current;
if (updateState(pending,__IDLE))
{
@@ -455,6 +457,9 @@
break;
default:
+ if (DEBUG)
+ LOG.debug("failed: {} {}", this, cause);
+
if (updateState(current,new FailedState(cause)))
return;
break;
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java
new file mode 100644
index 0000000..d0e0f77
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java
@@ -0,0 +1,73 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io.ssl;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SslClientConnectionFactory implements ClientConnectionFactory
+{
+ public static final String SSL_PEER_HOST_CONTEXT_KEY = "ssl.peer.host";
+ public static final String SSL_PEER_PORT_CONTEXT_KEY = "ssl.peer.port";
+ public static final String SSL_ENGINE_CONTEXT_KEY = "ssl.engine";
+
+ private final SslContextFactory sslContextFactory;
+ private final ByteBufferPool byteBufferPool;
+ private final Executor executor;
+ private final ClientConnectionFactory connectionFactory;
+
+ public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
+ {
+ this.sslContextFactory = sslContextFactory;
+ this.byteBufferPool = byteBufferPool;
+ this.executor = executor;
+ this.connectionFactory = connectionFactory;
+ }
+
+ @Override
+ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+ {
+ String host = (String)context.get(SSL_PEER_HOST_CONTEXT_KEY);
+ int port = (Integer)context.get(SSL_PEER_PORT_CONTEXT_KEY);
+ SSLEngine engine = sslContextFactory.newSSLEngine(host, port);
+ engine.setUseClientMode(true);
+ context.put(SSL_ENGINE_CONTEXT_KEY, engine);
+
+ SslConnection sslConnection = newSslConnection(byteBufferPool, executor, endPoint, engine);
+ sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
+ endPoint.setConnection(sslConnection);
+ EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
+ appEndPoint.setConnection(connectionFactory.newConnection(appEndPoint, context));
+
+ return sslConnection;
+ }
+
+ protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
+ {
+ return new SslConnection(byteBufferPool, executor, endPoint, engine);
+ }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
index 43a9969..cb9f9b6 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
@@ -23,6 +23,7 @@
import java.nio.channels.ClosedChannelException;
import java.util.Arrays;
import java.util.concurrent.Executor;
+
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
@@ -107,21 +108,6 @@
this._bufferPool = byteBufferPool;
this._sslEngine = sslEngine;
this._decryptedEndPoint = newDecryptedEndPoint();
-
- // commented out for now as it might cause native code being stuck in preClose0.
- // See: https://java.net/jira/browse/GRIZZLY-547
-
-// if (endPoint instanceof SocketBased)
-// {
-// try
-// {
-// ((SocketBased)endPoint).getSocket().setSoLinger(true, 30000);
-// }
-// catch (SocketException e)
-// {
-// throw new RuntimeIOException(e);
-// }
-// }
}
protected DecryptedEndPoint newDecryptedEndPoint()
@@ -647,11 +633,6 @@
}
}
}
- catch (SSLException e)
- {
- getEndPoint().close();
- throw new EofException(e);
- }
catch (Exception e)
{
getEndPoint().close();
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java
index 2d77b93..eb9c2ca 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java
@@ -29,7 +29,6 @@
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
-import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
@@ -50,15 +49,13 @@
public class IOTest
{
@Test
- public void testIO() throws InterruptedException
+ public void testIO() throws Exception
{
// Only a little test
ByteArrayInputStream in = new ByteArrayInputStream("The quick brown fox jumped over the lazy dog".getBytes());
ByteArrayOutputStream out = new ByteArrayOutputStream();
- IO.copyThread(in, out);
- Thread.sleep(1500);
- // System.err.println(out);
+ IO.copy(in, out);
assertEquals("copyThread", out.toString(), "The quick brown fox jumped over the lazy dog");
}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/IdleTimeoutTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/IdleTimeoutTest.java
index 5c4bb4e..d3974fc 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/IdleTimeoutTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/IdleTimeoutTest.java
@@ -22,6 +22,7 @@
import java.util.concurrent.TimeoutException;
import junit.framework.Assert;
+
import org.eclipse.jetty.util.thread.TimerScheduler;
import org.junit.After;
import org.junit.Before;
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java
index c796e86..e5fffd6 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java
@@ -18,12 +18,17 @@
package org.eclipse.jetty.io;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
+
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
@@ -40,10 +45,6 @@
import org.junit.Ignore;
import org.junit.Test;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-
public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest
{
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointTest.java
index 2050706..2f2801d 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointTest.java
@@ -18,6 +18,12 @@
package org.eclipse.jetty.io;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
@@ -36,7 +42,6 @@
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.FutureCallback;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
@@ -47,12 +52,6 @@
import org.junit.Before;
import org.junit.Test;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
public class SelectChannelEndPointTest
{
private static final Logger LOG = Log.getLogger(SelectChannelEndPointTest.class);
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
index 4167a2d..f171065 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
@@ -26,6 +26,7 @@
import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.Callback;
@@ -67,6 +68,7 @@
client.configureBlocking(false);
client.connect(address);
+ final AtomicBoolean timeoutConnection = new AtomicBoolean();
final long connectTimeout = 1000;
SelectorManager selectorManager = new SelectorManager(executor, scheduler)
{
@@ -81,7 +83,8 @@
{
try
{
- TimeUnit.MILLISECONDS.sleep(connectTimeout * 2);
+ if (timeoutConnection.get())
+ TimeUnit.MILLISECONDS.sleep(connectTimeout * 2);
return super.finishConnect(channel);
}
catch (InterruptedException e)
@@ -113,17 +116,30 @@
try
{
- final CountDownLatch latch = new CountDownLatch(1);
+ timeoutConnection.set(true);
+ final CountDownLatch latch1 = new CountDownLatch(1);
selectorManager.connect(client, new Callback.Adapter()
{
@Override
public void failed(Throwable x)
{
- latch.countDown();
+ latch1.countDown();
}
});
+ Assert.assertTrue(latch1.await(connectTimeout * 3, TimeUnit.MILLISECONDS));
- Assert.assertTrue(latch.await(connectTimeout * 3, TimeUnit.MILLISECONDS));
+ // Verify that after the failure we can connect successfully
+ timeoutConnection.set(false);
+ final CountDownLatch latch2 = new CountDownLatch(1);
+ selectorManager.connect(client, new Callback.Adapter()
+ {
+ @Override
+ public void failed(Throwable x)
+ {
+ latch2.countDown();
+ }
+ });
+ Assert.assertTrue(latch2.await(connectTimeout, TimeUnit.MILLISECONDS));
}
finally
{
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
index 83a2c40..c944ed0 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
@@ -31,6 +31,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocket;
@@ -38,7 +39,6 @@
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.FutureCallback;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.Scheduler;
@@ -62,12 +62,11 @@
final AtomicInteger _dispatches = new AtomicInteger();
protected QueuedThreadPool _threadPool = new QueuedThreadPool()
{
-
@Override
- public boolean dispatch(Runnable job)
+ public void execute(Runnable job)
{
_dispatches.incrementAndGet();
- return super.dispatch(job);
+ super.execute(job);
}
};
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java
index 85fe4f5..fcb1dd8 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java
@@ -18,6 +18,15 @@
package org.eclipse.jetty.io;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.when;
+
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.WritePendingException;
@@ -46,15 +55,6 @@
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.when;
-
@RunWith(MockitoJUnitRunner.class)
public class WriteFlusherTest
{
diff --git a/jetty-jaas/pom.xml b/jetty-jaas/pom.xml
index 889c13e..bbe85af 100644
--- a/jetty-jaas/pom.xml
+++ b/jetty-jaas/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-jaas</artifactId>
@@ -13,9 +13,6 @@
</properties>
<build>
<plugins>
-<!--
- COMMENTED OUT UNTIL CORRECT CONFIG IS FOUND FOR Export uses clauses
--->
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
@@ -29,7 +26,7 @@
<instructions>
<_versionpolicy> </_versionpolicy>
<Import-Package>javax.sql.*,javax.security.*,javax.naming.*,
- javax.servlet.*;version="2.6.0",
+ javax.servlet.*;version="[2.6.0,3.2)",
*</Import-Package>
</instructions>
</configuration>
diff --git a/jetty-jaas/src/main/config/etc/jetty-jaas.xml b/jetty-jaas/src/main/config/etc/jetty-jaas.xml
index 7494898..9381d10 100644
--- a/jetty-jaas/src/main/config/etc/jetty-jaas.xml
+++ b/jetty-jaas/src/main/config/etc/jetty-jaas.xml
@@ -11,7 +11,7 @@
<!-- ======================================================== -->
<Call class="java.lang.System" name="setProperty">
<Arg>java.security.auth.login.config</Arg>
- <Arg><Property name="jetty.home" default="." />/<Property name="jaas.login.conf" default="etc/login.conf"/></Arg>
+ <Arg><Property name="jetty.base" default="." />/<Property name="jaas.login.conf" default="etc/login.conf"/></Arg>
</Call>
</Configure>
diff --git a/jetty-jaas/src/main/config/modules/jaas.mod b/jetty-jaas/src/main/config/modules/jaas.mod
new file mode 100644
index 0000000..4932140
--- /dev/null
+++ b/jetty-jaas/src/main/config/modules/jaas.mod
@@ -0,0 +1,16 @@
+#
+# JAAS Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-jaas-${jetty.version}.jar
+
+[xml]
+etc/jetty-jaas.xml
+
+[ini-template]
+## JAAS Configuration
+jaas.login.conf=etc/login.conf
diff --git a/jetty-jaspi/pom.xml b/jetty-jaspi/pom.xml
index 5c7a3b2..504b195 100644
--- a/jetty-jaspi/pom.xml
+++ b/jetty-jaspi/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-jaspi</artifactId>
@@ -14,6 +14,23 @@
</properties>
<build>
<plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <descriptorRef>config</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
@@ -25,7 +42,7 @@
</goals>
<configuration>
<instructions>
- <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
+ <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",*</Import-Package>
<Export-Package>org.eclipse.jetty.security.jaspi.*;version="${parsedVersion.osgiVersion}"</Export-Package>
</instructions>
</configuration>
diff --git a/jetty-jaspi/src/main/config/modules/jaspi.mod b/jetty-jaspi/src/main/config/modules/jaspi.mod
new file mode 100644
index 0000000..e7019ae
--- /dev/null
+++ b/jetty-jaspi/src/main/config/modules/jaspi.mod
@@ -0,0 +1,10 @@
+#
+# Jetty JASPI Module
+#
+
+[depend]
+security
+
+[lib]
+lib/jetty-jaspi-${jetty.version}.jar
+lib/jaspi/*.jar
diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java
index 423ffe8..e15e59a 100644
--- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java
+++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java
@@ -42,7 +42,6 @@
import org.eclipse.jetty.security.jaspi.JaspiMessageInfo;
import org.eclipse.jetty.security.jaspi.callback.CredentialValidationCallback;
import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.security.Credential;
import org.eclipse.jetty.util.security.Password;
diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java
index 90441d9..2c38a61 100644
--- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java
+++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java
@@ -36,7 +36,6 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.QuotedStringTokenizer;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
diff --git a/jetty-jmx/pom.xml b/jetty-jmx/pom.xml
index 3e527c7..571058d 100644
--- a/jetty-jmx/pom.xml
+++ b/jetty-jmx/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-jmx</artifactId>
diff --git a/jetty-jmx/src/main/config/etc/jetty-jmx-remote.xml b/jetty-jmx/src/main/config/etc/jetty-jmx-remote.xml
new file mode 100644
index 0000000..20a2dda
--- /dev/null
+++ b/jetty-jmx/src/main/config/etc/jetty-jmx-remote.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+ <!-- Add a remote JMX connector. The parameters of the constructor
+ below specify the JMX service URL, and the object name string for the
+ connector server bean. The parameters of the JMXServiceURL constructor
+ specify the protocol that clients will use to connect to the remote JMX
+ connector (RMI), the hostname of the server (local hostname), port number
+ (automatically assigned), and the URL path. Note that URL path contains
+ the RMI registry hostname and port number, that may need to be modified
+ in order to comply with the firewall requirements.
+ -->
+ <New id="ConnectorServer" class="org.eclipse.jetty.jmx.ConnectorServer">
+ <Arg>
+ <New class="javax.management.remote.JMXServiceURL">
+ <Arg type="java.lang.String">rmi</Arg>
+ <Arg type="java.lang.String" />
+ <Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg>
+ <Arg type="java.lang.String">/jndi/rmi://<SystemProperty name="jetty.jmxrmihost" default="localhost"/>:<SystemProperty name="jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg>
+ </New>
+ </Arg>
+ <Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
+ <Call name="start" />
+ </New>
+</Configure>
+
diff --git a/jetty-jmx/src/main/config/etc/jetty-jmx.xml b/jetty-jmx/src/main/config/etc/jetty-jmx.xml
index 2732c40..aca96f7 100644
--- a/jetty-jmx/src/main/config/etc/jetty-jmx.xml
+++ b/jetty-jmx/src/main/config/etc/jetty-jmx.xml
@@ -39,49 +39,5 @@
<New class="org.eclipse.jetty.util.log.Log" />
</Arg>
</Call>
-
- <!-- In order to connect to the JMX server remotely from a different
- process, possibly running on a different host, Jetty JMX module
- can create a remote JMX connector. It requires RMI registry to
- be started prior to creating the connector server because the
- JMX specification uses RMI to facilitate connections.
- -->
-
- <!-- Optionally start the RMI registry. Normally RMI registry runs on
- port 1099. The argument below can be changed in order to comply
- with the firewall requirements.
- -->
- <!--
- <Call name="createRegistry" class="java.rmi.registry.LocateRegistry">
- <Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg>
- <Call name="sleep" class="java.lang.Thread">
- <Arg type="java.lang.Integer">1000</Arg>
- </Call>
- </Call>
- -->
-
- <!-- Optionally add a remote JMX connector. The parameters of the constructor
- below specify the JMX service URL, and the object name string for the
- connector server bean. The parameters of the JMXServiceURL constructor
- specify the protocol that clients will use to connect to the remote JMX
- connector (RMI), the hostname of the server (local hostname), port number
- (automatically assigned), and the URL path. Note that URL path contains
- the RMI registry hostname and port number, that may need to be modified
- in order to comply with the firewall requirements.
- -->
- <!--
- <New id="ConnectorServer" class="org.eclipse.jetty.jmx.ConnectorServer">
- <Arg>
- <New class="javax.management.remote.JMXServiceURL">
- <Arg type="java.lang.String">rmi</Arg>
- <Arg type="java.lang.String" />
- <Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg>
- <Arg type="java.lang.String">/jndi/rmi://<SystemProperty name="jetty.jmxrmihost" default="localhost"/>:<SystemProperty name="jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg>
- </New>
- </Arg>
- <Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
- <Call name="start" />
- </New>
- -->
</Configure>
diff --git a/jetty-jmx/src/main/config/modules/jmx-remote.mod b/jetty-jmx/src/main/config/modules/jmx-remote.mod
new file mode 100644
index 0000000..3696ff5
--- /dev/null
+++ b/jetty-jmx/src/main/config/modules/jmx-remote.mod
@@ -0,0 +1,10 @@
+#
+# JMX Remote Module
+#
+
+[depend]
+jmx
+
+[xml]
+etc/jetty-jmx-remote.xml
+
diff --git a/jetty-jmx/src/main/config/modules/jmx.mod b/jetty-jmx/src/main/config/modules/jmx.mod
new file mode 100644
index 0000000..0c0ebc4
--- /dev/null
+++ b/jetty-jmx/src/main/config/modules/jmx.mod
@@ -0,0 +1,18 @@
+#
+# JMX Module
+#
+
+[lib]
+lib/jetty-jmx-${jetty.version}.jar
+
+[xml]
+etc/jetty-jmx.xml
+
+[ini-template]
+## JMX Configuration
+## Enable the "jmx-remote" module for an open port accessible by remote machines
+# jetty.jmxrmihost=localhost
+# jetty.jmxrmiport=1099
+## Strictly speaking you shouldn't need --exec to use this in most environments.
+## If this isn't working, make sure you enable --exec as well
+# -Dcom.sun.management.jmxremote
diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java
index f1f0637..2f995a8 100644
--- a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java
+++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java
@@ -19,7 +19,6 @@
package org.eclipse.jetty.jmx;
import java.io.IOException;
-import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.WeakHashMap;
diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java
index e74baf4..071b4a1 100644
--- a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java
+++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java
@@ -130,7 +130,7 @@
try
{
- Class<?> mClass = (Object.class.equals(oClass))?oClass=ObjectMBean.class:Loader.loadClass(oClass,mName,true);
+ Class<?> mClass = (Object.class.equals(oClass))?oClass=ObjectMBean.class:Loader.loadClass(oClass,mName);
LOG.debug("ObjectMbean: mbeanFor {} mClass={}", o, mClass);
@@ -590,8 +590,8 @@
* getter and setter methods. Descriptions are obtained with a call to findDescription with the
* attribute name.
*
- * @param name
- * @param metaData "description" or "access:description" or "type:access:description" where type is
+ * @param method
+ * @param attributeAnnotation "description" or "access:description" or "type:access:description" where type is
* one of: <ul>
* <li>"Object" The field/method is on the managed object.
* <li>"MBean" The field/method is on the mbean proxy object
diff --git a/jetty-jndi/pom.xml b/jetty-jndi/pom.xml
index b0e13bc..5cb1f42 100644
--- a/jetty-jndi/pom.xml
+++ b/jetty-jndi/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-jndi</artifactId>
@@ -15,6 +15,23 @@
<build>
<plugins>
<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <descriptorRef>config</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
@@ -67,6 +84,8 @@
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.mail.glassfish</artifactId>
+ <version>1.4.1.v201005082020</version>
+ <scope>provided</scope>
</dependency>
</dependencies>
</project>
diff --git a/jetty-jndi/src/main/config/modules/jndi.mod b/jetty-jndi/src/main/config/modules/jndi.mod
new file mode 100644
index 0000000..33c077c
--- /dev/null
+++ b/jetty-jndi/src/main/config/modules/jndi.mod
@@ -0,0 +1,11 @@
+#
+# JNDI Support
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-jndi-${jetty.version}.jar
+lib/jndi/*.jar
+
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java
index c56b694..20ecfd1 100644
--- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java
@@ -182,7 +182,6 @@
* @param env
* @param name
* @param parentCtx
- * @return
* @throws Exception
*/
public NamingContext newNamingContext(Object obj, ClassLoader loader, Hashtable env, Name name, Context parentCtx)
@@ -203,7 +202,6 @@
/**
* Find the naming Context for the given classloader
* @param loader
- * @return
*/
public Context getContextForClassLoader(ClassLoader loader)
{
diff --git a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java
index 82e283d..23d134f 100644
--- a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java
+++ b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java
@@ -20,7 +20,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -45,10 +44,11 @@
import javax.servlet.ServletContextListener;
import org.eclipse.jetty.jndi.NamingContext;
+import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.junit.Ignore;
import org.junit.Test;
/**
*
@@ -78,17 +78,24 @@
public void testThreadContextClassloaderAndCurrentContext()
throws Exception
{
+
+
//create a jetty context, and start it so that its classloader it created
//and it is the current context
ClassLoader currentLoader = Thread.currentThread().getContextClassLoader();
ContextHandler ch = new ContextHandler();
URLClassLoader chLoader = new URLClassLoader(new URL[0], currentLoader);
ch.setClassLoader(chLoader);
-
+ Server server = new Server();
+ HandlerList hl = new HandlerList();
+ server.setHandler(hl);
+ hl.addHandler(ch);
+
//Create another one
ContextHandler ch2 = new ContextHandler();
URLClassLoader ch2Loader = new URLClassLoader(new URL[0], currentLoader);
ch2.setClassLoader(ch2Loader);
+ hl.addHandler(ch2);
try
{
diff --git a/jetty-jsp/pom.xml b/jetty-jsp/pom.xml
index 586ac2f..c3da8b3 100644
--- a/jetty-jsp/pom.xml
+++ b/jetty-jsp/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-jsp</artifactId>
@@ -10,50 +10,81 @@
<url>http://www.eclipse.org/jetty</url>
<packaging>jar</packaging>
<build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <descriptorRef>config</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
</build>
<dependencies>
+ <!-- Schemas -->
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-schemas</artifactId>
+ </dependency>
+
+ <!-- servlet api -->
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </dependency>
+
<!-- JSP Api -->
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet.jsp</artifactId>
- <version>2.2.0.v201112011158</version>
+ <groupId>javax.servlet.jsp</groupId>
+ <artifactId>javax.servlet.jsp-api</artifactId>
</dependency>
<!-- JSP Impl -->
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>org.apache.jasper.glassfish</artifactId>
- <version>2.2.2.v201112011158</version>
+ <groupId>org.glassfish.web</groupId>
+ <artifactId>javax.servlet.jsp</artifactId>
</dependency>
+
<!-- JSTL Api -->
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.servlet.jsp.jstl</artifactId>
- <version>1.2.0.v201105211821</version>
</dependency>
<!-- JSTL Impl -->
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>org.apache.taglibs.standard.glassfish</artifactId>
- <version>1.2.0.v201112081803</version>
</dependency>
+
<!-- EL Api -->
+ <!-- Not needed as glassfish impl jars contain also the api classes
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.el</artifactId>
- <version>2.2.0.v201303151357</version>
+ <groupId>javax.el</groupId>
+ <artifactId>javax.el-api</artifactId>
</dependency>
+ -->
+
<!-- EL Impl -->
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>com.sun.el</artifactId>
- <version>2.2.0.v201303151357</version>
+ <groupId>org.glassfish</groupId>
+ <artifactId>javax.el</artifactId>
</dependency>
+
+
<!-- Eclipse Java Compiler (for JSP Compilation) -->
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>org.eclipse.jdt.core</artifactId>
- <version>3.8.2.v20130121</version>
</dependency>
</dependencies>
</project>
diff --git a/jetty-jsp/src/main/config/modules/jsp.mod b/jetty-jsp/src/main/config/modules/jsp.mod
new file mode 100644
index 0000000..b67dfe2
--- /dev/null
+++ b/jetty-jsp/src/main/config/modules/jsp.mod
@@ -0,0 +1,14 @@
+#
+# Jetty JSP Module
+#
+
+[depend]
+servlet
+
+[lib]
+lib/jsp/*.jar
+
+[ini-template]
+# JSP Configuration
+# To use an non-jdk compiler for JSP compilation uncomment next line
+# -Dorg.apache.jasper.compiler.disablejsr199=true
diff --git a/jetty-jspc-maven-plugin/pom.xml b/jetty-jspc-maven-plugin/pom.xml
index 3658fb8..fc2a00b 100644
--- a/jetty-jspc-maven-plugin/pom.xml
+++ b/jetty-jspc-maven-plugin/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-jspc-maven-plugin</artifactId>
diff --git a/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/JspcMojo.java b/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/JspcMojo.java
index 224a7e9..817afef 100644
--- a/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/JspcMojo.java
+++ b/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/JspcMojo.java
@@ -163,12 +163,6 @@
*/
private boolean keepSources;
- /**
- * Default root package for all generated classes
- *
- * @parameter default-value="jsp"
- */
- private String packageRoot;
/**
* Root directory for all html/jsp etc files
@@ -209,48 +203,6 @@
*/
private File classesDirectory;
- /**
- * Whether or not to output more verbose messages during compilation.
- *
- * @parameter default-value="false";
- */
- private boolean verbose;
-
- /**
- * If true, validates tlds when parsing.
- *
- * @parameter default-value="false";
- */
- private boolean validateXml;
-
- /**
- * The encoding scheme to use.
- *
- * @parameter default-value="UTF-8"
- */
- private String javaEncoding;
-
- /**
- * Whether or not to generate JSR45 compliant debug info
- *
- * @parameter default-value="true";
- */
- private boolean suppressSmap;
-
- /**
- * Whether or not to ignore precompilation errors caused by jsp fragments.
- *
- * @parameter default-value="false"
- */
- private boolean ignoreJspFragmentErrors;
-
- /**
- * Allows a prefix to be appended to the standard schema locations so that
- * they can be loaded from elsewhere.
- *
- * @parameter
- */
- private String schemaResourcePrefix;
/**
* Patterns of jars on the system path that contain tlds. Use | to separate each pattern.
@@ -258,35 +210,32 @@
* @parameter default-value=".*taglibs[^/]*\.jar|.*jstl-impl[^/]*\.jar$
*/
private String tldJarNamePatterns;
-
-
-
+
+
/**
- * Should white spaces in template text between actions or directives be trimmed? Defaults to false.
+ *
+ * The JspC instance being used to compile the jsps.
+ *
* @parameter
*/
- private boolean trimSpaces = false;
+ private JspC jspc;
+
+
+
public void execute() throws MojoExecutionException, MojoFailureException
{
if (getLog().isDebugEnabled())
{
- getLog().info("verbose=" + verbose);
+
getLog().info("webAppSourceDirectory=" + webAppSourceDirectory);
getLog().info("generatedClasses=" + generatedClasses);
getLog().info("webXmlFragment=" + webXmlFragment);
getLog().info("webXml="+webXml);
- getLog().info("validateXml=" + validateXml);
- getLog().info("packageRoot=" + packageRoot);
- getLog().info("javaEncoding=" + javaEncoding);
getLog().info("insertionMarker="+ (insertionMarker == null || insertionMarker.equals("") ? END_OF_WEBAPP : insertionMarker));
getLog().info("keepSources=" + keepSources);
- getLog().info("mergeFragment=" + mergeFragment);
- getLog().info("suppressSmap=" + suppressSmap);
- getLog().info("ignoreJspFragmentErrors=" + ignoreJspFragmentErrors);
- getLog().info("schemaResourcePrefix=" + schemaResourcePrefix);
- getLog().info("trimSpaces=" + trimSpaces);
+ getLog().info("mergeFragment=" + mergeFragment);
}
try
{
@@ -338,22 +287,17 @@
}
Thread.currentThread().setContextClassLoader(webAppClassLoader);
-
- JspC jspc = new JspC();
+
+ if (jspc == null)
+ jspc = new JspC();
+
jspc.setWebXmlFragment(webXmlFragment);
- jspc.setUriroot(webAppSourceDirectory);
- jspc.setPackage(packageRoot);
+ jspc.setUriroot(webAppSourceDirectory);
jspc.setOutputDir(generatedClasses);
- jspc.setValidateXml(validateXml);
jspc.setClassPath(webAppClassPath.toString());
jspc.setCompile(true);
- jspc.setSmapSuppressed(suppressSmap);
- jspc.setSmapDumped(!suppressSmap);
- jspc.setJavaEncoding(javaEncoding);
- jspc.setTrimSpaces(trimSpaces);
jspc.setSystemClassPath(sysClassPath);
-
-
+
// JspC#setExtensions() does not exist, so
// always set concrete list of files that will be processed.
@@ -362,34 +306,8 @@
getLog().info("Includes="+includes);
getLog().info("Excludes="+excludes);
jspc.setJspFiles(jspFiles);
- if (verbose)
- {
- getLog().info("Files selected to precompile: " + jspFiles);
- }
-
- try
- {
- jspc.setIgnoreJspFragmentErrors(ignoreJspFragmentErrors);
- }
- catch (NoSuchMethodError e)
- {
- getLog().debug("Tomcat Jasper does not support configuration option 'ignoreJspFragmentErrors': ignored");
- }
-
- try
- {
- if (schemaResourcePrefix != null)
- jspc.setSchemaResourcePrefix(schemaResourcePrefix);
- }
- catch (NoSuchMethodError e)
- {
- getLog().debug("Tomcat Jasper does not support configuration option 'schemaResourcePrefix': ignored");
- }
- if (verbose)
- jspc.setVerbose(99);
- else
- jspc.setVerbose(0);
+ getLog().info("Files selected to precompile: " + jspFiles);
jspc.execute();
diff --git a/jetty-maven-plugin/pom.xml b/jetty-maven-plugin/pom.xml
index c7e4463..f0a801e 100644
--- a/jetty-maven-plugin/pom.xml
+++ b/jetty-maven-plugin/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-maven-plugin</artifactId>
@@ -112,6 +112,11 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>javax-websocket-server-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-server</artifactId>
<version>${project.version}</version>
</dependency>
@@ -120,11 +125,16 @@
<artifactId>jetty-jsp</artifactId>
<version>${project.version}</version>
</dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-continuation</artifactId>
- <version>${project.version}</version>
- </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.orbit</groupId>
+ <artifactId>javax.activation</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.transaction</groupId>
+ <artifactId>javax.transaction-api</artifactId>
+ <scope>compile</scope>
+ </dependency>
</dependencies>
<reporting>
<plugins>
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java
index d1dcf49..1426571 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java
@@ -132,11 +132,20 @@
* The temporary directory to use for the webapp.
* Defaults to target/tmp
*
- * @parameter expression="${project.build.directory}/tmp"
+ * @parameter alias="tmpDirectory" expression="${project.build.directory}/tmp"
* @required
* @readonly
*/
- protected File tmpDirectory;
+ protected File tempDirectory;
+
+
+
+ /**
+ * Whether temporary directory contents should survive webapp restarts.
+ *
+ * @parameter default-value="false"
+ */
+ private boolean persistTempDirectory;
/**
@@ -418,12 +427,14 @@
props.put("context.path", contextPath);
//sort out the tmp directory (make it if it doesn't exist)
- if (tmpDirectory != null)
+ if (tempDirectory != null)
{
- if (!tmpDirectory.exists())
- tmpDirectory.mkdirs();
- props.put("tmp.dir", tmpDirectory.getAbsolutePath());
+ if (!tempDirectory.exists())
+ tempDirectory.mkdirs();
+ props.put("tmp.dir", tempDirectory.getAbsolutePath());
}
+
+ props.put("tmp.dir.persist", Boolean.toString(persistTempDirectory));
//sort out base dir of webapp
if (webAppSourceDirectory == null || !webAppSourceDirectory.exists())
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
index 4b8f511..5e31a62 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
@@ -31,6 +31,7 @@
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
+import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.ServletHolder;
@@ -110,7 +111,7 @@
new MetaInfConfiguration(),
new FragmentConfiguration(),
_envConfig = new EnvConfiguration(),
- new org.eclipse.jetty.plus.webapp.PlusConfiguration(),
+ new PlusConfiguration(),
new AnnotationConfiguration(),
new JettyWebXmlConfiguration()
});
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java
index 9c939fe..bfa467b 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java
@@ -25,7 +25,6 @@
import java.util.List;
import java.util.Locale;
-import org.apache.maven.artifact.Artifact;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -46,7 +45,7 @@
*/
public class MavenWebInfConfiguration extends WebInfConfiguration
{
- private static final Logger LOG = Log.getLogger(WebInfConfiguration.class);
+ private static final Logger LOG = Log.getLogger(MavenWebInfConfiguration.class);
protected static int COUNTER = 0;
@@ -172,26 +171,26 @@
if (o.getConfig() != null && o.getConfig().isCurrentProject() && _originalResourceBase.exists())
{
resourceBaseCollection.add(_originalResourceBase);
- LOG.info("Adding virtual project to resource base list");
+ LOG.debug("Adding virtual project to resource base list");
continue;
}
Resource unpacked = unpackOverlay(jwac,o);
_unpackedOverlayResources.add(unpacked); //remember the unpacked overlays for later so we can delete the tmp files
resourceBaseCollection.add(unpacked); //add in the selectively unpacked overlay in the correct order to the webapps resource base
- LOG.info("Adding "+unpacked+" to resource base list");
+ LOG.debug("Adding "+unpacked+" to resource base list");
}
if (!resourceBaseCollection.contains(_originalResourceBase) && _originalResourceBase.exists())
{
if (jwac.getBaseAppFirst())
{
- LOG.info("Adding virtual project first in resource base list");
+ LOG.debug("Adding virtual project first in resource base list");
resourceBaseCollection.add(0, _originalResourceBase);
}
else
{
- LOG.info("Adding virtual project last in resource base list");
+ LOG.debug("Adding virtual project last in resource base list");
resourceBaseCollection.add(_originalResourceBase);
}
}
@@ -279,13 +278,11 @@
+
protected Resource unpackOverlay (WebAppContext context, Overlay overlay)
throws IOException
{
- LOG.info("Unpacking overlay: " + overlay);
-
- //resolve if not already resolved
- resolveTempDirectory(context);
+ LOG.debug("Unpacking overlay: " + overlay);
if (overlay.getResource() == null)
return null; //nothing to unpack
@@ -311,7 +308,7 @@
//use top level of unpacked content
Resource unpackedOverlay = Resource.newResource(dir.getCanonicalPath());
- LOG.info("Unpacked overlay: "+overlay+" to "+unpackedOverlay);
+ LOG.debug("Unpacked overlay: "+overlay+" to "+unpackedOverlay);
return unpackedOverlay;
}
}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java
index b4a8945..00c572e 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java
@@ -20,13 +20,11 @@
package org.eclipse.jetty.maven.plugin;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.codehaus.plexus.util.xml.Xpp3Dom;
-import org.apache.maven.artifact.Artifact;
-
-import java.util.Arrays;
/**
* OverlayConfig
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
index 87f7fe6..15414f6 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
@@ -207,6 +207,9 @@
if (str != null)
webApp.setTempDirectory(new File(str.trim()));
+ str = (String)props.getProperty("tmp.dir.persist");
+ if (str != null)
+ webApp.setPersistTempDirectory(Boolean.valueOf(str));
// - the base directory
str = (String)props.getProperty("base.dir");
@@ -219,7 +222,7 @@
// - put virtual webapp base resource first on resource path or not
str = (String)props.getProperty("base.first");
if (str != null && !"".equals(str.trim()))
- webApp.setBaseAppFirst(Boolean.getBoolean(str));
+ webApp.setBaseAppFirst(Boolean.valueOf(str));
//For overlays
diff --git a/jetty-monitor/README.txt b/jetty-monitor/README.TXT
similarity index 100%
rename from jetty-monitor/README.txt
rename to jetty-monitor/README.TXT
diff --git a/jetty-monitor/pom.xml b/jetty-monitor/pom.xml
index fdf8469..fd67341 100644
--- a/jetty-monitor/pom.xml
+++ b/jetty-monitor/pom.xml
@@ -19,7 +19,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-monitor</artifactId>
diff --git a/jetty-monitor/src/main/config/modules/monitor.mod b/jetty-monitor/src/main/config/modules/monitor.mod
new file mode 100644
index 0000000..67f006d
--- /dev/null
+++ b/jetty-monitor/src/main/config/modules/monitor.mod
@@ -0,0 +1,13 @@
+#
+# Jetty Monitor module
+#
+
+[depend]
+server
+client
+
+[lib]
+lib/jetty-monitor-${jetty.version}.jar
+
+[xml]
+etc/jetty-monitor.xml
\ No newline at end of file
diff --git a/jetty-monitor/src/test/resources/jetty-logging.properties b/jetty-monitor/src/test/resources/jetty-logging.properties
index e94eed3..2071498 100644
--- a/jetty-monitor/src/test/resources/jetty-logging.properties
+++ b/jetty-monitor/src/test/resources/jetty-logging.properties
@@ -1,3 +1,3 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG
-org.eclipse.jetty.monitor.LEVEL=DEBUG
+#org.eclipse.jetty.monitor.LEVEL=DEBUG
diff --git a/jetty-nosql/pom.xml b/jetty-nosql/pom.xml
index fae2e14..bd360c9 100644
--- a/jetty-nosql/pom.xml
+++ b/jetty-nosql/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-nosql</artifactId>
@@ -15,11 +15,28 @@
<defaultGoal>install</defaultGoal>
<plugins>
<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <descriptorRef>config</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
- <Import-Package>javax.servlet.*;version="2.6.0",org.eclipse.jetty.server.session.jmx;version="9.0.0";resolution:=optional,,org.eclipse.jetty.*;version="9.0",*</Import-Package>
+ <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.eclipse.jetty.server.session.jmx;version="9.1";resolution:=optional,,org.eclipse.jetty.*;version="9.1",*</Import-Package>
</instructions>
</configuration>
<extensions>true</extensions>
diff --git a/jetty-nosql/src/main/config/modules/nosql.mod b/jetty-nosql/src/main/config/modules/nosql.mod
new file mode 100644
index 0000000..a4189c9
--- /dev/null
+++ b/jetty-nosql/src/main/config/modules/nosql.mod
@@ -0,0 +1,9 @@
+#
+# Jetty Nosql module
+#
+
+[depend]
+webapp
+
+[lib]
+lib/jetty-nosql-${jetty.version}.jar
\ No newline at end of file
diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSessionManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSessionManager.java
index f802795..d5ebdd2 100644
--- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSessionManager.java
+++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSessionManager.java
@@ -356,6 +356,7 @@
__log.warn(e);
}
}
+ super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
}
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml
index 4243eae..bd4ea72 100644
--- a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml
+++ b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-osgi-boot-jsp</artifactId>
@@ -33,8 +33,8 @@
</dependency>
<!-- Orbit Servlet Deps -->
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
</dependency>
<!-- Orbit JSP Deps -->
<dependency>
@@ -88,31 +88,32 @@
<Bundle-Classpath />
<Fragment-Host>org.eclipse.jetty.osgi.boot</Fragment-Host>
<Export-Package>!org.eclipse.jetty.osgi.boot.*</Export-Package>
- <Import-Package>com.sun.el;resolution:=optional,
+ <Import-Package>org.eclipse.jdt.*;resolution:=optional,
+ com.sun.el;resolution:=optional,
com.sun.el.lang;resolution:=optional,
com.sun.el.parser;resolution:=optional,
com.sun.el.util;resolution:=optional,
- javax.el;version="2.2.0";resolution:=optional,
- javax.servlet;version="2.6.0",
- javax.servlet.jsp;version="2.2.0",
- javax.servlet.jsp.el;version="2.2.0",
+ javax.el;version="[3.0,3.1)",
+ javax.servlet;version="[3.1,3.2)",
+ javax.servlet.resources;version="[3.1,3.2)",
+ javax.servlet.jsp.resources;version="[3.1,3.2)",
+ javax.servlet.jsp;version="[2.3,2.4)",
+ javax.servlet.jsp.el;version="[2.3,2.4)",
+ javax.servlet.jsp.tagext;version="[2.3,2.4)",
javax.servlet.jsp.jstl.core;version="1.2.0";resolution:=optional,
javax.servlet.jsp.jstl.fmt;version="1.2.0";resolution:=optional,
javax.servlet.jsp.jstl.sql;version="1.2.0";resolution:=optional,
javax.servlet.jsp.jstl.tlv;version="1.2.0";resolution:=optional,
- javax.servlet.jsp.resources;version="2.2.0",
- javax.servlet.jsp.tagext;version="2.2.0",
- javax.servlet.resources;version="2.6.0",
- org.apache.jasper;version="2.2.2";resolution:=optional,
- org.apache.jasper.compiler;version="2.2.2";resolution:=optional,
- org.apache.jasper.compiler.tagplugin;version="2.2.2";resolution:=optional,
- org.apache.jasper.runtime;version="2.2.2";resolution:=optional,
- org.apache.jasper.security;version="2.2.2";resolution:=optional,
- org.apache.jasper.servlet;version="2.2.2";resolution:=optional,
- org.apache.jasper.tagplugins.jstl;version="2.2.2";resolution:=optional,
- org.apache.jasper.util;version="2.2.2";resolution:=optional,
- org.apache.jasper.xmlparser;version="2.2.2";resolution:=optional,
- org.glassfish.jsp.api;version="2.2.2";resolution:=optional,
+ org.apache.jasper;version="[2.3.2,2.4)";resolution:=optional,
+ org.apache.jasper.compiler;version="[2.3.2,2.4)";resolution:=optional,
+ org.apache.jasper.compiler.tagplugin;version="[2.3.2,2.4)";resolution:=optional,
+ org.apache.jasper.runtime;version="[2.3.2,2.4)";resolution:=optional,
+ org.apache.jasper.security;version="[2.3.2,2.4)";resolution:=optional,
+ org.apache.jasper.servlet;version="[2.3.2,2.4)";resolution:=optional,
+ org.apache.jasper.tagplugins.jstl;version="[2.3.2,2.4)";resolution:=optional,
+ org.apache.jasper.util;version="[2.3.2,2.4)";resolution:=optional,
+ org.apache.jasper.xmlparser;version="[2.3.2,2.4)";resolution:=optional,
+ org.glassfish.jsp.api;version="[2.3.2,2.4)";resolution:=optional,
org.apache.taglibs.standard;version="1.2.0";resolution:=optional,
org.apache.taglibs.standard.extra.spath;version="1.2.0";resolution:=optional,
org.apache.taglibs.standard.functions;version="1.2.0";resolution:=optional,
@@ -145,7 +146,7 @@
javax.xml.parser;resolution:=optional
</Import-Package>
<_nouses>true</_nouses>
- <DynamicImport-Package>org.apache.jasper.*;version="2.2"</DynamicImport-Package>
+ <DynamicImport-Package>org.apache.jasper.*;version="2.3"</DynamicImport-Package>
</instructions>
</configuration>
</plugin>
diff --git a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml
index fd5d2af..1cd02e0 100644
--- a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml
+++ b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml
index a67382f..1f26830 100644
--- a/jetty-osgi/jetty-osgi-boot/pom.xml
+++ b/jetty-osgi/jetty-osgi-boot/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-osgi-boot</artifactId>
@@ -102,14 +102,14 @@
<instructions>
<Bundle-SymbolicName>org.eclipse.jetty.osgi.boot;singleton:=true</Bundle-SymbolicName>
<Bundle-Activator>org.eclipse.jetty.osgi.boot.JettyBootstrapActivator</Bundle-Activator>
- <DynamicImport-Package>org.eclipse.jetty.*;version="[9.0,10.0)"</DynamicImport-Package>
+ <DynamicImport-Package>org.eclipse.jetty.*;version="[9.1,10.0)"</DynamicImport-Package>
<Import-Package>javax.mail;version="1.4.0";resolution:=optional,
javax.mail.event;version="1.4.0";resolution:=optional,
javax.mail.internet;version="1.4.0";resolution:=optional,
javax.mail.search;version="1.4.0";resolution:=optional,
javax.mail.util;version="1.4.0";resolution:=optional,
- javax.servlet;version="2.6.0",
- javax.servlet.http;version="2.6.0",
+ javax.servlet;version="[3.1,3.2)",
+ javax.servlet.http;version="[3.1,3.2)",
javax.transaction;version="1.1.0";resolution:=optional,
javax.transaction.xa;version="1.1.0";resolution:=optional,
org.eclipse.jetty.annotations;version="9.0.0";resolution:=optional,
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java
index b4ae909..d38cab3 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java
@@ -18,16 +18,14 @@
package org.eclipse.jetty.osgi.annotations;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
-import org.eclipse.jetty.annotations.AbstractDiscoverableAnnotationHandler;
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
+import org.eclipse.jetty.annotations.AnnotationParser.Handler;
import org.eclipse.jetty.annotations.ClassNameResolver;
import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
@@ -39,7 +37,27 @@
*/
public class AnnotationConfiguration extends org.eclipse.jetty.annotations.AnnotationConfiguration
{
+ public class BundleParserTask extends ParserTask
+ {
+
+ public BundleParserTask (AnnotationParser parser, Set<? extends Handler>handlers, Resource resource, ClassNameResolver resolver)
+ {
+ super(parser, handlers, resource, resolver);
+ }
+ public Void call() throws Exception
+ {
+ if (_parser != null)
+ {
+ org.eclipse.jetty.osgi.annotations.AnnotationParser osgiAnnotationParser = (org.eclipse.jetty.osgi.annotations.AnnotationParser)_parser;
+ Bundle bundle = osgiAnnotationParser.getBundle(_resource);
+ osgiAnnotationParser.parse(_handlers, bundle, _resolver);
+ }
+ return null;
+ }
+ }
+
+
/**
* This parser scans the bundles using the OSGi APIs instead of assuming a jar.
*/
@@ -69,7 +87,7 @@
Bundle[] fragAndRequiredBundles = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles(webbundle);
if (fragAndRequiredBundles != null)
{
- //index:
+ //index and scan fragments
for (Bundle bundle : fragAndRequiredBundles)
{
Resource bundleRes = oparser.indexBundle(bundle);
@@ -77,19 +95,16 @@
{
context.getMetaData().addWebInfJar(bundleRes);
}
- }
-
- //scan the fragments
- for (Bundle fragmentBundle : fragAndRequiredBundles)
- {
- if (fragmentBundle.getHeaders().get(Constants.FRAGMENT_HOST) != null)
+
+ if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null)
{
//a fragment indeed:
- parseFragmentBundle(context,oparser,webbundle,fragmentBundle);
+ parseFragmentBundle(context,oparser,webbundle,bundle);
}
}
}
//scan ourselves
+ oparser.indexBundle(webbundle);
parseWebBundle(context,oparser,webbundle);
//scan the WEB-INF/lib
@@ -151,27 +166,19 @@
}
protected void parseBundle(WebAppContext context, AnnotationParser parser,
- Bundle webbundle, Bundle bundle) throws Exception
- {
-
- Resource bundleRes = parser.getResource(bundle);
-
- parser.clearHandlers();
- for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
- {
- if (h instanceof AbstractDiscoverableAnnotationHandler)
- {
- if (webbundle == bundle)
- ((AbstractDiscoverableAnnotationHandler)h).setResource(null);
- else
- ((AbstractDiscoverableAnnotationHandler)h).setResource(bundleRes);
- }
- }
- parser.registerHandlers(_discoverableAnnotationHandlers);
- parser.registerHandler(_classInheritanceHandler);
- parser.registerHandlers(_containerInitializerAnnotationHandlers);
-
- parser.parse(bundle,createClassNameResolver(context));
+ Bundle webbundle, Bundle bundle) throws Exception
+ {
+
+ Resource bundleRes = parser.getResource(bundle);
+ Set<Handler> handlers = new HashSet<Handler>();
+ handlers.addAll(_discoverableAnnotationHandlers);
+ if (_classInheritanceHandler != null)
+ handlers.add(_classInheritanceHandler);
+ handlers.addAll(_containerInitializerAnnotationHandlers);
+
+ ClassNameResolver classNameResolver = createClassNameResolver(context);
+ if (_parserTasks != null)
+ _parserTasks.add(new BundleParserTask(parser, handlers, bundleRes, classNameResolver));
}
/**
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java
index 096cb8a..e502435 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java
@@ -29,9 +29,11 @@
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.annotations.ClassNameResolver;
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
+import org.eclipse.jetty.util.ConcurrentHashSet;
import org.eclipse.jetty.util.resource.Resource;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
@@ -41,11 +43,12 @@
*/
public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationParser
{
- private Set<URI> _alreadyParsed = new HashSet<URI>();
+ private Set<URI> _alreadyParsed = new ConcurrentHashSet<URI>();
- private Map<URI,Bundle> _uriToBundle = new HashMap<URI, Bundle>();
- private Map<Bundle,Resource> _resourceToBundle = new HashMap<Bundle,Resource>();
- private Map<Bundle,URI> _bundleToUri = new HashMap<Bundle, URI>();
+ private ConcurrentHashMap<URI,Bundle> _uriToBundle = new ConcurrentHashMap<URI, Bundle>();
+ private ConcurrentHashMap<Bundle,Resource> _bundleToResource = new ConcurrentHashMap<Bundle,Resource>();
+ private ConcurrentHashMap<Resource, Bundle> _resourceToBundle = new ConcurrentHashMap<Resource, Bundle>();
+ private ConcurrentHashMap<Bundle,URI> _bundleToUri = new ConcurrentHashMap<Bundle, URI>();
/**
* Keep track of a jetty URI Resource and its associated OSGi bundle.
@@ -58,9 +61,10 @@
File bundleFile = BundleFileLocatorHelper.DEFAULT.getBundleInstallLocation(bundle);
Resource resource = Resource.newResource(bundleFile.toURI());
URI uri = resource.getURI();
- _uriToBundle.put(uri,bundle);
- _bundleToUri.put(bundle,uri);
- _resourceToBundle.put(bundle,resource);
+ _uriToBundle.putIfAbsent(uri,bundle);
+ _bundleToUri.putIfAbsent(bundle,uri);
+ _bundleToResource.putIfAbsent(bundle,resource);
+ _resourceToBundle.putIfAbsent(resource,bundle);
return resource;
}
protected URI getURI(Bundle bundle)
@@ -69,13 +73,19 @@
}
protected Resource getResource(Bundle bundle)
{
- return _resourceToBundle.get(bundle);
+ return _bundleToResource.get(bundle);
}
+ protected Bundle getBundle (Resource resource)
+ {
+ return _resourceToBundle.get(resource);
+ }
+
+
/**
*
*/
@Override
- public void parse (URI[] uris, ClassNameResolver resolver)
+ public void parse (Set<? extends Handler> handlers, URI[] uris, ClassNameResolver resolver)
throws Exception
{
for (URI uri : uris)
@@ -89,16 +99,16 @@
}
//a jar in WEB-INF/lib or the WEB-INF/classes
//use the behavior of the super class for a standard jar.
- super.parse(new URI[] {uri},resolver);
+ super.parse(handlers, new URI[] {uri},resolver);
}
else
{
- parse(associatedBundle,resolver);
+ parse(handlers, associatedBundle,resolver);
}
}
}
- protected void parse(Bundle bundle, ClassNameResolver resolver)
+ protected void parse(Set<? extends Handler> handlers, Bundle bundle, ClassNameResolver resolver)
throws Exception
{
URI uri = _bundleToUri.get(bundle);
@@ -190,7 +200,7 @@
//transform into a classname to pass to the resolver
String shortName = name.replace('/', '.').substring(0,name.length()-6);
if ((resolver == null)|| (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
- scanClass(classUrl.openStream());
+ scanClass(handlers, getResource(bundle), classUrl.openStream());
}
}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java
index 197ee2c..b47b8ac 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java
@@ -21,7 +21,11 @@
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
@@ -53,8 +57,8 @@
@Override
public void preConfigure(final WebAppContext context) throws Exception
{
- List<Resource> frags = (List<Resource>) context.getAttribute(METAINF_FRAGMENTS);
- List<Resource> resfrags = (List<Resource>) context.getAttribute(METAINF_RESOURCES);
+ Map<Resource, Resource> frags = (Map<Resource, Resource>) context.getAttribute(METAINF_FRAGMENTS);
+ Set<Resource> resfrags = (Set<Resource>) context.getAttribute(METAINF_RESOURCES);
List<Resource> tldfrags = (List<Resource>) context.getAttribute(METAINF_TLDS);
Bundle[] fragments = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles((Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE));
@@ -73,10 +77,11 @@
{
if (frags == null)
{
- frags = new ArrayList<Resource>();
+ frags = new HashMap<Resource,Resource>();
context.setAttribute(METAINF_FRAGMENTS, frags);
}
- frags.add(Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(frag).toURI()));
+ frags.put(Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(frag).toURI()),
+ Resource.newResource(webFrag));
}
if (resEnum != null && resEnum.hasMoreElements())
{
@@ -92,7 +97,7 @@
{
if (resfrags == null)
{
- resfrags = new ArrayList<Resource>();
+ resfrags = new HashSet<Resource>();
context.setAttribute(METAINF_RESOURCES, resfrags);
}
resfrags.add(Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(resourcesEntry)));
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java
index b606db4..75d79a8 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java
@@ -212,7 +212,7 @@
}
}
if (!appendedResourcesPath.isEmpty())
- context.setAttribute(WebInfConfiguration.RESOURCE_URLS, new ArrayList<Resource>(appendedResourcesPath.values()));
+ context.setAttribute(WebInfConfiguration.RESOURCE_DIRS, new HashSet<Resource>(appendedResourcesPath.values()));
}
}
diff --git a/jetty-osgi/jetty-osgi-httpservice/pom.xml b/jetty-osgi/jetty-osgi-httpservice/pom.xml
index ee44d49..5017fb2 100644
--- a/jetty-osgi/jetty-osgi-httpservice/pom.xml
+++ b/jetty-osgi/jetty-osgi-httpservice/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-httpservice</artifactId>
@@ -30,8 +30,8 @@
<artifactId>org.eclipse.osgi</artifactId>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies>
@@ -96,10 +96,10 @@
<Bundle-SymbolicName>org.eclipse.jetty.osgi.httpservice</Bundle-SymbolicName>
<Bundle-Name>OSGi HttpService</Bundle-Name>
<Jetty-ContextFilePath>contexts/httpservice.xml</Jetty-ContextFilePath>
- <Import-Package>org.eclipse.jetty.server.handler;version="[9.0,10.0)",
-org.eclipse.jetty.util.component;version="[9.0,10.0)",
-org.eclipse.jetty.server.session;version="[9.0,10.0)",
-org.eclipse.jetty.servlet;version="[9.0,10.0)",
+ <Import-Package>org.eclipse.jetty.server.handler;version="[9.1,10.0)",
+org.eclipse.jetty.util.component;version="[9.1,10.0)",
+org.eclipse.jetty.server.session;version="[9.1,10.0)",
+org.eclipse.jetty.servlet;version="[9.1,10.0)",
org.eclipse.equinox.http.servlet,
*
</Import-Package>
diff --git a/jetty-osgi/jetty-osgi-npn/pom.xml b/jetty-osgi/jetty-osgi-npn/pom.xml
index fcb626a..1c73428 100644
--- a/jetty-osgi/jetty-osgi-npn/pom.xml
+++ b/jetty-osgi/jetty-osgi-npn/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-osgi-npn</artifactId>
diff --git a/jetty-osgi/pom.xml b/jetty-osgi/pom.xml
index 02c2b6b..1b42f35 100644
--- a/jetty-osgi/pom.xml
+++ b/jetty-osgi/pom.xml
@@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
diff --git a/jetty-osgi/test-jetty-osgi-context/pom.xml b/jetty-osgi/test-jetty-osgi-context/pom.xml
index 7e9c33f..7141620 100644
--- a/jetty-osgi/test-jetty-osgi-context/pom.xml
+++ b/jetty-osgi/test-jetty-osgi-context/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>test-jetty-osgi-context</artifactId>
@@ -26,6 +26,10 @@
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-schemas</artifactId>
+ </dependency>
</dependencies>
<build>
@@ -38,7 +42,15 @@
</resource>
</resources>
- <plugins>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <!-- DO NOT DEPLOY (or Release) -->
+ <skip>true</skip>
+ </configuration>
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
@@ -86,8 +98,8 @@
compilation time. -->
<_nouses>true</_nouses>
<Import-Package>
- javax.servlet;version="2.6.0",
- javax.servlet.resources;version="2.6.0",
+ javax.servlet;version="[3.1,3.2)",
+ javax.servlet.resources;version="[3.1,3.2)",
org.osgi.framework,
org.osgi.service.cm;version="1.2.0",
org.osgi.service.packageadmin,
@@ -101,7 +113,7 @@
org.xml.sax.helpers,
*
</Import-Package>
- <DynamicImport-Package>org.eclipse.jetty.*;version="[9.0,10.0)"</DynamicImport-Package>
+ <DynamicImport-Package>org.eclipse.jetty.*;version="[9.1,10.0)"</DynamicImport-Package>
</instructions>
</configuration>
</plugin>
diff --git a/jetty-osgi/test-jetty-osgi-webapp/pom.xml b/jetty-osgi/test-jetty-osgi-webapp/pom.xml
index 7e8cfcf..95a0ff9 100644
--- a/jetty-osgi/test-jetty-osgi-webapp/pom.xml
+++ b/jetty-osgi/test-jetty-osgi-webapp/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -35,7 +35,15 @@
</resource>
</resources>
- <plugins>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <!-- DO NOT DEPLOY (or Release) -->
+ <skip>true</skip>
+ </configuration>
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
@@ -96,7 +104,7 @@
org.xml.sax.helpers,
*
</Import-Package>
- <DynamicImport-Package>org.eclipse.jetty.*;version="[9.0,10.0)"</DynamicImport-Package>
+ <DynamicImport-Package>org.eclipse.jetty.*;version="[9.1,10.0)"</DynamicImport-Package>
</instructions>
</configuration>
</plugin>
diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml
index 8c92763..3430e4f 100644
--- a/jetty-osgi/test-jetty-osgi/pom.xml
+++ b/jetty-osgi/test-jetty-osgi/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -151,8 +151,8 @@
-->
<!-- Orbit Servlet Deps -->
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
<scope>test</scope>
</dependency>
<!-- Orbit JSP Deps -->
@@ -170,6 +170,12 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-jsp-fragment</artifactId>
+ <version>2.3.3</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-httpservice</artifactId>
<version>${project.version}</version>
@@ -260,6 +266,12 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
+ <dependency>
+ <groupId>javax.websocket</groupId>
+ <artifactId>javax.websocket-api</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+
<dependency>
<groupId>org.eclipse.jetty.spdy</groupId>
@@ -297,6 +309,11 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-schemas</artifactId>
+ <scope>runtime</scope>
+ </dependency>
<dependency>
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java
index b15e002..d3e980d 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.osgi.test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
@@ -29,8 +32,6 @@
import javax.inject.Inject;
-import junit.framework.Assert;
-
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
@@ -122,6 +123,7 @@
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
}
+
/**
*/
@Test
@@ -133,10 +135,10 @@
{
client.start();
ContentResponse response = client.GET("http://127.0.0.1:" + TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT + "/acme/index.html");
- Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertEquals(HttpStatus.OK_200, response.getStatus());
String content = new String(response.getContent());
- Assert.assertTrue(content.indexOf("<h1>Test OSGi Context</h1>") != -1);
+ assertTrue(content.indexOf("<h1>Test OSGi Context</h1>") != -1);
}
finally
{
@@ -144,8 +146,8 @@
}
ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
- Assert.assertNotNull(refs);
- Assert.assertEquals(1, refs.length);
+ assertNotNull(refs);
+ assertEquals(1, refs.length);
//uncomment for debugging
/*
String[] keys = refs[0].getPropertyKeys();
@@ -155,15 +157,15 @@
System.err.println("service property: " + k + ", " + refs[0].getProperty(k));
}*/
ContextHandler ch = (ContextHandler) bundleContext.getService(refs[0]);
- Assert.assertEquals("/acme", ch.getContextPath());
+ assertEquals("/acme", ch.getContextPath());
// Stop the bundle with the ContextHandler in it and check the jetty
// Context is destroyed for it.
// TODO: think of a better way to communicate this to the test, other
// than checking stderr output
Bundle testWebBundle = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.osgi.testcontext");
- Assert.assertNotNull("Could not find the org.eclipse.jetty.test-jetty-osgi-context.jar bundle", testWebBundle);
- Assert.assertTrue("The bundle org.eclipse.jetty.testcontext is not correctly resolved", testWebBundle.getState() == Bundle.ACTIVE);
+ assertNotNull("Could not find the org.eclipse.jetty.test-jetty-osgi-context.jar bundle", testWebBundle);
+ assertTrue("The bundle org.eclipse.jetty.testcontext is not correctly resolved", testWebBundle.getState() == Bundle.ACTIVE);
testWebBundle.stop();
}
}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootCore.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootCore.java
index aaf9fd1..ca99c64 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootCore.java
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootCore.java
@@ -78,7 +78,8 @@
res.add(mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot" ).versionAsInProject().start());
- res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet" ).versionAsInProject().noStart());
+ res.add(mavenBundle().groupId( "javax.servlet" ).artifactId( "javax.servlet-api" ).versionAsInProject().noStart());
+ res.add(mavenBundle().groupId( "org.eclipse.jetty.toolchain" ).artifactId( "jetty-schemas" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-deploy" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-server" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-servlet" ).versionAsInProject().noStart());
@@ -95,6 +96,7 @@
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-common" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-servlet" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-server" ).versionAsInProject().noStart());
+ res.add(mavenBundle().groupId( "javax.websocket" ).artifactId( "javax.websocket-api" ).versionAsInProject().noStart());
return res;
}
@@ -126,6 +128,4 @@
{
TestOSGiUtil.testHttpServiceGreetings(bundleContext, "http", DEFAULT_JETTY_HTTP_PORT);
}
-
-
}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootSpdy.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootSpdy.java
index a99d744..c17838a 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootSpdy.java
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootSpdy.java
@@ -104,6 +104,7 @@
res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-npn").versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-core").versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-server").versionAsInProject().noStart());
+ res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-http-common").versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-http-server").versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-client").versionAsInProject().noStart());
return res;
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java
index be7cd78..9510412 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.osgi.test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
@@ -29,8 +32,6 @@
import javax.inject.Inject;
-import junit.framework.Assert;
-
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
@@ -82,7 +83,6 @@
if (LOGGING_ENABLED)
logLevel = "INFO";
-
options.addAll(Arrays.asList(options(
// install log service using pax runners profile abstraction (there
// are more profiles, like DS)
@@ -94,8 +94,8 @@
options.addAll(jspDependencies());
return options.toArray(new Option[options.size()]);
-
}
+
public static List<Option> configureJettyHomeAndPort(String jettySelectorFileName)
{
@@ -119,16 +119,14 @@
public static List<Option> jspDependencies()
{
List<Option> res = new ArrayList<Option>();
- /* orbit deps */
- res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.servlet.jsp").versionAsInProject());
+
+ //jsp bundles
+ res.add(mavenBundle().groupId("javax.servlet.jsp").artifactId("javax.servlet.jsp-api").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.servlet.jsp.jstl").versionAsInProject());
- res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.el").versionAsInProject());
- res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("com.sun.el").versionAsInProject());
- res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.apache.jasper.glassfish").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.apache.taglibs.standard.glassfish").versionAsInProject());
+ res.add(mavenBundle().groupId("org.glassfish").artifactId("javax.el").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.eclipse.jdt.core").versionAsInProject());
-
- /* jetty-osgi deps */
+ res.add(mavenBundle().groupId("org.eclipse.jetty.toolchain").artifactId("jetty-jsp-fragment").versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-boot-jsp").versionAsInProject().noStart());
// a bundle that registers a webapp as a service for the jetty osgi core
@@ -148,17 +146,17 @@
@Test
public void testBundle() throws Exception
{
- // now test the jsp/dump.jsp
+ // now test getting a static file
HttpClient client = new HttpClient();
try
{
client.start();
ContentResponse response = client.GET("http://127.0.0.1:" + TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT + "/acme/index.html");
- Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertEquals(HttpStatus.OK_200, response.getStatus());
String content = new String(response.getContent());
- Assert.assertTrue(content.indexOf("<h1>Test OSGi WebApp</h1>") != -1);
+ assertTrue(content.indexOf("<h1>Test OSGi WebApp</h1>") != -1);
}
finally
{
@@ -166,10 +164,10 @@
}
ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
- Assert.assertNotNull(refs);
- Assert.assertEquals(1, refs.length);
+ assertNotNull(refs);
+ assertEquals(1, refs.length);
WebAppContext wac = (WebAppContext) bundleContext.getService(refs[0]);
- Assert.assertEquals("/acme", wac.getContextPath());
+ assertEquals("/acme", wac.getContextPath());
}
}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java
index 3c23e22..45e80fc 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.osgi.test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
@@ -29,8 +31,6 @@
import javax.inject.Inject;
-import junit.framework.Assert;
-
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
@@ -135,23 +135,23 @@
public static List<Option> jspDependencies()
{
List<Option> res = new ArrayList<Option>();
- /* orbit deps */
- res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.servlet.jsp").versionAsInProject());
+
+ //jetty jsp bundles
+ res.add(mavenBundle().groupId("javax.servlet.jsp").artifactId("javax.servlet.jsp-api").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.servlet.jsp.jstl").versionAsInProject());
- res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.el").versionAsInProject());
- res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("com.sun.el").versionAsInProject());
- res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.apache.jasper.glassfish").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.apache.taglibs.standard.glassfish").versionAsInProject());
+ res.add(mavenBundle().groupId("org.glassfish").artifactId("javax.el").versionAsInProject());
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.eclipse.jdt.core").versionAsInProject());
-
- /* jetty-osgi deps */
+ res.add(mavenBundle().groupId("org.eclipse.jetty.toolchain").artifactId("jetty-jsp-fragment").versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-boot-jsp").versionAsInProject().noStart());
-
+
+ //test webapp bundle
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("test-jetty-webapp").classifier("webbundle").versionAsInProject());
return res;
}
+
@Test
public void assertAllBundlesActiveOrResolved()
{
@@ -175,16 +175,14 @@
{
client.start();
ContentResponse response = client.GET("http://127.0.0.1:" + TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT + "/jsp/dump.jsp");
- Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertEquals(HttpStatus.OK_200, response.getStatus());
String content = new String(response.getContent());
- Assert.assertTrue(content.contains("<tr><th>ServletPath:</th><td>/jsp/dump.jsp</td></tr>"));
+ assertTrue(content.contains("<tr><th>ServletPath:</th><td>/jsp/dump.jsp</td></tr>"));
}
finally
{
client.stop();
}
-
}
-
}
diff --git a/jetty-overlay-deployer/pom.xml b/jetty-overlay-deployer/pom.xml
index 589b5bb..a531f46 100644
--- a/jetty-overlay-deployer/pom.xml
+++ b/jetty-overlay-deployer/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-overlay-deployer</artifactId>
@@ -49,8 +49,8 @@
<version>${project.version}</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.transaction</artifactId>
+ <groupId>javax.transaction</groupId>
+ <artifactId>javax.transaction-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
diff --git a/jetty-overlay-deployer/src/main/config/modules/overlay.mod b/jetty-overlay-deployer/src/main/config/modules/overlay.mod
new file mode 100644
index 0000000..87bf9e1
--- /dev/null
+++ b/jetty-overlay-deployer/src/main/config/modules/overlay.mod
@@ -0,0 +1,12 @@
+#
+# Jetty Overlay module
+#
+
+[depend]
+deploy
+
+[lib]
+lib/jetty-overlay-deployer-${jetty.version}.jar
+
+[xml]
+etc/jetty-overlay.xml
diff --git a/jetty-plus/pom.xml b/jetty-plus/pom.xml
index cc10e25..12b0d21 100644
--- a/jetty-plus/pom.xml
+++ b/jetty-plus/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-plus</artifactId>
@@ -14,9 +14,6 @@
</properties>
<build>
<plugins>
-<!--
- COMMENTED OUT UNTIL CORRECT CONFIG IS FOUND FOR Export uses clauses
--->
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
@@ -30,7 +27,7 @@
<instructions>
<_versionpolicy> </_versionpolicy>
<Import-Package>javax.sql.*,javax.security.*,javax.naming.*,
- javax.servlet.*;version="2.6.0",javax.transaction.*;version="[1.1,1.2)",
+ javax.servlet.*;version="[2.6.0,3.2)",javax.transaction.*;version="[1.1,1.3)",
*</Import-Package>
</instructions>
</configuration>
@@ -91,8 +88,8 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.transaction</artifactId>
+ <groupId>javax.transaction</groupId>
+ <artifactId>javax.transaction-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
diff --git a/jetty-plus/src/main/config/modules/plus.mod b/jetty-plus/src/main/config/modules/plus.mod
new file mode 100644
index 0000000..d8dee1e
--- /dev/null
+++ b/jetty-plus/src/main/config/modules/plus.mod
@@ -0,0 +1,14 @@
+#
+# Jetty Plus module
+#
+
+[depend]
+server
+security
+jndi
+
+[lib]
+lib/jetty-plus-${jetty.version}.jar
+
+[xml]
+etc/jetty-plus.xml
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java
index 9d93203..855f9eb 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java
@@ -18,25 +18,33 @@
package org.eclipse.jetty.plus.annotation;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContainerInitializer;
+import org.eclipse.jetty.util.ConcurrentHashSet;
import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.WebAppContext;
public class ContainerInitializer
{
- protected ServletContainerInitializer _target;
- protected Class[] _interestedTypes;
- protected Set<String> _applicableTypeNames;
- protected Set<String> _annotatedTypeNames;
+ private static final Logger LOG = Log.getLogger(ContainerInitializer.class);
+
+ final protected ServletContainerInitializer _target;
+ final protected Class[] _interestedTypes;
+ protected Set<String> _applicableTypeNames = new ConcurrentHashSet<String>();
+ protected Set<String> _annotatedTypeNames = new ConcurrentHashSet<String>();
- public void setTarget (ServletContainerInitializer target)
+ public ContainerInitializer (ServletContainerInitializer target, Class[] classes)
{
_target = target;
+ _interestedTypes = classes;
}
public ServletContainerInitializer getTarget ()
@@ -49,10 +57,6 @@
return _interestedTypes;
}
- public void setInterestedTypes (Class[] interestedTypes)
- {
- _interestedTypes = interestedTypes;
- }
/**
* A class has been found that has an annotation of interest
@@ -61,26 +65,22 @@
*/
public void addAnnotatedTypeName (String className)
{
- if (_annotatedTypeNames == null)
- _annotatedTypeNames = new HashSet<String>();
_annotatedTypeNames.add(className);
}
public Set<String> getAnnotatedTypeNames ()
{
- return _annotatedTypeNames;
+ return Collections.unmodifiableSet(_annotatedTypeNames);
}
public void addApplicableTypeName (String className)
{
- if (_applicableTypeNames == null)
- _applicableTypeNames = new HashSet<String>();
_applicableTypeNames.add(className);
}
public Set<String> getApplicableTypeNames ()
{
- return _applicableTypeNames;
+ return Collections.unmodifiableSet(_applicableTypeNames);
}
@@ -96,16 +96,22 @@
try
{
- if (_applicableTypeNames != null)
- {
- for (String s : _applicableTypeNames)
- classes.add(Loader.loadClass(context.getClass(), s));
- }
+ for (String s : _applicableTypeNames)
+ classes.add(Loader.loadClass(context.getClass(), s));
- _target.onStartup(classes, context.getServletContext());
+ context.getServletContext().setExtendedListenerTypes(true);
+ if (LOG.isDebugEnabled())
+ {
+ long start = System.nanoTime();
+ _target.onStartup(classes, context.getServletContext());
+ LOG.debug("ContainerInitializer {} called in {}ms", _target.getClass().getName(), TimeUnit.MILLISECONDS.convert(System.nanoTime()-start, TimeUnit.NANOSECONDS));
+ }
+ else
+ _target.onStartup(classes, context.getServletContext());
}
finally
- {
+ {
+ context.getServletContext().setExtendedListenerTypes(false);
Thread.currentThread().setContextClassLoader(oldLoader);
}
}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java
index de3cae7..ab0c954 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java
@@ -18,8 +18,6 @@
package org.eclipse.jetty.plus.annotation;
-import javax.servlet.ServletException;
-
import org.eclipse.jetty.servlet.ServletHolder;
/**
@@ -57,8 +55,10 @@
}
+ /**
+ * @param holder
+ */
public void setRunAs (ServletHolder holder)
- throws ServletException
{
if (holder == null)
return;
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java
index b43ca86..0ce3ff8 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java
@@ -20,8 +20,6 @@
import java.util.HashMap;
-import javax.servlet.ServletException;
-
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -52,7 +50,6 @@
}
public RunAs getRunAs (Object o)
- throws ServletException
{
if (o==null)
return null;
@@ -61,7 +58,6 @@
}
public void setRunAs(Object o)
- throws ServletException
{
if (o == null)
return;
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDecorator.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDecorator.java
index 68f50bc..b55435f 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDecorator.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDecorator.java
@@ -18,24 +18,16 @@
package org.eclipse.jetty.plus.webapp;
-import java.util.EventListener;
-
-import javax.servlet.Filter;
-import javax.servlet.Servlet;
-import javax.servlet.ServletException;
-
import org.eclipse.jetty.plus.annotation.InjectionCollection;
import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection;
import org.eclipse.jetty.plus.annotation.RunAsCollection;
-import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler.Decorator;
-import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.WebAppContext;
/**
- * WebAppDecorator
+ * PlusDecorator
*
*
*/
@@ -50,83 +42,7 @@
_context = context;
}
- /* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateFilterHolder(org.eclipse.jetty.servlet.FilterHolder)
- */
- public void decorateFilterHolder(FilterHolder filter) throws ServletException
- {
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateFilterInstance(javax.servlet.Filter)
- */
- public <T extends Filter> T decorateFilterInstance(T filter) throws ServletException
- {
- decorate(filter);
- return filter;
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateListenerInstance(java.util.EventListener)
- */
- public <T extends EventListener> T decorateListenerInstance(T listener) throws ServletException
- {
- decorate(listener);
- return listener;
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateServletHolder(org.eclipse.jetty.servlet.ServletHolder)
- */
- public void decorateServletHolder(ServletHolder holder) throws ServletException
- {
- decorate(holder);
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateServletInstance(javax.servlet.Servlet)
- */
- public <T extends Servlet> T decorateServletInstance(T servlet) throws ServletException
- {
- decorate(servlet);
- return servlet;
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyFilterInstance(javax.servlet.Filter)
- */
- public void destroyFilterInstance(Filter f)
- {
- destroy(f);
- }
-
-
- /* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyServletInstance(javax.servlet.Servlet)
- */
- public void destroyServletInstance(Servlet s)
- {
- destroy(s);
- }
-
- /**
- * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyListenerInstance(java.util.EventListener)
- */
- public void destroyListenerInstance(EventListener l)
- {
- destroy(l);
- }
-
-
- protected void decorate (Object o)
- throws ServletException
+ public Object decorate (Object o)
{
RunAsCollection runAses = (RunAsCollection)_context.getAttribute(RunAsCollection.RUNAS_COLLECTION);
@@ -146,12 +62,13 @@
}
catch (Exception e)
{
- throw new ServletException(e);
+ throw new RuntimeException(e);
}
}
+ return o;
}
- protected void destroy (Object o)
+ public void destroy (Object o)
{
LifeCycleCallbackCollection callbacks = (LifeCycleCallbackCollection)_context.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
if (callbacks != null)
diff --git a/jetty-plus/src/test/resources/web-fragment-1.xml b/jetty-plus/src/test/resources/web-fragment-1.xml
index b213172..2c563e8 100644
--- a/jetty-plus/src/test/resources/web-fragment-1.xml
+++ b/jetty-plus/src/test/resources/web-fragment-1.xml
@@ -9,7 +9,7 @@
<name>Fragment1</name>
<ordering>
- <after>others</after>
+ <after><others/></after>
</ordering>
<resource-ref>
diff --git a/jetty-plus/src/test/resources/web-fragment-2.xml b/jetty-plus/src/test/resources/web-fragment-2.xml
index e2fff67..878ec0f 100644
--- a/jetty-plus/src/test/resources/web-fragment-2.xml
+++ b/jetty-plus/src/test/resources/web-fragment-2.xml
@@ -9,13 +9,13 @@
<name>Fragment2</name>
<ordering>
- <after>others</after>
+ <after><others/></after>
</ordering>
<resource-ref>
<res-ref-name>jdbc/mydatasource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
- <res-auth>User</res-auth>
+ <res-auth>Application</res-auth>
<!--
<injection-target>
<injection-target-class>com.acme.Bar</injection-target-class>
diff --git a/jetty-plus/src/test/resources/web-fragment-3.xml b/jetty-plus/src/test/resources/web-fragment-3.xml
index da1f3d5..4380188 100644
--- a/jetty-plus/src/test/resources/web-fragment-3.xml
+++ b/jetty-plus/src/test/resources/web-fragment-3.xml
@@ -9,7 +9,7 @@
<name>Fragment3</name>
<ordering>
- <after>others</after>
+ <after><others/></after>
</ordering>
<resource-ref>
diff --git a/jetty-proxy/pom.xml b/jetty-proxy/pom.xml
index ea2da7b..b4cbb77 100644
--- a/jetty-proxy/pom.xml
+++ b/jetty-proxy/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-proxy</artifactId>
@@ -25,7 +25,7 @@
</goals>
<configuration>
<instructions>
- <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
+ <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",*</Import-Package>
</instructions>
</configuration>
</execution>
@@ -87,8 +87,13 @@
<version>${project.version}</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
diff --git a/jetty-proxy/src/main/config/etc/jetty-proxy.xml b/jetty-proxy/src/main/config/etc/jetty-proxy.xml
index 8a09bba..0e96724 100644
--- a/jetty-proxy/src/main/config/etc/jetty-proxy.xml
+++ b/jetty-proxy/src/main/config/etc/jetty-proxy.xml
@@ -1,48 +1,35 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
-<!-- ========================================================================== -->
-<!-- Configure the Jetty Server instance with an ID "Proxy" -->
-<!-- by adding a proxy functionalities. -->
-<!-- ========================================================================== -->
-<Configure id="Proxy" class="org.eclipse.jetty.server.Server">
-
- <Arg name="threadpool">
- <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
- <Set name="minThreads">16</Set>
- <Set name="maxThreads">256</Set>
- </New>
- </Arg>
-
- <Call name="addConnector">
- <Arg>
- <New class="org.eclipse.jetty.server.ServerConnector">
- <Arg name="server"><Ref refid="Proxy" /></Arg>
- <Set name="host"><Property name="jetty.host" /></Set>
- <Set name="port"><Property name="jetty.port" default="8888"/></Set>
- <Set name="idleTimeout">300000</Set>
- </New>
- </Arg>
- </Call>
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Set name="handler">
<New class="org.eclipse.jetty.proxy.ConnectHandler">
<Set name="handler">
- <New class="org.eclipse.jetty.servlet.ServletHandler">
- <Call id="proxyHolder" name="addServletWithMapping">
- <Arg>org.eclipse.jetty.proxy.ProxyServlet</Arg>
- <Arg>/</Arg>
- <Call name="setInitParameter">
- <Arg>maxThreads</Arg>
- <Arg>128</Arg>
+ <New class="org.eclipse.jetty.servlet.ServletHandler">
+ <Call id="proxyHolder" name="addServletWithMapping">
+ <Arg><Property name="jetty.proxy.servletClass" default="org.eclipse.jetty.proxy.ProxyServlet"/></Arg>
+ <Arg><Property name="jetty.proxy.servletMapping" default="/*"/></Arg>
+ <Call name="setInitParameter">
+ <Arg>maxThreads</Arg>
+ <Arg><Property name="jetty.proxy.maxThreads" default="128" /></Arg>
+ </Call>
+ <Call name="setInitParameter">
+ <Arg>maxConnections</Arg>
+ <Arg><Property name="jetty.proxy.maxConnections" default="256" /></Arg>
+ </Call>
+ <Call name="setInitParameter">
+ <Arg>idleTimeout</Arg>
+ <Arg><Property name="jetty.proxy.idleTimeout" default="30000" /></Arg>
+ </Call>
+ <Call name="setInitParameter">
+ <Arg>timeout</Arg>
+ <Arg><Property name="jetty.proxy.timeout" default="60000" /></Arg>
+ </Call>
</Call>
- </Call>
- </New>
+ </New>
</Set>
</New>
</Set>
- <Set name="stopAtShutdown">true</Set>
- <Set name="stopTimeout">1000</Set>
-
</Configure>
diff --git a/jetty-proxy/src/main/config/modules/proxy.mod b/jetty-proxy/src/main/config/modules/proxy.mod
new file mode 100644
index 0000000..a879ae1
--- /dev/null
+++ b/jetty-proxy/src/main/config/modules/proxy.mod
@@ -0,0 +1,22 @@
+#
+# Jetty Proxy module
+#
+
+[depend]
+servlet
+client
+
+[lib]
+lib/jetty-proxy-${jetty.version}.jar
+
+[xml]
+etc/jetty-proxy.xml
+
+[ini-template]
+## Proxy Configuration
+#jetty.proxy.servletClass=org.eclipse.jetty.proxy.ProxyServlet
+#jetty.proxy.servletMapping=/*
+#jetty.proxy.maxThreads=128
+#jetty.proxy.maxConnections=256
+#jetty.proxy.idleTimeout=30000
+#jetty.proxy.timeout=60000
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/BalancerServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/BalancerServlet.java
index 44a323b..8e37183 100644
--- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/BalancerServlet.java
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/BalancerServlet.java
@@ -175,10 +175,14 @@
private String getBalancerMemberNameFromSessionCookie(HttpServletRequest request)
{
- for (Cookie cookie : request.getCookies())
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null)
{
- if (JSESSIONID.equalsIgnoreCase(cookie.getName()))
- return extractBalancerMemberNameFromSessionId(cookie.getValue());
+ for (Cookie cookie : cookies)
+ {
+ if (JSESSIONID.equalsIgnoreCase(cookie.getName()))
+ return extractBalancerMemberNameFromSessionId(cookie.getValue());
+ }
}
return null;
}
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java
index 2e0760f..9e834fd 100644
--- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java
@@ -28,6 +28,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
+
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -177,7 +178,7 @@
protected SelectorManager newSelectorManager()
{
- return new Manager(getExecutor(), getScheduler(), 1);
+ return new ConnectManager(getExecutor(), getScheduler(), 1);
}
@Override
@@ -429,10 +430,10 @@
dump(out, indent, getBeans(), TypeUtil.asList(getHandlers()));
}
- protected class Manager extends SelectorManager
+ protected class ConnectManager extends SelectorManager
{
- private Manager(Executor executor, Scheduler scheduler, int selectors)
+ private ConnectManager(Executor executor, Scheduler scheduler, int selectors)
{
super(executor, scheduler, selectors);
}
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
index 3c1c8e1..77143fe 100644
--- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
@@ -28,10 +28,12 @@
import java.util.Iterator;
import java.util.Locale;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.servlet.AsyncContext;
import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServlet;
@@ -47,7 +49,6 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.HttpCookieStore;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -61,7 +62,7 @@
* <p/>
* To facilitate JMX monitoring, the {@link HttpClient} instance is set as context attribute,
* prefixed with the servlet's name and exposed by the mechanism provided by
- * {@link ContextHandler#MANAGED_ATTRIBUTES}.
+ * {@link ServletContext#setAttribute(String, Object)}.
* <p/>
* The following init parameters may be used to configure the servlet:
* <ul>
@@ -136,6 +137,11 @@
}
}
+ public String getViaHost()
+ {
+ return _viaHost;
+ }
+
public long getTimeout()
{
return _timeout;
@@ -206,7 +212,8 @@
* <tr>
* <td>maxThreads</td>
* <td>256</td>
- * <td>The max number of threads of HttpClient's Executor</td>
+ * <td>The max number of threads of HttpClient's Executor. If not set, or set to the value of "-", then the
+ * Jetty server thread pool will be used.</td>
* </tr>
* <tr>
* <td>maxConnections</td>
@@ -244,26 +251,37 @@
ServletConfig config = getServletConfig();
HttpClient client = newHttpClient();
+
// Redirects must be proxied as is, not followed
client.setFollowRedirects(false);
// Must not store cookies, otherwise cookies of different clients will mix
client.setCookieStore(new HttpCookieStore.Empty());
+ Executor executor;
String value = config.getInitParameter("maxThreads");
- if (value == null)
- value = "256";
- QueuedThreadPool executor = new QueuedThreadPool(Integer.parseInt(value));
- String servletName = config.getServletName();
- int dot = servletName.lastIndexOf('.');
- if (dot >= 0)
- servletName = servletName.substring(dot + 1);
- executor.setName(servletName);
+ if (value == null || "-".equals(value))
+ {
+ executor = (Executor)getServletContext().getAttribute("org.eclipse.jetty.server.Executor");
+ if (executor==null)
+ throw new IllegalStateException("No server executor for proxy");
+ }
+ else
+ {
+ QueuedThreadPool qtp= new QueuedThreadPool(Integer.parseInt(value));
+ String servletName = config.getServletName();
+ int dot = servletName.lastIndexOf('.');
+ if (dot >= 0)
+ servletName = servletName.substring(dot + 1);
+ qtp.setName(servletName);
+ executor=qtp;
+ }
+
client.setExecutor(executor);
value = config.getInitParameter("maxConnections");
if (value == null)
- value = "32768";
+ value = "256";
client.setMaxConnectionsPerDestination(Integer.parseInt(value));
value = config.getInitParameter("idleTimeout");
@@ -376,6 +394,7 @@
.version(HttpVersion.fromString(request.getProtocol()));
// Copy headers
+ boolean hasContent = false;
for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
{
String headerName = headerNames.nextElement();
@@ -385,9 +404,13 @@
if (HOP_HEADERS.contains(lowerHeaderName))
continue;
- if (_hostHeader!=null && lowerHeaderName.equals("host"))
+ if (_hostHeader != null && HttpHeader.HOST.is(headerName))
continue;
+ if (request.getContentLength() > 0 || request.getContentType() != null ||
+ HttpHeader.TRANSFER_ENCODING.is(headerName))
+ hasContent = true;
+
for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
{
String headerValue = headerValues.nextElement();
@@ -401,27 +424,27 @@
proxyRequest.header(HttpHeader.HOST, _hostHeader);
// Add proxy headers
- proxyRequest.header(HttpHeader.VIA, "http/1.1 " + _viaHost);
- proxyRequest.header(HttpHeader.X_FORWARDED_FOR, request.getRemoteAddr());
- proxyRequest.header(HttpHeader.X_FORWARDED_PROTO, request.getScheme());
- proxyRequest.header(HttpHeader.X_FORWARDED_HOST, request.getHeader(HttpHeader.HOST.asString()));
- proxyRequest.header(HttpHeader.X_FORWARDED_SERVER, request.getLocalName());
+ addViaHeader(proxyRequest);
+ addXForwardedHeaders(proxyRequest, request);
- proxyRequest.content(new InputStreamContentProvider(request.getInputStream())
+ if (hasContent)
{
- @Override
- public long getLength()
+ proxyRequest.content(new InputStreamContentProvider(request.getInputStream())
{
- return request.getContentLength();
- }
+ @Override
+ public long getLength()
+ {
+ return request.getContentLength();
+ }
- @Override
- protected ByteBuffer onRead(byte[] buffer, int offset, int length)
- {
- _log.debug("{} proxying content to upstream: {} bytes", requestId, length);
- return super.onRead(buffer, offset, length);
- }
- });
+ @Override
+ protected ByteBuffer onRead(byte[] buffer, int offset, int length)
+ {
+ _log.debug("{} proxying content to upstream: {} bytes", requestId, length);
+ return super.onRead(buffer, offset, length);
+ }
+ });
+ }
final AsyncContext asyncContext = request.startAsync();
// We do not timeout the continuation, but the proxy request
@@ -467,6 +490,19 @@
proxyRequest.send(new ProxyResponseListener(request, response));
}
+ protected Request addViaHeader(Request proxyRequest)
+ {
+ return proxyRequest.header(HttpHeader.VIA, "http/1.1 " + getViaHost());
+ }
+
+ protected void addXForwardedHeaders(Request proxyRequest, HttpServletRequest request)
+ {
+ proxyRequest.header(HttpHeader.X_FORWARDED_FOR, request.getRemoteAddr());
+ proxyRequest.header(HttpHeader.X_FORWARDED_PROTO, request.getScheme());
+ proxyRequest.header(HttpHeader.X_FORWARDED_HOST, request.getHeader(HttpHeader.HOST.asString()));
+ proxyRequest.header(HttpHeader.X_FORWARDED_SERVER, request.getLocalName());
+ }
+
protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
{
for (HttpField field : proxyResponse.getHeaders())
@@ -616,7 +652,12 @@
return null;
StringBuilder uri = new StringBuilder(_proxyTo);
- uri.append(path.substring(_prefix.length()));
+ if (_proxyTo.endsWith("/"))
+ uri.setLength(uri.length() - 1);
+ String rest = path.substring(_prefix.length());
+ if (!rest.startsWith("/"))
+ uri.append("/");
+ uri.append(rest);
String query = request.getQueryString();
if (query != null)
uri.append("?").append(query);
@@ -629,7 +670,7 @@
}
}
- private class ProxyResponseListener extends Response.Listener.Empty
+ private class ProxyResponseListener extends Response.Listener.Adapter
{
private final HttpServletRequest request;
private final HttpServletResponse response;
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/BalancerServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/BalancerServletTest.java
index ee3c4c6..db1b053 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/BalancerServletTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/BalancerServletTest.java
@@ -32,7 +32,6 @@
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
-import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.session.AbstractSessionIdManager;
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java
index 7739fe8..0637117 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.proxy;
+import static java.nio.file.StandardOpenOption.CREATE;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -28,15 +30,16 @@
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.zip.GZIPOutputStream;
+
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
@@ -48,8 +51,8 @@
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpContentResponse;
+import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.api.ContentResponse;
-import org.eclipse.jetty.client.api.ProxyConfiguration;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
@@ -76,8 +79,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import static java.nio.file.StandardOpenOption.CREATE;
-
@RunWith(AdvancedRunner.class)
public class ProxyServletTest
{
@@ -110,7 +111,7 @@
private HttpClient prepareClient() throws Exception
{
HttpClient result = new HttpClient();
- result.setProxyConfiguration(new ProxyConfiguration("localhost", proxyConnector.getLocalPort()));
+ result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
result.start();
return result;
}
@@ -236,17 +237,17 @@
prepareProxy(new ProxyServlet());
HttpClient result = new HttpClient();
- result.setProxyConfiguration(new ProxyConfiguration("localhost", proxyConnector.getLocalPort()));
+ result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setName("foo");
- threadPool.setMaxThreads(2);
+ threadPool.setMaxThreads(20);
result.setExecutor(threadPool);
result.start();
ContentResponse[] responses = new ContentResponse[10];
final byte[] content = new byte[1024];
- Arrays.fill(content, (byte)'A');
+ new Random().nextBytes(content);
prepareServer(new HttpServlet()
{
@Override
@@ -269,10 +270,9 @@
for ( int i = 0; i < 10; ++i )
{
-
- Assert.assertEquals(200, responses[i].getStatus());
- Assert.assertTrue(responses[i].getHeaders().containsKey(PROXIED_HEADER));
- Assert.assertArrayEquals(content, responses[i].getContent());
+ Assert.assertEquals(200, responses[i].getStatus());
+ Assert.assertTrue(responses[i].getHeaders().containsKey(PROXIED_HEADER));
+ Assert.assertArrayEquals(content, responses[i].getContent());
}
}
@@ -292,7 +292,7 @@
});
byte[] content = new byte[1024];
- Arrays.fill(content, (byte)'A');
+ new Random().nextBytes(content);
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.method(HttpMethod.POST)
.content(new BytesContentProvider(content))
@@ -332,6 +332,9 @@
@Test
public void testProxyWithBigRequestContentConsumed() throws Exception
{
+ final byte[] content = new byte[128 * 1024];
+ new Random().nextBytes(content);
+
prepareProxy(new ProxyServlet());
prepareServer(new HttpServlet()
{
@@ -341,13 +344,18 @@
if (req.getHeader("Via") != null)
resp.addHeader(PROXIED_HEADER, "true");
InputStream input = req.getInputStream();
+ int index = 0;
while (true)
- if (input.read() < 0)
+ {
+ int value = input.read();
+ if (value < 0)
break;
+ Assert.assertEquals("Content mismatch at index=" + index, content[index] & 0xFF, value);
+ ++index;
+ }
}
});
- byte[] content = new byte[128 * 1024];
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.method(HttpMethod.POST)
.content(new BytesContentProvider(content))
@@ -370,7 +378,7 @@
Files.createDirectories(targetTestsDir);
final Path temp = Files.createTempFile(targetTestsDir, "test_", null);
byte[] kb = new byte[1024];
- Arrays.fill(kb, (byte)'X');
+ new Random().nextBytes(kb);
try (OutputStream output = Files.newOutputStream(temp, CREATE))
{
for (int i = 0; i < length; ++i)
@@ -632,7 +640,7 @@
}
});
int port = serverConnector.getLocalPort();
- client.getProxyConfiguration().getExcludedOrigins().add("127.0.0.1:" + port);
+ client.getProxyConfiguration().getProxies().get(0).getExcludedAddresses().add("127.0.0.1:" + port);
// Try with a proxied host
ContentResponse response = client.newRequest("localhost", port)
@@ -652,6 +660,17 @@
@Test
public void testTransparentProxy() throws Exception
{
+ testTransparentProxyWithPrefix("/proxy");
+ }
+
+ @Test
+ public void testTransparentProxyWithRootContext() throws Exception
+ {
+ testTransparentProxyWithPrefix("/");
+ }
+
+ private void testTransparentProxyWithPrefix(String prefix) throws Exception
+ {
final String target = "/test";
prepareServer(new HttpServlet()
{
@@ -665,13 +684,12 @@
});
String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
- String prefix = "/proxy";
ProxyServlet.Transparent proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix);
prepareProxy(proxyServlet);
// Make the request to the proxy, it should transparently forward to the server
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
- .path(prefix + target)
+ .path((prefix + target).replaceAll("//", "/"))
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(200, response.getStatus());
@@ -866,7 +884,7 @@
}
});
- ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+ client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.send();
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
index e4de3f3..5b9d32a 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.proxy;
+import static org.junit.Assert.assertEquals;
+
import java.io.IOException;
import java.net.ConnectException;
import java.net.Socket;
@@ -33,10 +35,10 @@
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpProxy;
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.ProxyConfiguration;
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
@@ -62,8 +64,6 @@
import org.junit.Rule;
import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-
public class ProxyTunnellingTest
{
@Rule
@@ -143,7 +143,7 @@
startProxy();
HttpClient httpClient = new HttpClient(sslContextFactory);
- httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
+ httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
httpClient.start();
try
@@ -172,7 +172,7 @@
startProxy();
HttpClient httpClient = new HttpClient(sslContextFactory);
- httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
+ httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
httpClient.start();
try
@@ -215,7 +215,7 @@
startProxy();
final HttpClient httpClient = new HttpClient(sslContextFactory);
- httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
+ httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
httpClient.start();
try
@@ -285,7 +285,7 @@
stopProxy();
HttpClient httpClient = new HttpClient(sslContextFactory);
- httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort));
+ httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
httpClient.start();
try
@@ -317,7 +317,7 @@
startProxy();
HttpClient httpClient = new HttpClient(sslContextFactory);
- httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
+ httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
httpClient.start();
try
@@ -354,7 +354,7 @@
});
HttpClient httpClient = new HttpClient(sslContextFactory);
- httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
+ httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
httpClient.start();
try
@@ -375,7 +375,7 @@
}
@Test
- @Ignore // to delicate to rely on external proxy.
+ @Ignore("External Proxy Server no longer stable enough for testing")
public void testExternalProxy() throws Exception
{
// Free proxy server obtained from http://hidemyass.com/proxy-list/
@@ -394,7 +394,7 @@
sslContextFactory.start();
HttpClient httpClient = new HttpClient(sslContextFactory);
- httpClient.setProxyConfiguration(new ProxyConfiguration(proxyHost, proxyPort));
+ httpClient.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
httpClient.start();
try
diff --git a/jetty-rewrite/pom.xml b/jetty-rewrite/pom.xml
index ca45323..61d03c1 100644
--- a/jetty-rewrite/pom.xml
+++ b/jetty-rewrite/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-rewrite</artifactId>
@@ -25,7 +25,7 @@
</goals>
<configuration>
<instructions>
- <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
+ <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",*</Import-Package>
</instructions>
</configuration>
</execution>
@@ -86,8 +86,8 @@
<version>${project.version}</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies>
</project>
diff --git a/jetty-rewrite/src/main/config/modules/rewrite.mod b/jetty-rewrite/src/main/config/modules/rewrite.mod
new file mode 100644
index 0000000..d2e00c8
--- /dev/null
+++ b/jetty-rewrite/src/main/config/modules/rewrite.mod
@@ -0,0 +1,12 @@
+#
+# Jetty Rewrite module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-rewrite-${jetty.version}.jar
+
+[xml]
+etc/jetty-rewrite.xml
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CompactPathRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CompactPathRule.java
new file mode 100644
index 0000000..25dc5c2
--- /dev/null
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CompactPathRule.java
@@ -0,0 +1,56 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.rewrite.handler;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.URIUtil;
+
+/**
+ * Rewrite the URI by compacting to remove //
+ */
+public class CompactPathRule extends Rule implements Rule.ApplyURI
+{
+ public CompactPathRule()
+ {
+ _handling = false;
+ _terminating = false;
+ }
+
+ @Override
+ public void applyURI(Request request, String oldTarget, String newTarget) throws IOException
+ {
+ String uri = request.getRequestURI();
+ if (uri.startsWith("/"))
+ uri = URIUtil.compactPath(uri);
+ request.setRequestURI(uri);
+ }
+
+ @Override
+ public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ if (target.startsWith("/"))
+ return URIUtil.compactPath(target);
+ return target;
+ }
+}
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java
index d4298be..d723b10 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java
@@ -26,7 +26,6 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.util.ArrayTernaryTrie;
-import org.eclipse.jetty.util.StringMap;
import org.eclipse.jetty.util.Trie;
/**
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java
index 4f15f42..662c0a0 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java
@@ -50,7 +50,6 @@
* <li> {@link ResponsePatternRule} - sets the status/error codes. </li>
* <li> {@link RewritePatternRule} - rewrites the requested URI. </li>
* <li> {@link RewriteRegexRule} - rewrites the requested URI using regular expression for pattern matching. </li>
- * <li> {@link ProxyRule} - proxies the requested URI to the host defined in proxyTo. </li>
* <li> {@link MsieSslRule} - disables the keep alive on SSL for IE5 and IE6. </li>
* <li> {@link LegacyRule} - the old version of rewrite. </li>
* <li> {@link ForwardedSchemeHeaderRule} - set the scheme according to the headers present. </li>
@@ -72,13 +71,6 @@
* </Item>
*
* <Item>
- * <New id="rewrite" class="org.eclipse.jetty.rewrite.handler.ProxyRule">
- * <Set name="pattern">/*</Set>
- * <Set name="proxyTo">http://webtide.com:8080</Set>
- * </New>
- * </Item>
- *
- * <Item>
* <New id="response" class="org.eclipse.jetty.rewrite.handler.ResponsePatternRule">
* <Set name="pattern">/session/</Set>
* <Set name="code">400</Set>
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java
index d6bd65d..23e082a 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.rewrite.handler;
import java.io.IOException;
+
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java
index 20d52fb..06f3a23 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java
@@ -18,15 +18,15 @@
package org.eclipse.jetty.rewrite.handler;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
import java.io.IOException;
import org.eclipse.jetty.http.HttpURI;
import org.junit.Before;
import org.junit.Test;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-
public class RewritePatternRuleTest extends AbstractRuleTestCase
{
private String[][] _tests =
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java
index 58357fd..3231f70 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java
@@ -25,7 +25,6 @@
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.UrlEncoded;
import org.junit.Before;
import org.junit.Test;
diff --git a/jetty-rhttp/README.txt b/jetty-rhttp/README.TXT
similarity index 100%
rename from jetty-rhttp/README.txt
rename to jetty-rhttp/README.TXT
diff --git a/jetty-rhttp/jetty-rhttp-connector/pom.xml b/jetty-rhttp/jetty-rhttp-connector/pom.xml
index 95b8d9c..b248c48 100644
--- a/jetty-rhttp/jetty-rhttp-connector/pom.xml
+++ b/jetty-rhttp/jetty-rhttp-connector/pom.xml
@@ -88,7 +88,7 @@
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
- <artifactId>servlet-api</artifactId>
+ <artifactId>javax.servlet-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
diff --git a/jetty-rhttp/jetty-rhttp-gateway/pom.xml b/jetty-rhttp/jetty-rhttp-gateway/pom.xml
index 1243cd8..054bbd5 100644
--- a/jetty-rhttp/jetty-rhttp-gateway/pom.xml
+++ b/jetty-rhttp/jetty-rhttp-gateway/pom.xml
@@ -66,7 +66,7 @@
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
- <artifactId>servlet-api</artifactId>
+ <artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
diff --git a/jetty-runner/pom.xml b/jetty-runner/pom.xml
index 460e509..d758892 100644
--- a/jetty-runner/pom.xml
+++ b/jetty-runner/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty</groupId>
diff --git a/jetty-security/pom.xml b/jetty-security/pom.xml
index d85f64e..704ba0f 100644
--- a/jetty-security/pom.xml
+++ b/jetty-security/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-security</artifactId>
@@ -25,7 +25,7 @@
</goals>
<configuration>
<instructions>
- <Import-Package>javax.servlet.*;version="2.6.0",javax.security.cert,*</Import-Package>
+ <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",javax.security.cert,*</Import-Package>
</instructions>
</configuration>
diff --git a/jetty-security/src/main/config/modules/security.mod b/jetty-security/src/main/config/modules/security.mod
new file mode 100644
index 0000000..ba31632
--- /dev/null
+++ b/jetty-security/src/main/config/modules/security.mod
@@ -0,0 +1,9 @@
+#
+# Jetty Security Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-security-${jetty.version}.jar
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractUserAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractUserAuthentication.java
new file mode 100644
index 0000000..ab20ba8
--- /dev/null
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractUserAuthentication.java
@@ -0,0 +1,95 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.Set;
+
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.UserIdentity.Scope;
+
+/**
+ * AbstractUserAuthentication
+ *
+ *
+ * Base class for representing an authenticated user.
+ */
+public abstract class AbstractUserAuthentication implements User
+{
+ protected String _method;
+ protected UserIdentity _userIdentity;
+
+
+ public AbstractUserAuthentication(String method, UserIdentity userIdentity)
+ {
+ _method = method;
+ _userIdentity = userIdentity;
+ }
+
+
+ @Override
+ public String getAuthMethod()
+ {
+ return _method;
+ }
+
+ @Override
+ public UserIdentity getUserIdentity()
+ {
+ return _userIdentity;
+ }
+
+ @Override
+ public boolean isUserInRole(Scope scope, String role)
+ {
+ String roleToTest = null;
+ if (scope!=null && scope.getRoleRefMap()!=null)
+ roleToTest=scope.getRoleRefMap().get(role);
+ if (roleToTest==null)
+ roleToTest=role;
+ //Servlet Spec 3.1 pg 125 if testing special role **
+ if ("**".equals(roleToTest.trim()))
+ {
+ //if ** is NOT a declared role name, the we return true
+ //as the user is authenticated. If ** HAS been declared as a
+ //role name, then we have to check if the user has that role
+ if (!declaredRolesContains("**"))
+ return true;
+ else
+ return _userIdentity.isUserInRole(role, scope);
+ }
+
+ return _userIdentity.isUserInRole(role, scope);
+ }
+
+ public boolean declaredRolesContains(String roleName)
+ {
+ SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+ if (security==null)
+ return false;
+
+ if (security instanceof ConstraintAware)
+ {
+ Set<String> declaredRoles = ((ConstraintAware)security).getRoles();
+ return (declaredRoles != null) && declaredRoles.contains(roleName);
+ }
+
+ return false;
+ }
+}
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java
index 2e079db..c487319 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java
@@ -52,9 +52,25 @@
* @return The name of the authentication method
*/
String getAuthMethod();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Called prior to validateRequest. The authenticator can
+ * manipulate the request to update it with information that
+ * can be inspected prior to validateRequest being called.
+ * The primary purpose of this method is to satisfy the Servlet
+ * Spec 3.1 section 13.6.3 on handling Form authentication
+ * where the http method of the original request causing authentication
+ * is not the same as the http method resulting from the redirect
+ * after authentication.
+ * @param request
+ */
+ void prepareRequest(ServletRequest request);
+
/* ------------------------------------------------------------ */
- /** Validate a response
+ /** Validate a request
* @param request The request
* @param response The response
* @param mandatory True if authentication is mandatory.
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java
index bec6764..d5c74a6 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java
@@ -51,4 +51,20 @@
* @param role
*/
void addRole(String role);
+
+ /**
+ * See Servlet Spec 31, sec 13.8.4, pg 145
+ * When true, requests with http methods not explicitly covered either by inclusion or omissions
+ * in constraints, will have access denied.
+ * @param deny
+ */
+ void setDenyUncoveredHttpMethods(boolean deny);
+
+ boolean isDenyUncoveredHttpMethods();
+
+ /**
+ * See Servlet Spec 31, sec 13.8.4, pg 145
+ * Container must check if there are urls with uncovered http methods
+ */
+ boolean checkPathsWithUncoveredHttpMethods();
}
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
index fd9a415..d68a548 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
@@ -45,28 +45,32 @@
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.security.Constraint;
/* ------------------------------------------------------------ */
/**
+ * ConstraintSecurityHandler
+ *
* Handler to enforce SecurityConstraints. This implementation is servlet spec
- * 3.0 compliant and pre-computes the constraint combinations for runtime
+ * 3.1 compliant and pre-computes the constraint combinations for runtime
* efficiency.
*
*/
public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
{
+ private static final Logger LOG = Log.getLogger(SecurityHandler.class); //use same as SecurityHandler
+
private static final String OMISSION_SUFFIX = ".omission";
private static final String ALL_METHODS = "*";
private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<>();
private final Set<String> _roles = new CopyOnWriteArraySet<>();
private final PathMap<Map<String, RoleInfo>> _constraintMap = new PathMap<>();
- private boolean _strict = true;
+ private boolean _denyUncoveredMethods = false;
+
/* ------------------------------------------------------------ */
- /**
- * @return
- */
public static Constraint createConstraint()
{
return new Constraint();
@@ -75,7 +79,6 @@
/* ------------------------------------------------------------ */
/**
* @param constraint
- * @return
*/
public static Constraint createConstraint(Constraint constraint)
{
@@ -97,7 +100,6 @@
* @param authenticate
* @param roles
* @param dataConstraint
- * @return
*/
public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint)
{
@@ -115,7 +117,6 @@
/**
* @param name
* @param element
- * @return
*/
public static Constraint createConstraint (String name, HttpConstraintElement element)
{
@@ -129,7 +130,6 @@
* @param rolesAllowed
* @param permitOrDeny
* @param transport
- * @return
*/
public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
{
@@ -169,7 +169,6 @@
/**
* @param pathSpec
* @param constraintMappings
- * @return
*/
public static List<ConstraintMapping> getConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
{
@@ -194,7 +193,6 @@
*
* @param pathSpec
* @param constraintMappings a new list minus the matching constraints
- * @return
*/
public static List<ConstraintMapping> removeConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
{
@@ -228,76 +226,56 @@
List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
//Create a constraint that will describe the default case (ie if not overridden by specific HttpMethodConstraints)
- Constraint constraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
+ Constraint httpConstraint = null;
+ ConstraintMapping httpConstraintMapping = null;
+
+ if (securityElement.getEmptyRoleSemantic() != EmptyRoleSemantic.PERMIT ||
+ securityElement.getRolesAllowed().length != 0 ||
+ securityElement.getTransportGuarantee() != TransportGuarantee.NONE)
+ {
+ httpConstraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
- //Create a mapping for the pathSpec for the default case
- ConstraintMapping defaultMapping = new ConstraintMapping();
- defaultMapping.setPathSpec(pathSpec);
- defaultMapping.setConstraint(constraint);
- mappings.add(defaultMapping);
-
+ //Create a mapping for the pathSpec for the default case
+ httpConstraintMapping = new ConstraintMapping();
+ httpConstraintMapping.setPathSpec(pathSpec);
+ httpConstraintMapping.setConstraint(httpConstraint);
+ mappings.add(httpConstraintMapping);
+ }
+
//See Spec 13.4.1.2 p127
List<String> methodOmissions = new ArrayList<String>();
//make constraint mappings for this url for each of the HttpMethodConstraintElements
- Collection<HttpMethodConstraintElement> methodConstraints = securityElement.getHttpMethodConstraints();
- if (methodConstraints != null)
+ Collection<HttpMethodConstraintElement> methodConstraintElements = securityElement.getHttpMethodConstraints();
+ if (methodConstraintElements != null)
{
- for (HttpMethodConstraintElement methodConstraint:methodConstraints)
+ for (HttpMethodConstraintElement methodConstraintElement:methodConstraintElements)
{
//Make a Constraint that captures the <auth-constraint> and <user-data-constraint> elements supplied for the HttpMethodConstraintElement
- Constraint mconstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraint);
+ Constraint methodConstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraintElement);
ConstraintMapping mapping = new ConstraintMapping();
- mapping.setConstraint(mconstraint);
+ mapping.setConstraint(methodConstraint);
mapping.setPathSpec(pathSpec);
- if (methodConstraint.getMethodName() != null)
+ if (methodConstraintElement.getMethodName() != null)
{
- mapping.setMethod(methodConstraint.getMethodName());
+ mapping.setMethod(methodConstraintElement.getMethodName());
//See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
- methodOmissions.add(methodConstraint.getMethodName());
+ methodOmissions.add(methodConstraintElement.getMethodName());
}
mappings.add(mapping);
}
}
//See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
- if (methodOmissions.size() > 0)
- defaultMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
+ //UNLESS the default constraint contains all default values. In that case, we won't add it. See Servlet Spec 3.1 pg 129
+ if (methodOmissions.size() > 0 && httpConstraintMapping != null)
+ httpConstraintMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
return mappings;
}
-
- /* ------------------------------------------------------------ */
- /** Get the strict mode.
- * @return true if the security handler is running in strict mode.
- */
- public boolean isStrict()
- {
- return _strict;
- }
- /* ------------------------------------------------------------ */
- /** Set the strict mode of the security handler.
- * <p>
- * When in strict mode (the default), the full servlet specification
- * will be implemented.
- * If not in strict mode, some additional flexibility in configuration
- * is allowed:<ul>
- * <li>All users do not need to have a role defined in the deployment descriptor
- * <li>The * role in a constraint applies to ANY role rather than all roles defined in
- * the deployment descriptor.
- * </ul>
- *
- * @param strict the strict to set
- * @see #setRoles(Set)
- * @see #setConstraintMappings(List, Set)
- */
- public void setStrict(boolean strict)
- {
- _strict = strict;
- }
/* ------------------------------------------------------------ */
/**
@@ -388,7 +366,6 @@
* Set the known roles.
* This may be overridden by a subsequent call to {@link #setConstraintMappings(ConstraintMapping[])} or
* {@link #setConstraintMappings(List, Set)}.
- * @see #setStrict(boolean)
* @param roles The known roles (or null to determine them from the mappings)
*/
public void setRoles(Set<String> roles)
@@ -408,8 +385,16 @@
{
_constraintMappings.add(mapping);
if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
+ {
+ //allow for lazy role naming: if a role is named in a security constraint, try and
+ //add it to the list of declared roles (ie as if it was declared with a security-role
for (String role : mapping.getConstraint().getRoles())
+ {
+ if ("*".equals(role) || "**".equals(role))
+ continue;
addRole(role);
+ }
+ }
if (isStarted())
{
@@ -424,8 +409,9 @@
@Override
public void addRole(String role)
{
+ //add to list of declared roles
boolean modified = _roles.add(role);
- if (isStarted() && modified && isStrict())
+ if (isStarted() && modified)
{
// Add the new role to currently defined any role role infos
for (Map<String,RoleInfo> map : _constraintMap.values())
@@ -454,9 +440,13 @@
processConstraintMapping(mapping);
}
}
+
+ //Servlet Spec 3.1 pg 147 sec 13.8.4.2 log paths for which there are uncovered http methods
+ checkPathsWithUncoveredHttpMethods();
+
super.doStart();
}
-
+
/* ------------------------------------------------------------ */
@Override
@@ -538,13 +528,13 @@
/* ------------------------------------------------------------ */
/** Constraints that name method omissions are dealt with differently.
- * We create an entry in the mappings with key "method.omission". This entry
+ * We create an entry in the mappings with key "<method>.omission". This entry
* is only ever combined with other omissions for the same method to produce a
* consolidated RoleInfo. Then, when we wish to find the relevant constraints for
* a given Request (in prepareConstraintInfo()), we consult 3 types of entries in
* the mappings: an entry that names the method of the Request specifically, an
* entry that names constraints that apply to all methods, entries of the form
- * method.omission, where the method of the Request is not named in the omission.
+ * <method>.omission, where the method of the Request is not named in the omission.
* @param mapping
* @param mappings
*/
@@ -559,7 +549,6 @@
sb.append(omissions[i]);
}
sb.append(OMISSION_SUFFIX);
-
RoleInfo ri = new RoleInfo();
mappings.put(sb.toString(), ri);
configureRoleInfo(ri, mapping);
@@ -573,7 +562,7 @@
* @param mapping
*/
protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping)
- {
+ {
Constraint constraint = mapping.getConstraint();
boolean forbidden = constraint.isForbidden();
ri.setForbidden(forbidden);
@@ -582,7 +571,6 @@
//which we need in order to do combining of omissions in prepareConstraintInfo
UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
ri.setUserDataConstraint(userDataConstraint);
-
//if forbidden, no point setting up roles
if (!ri.isForbidden())
@@ -590,26 +578,29 @@
//add in the roles
boolean checked = mapping.getConstraint().getAuthenticate();
ri.setChecked(checked);
+
if (ri.isChecked())
{
if (mapping.getConstraint().isAnyRole())
- {
- if (_strict)
- {
- // * means "all defined roles"
- for (String role : _roles)
- ri.addRole(role);
- }
- else
- // * means any role
- ri.setAnyRole(true);
- }
- else
- {
+ {
+ // * means matches any defined role
+ for (String role : _roles)
+ ri.addRole(role);
+ ri.setAnyRole(true);
+ }
+ else if (mapping.getConstraint().isAnyAuth())
+ {
+ //being authenticated is sufficient, not necessary to check roles
+ ri.setAnyAuth(true);
+ }
+ else
+ {
+ //user must be in one of the named roles
String[] newRoles = mapping.getConstraint().getRoles();
for (String role : newRoles)
{
- if (_strict &&!_roles.contains(role))
+ //check role has been defined
+ if (!_roles.contains(role))
throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
ri.addRole(role);
}
@@ -626,7 +617,7 @@
* represents a merged set of user data constraints, roles etc -:
* <ol>
* <li>A mapping of an exact method name </li>
- * <li>A mapping will null key that matches every method name</li>
+ * <li>A mapping with key * that matches every method name</li>
* <li>Mappings with keys of the form "<method>.<method>.<method>.omission" that indicates it will match every method name EXCEPT those given</li>
* </ol>
*
@@ -660,7 +651,12 @@
applicableConstraints.add(entry.getValue());
}
- if (applicableConstraints.size() == 1)
+ if (applicableConstraints.size() == 0 && isDenyUncoveredHttpMethods())
+ {
+ roleInfo = new RoleInfo();
+ roleInfo.setForbidden(true);
+ }
+ else if (applicableConstraints.size() == 1)
roleInfo = applicableConstraints.get(0);
else
{
@@ -672,6 +668,7 @@
}
}
+
return roleInfo;
}
@@ -693,7 +690,6 @@
HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration();
-
if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral)
{
if (request.isSecure())
@@ -750,14 +746,35 @@
return true;
}
- if (roleInfo.isAnyRole() && request.getAuthType()!=null)
+ //handle ** role constraint
+ if (roleInfo.isAnyAuth() && request.getUserPrincipal() != null)
+ {
return true;
-
+ }
+
+ //check if user is any of the allowed roles
+ boolean isUserInRole = false;
for (String role : roleInfo.getRoles())
{
if (userIdentity.isUserInRole(role, null))
- return true;
+ {
+ isUserInRole = true;
+ break;
+ }
}
+
+ //handle * role constraint
+ if (roleInfo.isAnyRole() && request.getUserPrincipal() != null && isUserInRole)
+ {
+ return true;
+ }
+
+ //normal role check
+ if (isUserInRole)
+ {
+ return true;
+ }
+
return false;
}
@@ -773,5 +790,136 @@
Collections.singleton(_roles),
_constraintMap.entrySet());
}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.ConstraintAware#setDenyUncoveredHttpMethods(boolean)
+ */
+ @Override
+ public void setDenyUncoveredHttpMethods(boolean deny)
+ {
+ _denyUncoveredMethods = deny;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isDenyUncoveredHttpMethods()
+ {
+ return _denyUncoveredMethods;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Servlet spec 3.1 pg. 147.
+ */
+ @Override
+ public boolean checkPathsWithUncoveredHttpMethods()
+ {
+ Set<String> paths = getPathsWithUncoveredHttpMethods();
+ if (paths != null && !paths.isEmpty())
+ {
+ for (String p:paths)
+ LOG.warn("Path with uncovered http methods: {}",p);
+ return true;
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Servlet spec 3.1 pg. 147.
+ * The container must check all the combined security constraint
+ * information and log any methods that are not protected and the
+ * urls at which they are not protected
+ *
+ * @return list of paths for which there are uncovered methods
+ */
+ public Set<String> getPathsWithUncoveredHttpMethods ()
+ {
+ //if automatically denying uncovered methods, there are no uncovered methods
+ if (_denyUncoveredMethods)
+ return Collections.emptySet();
+
+ Set<String> uncoveredPaths = new HashSet<String>();
+
+ for (String path:_constraintMap.keySet())
+ {
+ Map<String, RoleInfo> methodMappings = _constraintMap.get(path);
+ //Each key is either:
+ // : an exact method name
+ // : * which means that the constraint applies to every method
+ // : a name of the form <method>.<method>.<method>.omission, which means it applies to every method EXCEPT those named
+ if (methodMappings.get(ALL_METHODS) != null)
+ continue; //can't be any uncovered methods for this url path
+
+ boolean hasOmissions = omissionsExist(path, methodMappings);
+
+ for (String method:methodMappings.keySet())
+ {
+ if (method.endsWith(OMISSION_SUFFIX))
+ {
+ Set<String> omittedMethods = getOmittedMethods(method);
+ for (String m:omittedMethods)
+ {
+ if (!methodMappings.containsKey(m))
+ uncoveredPaths.add(path);
+ }
+ }
+ else
+ {
+ //an exact method name
+ if (!hasOmissions)
+ //a http-method does not have http-method-omission to cover the other method names
+ uncoveredPaths.add(path);
+ }
+
+ }
+ }
+ return uncoveredPaths;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Check if any http method omissions exist in the list of method
+ * to auth info mappings.
+ *
+ * @param path
+ * @param methodMappings
+ * @return
+ */
+ protected boolean omissionsExist (String path, Map<String, RoleInfo> methodMappings)
+ {
+ if (methodMappings == null)
+ return false;
+ boolean hasOmissions = false;
+ for (String m:methodMappings.keySet())
+ {
+ if (m.endsWith(OMISSION_SUFFIX))
+ hasOmissions = true;
+ }
+ return hasOmissions;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Given a string of the form <method>.<method>.omission
+ * split out the individual method names.
+ *
+ * @param omission
+ * @return
+ */
+ protected Set<String> getOmittedMethods (String omission)
+ {
+ if (omission == null || !omission.endsWith(OMISSION_SUFFIX))
+ return Collections.emptySet();
+
+ String[] strings = omission.split("\\.");
+ Set<String> methods = new HashSet<String>();
+ for (int i=0;i<strings.length-1;i++)
+ methods.add(strings[i]);
+ return methods;
+ }
}
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java
index dd12b1d..52e93ba 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java
@@ -54,19 +54,22 @@
}
public boolean isUserInRole(String role, Scope scope)
- {
- if (scope!=null && scope.getRoleRefMap()!=null)
- {
- String mappedRole = scope.getRoleRefMap().get(role);
- if (mappedRole != null)
- role = mappedRole;
- }
+ {
+ //Servlet Spec 3.1, pg 125
+ if ("*".equals(role))
+ return false;
+ String roleToTest = null;
+ if (scope!=null && scope.getRoleRefMap()!=null)
+ roleToTest=scope.getRoleRefMap().get(role);
+
+ //Servlet Spec 3.1, pg 125
+ if (roleToTest == null)
+ roleToTest = role;
+
for (String r :_roles)
- {
- if (r.equals(role))
+ if (r.equals(roleToTest))
return true;
- }
return false;
}
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java b/jetty-security/src/main/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java
index 7c99a3e..ed0a5f1 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java
@@ -48,7 +48,11 @@
public T fetch(HttpServletRequest request)
{
- for (Cookie cookie : request.getCookies())
+ Cookie[] cookies = request.getCookies();
+ if (cookies == null)
+ return null;
+
+ for (Cookie cookie : cookies)
{
if (_cookieName.equals(cookie.getName()))
{
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java
index 5373161..74498d1 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java
@@ -261,7 +261,7 @@
roles.add(rs2.getString(_roleTableRoleField));
}
}
- return putUser(username, Credential.getCredential(credentials),roles.toArray(new String[roles.size()]));
+ return putUser(username, credentials, roles.toArray(new String[roles.size()]));
}
}
}
@@ -273,6 +273,13 @@
}
return null;
}
+
+ /* ------------------------------------------------------------ */
+ protected UserIdentity putUser (String username, String credentials, String[] roles)
+ {
+ return putUser(username, Credential.getCredential(credentials),roles);
+ }
+
/**
* Close an existing connection
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/RoleInfo.java b/jetty-security/src/main/java/org/eclipse/jetty/security/RoleInfo.java
index 3168e78..5fcab95 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/RoleInfo.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/RoleInfo.java
@@ -22,6 +22,7 @@
import java.util.concurrent.CopyOnWriteArraySet;
/**
+ * RoleInfo
*
* Badly named class that holds the role and user data constraint info for a
* path/http method combination, extracted and combined from security
@@ -31,11 +32,15 @@
*/
public class RoleInfo
{
+ private boolean _isAnyAuth;
private boolean _isAnyRole;
private boolean _checked;
private boolean _forbidden;
private UserDataConstraint _userDataConstraint;
+ /**
+ * List of permitted roles
+ */
private final Set<String> _roles = new CopyOnWriteArraySet<String>();
public RoleInfo()
@@ -55,6 +60,7 @@
_forbidden=false;
_roles.clear();
_isAnyRole=false;
+ _isAnyAuth=false;
}
}
@@ -71,6 +77,7 @@
_checked = true;
_userDataConstraint = null;
_isAnyRole=false;
+ _isAnyAuth=false;
_roles.clear();
}
}
@@ -84,10 +91,19 @@
{
this._isAnyRole=anyRole;
if (anyRole)
- {
_checked = true;
- _roles.clear();
- }
+ }
+
+ public boolean isAnyAuth ()
+ {
+ return _isAnyAuth;
+ }
+
+ public void setAnyAuth(boolean anyAuth)
+ {
+ this._isAnyAuth=anyAuth;
+ if (anyAuth)
+ _checked = true;
}
public UserDataConstraint getUserDataConstraint()
@@ -100,6 +116,7 @@
if (userDataConstraint == null) throw new NullPointerException("Null UserDataConstraint");
if (this._userDataConstraint == null)
{
+
this._userDataConstraint = userDataConstraint;
}
else
@@ -126,6 +143,8 @@
setChecked(true);
else if (other._isAnyRole)
setAnyRole(true);
+ else if (other._isAnyAuth)
+ setAnyAuth(true);
else if (!_isAnyRole)
{
for (String r : other._roles)
@@ -138,6 +157,6 @@
@Override
public String toString()
{
- return "{RoleInfo"+(_forbidden?",F":"")+(_checked?",C":"")+(_isAnyRole?",*":_roles)+"}";
+ return "{RoleInfo"+(_forbidden?",F":"")+(_checked?",C":"")+(_isAnyRole?",*":_roles)+(_userDataConstraint!=null?","+_userDataConstraint:"")+"}";
}
}
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java
index df8a4eb..ef31416 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java
@@ -462,6 +462,10 @@
if (checkSecurity(baseRequest))
{
+ //See Servlet Spec 3.1 sec 13.6.3
+ if (authenticator != null)
+ authenticator.prepareRequest(baseRequest);
+
RoleInfo roleInfo = prepareConstraintInfo(pathInContext, baseRequest);
// Check data constraints
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java
index 3d8dcba..58847bf 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java
@@ -18,39 +18,20 @@
package org.eclipse.jetty.security;
-import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.UserIdentity;
-import org.eclipse.jetty.server.UserIdentity.Scope;
/**
* @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
*/
-public class UserAuthentication implements Authentication.User
+public class UserAuthentication extends AbstractUserAuthentication
{
- private final String _method;
- private final UserIdentity _userIdentity;
-
+
public UserAuthentication(String method, UserIdentity userIdentity)
{
- _method = method;
- _userIdentity = userIdentity;
- }
-
- public String getAuthMethod()
- {
- return _method;
+ super(method, userIdentity);
}
- public UserIdentity getUserIdentity()
- {
- return _userIdentity;
- }
-
- public boolean isUserInRole(Scope scope, String role)
- {
- return _userIdentity.isUserInRole(role, scope);
- }
@Override
public String toString()
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java
index 4eeeec8..a81cfc7 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java
@@ -33,7 +33,6 @@
import org.eclipse.jetty.server.Authentication.User;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.security.Constraint;
/**
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
index b1a2c60..3853ebd 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
@@ -28,6 +28,7 @@
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
@@ -111,7 +112,7 @@
/* ------------------------------------------------------------ */
/**
- * @see org.eclipse.jetty.server.Authentication.Deferred#login(java.lang.String, java.lang.String)
+ * @see org.eclipse.jetty.server.Authentication.Deferred#login(String, Object, ServletRequest)
*/
@Override
public Authentication login(String username, Object password, ServletRequest request)
@@ -313,6 +314,11 @@
public void setContentLength(int len)
{
}
+
+ public void setContentLengthLong(long len)
+ {
+
+ }
@Override
public void setContentType(String type)
@@ -348,6 +354,7 @@
return 0;
}
+
};
/* ------------------------------------------------------------ */
@@ -355,17 +362,33 @@
/* ------------------------------------------------------------ */
private static ServletOutputStream __nullOut = new ServletOutputStream()
{
+ @Override
public void write(int b) throws IOException
{
}
-
+
+ @Override
public void print(String s) throws IOException
{
}
-
+
+ @Override
public void println(String s) throws IOException
{
}
+
+
+ @Override
+ public void setWriteListener(WriteListener writeListener)
+ {
+
+ }
+
+ @Override
+ public boolean isReady()
+ {
+ return false;
+ }
};
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
index 49f924a..e7a5d4e 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
@@ -43,7 +43,6 @@
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.QuotedStringTokenizer;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
index 71bba4a..0de7288 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
@@ -36,6 +36,7 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication;
@@ -43,6 +44,7 @@
import org.eclipse.jetty.server.Authentication.User;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.StringUtil;
@@ -75,6 +77,7 @@
public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
public final static String __J_URI = "org.eclipse.jetty.security.form_URI";
public final static String __J_POST = "org.eclipse.jetty.security.form_POST";
+ public final static String __J_METHOD = "org.eclipse.jetty.security.form_METHOD";
public final static String __J_SECURITY_CHECK = "/j_security_check";
public final static String __J_USERNAME = "j_username";
public final static String __J_PASSWORD = "j_password";
@@ -198,6 +201,45 @@
}
return user;
}
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void prepareRequest(ServletRequest request)
+ {
+ //if this is a request resulting from a redirect after auth is complete
+ //(ie its from a redirect to the original request uri) then due to
+ //browser handling of 302 redirects, the method may not be the same as
+ //that of the original request. Replace the method and original post
+ //params (if it was a post).
+ //
+ //See Servlet Spec 3.1 sec 13.6.3
+ HttpServletRequest httpRequest = (HttpServletRequest)request;
+ HttpSession session = httpRequest.getSession(false);
+ if (session == null || session.getAttribute(SessionAuthentication.__J_AUTHENTICATED) == null)
+ return; //not authenticated yet
+
+ String juri = (String)session.getAttribute(__J_URI);
+ if (juri == null || juri.length() == 0)
+ return; //no original uri saved
+
+ String method = (String)session.getAttribute(__J_METHOD);
+ if (method == null || method.length() == 0)
+ return; //didn't save original request method
+
+ StringBuffer buf = httpRequest.getRequestURL();
+ if (httpRequest.getQueryString() != null)
+ buf.append("?").append(httpRequest.getQueryString());
+
+ if (!juri.equals(buf.toString()))
+ return; //this request is not for the same url as the original
+
+ //restore the original request's method on this request
+ if (LOG.isDebugEnabled()) LOG.debug("Restoring original method {} for {} with method {}", method, juri,httpRequest.getMethod());
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ HttpMethod m = HttpMethod.fromString(method);
+ base_request.setMethod(m,m.asString());
+ }
/* ------------------------------------------------------------ */
@Override
@@ -249,7 +291,10 @@
LOG.debug("authenticated {}->{}",form_auth,nuri);
response.setContentLength(0);
- response.sendRedirect(response.encodeRedirectURL(nuri));
+ Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+ base_response.sendRedirect(redirectCode, response.encodeRedirectURL(nuri));
return form_auth;
}
@@ -273,7 +318,10 @@
else
{
LOG.debug("auth failed {}->{}",username,_formErrorPage);
- response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
+ Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+ base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
}
return Authentication.SEND_FAILURE;
@@ -298,28 +346,26 @@
String j_uri=(String)session.getAttribute(__J_URI);
if (j_uri!=null)
{
+ //check if the request is for the same url as the original and restore
+ //params if it was a post
LOG.debug("auth retry {}->{}",authentication,j_uri);
- MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
- if (j_post!=null)
+ StringBuffer buf = request.getRequestURL();
+ if (request.getQueryString() != null)
+ buf.append("?").append(request.getQueryString());
+
+ if (j_uri.equals(buf.toString()))
{
- LOG.debug("auth rePOST {}->{}",authentication,j_uri);
- StringBuffer buf = request.getRequestURL();
- if (request.getQueryString() != null)
- buf.append("?").append(request.getQueryString());
-
- if (j_uri.equals(buf.toString()))
+ MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
+ if (j_post!=null)
{
- // This is a retry of an original POST request
- // so restore method and parameters
-
- session.removeAttribute(__J_POST);
+ LOG.debug("auth rePOST {}->{}",authentication,j_uri);
Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
- base_request.setMethod(HttpMethod.POST,HttpMethod.POST.asString());
base_request.setParameters(j_post);
}
- }
- else
session.removeAttribute(__J_URI);
+ session.removeAttribute(__J_METHOD);
+ session.removeAttribute(__J_POST);
+ }
}
}
LOG.debug("auth {}",authentication);
@@ -344,6 +390,7 @@
if (request.getQueryString() != null)
buf.append("?").append(request.getQueryString());
session.setAttribute(__J_URI, buf.toString());
+ session.setAttribute(__J_METHOD, request.getMethod());
if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
{
@@ -366,7 +413,10 @@
else
{
LOG.debug("challenge {}->{}",session.getId(),_formLoginPage);
- response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
+ Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+ base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
}
return Authentication.SEND_CONTINUE;
}
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java
index 51ad8e9..d34a438 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java
@@ -40,11 +40,20 @@
protected LoginService _loginService;
protected IdentityService _identityService;
private boolean _renewSession;
-
+
+
+ /* ------------------------------------------------------------ */
protected LoginAuthenticator()
{
}
+ /* ------------------------------------------------------------ */
+ @Override
+ public void prepareRequest(ServletRequest request)
+ {
+ //empty implementation as the default
+ }
+
/* ------------------------------------------------------------ */
public UserIdentity login(String username, Object password, ServletRequest request)
@@ -58,7 +67,7 @@
return null;
}
-
+ /* ------------------------------------------------------------ */
@Override
public void setConfiguration(AuthConfiguration configuration)
{
@@ -70,12 +79,16 @@
throw new IllegalStateException("No IdentityService for "+this+" in "+configuration);
_renewSession=configuration.isSessionRenewedOnAuthentication();
}
-
+
+
+ /* ------------------------------------------------------------ */
public LoginService getLoginService()
{
return _loginService;
}
-
+
+
+ /* ------------------------------------------------------------ */
/** Change the session id.
* The session is changed to a new instance with a new ID if and only if:<ul>
* <li>A session exists.
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java
index ab0888e..dd4c31a 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java
@@ -29,16 +29,15 @@
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionEvent;
+import org.eclipse.jetty.security.AbstractUserAuthentication;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.SecurityHandler;
-import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.UserIdentity;
-import org.eclipse.jetty.server.UserIdentity.Scope;
import org.eclipse.jetty.server.session.AbstractSession;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-public class SessionAuthentication implements Authentication.User, Serializable, HttpSessionActivationListener, HttpSessionBindingListener
+public class SessionAuthentication extends AbstractUserAuthentication implements Serializable, HttpSessionActivationListener, HttpSessionBindingListener
{
private static final Logger LOG = Log.getLogger(SessionAuthentication.class);
@@ -48,35 +47,17 @@
public final static String __J_AUTHENTICATED="org.eclipse.jetty.security.UserIdentity";
- private final String _method;
private final String _name;
private final Object _credentials;
-
- private transient UserIdentity _userIdentity;
private transient HttpSession _session;
public SessionAuthentication(String method, UserIdentity userIdentity, Object credentials)
{
- _method = method;
- _userIdentity = userIdentity;
- _name=_userIdentity.getUserPrincipal().getName();
+ super(method, userIdentity);
+ _name=userIdentity.getUserPrincipal().getName();
_credentials=credentials;
}
- public String getAuthMethod()
- {
- return _method;
- }
-
- public UserIdentity getUserIdentity()
- {
- return _userIdentity;
- }
-
- public boolean isUserInRole(Scope scope, String role)
- {
- return _userIdentity.isUserInRole(role, scope);
- }
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException
diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
index 8b211fb..32b29fd 100644
--- a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
@@ -19,7 +19,9 @@
package org.eclipse.jetty.security;
import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.matchers.JUnitMatchers.containsString;
@@ -29,6 +31,7 @@
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -38,7 +41,12 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.servlet.HttpConstraintElement;
+import javax.servlet.HttpMethodConstraintElement;
import javax.servlet.ServletException;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -57,10 +65,8 @@
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.StringUtil;
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.security.Password;
import org.junit.After;
import org.junit.Before;
@@ -89,7 +95,8 @@
SessionHandler _session = new SessionHandler();
HashLoginService _loginService = new HashLoginService(TEST_REALM);
- _loginService.putUser("user",new Password("password"));
+ _loginService.putUser("user0", new Password("password"), new String[]{});
+ _loginService.putUser("user",new Password("password"), new String[] {"user"});
_loginService.putUser("user2",new Password("password"), new String[] {"user"});
_loginService.putUser("admin",new Password("password"), new String[] {"user","administrator"});
_loginService.putUser("user3", new Password("password"), new String[] {"foo"});
@@ -181,7 +188,16 @@
mapping6.setPathSpec("/data/*");
mapping6.setConstraint(constraint6);
- return Arrays.asList(mapping0, mapping1, mapping2, mapping3, mapping4, mapping5, mapping6);
+ Constraint constraint7 = new Constraint();
+ constraint7.setAuthenticate(true);
+ constraint7.setName("** constraint");
+ constraint7.setRoles(new String[]{Constraint.ANY_AUTH,"user"}); //the "user" role is superfluous once ** has been defined
+ ConstraintMapping mapping7 = new ConstraintMapping();
+ mapping7.setPathSpec("/starstar/*");
+ mapping7.setConstraint(constraint7);
+
+
+ return Arrays.asList(mapping0, mapping1, mapping2, mapping3, mapping4, mapping5, mapping6, mapping7);
}
@Test
@@ -209,6 +225,224 @@
assertTrue (mappings.get(2).getConstraint().getAuthenticate());
assertFalse(mappings.get(3).getConstraint().getAuthenticate());
}
+
+
+ /**
+ * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-1
+ * @ServletSecurity
+ * @throws Exception
+ */
+ @Test
+ public void testSecurityElementExample13_1() throws Exception
+ {
+ ServletSecurityElement element = new ServletSecurityElement();
+ List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+ assertTrue(mappings.isEmpty());
+ }
+
+
+ /**
+ * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-2
+ * @ServletSecurity(@HttpConstraint(transportGuarantee = TransportGuarantee.CONFIDENTIAL))
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testSecurityElementExample13_2() throws Exception
+ {
+ HttpConstraintElement httpConstraintElement = new HttpConstraintElement(TransportGuarantee.CONFIDENTIAL, new String[]{});
+ ServletSecurityElement element = new ServletSecurityElement(httpConstraintElement);
+ List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+ assertTrue(!mappings.isEmpty());
+ assertEquals(1, mappings.size());
+ ConstraintMapping mapping = mappings.get(0);
+ assertEquals(2, mapping.getConstraint().getDataConstraint());
+ }
+
+ /**
+ * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-3
+ * @ServletSecurity(@HttpConstraint(EmptyRoleSemantic.DENY))
+ * @throws Exception
+ */
+ @Test
+ public void testSecurityElementExample13_3() throws Exception
+ {
+ HttpConstraintElement httpConstraintElement = new HttpConstraintElement(EmptyRoleSemantic.DENY);
+ ServletSecurityElement element = new ServletSecurityElement(httpConstraintElement);
+ List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+ assertTrue(!mappings.isEmpty());
+ assertEquals(1, mappings.size());
+ ConstraintMapping mapping = mappings.get(0);
+ assertTrue(mapping.getConstraint().isForbidden());
+ }
+
+ /**
+ * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-4
+ * @ServletSecurity(@HttpConstraint(rolesAllowed = "R1"))
+ * @throws Exception
+ */
+ @Test
+ public void testSecurityElementExample13_4() throws Exception
+ {
+ HttpConstraintElement httpConstraintElement = new HttpConstraintElement(TransportGuarantee.NONE, "R1");
+ ServletSecurityElement element = new ServletSecurityElement(httpConstraintElement);
+ List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+ assertTrue(!mappings.isEmpty());
+ assertEquals(1, mappings.size());
+ ConstraintMapping mapping = mappings.get(0);
+ assertTrue(mapping.getConstraint().getAuthenticate());
+ assertTrue(mapping.getConstraint().getRoles() != null);
+ assertEquals(1, mapping.getConstraint().getRoles().length);
+ assertEquals("R1", mapping.getConstraint().getRoles()[0]);
+ assertEquals(0, mapping.getConstraint().getDataConstraint());
+ }
+
+ /**
+ * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-5
+ * @ServletSecurity((httpMethodConstraints = {
+ * @HttpMethodConstraint(value = "GET", rolesAllowed = "R1"),
+ * @HttpMethodConstraint(value = "POST", rolesAllowed = "R1",
+ * transportGuarantee = TransportGuarantee.CONFIDENTIAL)})
+ * @throws Exception
+ */
+ @Test
+ public void testSecurityElementExample13_5() throws Exception
+ {
+ List<HttpMethodConstraintElement> methodElements = new ArrayList<HttpMethodConstraintElement>();
+ methodElements.add(new HttpMethodConstraintElement("GET", new HttpConstraintElement(TransportGuarantee.NONE, "R1")));
+ methodElements.add(new HttpMethodConstraintElement("POST", new HttpConstraintElement(TransportGuarantee.CONFIDENTIAL, "R1")));
+ ServletSecurityElement element = new ServletSecurityElement(methodElements);
+ List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+ assertTrue(!mappings.isEmpty());
+ assertEquals(2, mappings.size());
+ assertEquals("GET", mappings.get(0).getMethod());
+ assertEquals("R1", mappings.get(0).getConstraint().getRoles()[0]);
+ assertTrue(mappings.get(0).getMethodOmissions() == null);
+ assertEquals(0, mappings.get(0).getConstraint().getDataConstraint());
+ assertEquals("POST", mappings.get(1).getMethod());
+ assertEquals("R1", mappings.get(1).getConstraint().getRoles()[0]);
+ assertEquals(2, mappings.get(1).getConstraint().getDataConstraint());
+ assertTrue(mappings.get(1).getMethodOmissions() == null);
+ }
+
+ /**
+ * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-6
+ * @ServletSecurity(value = @HttpConstraint(rolesAllowed = "R1"), httpMethodConstraints = @HttpMethodConstraint("GET"))
+ * @throws Exception
+ */
+ @Test
+ public void testSecurityElementExample13_6 () throws Exception
+ {
+ List<HttpMethodConstraintElement> methodElements = new ArrayList<HttpMethodConstraintElement>();
+ methodElements.add(new HttpMethodConstraintElement("GET"));
+ ServletSecurityElement element = new ServletSecurityElement(new HttpConstraintElement(TransportGuarantee.NONE, "R1"), methodElements);
+ List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+ assertTrue(!mappings.isEmpty());
+ assertEquals(2, mappings.size());
+ assertTrue(mappings.get(0).getMethodOmissions() != null);
+ assertEquals("GET", mappings.get(0).getMethodOmissions()[0]);
+ assertTrue(mappings.get(0).getConstraint().getAuthenticate());
+ assertEquals("R1", mappings.get(0).getConstraint().getRoles()[0]);
+ assertEquals("GET", mappings.get(1).getMethod());
+ assertTrue(mappings.get(1).getMethodOmissions() == null);
+ assertEquals(0, mappings.get(1).getConstraint().getDataConstraint());
+ assertFalse(mappings.get(1).getConstraint().getAuthenticate());
+ }
+
+ /**
+ * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-7
+ * @ServletSecurity(value = @HttpConstraint(rolesAllowed = "R1"),
+ * httpMethodConstraints = @HttpMethodConstraint(value="TRACE",
+ * emptyRoleSemantic = EmptyRoleSemantic.DENY))
+ * @throws Exception
+ */
+ @Test
+ public void testSecurityElementExample13_7() throws Exception
+ {
+ List<HttpMethodConstraintElement> methodElements = new ArrayList<HttpMethodConstraintElement>();
+ methodElements.add(new HttpMethodConstraintElement("TRACE", new HttpConstraintElement(EmptyRoleSemantic.DENY)));
+ ServletSecurityElement element = new ServletSecurityElement(new HttpConstraintElement(TransportGuarantee.NONE, "R1"), methodElements);
+ List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+ assertTrue(!mappings.isEmpty());
+ assertEquals(2, mappings.size());
+ assertTrue(mappings.get(0).getMethodOmissions() != null);
+ assertEquals("TRACE", mappings.get(0).getMethodOmissions()[0]);
+ assertTrue(mappings.get(0).getConstraint().getAuthenticate());
+ assertEquals("R1", mappings.get(0).getConstraint().getRoles()[0]);
+ assertEquals("TRACE", mappings.get(1).getMethod());
+ assertTrue(mappings.get(1).getMethodOmissions() == null);
+ assertEquals(0, mappings.get(1).getConstraint().getDataConstraint());
+ assertTrue(mappings.get(1).getConstraint().isForbidden());
+ }
+
+ @Test
+ public void testUncoveredHttpMethodDetection() throws Exception
+ {
+ //Test no methods named
+ Constraint constraint1 = new Constraint();
+ constraint1.setAuthenticate(true);
+ constraint1.setName("** constraint");
+ constraint1.setRoles(new String[]{Constraint.ANY_AUTH,"user"}); //No methods named, no uncovered methods
+ ConstraintMapping mapping1 = new ConstraintMapping();
+ mapping1.setPathSpec("/starstar/*");
+ mapping1.setConstraint(constraint1);
+
+ _security.setConstraintMappings(Collections.singletonList(mapping1));
+ _security.setAuthenticator(new BasicAuthenticator());
+ _server.start();
+
+ Set<String> uncoveredPaths = _security.getPathsWithUncoveredHttpMethods();
+ assertTrue(uncoveredPaths.isEmpty()); //no uncovered methods
+
+ //Test only an explicitly named method, no omissions to cover other methods
+ Constraint constraint2 = new Constraint();
+ constraint2.setAuthenticate(true);
+ constraint2.setName("user constraint");
+ constraint2.setRoles(new String[]{"user"});
+ ConstraintMapping mapping2 = new ConstraintMapping();
+ mapping2.setPathSpec("/user/*");
+ mapping2.setMethod("GET");
+ mapping2.setConstraint(constraint2);
+
+ _security.addConstraintMapping(mapping2);
+ uncoveredPaths = _security.getPathsWithUncoveredHttpMethods();
+ assertNotNull(uncoveredPaths);
+ assertEquals(1, uncoveredPaths.size());
+ assertTrue(uncoveredPaths.contains("/user/*"));
+
+ //Test an explicitly named method with a http-method-omission to cover all other methods
+ Constraint constraint2a = new Constraint();
+ constraint2a.setAuthenticate(true);
+ constraint2a.setName("forbid constraint");
+ ConstraintMapping mapping2a = new ConstraintMapping();
+ mapping2a.setPathSpec("/user/*");
+ mapping2a.setMethodOmissions(new String[]{"GET"});
+ mapping2a.setConstraint(constraint2a);
+
+ _security.addConstraintMapping(mapping2a);
+ uncoveredPaths = _security.getPathsWithUncoveredHttpMethods();
+ assertNotNull(uncoveredPaths);
+ assertEquals(0, uncoveredPaths.size());
+
+ //Test a http-method-omission only
+ Constraint constraint3 = new Constraint();
+ constraint3.setAuthenticate(true);
+ constraint3.setName("omit constraint");
+ ConstraintMapping mapping3 = new ConstraintMapping();
+ mapping3.setPathSpec("/omit/*");
+ mapping3.setMethodOmissions(new String[]{"GET", "POST"});
+ mapping3.setConstraint(constraint3);
+
+ _security.addConstraintMapping(mapping3);
+ uncoveredPaths = _security.getPathsWithUncoveredHttpMethods();
+ assertNotNull(uncoveredPaths);
+ assertTrue(uncoveredPaths.contains("/omit/*"));
+
+ _security.setDenyUncoveredHttpMethods(true);
+ uncoveredPaths = _security.getPathsWithUncoveredHttpMethods();
+ assertNotNull(uncoveredPaths);
+ assertEquals(0, uncoveredPaths.size());
+ }
@Test
public void testBasic() throws Exception
@@ -253,7 +487,6 @@
_security.setAuthenticator(new BasicAuthenticator());
- _security.setStrict(false);
_server.start();
String response;
@@ -378,7 +611,6 @@
DigestAuthenticator authenticator = new DigestAuthenticator();
authenticator.setMaxNonceCount(5);
_security.setAuthenticator(authenticator);
- _security.setStrict(false);
_server.start();
String response;
@@ -465,7 +697,6 @@
public void testFormDispatch() throws Exception
{
_security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",true));
- _security.setStrict(false);
_server.start();
String response;
@@ -520,7 +751,6 @@
public void testFormRedirect() throws Exception
{
_security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false));
- _security.setStrict(false);
_server.start();
String response;
@@ -577,7 +807,6 @@
public void testFormPostRedirect() throws Exception
{
_security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false));
- _security.setStrict(false);
_server.start();
String response;
@@ -609,6 +838,7 @@
"Content-Length: 31\r\n" +
"\r\n" +
"j_username=user&j_password=wrong\r\n");
+
assertThat(response,containsString("Location"));
response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
@@ -647,7 +877,6 @@
public void testFormNoCookies() throws Exception
{
_security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false));
- _security.setStrict(false);
_server.start();
String response;
@@ -720,7 +949,7 @@
assertThat(response,containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
- "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" +
+ "Authorization: Basic " + B64Code.encode("user3:password") + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 403"));
@@ -794,9 +1023,9 @@
response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
- "Content-Length: 35\r\n" +
+ "Content-Length: 36\r\n" +
"\r\n" +
- "j_username=user&j_password=password\r\n");
+ "j_username=user0&j_password=password\r\n");
assertThat(response,startsWith("HTTP/1.1 302 "));
assertThat(response,containsString("Location"));
assertThat(response,containsString("/ctx/auth/info"));
@@ -905,9 +1134,9 @@
response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
- "Content-Length: 35\r\n" +
+ "Content-Length: 36\r\n" +
"\r\n" +
- "j_username=user&j_password=password\r\n");
+ "j_username=user3&j_password=password\r\n");
assertThat(response,startsWith("HTTP/1.1 302 "));
assertThat(response,containsString("Location"));
assertThat(response,containsString("/ctx/auth/info"));
@@ -950,12 +1179,35 @@
"\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
+ //check user2 does not have right role to access /admin/*
response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 403"));
assertThat(response,containsString("!role"));
+
+ //log in as user3, who doesn't have a valid role, but we are checking a constraint
+ //of ** which just means they have to be authenticated
+ response = _connector.getResponses("GET /ctx/starstar/info HTTP/1.0\r\n\r\n");
+ assertThat(response,startsWith("HTTP/1.1 302 "));
+ assertThat(response,containsString("testLoginPage"));
+ session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
+ response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
+ "Cookie: JSESSIONID=" + session + "\r\n" +
+ "Content-Type: application/x-www-form-urlencoded\r\n" +
+ "Content-Length: 36\r\n" +
+ "\r\n" +
+ "j_username=user3&j_password=password\r\n");
+ assertThat(response,startsWith("HTTP/1.1 302 "));
+ assertThat(response,containsString("Location"));
+ assertThat(response,containsString("/ctx/starstar/info"));
+ session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
+
+ response = _connector.getResponses("GET /ctx/starstar/info HTTP/1.0\r\n" +
+ "Cookie: JSESSIONID=" + session + "\r\n" +
+ "\r\n");
+ assertThat(response,startsWith("HTTP/1.1 200 OK"));
// log in again as admin
@@ -1034,7 +1286,6 @@
RoleCheckHandler check=new RoleCheckHandler();
_security.setHandler(check);
_security.setAuthenticator(new BasicAuthenticator());
- _security.setStrict(false);
_server.start();
@@ -1066,7 +1317,6 @@
public void testDeferredBasic() throws Exception
{
_security.setAuthenticator(new BasicAuthenticator());
- _security.setStrict(false);
_server.start();
String response;
@@ -1093,7 +1343,6 @@
public void testRelaxedMethod() throws Exception
{
_security.setAuthenticator(new BasicAuthenticator());
- _security.setStrict(false);
_server.start();
String response;
diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/SpecExampleConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/SpecExampleConstraintTest.java
index 1d656ae..ff20051 100644
--- a/jetty-security/src/test/java/org/eclipse/jetty/security/SpecExampleConstraintTest.java
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/SpecExampleConstraintTest.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.security;
import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -232,6 +233,45 @@
}
}
+ @Test
+ public void testUncoveredHttpMethodDetection() throws Exception
+ {
+ _security.setAuthenticator(new BasicAuthenticator());
+ _server.start();
+
+ Set<String> paths = _security.getPathsWithUncoveredHttpMethods();
+ assertEquals(1, paths.size());
+ assertEquals("/*", paths.iterator().next());
+ }
+
+ @Test
+ public void testUncoveredHttpMethodsDenied() throws Exception
+ {
+ try
+ {
+ _security.setDenyUncoveredHttpMethods(false);
+ _security.setAuthenticator(new BasicAuthenticator());
+ _server.start();
+
+ //There are uncovered methods for GET/POST at url /*
+ //without deny-uncovered-http-methods they should be accessible
+ String response;
+ response = _connector.getResponses("GET /ctx/index.html HTTP/1.0\r\n\r\n");
+ assertThat(response,startsWith("HTTP/1.1 200 OK"));
+
+ //set deny-uncovered-http-methods true
+ _security.setDenyUncoveredHttpMethods(true);
+
+ //check they cannot be accessed
+ response = _connector.getResponses("GET /ctx/index.html HTTP/1.0\r\n\r\n");
+ assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
+ }
+ finally
+ {
+ _security.setDenyUncoveredHttpMethods(false);
+ }
+
+ }
@Test
@@ -239,7 +279,6 @@
{
_security.setAuthenticator(new BasicAuthenticator());
- _security.setStrict(false);
_server.start();
String response;
diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml
index a48e1c1..b7c3d71 100644
--- a/jetty-server/pom.xml
+++ b/jetty-server/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-server</artifactId>
@@ -26,7 +26,7 @@
</goals>
<configuration>
<instructions>
- <Import-Package>javax.servlet.*;version="2.6.0",org.eclipse.jetty.jmx.*;version="9.0";resolution:=optional,*</Import-Package>
+ <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.eclipse.jetty.jmx.*;version="9.1";resolution:=optional,*</Import-Package>
<_nouses>true</_nouses>
</instructions>
</configuration>
@@ -89,8 +89,12 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+<!--
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.servlet</artifactId>
+-->
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
diff --git a/jetty-server/src/main/config/etc/jetty-http.xml b/jetty-server/src/main/config/etc/jetty-http.xml
index 7ae3064..b3c53c7 100644
--- a/jetty-server/src/main/config/etc/jetty-http.xml
+++ b/jetty-server/src/main/config/etc/jetty-http.xml
@@ -32,7 +32,7 @@
</Array>
</Arg>
<Set name="host"><Property name="jetty.host" /></Set>
- <Set name="port"><Property name="jetty.port" default="8080" /></Set>
+ <Set name="port"><Property name="jetty.port" default="80" /></Set>
<Set name="idleTimeout"><Property name="http.timeout" default="30000"/></Set>
</New>
</Arg>
diff --git a/jetty-server/src/main/config/etc/jetty-https.xml b/jetty-server/src/main/config/etc/jetty-https.xml
index a6bef16..b5d9a18 100644
--- a/jetty-server/src/main/config/etc/jetty-https.xml
+++ b/jetty-server/src/main/config/etc/jetty-https.xml
@@ -39,8 +39,8 @@
</Array>
</Arg>
<Set name="host"><Property name="jetty.host" /></Set>
- <Set name="port"><Property name="jetty.https.port" default="8443" /></Set>
- <Set name="idleTimeout">30000</Set>
+ <Set name="port"><Property name="https.port" default="443" /></Set>
+ <Set name="idleTimeout"><Property name="https.timeout" default="30000"/></Set>
</New>
</Arg>
</Call>
diff --git a/jetty-server/src/main/config/etc/jetty-ipaccess.xml b/jetty-server/src/main/config/etc/jetty-ipaccess.xml
index 33a43be..a44aa1d 100644
--- a/jetty-server/src/main/config/etc/jetty-ipaccess.xml
+++ b/jetty-server/src/main/config/etc/jetty-ipaccess.xml
@@ -15,18 +15,17 @@
<Set name="handler"><Ref refid="oldhandler"/></Set>
<Set name="white">
<Array type="String">
- <Item>127.0.0.1</Item>
- <Item>127.0.0.2/*.html</Item>
- </Array>
+ <Item>127.0.0.1</Item>
+ <Item>127.0.0.2/*.html</Item>
+ </Array>
</Set>
<Set name="black">
<Array type="String">
- <Item>127.0.0.1/blacklisted</Item>
- <Item>127.0.0.2/black.html</Item>
- </Array>
+ <Item>127.0.0.1/blacklisted</Item>
+ <Item>127.0.0.2/black.html</Item>
+ </Array>
</Set>
<Set name="whiteListByPath">false</Set>
</New>
</Set>
-
</Configure>
diff --git a/jetty-server/src/main/config/etc/jetty-requestlog.xml b/jetty-server/src/main/config/etc/jetty-requestlog.xml
index 2131777..f4f0eb8 100644
--- a/jetty-server/src/main/config/etc/jetty-requestlog.xml
+++ b/jetty-server/src/main/config/etc/jetty-requestlog.xml
@@ -15,13 +15,13 @@
<New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler">
<Set name="requestLog">
<New id="RequestLogImpl" class="org.eclipse.jetty.server.AsyncNCSARequestLog">
- <Set name="filename"><Property name="jetty.logs" default="./logs" />/yyyy_mm_dd.request.log</Set>
+ <Set name="filename"><Property name="jetty.base" default="." />/logs/yyyy_mm_dd.request.log</Set>
<Set name="filenameDateFormat">yyyy_MM_dd</Set>
<Set name="retainDays"><Property name="requestlog.retain" default="90"/></Set>
<Set name="append"><Property name="requestlog.append" default="false"/></Set>
<Set name="extended"><Property name="requestlog.extended" default="false"/></Set>
- <Set name="logCookies">false</Set>
- <Set name="LogTimeZone">GMT</Set>
+ <Set name="logCookies"><Property name="requestlog.cookies" default="false"/></Set>
+ <Set name="LogTimeZone"><Property name="requestlog.timezone" default="GMT"/></Set>
</New>
</Set>
</New>
diff --git a/jetty-server/src/main/config/etc/jetty-ssl.xml b/jetty-server/src/main/config/etc/jetty-ssl.xml
index b4c3551..8eef03d 100644
--- a/jetty-server/src/main/config/etc/jetty-ssl.xml
+++ b/jetty-server/src/main/config/etc/jetty-ssl.xml
@@ -7,10 +7,10 @@
<!-- and either jetty-https.xml or jetty-spdy.xml (but not both) -->
<!-- ============================================================= -->
<Configure id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
- <Set name="KeyStorePath"><Property name="jetty.home" default="." />/<Property name="jetty.keystore" default="etc/keystore"/></Set>
+ <Set name="KeyStorePath"><Property name="jetty.base" default="." />/<Property name="jetty.keystore" default="etc/keystore"/></Set>
<Set name="KeyStorePassword"><Property name="jetty.keystore.password" default="OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"/></Set>
<Set name="KeyManagerPassword"><Property name="jetty.keymanager.password" default="OBF:1u2u1wml1z7s1z7a1wnl1u2g"/></Set>
- <Set name="TrustStorePath"><Property name="jetty.home" default="." />/<Property name="jetty.truststore" default="etc/keystore"/></Set>
+ <Set name="TrustStorePath"><Property name="jetty.base" default="." />/<Property name="jetty.truststore" default="etc/keystore"/></Set>
<Set name="TrustStorePassword"><Property name="jetty.truststore.password" default="OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"/></Set>
<Set name="EndpointIdentificationAlgorithm"></Set>
<Set name="ExcludeCipherSuites">
diff --git a/jetty-server/src/main/config/etc/jetty-xinetd.xml b/jetty-server/src/main/config/etc/jetty-xinetd.xml
index c59913b..a4750de 100644
--- a/jetty-server/src/main/config/etc/jetty-xinetd.xml
+++ b/jetty-server/src/main/config/etc/jetty-xinetd.xml
@@ -44,9 +44,9 @@
-->
<!-- sane defaults -->
- <Set name="idleTimeout">300000</Set>
- <Set name="Acceptors">2</Set>
- <Set name="statsOn">false</Set>
+ <Set name="idleTimeout"><Property name="jetty.xinetd.idleTimeout" default="300000"/></Set>
+ <Set name="Acceptors"><Property name="jetty.xinetd.acceptors" default="2"/></Set>
+ <Set name="statsOn"><Property name="jetty.xinetd.statsOn" default="false"/></Set>
</New>
</Arg>
</Call>
diff --git a/jetty-server/src/main/config/modules/continuation.mod b/jetty-server/src/main/config/modules/continuation.mod
new file mode 100644
index 0000000..231c09d
--- /dev/null
+++ b/jetty-server/src/main/config/modules/continuation.mod
@@ -0,0 +1,6 @@
+#
+# Classic Jetty Continuation Support Module
+#
+
+[lib]
+lib/jetty-continuation-${jetty.version}.jar
diff --git a/jetty-server/src/main/config/modules/debug.mod b/jetty-server/src/main/config/modules/debug.mod
new file mode 100644
index 0000000..f740ea2
--- /dev/null
+++ b/jetty-server/src/main/config/modules/debug.mod
@@ -0,0 +1,9 @@
+#
+# Debug module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-debug.xml
diff --git a/jetty-server/src/main/config/modules/ext.mod b/jetty-server/src/main/config/modules/ext.mod
new file mode 100644
index 0000000..329a833
--- /dev/null
+++ b/jetty-server/src/main/config/modules/ext.mod
@@ -0,0 +1,11 @@
+#
+# Module to add all lib/ext/*.jar files to classpath
+#
+
+[lib]
+lib/ext/*.jar
+
+[files]
+lib/
+lib/ext/
+
diff --git a/jetty-server/src/main/config/modules/http.mod b/jetty-server/src/main/config/modules/http.mod
new file mode 100644
index 0000000..c8a325f
--- /dev/null
+++ b/jetty-server/src/main/config/modules/http.mod
@@ -0,0 +1,14 @@
+#
+# Jetty HTTP Connector
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-http.xml
+
+[ini-template]
+## HTTP Connector Configuration
+jetty.port=8080
+http.timeout=30000
diff --git a/jetty-server/src/main/config/modules/https.mod b/jetty-server/src/main/config/modules/https.mod
new file mode 100644
index 0000000..7838ea6
--- /dev/null
+++ b/jetty-server/src/main/config/modules/https.mod
@@ -0,0 +1,16 @@
+#
+# Jetty HTTPS Connector
+#
+
+[depend]
+ssl
+
+[xml]
+etc/jetty-https.xml
+
+[ini-template]
+## HTTPS Configuration
+# HTTP port to listen on
+https.port=8443
+# HTTPS idle timeout in milliseconds
+https.timeout=30000
diff --git a/jetty-server/src/main/config/modules/ipaccess.mod b/jetty-server/src/main/config/modules/ipaccess.mod
new file mode 100644
index 0000000..956ea0f
--- /dev/null
+++ b/jetty-server/src/main/config/modules/ipaccess.mod
@@ -0,0 +1,9 @@
+#
+# IPAccess module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-ipaccess.xml
diff --git a/jetty-server/src/main/config/modules/jvm.mod b/jetty-server/src/main/config/modules/jvm.mod
new file mode 100644
index 0000000..195521c
--- /dev/null
+++ b/jetty-server/src/main/config/modules/jvm.mod
@@ -0,0 +1,23 @@
+[ini-template]
+## JVM Configuration
+## If JVM args are include in an ini file then --exec is needed
+## to start a new JVM from start.jar with the extra args.
+##
+## If you wish to avoid an extra JVM running, place JVM args
+## on the normal command line and do not use --exec
+# --exec
+# -Xmx2000m
+# -Xmn512m
+# -XX:+UseConcMarkSweepGC
+# -XX:ParallelCMSThreads=2
+# -XX:+CMSClassUnloadingEnabled
+# -XX:+UseCMSCompactAtFullCollection
+# -XX:CMSInitiatingOccupancyFraction=80
+# -verbose:gc
+# -XX:+PrintGCDateStamps
+# -XX:+PrintGCTimeStamps
+# -XX:+PrintGCDetails
+# -XX:+PrintTenuringDistribution
+# -XX:+PrintCommandLineFlags
+# -XX:+DisableExplicitGC
+# -Dorg.apache.jasper.compiler.disablejsr199=true
diff --git a/jetty-server/src/main/config/modules/lowresources.mod b/jetty-server/src/main/config/modules/lowresources.mod
new file mode 100644
index 0000000..99112d5
--- /dev/null
+++ b/jetty-server/src/main/config/modules/lowresources.mod
@@ -0,0 +1,18 @@
+#
+# Low Resources module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-lowresources.xml
+
+[ini-template]
+## Low Resources Configuration
+# lowresources.period=1050
+# lowresources.lowResourcesIdleTimeout=200
+# lowresources.monitorThreads=true
+# lowresources.maxConnections=0
+# lowresources.maxMemory=0
+# lowresources.maxLowResourcesTime=5000
diff --git a/jetty-server/src/main/config/modules/requestlog.mod b/jetty-server/src/main/config/modules/requestlog.mod
new file mode 100644
index 0000000..43e1e00
--- /dev/null
+++ b/jetty-server/src/main/config/modules/requestlog.mod
@@ -0,0 +1,26 @@
+#
+# Request Log module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-requestlog.xml
+
+[files]
+logs/
+
+[ini-template]
+## Request Log Configuration
+# How many days to retain the logs
+# requestlog.retain=90
+# If an existing log with the same name is found, just append to it
+# requestlog.append=true
+# Use the extended log output
+# requestlog.extended=true
+# Log http cookie information as well
+# requestlog.cookies=true
+# Set the log output timezone
+# requestlog.timezone=GMT
+
diff --git a/jetty-server/src/main/config/modules/resources.mod b/jetty-server/src/main/config/modules/resources.mod
new file mode 100644
index 0000000..b3a4d2d
--- /dev/null
+++ b/jetty-server/src/main/config/modules/resources.mod
@@ -0,0 +1,10 @@
+#
+# Module to add resources directory to classpath
+#
+
+[lib]
+resources
+
+[files]
+resources/
+
diff --git a/jetty-server/src/main/config/modules/server.mod b/jetty-server/src/main/config/modules/server.mod
new file mode 100644
index 0000000..cdaf904
--- /dev/null
+++ b/jetty-server/src/main/config/modules/server.mod
@@ -0,0 +1,38 @@
+#
+# Base Server Module
+#
+
+[optional]
+jvm
+jmx
+ext
+resources
+
+[lib]
+lib/servlet-api-3.1.jar
+lib/jetty-schemas-3.1.jar
+lib/jetty-http-${jetty.version}.jar
+lib/jetty-server-${jetty.version}.jar
+lib/jetty-xml-${jetty.version}.jar
+lib/jetty-util-${jetty.version}.jar
+lib/jetty-io-${jetty.version}.jar
+
+[xml]
+etc/jetty.xml
+
+[ini-template]
+## Server Threading Configuration
+# minimum number of threads
+threads.min=10
+# maximum number of threads
+threads.max=200
+# thread idle timeout in milliseconds
+threads.timeout=60000
+# What host to listen on (leave commented to listen on all interfaces)
+#jetty.host=myhost.com
+# Dump the state of the Jetty server, components, and webapps after startup
+jetty.dump.start=false
+# Dump the state of the Jetty server, before stop
+jetty.dump.stop=false
+
+
diff --git a/jetty-server/src/main/config/modules/ssl.mod b/jetty-server/src/main/config/modules/ssl.mod
new file mode 100644
index 0000000..915f472
--- /dev/null
+++ b/jetty-server/src/main/config/modules/ssl.mod
@@ -0,0 +1,29 @@
+#
+# SSL Keystore module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-ssl.xml
+
+[files]
+http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/plain/jetty-server/src/main/config/etc/keystore:etc/keystore
+
+[ini-template]
+## SSL Keystore Configuration
+# define the port to use for secure redirection
+jetty.secure.port=8443
+
+# Setup a demonstration keystore and truststore
+jetty.keystore=etc/keystore
+jetty.truststore=etc/keystore
+
+# Set the demonstration passwords.
+# Note that OBF passwords are not secure, just protected from casual observation
+# See http://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html
+jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
+jetty.keymanager.password=OBF:1u2u1wml1z7s1z7a1wnl1u2g
+jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
+
diff --git a/jetty-server/src/main/config/modules/stats.mod b/jetty-server/src/main/config/modules/stats.mod
new file mode 100644
index 0000000..0922469
--- /dev/null
+++ b/jetty-server/src/main/config/modules/stats.mod
@@ -0,0 +1,9 @@
+#
+# Stats module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-stats.xml
diff --git a/jetty-server/src/main/config/modules/xinetd.mod b/jetty-server/src/main/config/modules/xinetd.mod
new file mode 100644
index 0000000..e53618e
--- /dev/null
+++ b/jetty-server/src/main/config/modules/xinetd.mod
@@ -0,0 +1,17 @@
+#
+# Xinetd module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-xinetd.xml
+
+[ini-template]
+## Xinetd Configuration
+## See ${jetty.home}/etc/jetty-xinetd.xml for example service entry
+jetty.xinetd.idleTimeout=300000
+jetty.xinetd.acceptors=2
+jetty.xinetd.statsOn=false
+
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
index 28fea90..aeb54b4 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
@@ -189,7 +189,7 @@
for (ConnectionFactory factory:factories)
addConnectionFactory(factory);
- if (acceptors<=0)
+ if (acceptors<0)
acceptors=Math.max(1,(Runtime.getRuntime().availableProcessors()) / 2);
if (acceptors > 2 * Runtime.getRuntime().availableProcessors())
LOG.warn("Acceptors should be <= 2*availableProcessors: " + this);
@@ -268,10 +268,13 @@
protected void interruptAcceptors()
{
- for (Thread thread : _acceptors)
+ synchronized (this)
{
- if (thread != null)
- thread.interrupt();
+ for (Thread thread : _acceptors)
+ {
+ if (thread != null)
+ thread.interrupt();
+ }
}
}
@@ -306,9 +309,12 @@
public void join(long timeout) throws InterruptedException
{
- for (Thread thread : _acceptors)
- if (thread != null)
- thread.join(timeout);
+ synchronized (this)
+ {
+ for (Thread thread : _acceptors)
+ if (thread != null)
+ thread.join(timeout);
+ }
}
protected abstract void accept(int acceptorID) throws IOException, InterruptedException;
@@ -464,7 +470,7 @@
if (isAccepting())
LOG.warn(e);
else
- LOG.debug(e);
+ LOG.ignore(e);
}
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
index f012b79..299411b 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
@@ -397,8 +397,7 @@
{
if (_logDateFormat != null)
{
- _logDateCache = new DateCache(_logDateFormat, _logLocale);
- _logDateCache.setTimeZoneID(_logTimeZone);
+ _logDateCache = new DateCache(_logDateFormat, _logLocale ,_logTimeZone);
}
if (_ignorePaths != null && _ignorePaths.length > 0)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
index 876e7ae..2538f41 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
@@ -28,6 +28,8 @@
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
+import org.eclipse.jetty.server.handler.ContextHandler;
+
public class AsyncContextState implements AsyncContext
{
@@ -92,17 +94,20 @@
@Override
public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException
- {
+ {
+ ContextHandler contextHandler = state().getContextHandler();
+ if (contextHandler != null)
+ return contextHandler.getServletContext().createListener(clazz);
try
{
return clazz.newInstance();
}
- catch(Exception e)
+ catch (Exception e)
{
throw new ServletException(e);
}
}
-
+
@Override
public void dispatch()
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Authentication.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Authentication.java
index ba18ef7..656ac82 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Authentication.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Authentication.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.server;
-import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferHttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferHttpInput.java
deleted file mode 100644
index 90d81d9..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferHttpInput.java
+++ /dev/null
@@ -1,46 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import java.nio.ByteBuffer;
-
-/**
- * <p>An implementation of HttpInput using {@link ByteBuffer} as items.</p>
- */
-public class ByteBufferHttpInput extends HttpInput<ByteBuffer>
-{
- @Override
- protected int remaining(ByteBuffer item)
- {
- return item.remaining();
- }
-
- @Override
- protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
- {
- int l = Math.min(item.remaining(), length);
- item.get(buffer, offset, l);
- return l;
- }
-
- @Override
- protected void onContentConsumed(ByteBuffer item)
- {
- }
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java
new file mode 100644
index 0000000..e380eb0
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java
@@ -0,0 +1,53 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.nio.ByteBuffer;
+
+/**
+ * <p>An implementation of HttpInput using {@link ByteBuffer} as items.</p>
+ */
+public class ByteBufferQueuedHttpInput extends QueuedHttpInput<ByteBuffer>
+{
+ @Override
+ protected int remaining(ByteBuffer item)
+ {
+ return item.remaining();
+ }
+
+ @Override
+ protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
+ {
+ int l = Math.min(item.remaining(), length);
+ item.get(buffer, offset, l);
+ return l;
+ }
+
+ @Override
+ protected void consume(ByteBuffer item, int length)
+ {
+ item.position(item.position()+length);
+ }
+
+ @Override
+ protected void onContentConsumed(ByteBuffer item)
+ {
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java
index 7e1dd93..8d709d8 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java
@@ -17,11 +17,12 @@
//
package org.eclipse.jetty.server;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
import javax.servlet.http.Cookie;
-import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -41,10 +42,9 @@
{
private static final Logger LOG = Log.getLogger(CookieCutter.class);
-
private Cookie[] _cookies;
private Cookie[] _lastCookies;
- Object _lazyFields;
+ private final List<String> _fieldList = new ArrayList<>();
int _fields;
public CookieCutter()
@@ -56,9 +56,7 @@
if (_cookies!=null)
return _cookies;
- if (_lastCookies!=null &&
- _lazyFields!=null &&
- _fields==LazyList.size(_lazyFields))
+ if (_lastCookies!=null && _fields==_fieldList.size())
_cookies=_lastCookies;
else
parseFields();
@@ -70,7 +68,7 @@
{
_cookies=cookies;
_lastCookies=null;
- _lazyFields=null;
+ _fieldList.clear();
_fields=0;
}
@@ -88,20 +86,20 @@
if (f.length()==0)
return;
- if (LazyList.size(_lazyFields)>_fields)
+ if (_fieldList.size()>_fields)
{
- if (f.equals(LazyList.get(_lazyFields,_fields)))
+ if (f.equals(_fieldList.get(_fields)))
{
_fields++;
return;
}
- while (LazyList.size(_lazyFields)>_fields)
- _lazyFields=LazyList.remove(_lazyFields,_fields);
+ while (_fieldList.size()>_fields)
+ _fieldList.remove(_fields);
}
_cookies=null;
_lastCookies=null;
- _lazyFields=LazyList.add(_lazyFields,_fields++,f);
+ _fieldList.add(_fields++,f);
}
@@ -110,19 +108,17 @@
_lastCookies=null;
_cookies=null;
- Object cookies = null;
+ List<Cookie> cookies = new ArrayList<>();
int version = 0;
// delete excess fields
- while (LazyList.size(_lazyFields)>_fields)
- _lazyFields=LazyList.remove(_lazyFields,_fields);
+ while (_fieldList.size()>_fields)
+ _fieldList.remove(_fields);
// For each cookie field
- for (int f=0;f<_fields;f++)
+ for (String hdr : _fieldList)
{
- String hdr = LazyList.get(_lazyFields,f);
-
// Parse the header
String name = null;
String value = null;
@@ -311,7 +307,7 @@
cookie = new Cookie(name, value);
if (version > 0)
cookie.setVersion(version);
- cookies = LazyList.add(cookies, cookie);
+ cookies.add(cookie);
}
}
catch (Exception e)
@@ -325,7 +321,7 @@
}
}
- _cookies = (Cookie[]) LazyList.toArray(cookies,Cookie.class);
+ _cookies = (Cookie[]) cookies.toArray(new Cookie[cookies.size()]);
_lastCookies=_cookies;
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/EncodingHttpWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/EncodingHttpWriter.java
index 7725a34..fd8c1c5 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/EncodingHttpWriter.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/EncodingHttpWriter.java
@@ -49,10 +49,10 @@
public void write (char[] s,int offset, int length) throws IOException
{
HttpOutput out = _out;
- if (length==0)
+ if (length==0 && out.isAllContentWritten())
{
- if (_out.isAllContentWritten())
- close();
+ out.close();
+ return;
}
while (length > 0)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
index 608a861..845cbc8 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
@@ -44,7 +44,7 @@
* the request came</p>
* <p>Headers can also be defined so that forwarded SSL Session IDs and Cipher
* suites may be customised</p>
- * @see http://en.wikipedia.org/wiki/X-Forwarded-For
+ * @see <a href="http://en.wikipedia.org/wiki/X-Forwarded-For">Wikipedia: X-Forwarded-For</a>
*/
public class ForwardedRequestCustomizer implements Customizer
{
@@ -66,7 +66,6 @@
/* ------------------------------------------------------------ */
/**
* Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
- * This value is only used if {@link #isForwarded()} is true.
*
* @param hostHeader
* The value of the host header to force.
@@ -90,7 +89,6 @@
/**
* @param forwardedHostHeader
* The header name for forwarded hosts (default x-forwarded-host)
- * @see #setForwarded(boolean)
*/
public void setForwardedHostHeader(String forwardedHostHeader)
{
@@ -100,7 +98,6 @@
/* ------------------------------------------------------------ */
/**
* @return the header name for forwarded server.
- * @see #setForwarded(boolean)
*/
public String getForwardedServerHeader()
{
@@ -111,7 +108,6 @@
/**
* @param forwardedServerHeader
* The header name for forwarded server (default x-forwarded-server)
- * @see #setForwarded(boolean)
*/
public void setForwardedServerHeader(String forwardedServerHeader)
{
@@ -121,7 +117,6 @@
/* ------------------------------------------------------------ */
/**
* @return the forwarded for header
- * @see #setForwarded(boolean)
*/
public String getForwardedForHeader()
{
@@ -132,7 +127,6 @@
/**
* @param forwardedRemoteAddressHeader
* The header name for forwarded for (default x-forwarded-for)
- * @see #setForwarded(boolean)
*/
public void setForwardedForHeader(String forwardedRemoteAddressHeader)
{
@@ -144,7 +138,6 @@
* Get the forwardedProtoHeader.
*
* @return the forwardedProtoHeader (default X-Forwarded-For)
- * @see #setForwarded(boolean)
*/
public String getForwardedProtoHeader()
{
@@ -157,7 +150,6 @@
*
* @param forwardedProtoHeader
* the forwardedProtoHeader to set (default X-Forwarded-For)
- * @see #setForwarded(boolean)
*/
public void setForwardedProtoHeader(String forwardedProtoHeader)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java
index 7cedf16..bb96ef6 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java
@@ -56,10 +56,10 @@
* @param target The target of the request - either a URI or a name.
* @param baseRequest The original unwrapped request object.
* @param request The request either as the {@link Request}
- * object or a wrapper of that request. The {@link AbstractHttpConnection#getCurrentHttpChannel()}
+ * object or a wrapper of that request. The {@link HttpChannel#getCurrentHttpChannel()}
* method can be used access the Request object if required.
* @param response The response as the {@link Response}
- * object or a wrapper of that request. The {@link AbstractHttpConnection#getCurrentHttpChannel()}
+ * object or a wrapper of that request. The {@link HttpChannel#getCurrentHttpChannel()}
* method can be used access the Response object if required.
* @throws IOException
* @throws ServletException
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HostHeaderCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HostHeaderCustomizer.java
index dd81a7f..cbbc001 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HostHeaderCustomizer.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HostHeaderCustomizer.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.server;
import java.util.Objects;
+
import javax.servlet.http.HttpServletRequest;
/**
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
index 4a0732e..106e007 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
@@ -46,11 +46,11 @@
import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.server.HttpChannelState.Next;
+import org.eclipse.jetty.server.HttpChannelState.Action;
+import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.util.BlockingCallback;
import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -108,6 +108,7 @@
_uri = new HttpURI(URIUtil.__CHARSET);
_state = new HttpChannelState(this);
+ input.init(_state);
_request = new Request(this, input);
_response = new Response(this, new HttpOutput(this));
}
@@ -140,6 +141,11 @@
return _connector;
}
+ public HttpTransport getHttpTransport()
+ {
+ return _transport;
+ }
+
public ByteBufferPool getByteBufferPool()
{
return _connector.getByteBufferPool();
@@ -253,28 +259,37 @@
// The loop is controlled by the call to async.unhandle in the
// finally block below. Unhandle will return false only if an async dispatch has
// already happened when unhandle is called.
- HttpChannelState.Next next = _state.handling();
- while (next==Next.CONTINUE && getServer().isRunning())
+ HttpChannelState.Action action = _state.handling();
+ loop: while (action.ordinal()<HttpChannelState.Action.WAIT.ordinal() && getServer().isRunning())
{
boolean error=false;
try
{
- _request.setHandled(false);
- _response.getHttpOutput().reopen();
-
- if (_state.isInitial())
+ LOG.debug("{} action {}",this,action);
+
+ switch(action)
{
- _request.setTimeStamp(System.currentTimeMillis());
- _request.setDispatcherType(DispatcherType.REQUEST);
+ case REQUEST_DISPATCH:
+ _request.setHandled(false);
+ _response.getHttpOutput().reopen();
+ _request.setTimeStamp(System.currentTimeMillis());
+ _request.setDispatcherType(DispatcherType.REQUEST);
- for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers())
- customizer.customize(getConnector(),_configuration,_request);
- getServer().handle(this);
- }
- else
- {
- if (_request.getHttpChannelState().isExpired())
- {
+ for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers())
+ customizer.customize(getConnector(),_configuration,_request);
+ getServer().handle(this);
+ break;
+
+ case ASYNC_DISPATCH:
+ _request.setHandled(false);
+ _response.getHttpOutput().reopen();
+ _request.setDispatcherType(DispatcherType.ASYNC);
+ getServer().handleAsync(this);
+ break;
+
+ case ASYNC_EXPIRED:
+ _request.setHandled(false);
+ _response.getHttpOutput().reopen();
_request.setDispatcherType(DispatcherType.ERROR);
Throwable ex=_state.getAsyncContextEvent().getThrowable();
@@ -297,10 +312,34 @@
if (error_page!=null)
_state.getAsyncContextEvent().setDispatchPath(error_page);
}
+
+ getServer().handleAsync(this);
+ break;
+
+ case READ_CALLBACK:
+ {
+ ContextHandler handler=_state.getContextHandler();
+ if (handler!=null)
+ handler.handle(_request.getHttpInput());
+ else
+ _request.getHttpInput().run();
+ break;
}
- else
- _request.setDispatcherType(DispatcherType.ASYNC);
- getServer().handleAsync(this);
+
+ case WRITE_CALLBACK:
+ {
+ ContextHandler handler=_state.getContextHandler();
+
+ if (handler!=null)
+ handler.handle(_response.getHttpOutput());
+ else
+ _response.getHttpOutput().run();
+ break;
+ }
+
+ default:
+ break loop;
+
}
}
catch (Error e)
@@ -328,7 +367,7 @@
{
if (error && _state.isAsyncStarted())
_state.errorComplete();
- next = _state.unhandle();
+ action = _state.unhandle();
}
}
@@ -336,7 +375,7 @@
Thread.currentThread().setName(threadName);
setCurrentHttpChannel(null);
- if (next==Next.COMPLETE)
+ if (action==Action.COMPLETE)
{
try
{
@@ -363,9 +402,9 @@
}
}
- LOG.debug("{} handle exit, result {}", this, next);
+ LOG.debug("{} handle exit, result {}", this, action);
- return next!=Next.WAIT;
+ return action!=Action.WAIT;
}
/**
@@ -606,7 +645,8 @@
@Override
public boolean messageComplete()
{
- _request.getHttpInput().shutdown();
+ LOG.debug("{} messageComplete", this);
+ _request.getHttpInput().messageComplete();
return true;
}
@@ -624,17 +664,19 @@
try
{
- if (_state.handling()==Next.CONTINUE)
+ if (_state.handling()==Action.REQUEST_DISPATCH)
sendResponse(new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(),0,status,reason,false),null,true);
}
catch (IOException e)
{
- LOG.warn("badMessage",e);
+ LOG.debug(e);
}
finally
{
- if (_state.unhandle()==Next.COMPLETE)
+ if (_state.unhandle()==Action.COMPLETE)
_state.completed();
+ else
+ throw new IllegalStateException();
}
}
@@ -757,7 +799,7 @@
}
else
{
- LOG.warn("commit failed",x);
+ LOG.warn("Commit failed",x);
_transport.send(HttpGenerator.RESPONSE_500_INFO,null,true,new Callback()
{
@Override
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
index 29804a5..9ea5a90 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
@@ -22,7 +22,6 @@
import java.util.List;
import java.util.concurrent.TimeUnit;
-import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
@@ -62,32 +61,41 @@
{
IDLE, // Idle request
DISPATCHED, // Request dispatched to filter/servlet
- ASYNCSTARTED, // Suspend called, but not yet returned to container
- REDISPATCHING, // resumed while dispatched
ASYNCWAIT, // Suspended and parked
- REDISPATCH, // Has been scheduled
- REDISPATCHED, // Request redispatched to filter/servlet
- COMPLETECALLED,// complete called
+ ASYNCIO, // Has been dispatched for async IO
COMPLETING, // Request is completable
COMPLETED // Request is complete
}
- public enum Next
+ public enum Action
{
- CONTINUE, // Continue handling the channel
- WAIT, // Wait for further events
- COMPLETE // Complete the channel
+ REQUEST_DISPATCH, // handle a normal request dispatch
+ ASYNC_DISPATCH, // handle an async request dispatch
+ ASYNC_EXPIRED, // handle an async timeout
+ WRITE_CALLBACK, // handle an IO write callback
+ READ_CALLBACK, // handle an IO read callback
+ WAIT, // Wait for further events
+ COMPLETE // Complete the channel
+ }
+
+ public enum Async
+ {
+ STARTED,
+ DISPATCH,
+ COMPLETE,
+ EXPIRING,
+ EXPIRED
}
+ private final boolean DEBUG=LOG.isDebugEnabled();
private final HttpChannel<?> _channel;
- private List<AsyncListener> _lastAsyncListeners;
- private List<AsyncListener> _asyncListeners;
+ private List<AsyncListener> _asyncListeners;
private State _state;
+ private Async _async;
private boolean _initial;
- private boolean _dispatched;
- private boolean _expired;
- private volatile boolean _responseWrapped;
+ private boolean _asyncRead;
+ private boolean _asyncWrite;
private long _timeoutMs=DEFAULT_TIMEOUT;
private AsyncContextEvent _event;
@@ -95,6 +103,7 @@
{
_channel=channel;
_state=State.IDLE;
+ _async=null;
_initial=true;
}
@@ -145,7 +154,7 @@
{
synchronized (this)
{
- return super.toString()+"@"+getStatusString();
+ return String.format("%s@%x{s=%s i=%b a=%s}",getClass().getSimpleName(),hashCode(),_state,_initial,_async);
}
}
@@ -153,96 +162,102 @@
{
synchronized (this)
{
- return _state+
- (_initial?",initial":"")+
- (_dispatched?",resumed":"")+
- (_expired?",expired":"");
+ return String.format("s=%s i=%b a=%s",_state,_initial,_async);
}
}
/**
* @return Next handling of the request should proceed
*/
- protected Next handling()
+ protected Action handling()
{
synchronized (this)
{
+ if(DEBUG)
+ LOG.debug("{} handling {}",this,_state);
switch(_state)
{
case IDLE:
_initial=true;
_state=State.DISPATCHED;
- if (_lastAsyncListeners!=null)
- _lastAsyncListeners.clear();
- if (_asyncListeners!=null)
- _asyncListeners.clear();
- else
- {
- _asyncListeners=_lastAsyncListeners;
- _lastAsyncListeners=null;
- }
- break;
-
- case COMPLETECALLED:
- _state=State.COMPLETING;
- return Next.COMPLETE;
+ return Action.REQUEST_DISPATCH;
case COMPLETING:
- return Next.COMPLETE;
-
- case ASYNCWAIT:
- return Next.WAIT;
+ return Action.COMPLETE;
case COMPLETED:
- return Next.WAIT;
+ return Action.WAIT;
- case REDISPATCH:
- _state=State.REDISPATCHED;
- break;
+ case ASYNCWAIT:
+ if (_asyncRead)
+ {
+ _state=State.ASYNCIO;
+ _asyncRead=false;
+ return Action.READ_CALLBACK;
+ }
+ if (_asyncWrite)
+ {
+ _state=State.ASYNCIO;
+ _asyncWrite=false;
+ return Action.WRITE_CALLBACK;
+ }
+
+ if (_async!=null)
+ {
+ Async async=_async;
+ switch(async)
+ {
+ case COMPLETE:
+ _state=State.COMPLETING;
+ return Action.COMPLETE;
+ case DISPATCH:
+ _state=State.DISPATCHED;
+ _async=null;
+ return Action.ASYNC_DISPATCH;
+ case EXPIRING:
+ break;
+ case EXPIRED:
+ _state=State.DISPATCHED;
+ _async=null;
+ return Action.ASYNC_EXPIRED;
+ case STARTED:
+ if (DEBUG)
+ LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this
+ .getStatusString()));
+ return Action.WAIT;
+ }
+ }
+
+ return Action.WAIT;
default:
throw new IllegalStateException(this.getStatusString());
}
-
- _responseWrapped=false;
- return Next.CONTINUE;
}
}
-
public void startAsync(AsyncContextEvent event)
{
+ final List<AsyncListener> lastAsyncListeners;
+
synchronized (this)
{
- switch(_state)
- {
- case DISPATCHED:
- case REDISPATCHED:
- _dispatched=false;
- _expired=false;
- _responseWrapped=event.getSuppliedResponse()!=_channel.getResponse();
- _responseWrapped=false;
- _event=event;
- _state=State.ASYNCSTARTED;
- List<AsyncListener> listeners=_lastAsyncListeners;
- _lastAsyncListeners=_asyncListeners;
- if (listeners!=null)
- listeners.clear();
- _asyncListeners=listeners;
- break;
-
- default:
- throw new IllegalStateException(this.getStatusString());
- }
+ if (_state!=State.DISPATCHED || _async!=null)
+ throw new IllegalStateException(this.getStatusString());
+
+ _async=Async.STARTED;
+ _event=event;
+ lastAsyncListeners=_asyncListeners;
+ _asyncListeners=null;
}
- if (_lastAsyncListeners!=null)
+ if (lastAsyncListeners!=null)
{
- for (AsyncListener listener : _lastAsyncListeners)
+ for (AsyncListener listener : lastAsyncListeners)
{
try
{
- listener.onStartAsync(_event);
+ listener.onStartAsync(event);
}
catch(Exception e)
{
@@ -268,39 +283,63 @@
* @return next actions
* be handled again (eg because of a resume that happened before unhandle was called)
*/
- protected Next unhandle()
+ protected Action unhandle()
{
synchronized (this)
{
+ if(DEBUG)
+ LOG.debug("{} unhandle {}",this,_state);
+
switch(_state)
{
- case REDISPATCHED:
case DISPATCHED:
- _state=State.COMPLETING;
- return Next.COMPLETE;
-
- case IDLE:
- throw new IllegalStateException(this.getStatusString());
-
- case ASYNCSTARTED:
- _initial=false;
- _state=State.ASYNCWAIT;
- scheduleTimeout();
- return Next.WAIT;
-
- case REDISPATCHING:
- _initial=false;
- _state=State.REDISPATCHED;
- return Next.CONTINUE;
-
- case COMPLETECALLED:
- _initial=false;
- _state=State.COMPLETING;
- return Next.COMPLETE;
-
+ case ASYNCIO:
+ break;
default:
throw new IllegalStateException(this.getStatusString());
}
+
+ if (_asyncRead)
+ {
+ _state=State.ASYNCIO;
+ _asyncRead=false;
+ return Action.READ_CALLBACK;
+ }
+
+ if (_asyncWrite)
+ {
+ _asyncWrite=false;
+ _state=State.ASYNCIO;
+ return Action.WRITE_CALLBACK;
+ }
+
+ if (_async!=null)
+ {
+ _initial=false;
+ switch(_async)
+ {
+ case COMPLETE:
+ _state=State.COMPLETING;
+ _async=null;
+ return Action.COMPLETE;
+ case DISPATCH:
+ _state=State.DISPATCHED;
+ _async=null;
+ return Action.ASYNC_DISPATCH;
+ case EXPIRED:
+ _state=State.DISPATCHED;
+ _async=null;
+ return Action.ASYNC_EXPIRED;
+ case EXPIRING:
+ case STARTED:
+ scheduleTimeout();
+ _state=State.ASYNCWAIT;
+ return Action.WAIT;
+ }
+ }
+
+ _state=State.COMPLETING;
+ return Action.COMPLETE;
}
}
@@ -309,43 +348,30 @@
boolean dispatch;
synchronized (this)
{
- switch(_state)
- {
- case ASYNCSTARTED:
- _state=State.REDISPATCHING;
- _dispatched=true;
- dispatch=false;
- break;
-
- case ASYNCWAIT:
- dispatch=!_expired;
- _state=State.REDISPATCH;
- _dispatched=true;
- break;
-
- default:
- throw new IllegalStateException(this.getStatusString());
- }
-
+ if (_async!=Async.STARTED && _async!=Async.EXPIRING)
+ throw new IllegalStateException("AsyncContext#dispath "+this.getStatusString());
+ _async=Async.DISPATCH;
+
if (context!=null)
_event.setDispatchContext(context);
if (path!=null)
_event.setDispatchPath(path);
+
+ switch(_state)
+ {
+ case DISPATCHED:
+ case ASYNCIO:
+ dispatch=false;
+ break;
+ default:
+ dispatch=true;
+ break;
+ }
}
+ cancelTimeout();
if (dispatch)
- {
- cancelTimeout();
scheduleDispatch();
- }
- }
-
- public boolean isDispatched()
- {
- synchronized (this)
- {
- return _dispatched;
- }
}
protected void expired()
@@ -354,17 +380,11 @@
AsyncContextEvent event;
synchronized (this)
{
- switch(_state)
- {
- case ASYNCSTARTED:
- case ASYNCWAIT:
- _expired=true;
- event=_event;
- aListeners=_asyncListeners;
- break;
- default:
- return;
- }
+ if (_async!=Async.STARTED)
+ return;
+ _async=Async.EXPIRING;
+ event=_event;
+ aListeners=_asyncListeners;
}
if (aListeners!=null)
@@ -384,22 +404,20 @@
}
}
}
-
+
+ boolean dispatch=false;
synchronized (this)
{
- switch(_state)
+ if (_async==Async.EXPIRING)
{
- case ASYNCSTARTED:
- case ASYNCWAIT:
- _state=State.REDISPATCH;
- break;
- default:
- _expired=false;
- break;
+ _async=Async.EXPIRED;
+ if (_state==State.ASYNCWAIT)
+ dispatch=true;
}
}
- scheduleDispatch();
+ if (dispatch)
+ scheduleDispatch();
}
public void complete()
@@ -408,30 +426,15 @@
boolean handle;
synchronized (this)
{
- switch(_state)
- {
- case DISPATCHED:
- case REDISPATCHED:
- throw new IllegalStateException(this.getStatusString());
-
- case IDLE:
- case ASYNCSTARTED:
- _state=State.COMPLETECALLED;
- return;
-
- case ASYNCWAIT:
- _state=State.COMPLETECALLED;
- handle=!_expired;
- break;
-
- default:
- throw new IllegalStateException(this.getStatusString());
- }
+ if (_async!=Async.STARTED && _async!=Async.EXPIRING)
+ throw new IllegalStateException(this.getStatusString());
+ _async=Async.COMPLETE;
+ handle=_state==State.ASYNCWAIT;
}
+ cancelTimeout();
if (handle)
{
- cancelTimeout();
ContextHandler handler=getContextHandler();
if (handler!=null)
handler.handle(_channel);
@@ -444,24 +447,12 @@
{
synchronized (this)
{
- switch(_state)
- {
- case ASYNCSTARTED:
- case REDISPATCHING:
- _state=State.COMPLETECALLED;
- break;
-
- case COMPLETECALLED:
- break;
-
- default:
- throw new IllegalStateException(this.getStatusString());
- }
-
- _dispatched=false;
+ _async=Async.COMPLETE;
_event.setDispatchContext(null);
_event.setDispatchPath(null);
}
+
+ cancelTimeout();
}
protected void completed()
@@ -483,30 +474,34 @@
}
}
- if (aListeners!=null)
+ if (event!=null)
{
- for (AsyncListener listener : aListeners)
+ if (aListeners!=null)
{
- try
+ if (event.getThrowable()!=null)
{
- if (event!=null && event.getThrowable()!=null)
- {
- event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
- event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
- listener.onError(event);
- }
- else
- listener.onComplete(event);
+ event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
+ event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
}
- catch(Exception e)
+
+ for (AsyncListener listener : aListeners)
{
- LOG.warn(e);
+ try
+ {
+ if (event.getThrowable()!=null)
+ listener.onError(event);
+ else
+ listener.onComplete(event);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
}
}
- }
- if (event!=null)
event.completed();
+ }
}
protected void recycle()
@@ -516,17 +511,19 @@
switch(_state)
{
case DISPATCHED:
- case REDISPATCHED:
+ case ASYNCIO:
throw new IllegalStateException(getStatusString());
default:
- _state=State.IDLE;
+ break;
}
- _initial = true;
- _dispatched=false;
- _expired=false;
- _responseWrapped=false;
- cancelTimeout();
+ _asyncListeners=null;
+ _state=State.IDLE;
+ _async=null;
+ _initial=true;
+ _asyncRead=false;
+ _asyncWrite=false;
_timeoutMs=DEFAULT_TIMEOUT;
+ cancelTimeout();
_event=null;
}
}
@@ -545,7 +542,11 @@
protected void cancelTimeout()
{
- AsyncContextEvent event=_event;
+ final AsyncContextEvent event;
+ synchronized (this)
+ {
+ event=_event;
+ }
if (event!=null)
event.cancelTimeoutTask();
}
@@ -554,7 +555,7 @@
{
synchronized (this)
{
- return _expired;
+ return _async==Async.EXPIRED;
}
}
@@ -570,17 +571,7 @@
{
synchronized(this)
{
- switch(_state)
- {
- case ASYNCSTARTED:
- case REDISPATCHING:
- case COMPLETECALLED:
- case ASYNCWAIT:
- return true;
-
- default:
- return false;
- }
+ return _state==State.ASYNCWAIT || _state==State.DISPATCHED && _async==Async.STARTED;
}
}
@@ -603,18 +594,10 @@
public boolean isAsyncStarted()
{
synchronized (this)
- {
- switch(_state)
- {
- case ASYNCSTARTED: // Suspend called, but not yet returned to container
- case REDISPATCHING: // resumed while dispatched
- case COMPLETECALLED: // complete called
- case ASYNCWAIT:
- return true;
-
- default:
- return false;
- }
+ {
+ if (_state==State.DISPATCHED)
+ return _async!=null;
+ return _async==Async.STARTED || _async==Async.EXPIRING;
}
}
@@ -622,19 +605,7 @@
{
synchronized (this)
{
- switch(_state)
- {
- case ASYNCSTARTED:
- case REDISPATCHING:
- case ASYNCWAIT:
- case REDISPATCHED:
- case REDISPATCH:
- case COMPLETECALLED:
- return true;
-
- default:
- return false;
- }
+ return !_initial || _async!=null;
}
}
@@ -650,7 +621,12 @@
public ContextHandler getContextHandler()
{
- final AsyncContextEvent event=_event;
+ final AsyncContextEvent event;
+ synchronized (this)
+ {
+ event=_event;
+ }
+
if (event!=null)
{
Context context=((Context)event.getServletContext());
@@ -662,8 +638,13 @@
public ServletResponse getServletResponse()
{
- if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
- return _event.getSuppliedResponse();
+ final AsyncContextEvent event;
+ synchronized (this)
+ {
+ event=_event;
+ }
+ if (event!=null && event.getSuppliedResponse()!=null)
+ return event.getSuppliedResponse();
return _channel.getResponse();
}
@@ -682,6 +663,34 @@
_channel.getRequest().setAttribute(name,attribute);
}
+ public void onReadPossible()
+ {
+ boolean handle;
+
+ synchronized (this)
+ {
+ _asyncRead=true;
+ handle=_state==State.ASYNCWAIT;
+ }
+
+ if (handle)
+ _channel.execute(_channel);
+ }
+
+ public void onWritePossible()
+ {
+ boolean handle;
+
+ synchronized (this)
+ {
+ _asyncWrite=true;
+ handle=_state==State.ASYNCWAIT;
+ }
+
+ if (handle)
+ _channel.execute(_channel);
+ }
+
public class AsyncTimeout implements Runnable
{
@Override
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
index a5de0a8..c592f34 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
@@ -18,10 +18,8 @@
package org.eclipse.jetty.server;
-import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
-import java.nio.channels.ClosedChannelException;
import java.util.concurrent.RejectedExecutionException;
import org.eclipse.jetty.http.HttpGenerator;
@@ -37,10 +35,9 @@
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.util.BlockingCallback;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.IteratingNestedCallback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -64,8 +61,6 @@
private final HttpParser _parser;
private volatile ByteBuffer _requestBuffer = null;
private volatile ByteBuffer _chunk = null;
- private BlockingCallback _readBlocker = new BlockingCallback();
- private BlockingCallback _writeBlocker = new BlockingCallback();
public static HttpConnection getCurrentConnection()
@@ -96,7 +91,6 @@
HttpInput<ByteBuffer> input = newHttpInput();
_channel = newHttpChannel(input);
_parser = newHttpParser();
-
LOG.debug("New HTTP Connection {}", this);
}
@@ -107,7 +101,7 @@
protected HttpInput<ByteBuffer> newHttpInput()
{
- return new Input();
+ return new HttpInputOverHTTP(this);
}
protected HttpChannelOverHttp newHttpChannel(HttpInput<ByteBuffer> httpInput)
@@ -140,42 +134,11 @@
return _channel;
}
- public void reset()
+ public HttpParser getParser()
{
- // If we are still expecting
- if (_channel.isExpecting100Continue())
- {
- // reset to avoid seeking remaining content
- _parser.reset();
- // close to seek EOF
- _parser.close();
- if (getEndPoint().isOpen())
- fillInterested();
- }
- // else if we are persistent
- else if (_generator.isPersistent())
- // reset to seek next request
- _parser.reset();
- else
- {
- // else seek EOF
- _parser.close();
- if (getEndPoint().isOpen())
- fillInterested();
- }
-
- _generator.reset();
- _channel.reset();
-
- releaseRequestBuffer();
- if (_chunk!=null)
- {
- _bufferPool.release(_chunk);
- _chunk=null;
- }
+ return _parser;
}
-
@Override
public int getMessagesIn()
{
@@ -188,16 +151,7 @@
return getHttpChannel().getRequests();
}
- @Override
- public String toString()
- {
- return String.format("%s,g=%s,p=%s",
- super.toString(),
- _generator,
- _parser);
- }
-
- private void releaseRequestBuffer()
+ void releaseRequestBuffer()
{
if (_requestBuffer != null && !_requestBuffer.hasRemaining())
{
@@ -206,6 +160,13 @@
_bufferPool.release(buffer);
}
}
+
+ public ByteBuffer getRequestBuffer()
+ {
+ if (_requestBuffer == null)
+ _requestBuffer = _bufferPool.acquire(getInputBufferSize(), REQUEST_BUFFER_DIRECT);
+ return _requestBuffer;
+ }
/**
* <p>Parses and handles HTTP messages.</p>
@@ -221,77 +182,53 @@
LOG.debug("{} onFillable {}", this, _channel.getState());
setCurrentConnection(this);
+ int filled=Integer.MAX_VALUE;
+ boolean suspended=false;
try
{
- while (true)
+ // while not suspended and not upgraded
+ while (!suspended && getEndPoint().getConnection()==this)
{
- // Can the parser progress (even with an empty buffer)
- boolean call_channel=_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer);
-
- // Parse the buffer
- if (call_channel)
+ // Do we need some data to parse
+ if (BufferUtil.isEmpty(_requestBuffer))
{
- // Parse as much content as there is available before calling the channel
- // this is both efficient (may queue many chunks), will correctly set available for 100 continues
- // and will drive the parser to completion if all content is available.
- while (_parser.inContentState())
+ // If the previous iteration filled 0 bytes or saw a close, then break here
+ if (filled<=0)
+ break;
+
+ // Can we fill?
+ if(getEndPoint().isInputShutdown())
{
- if (!_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer))
- break;
+ // No pretend we read -1
+ filled=-1;
+ _parser.atEOF();
}
+ else
+ {
+ // Get a buffer
+ if (_requestBuffer == null)
+ _requestBuffer = _bufferPool.acquire(getInputBufferSize(), REQUEST_BUFFER_DIRECT);
+ // fill
+ filled = getEndPoint().fill(_requestBuffer);
+ if (filled==0) // Do a retry on fill 0 (optimization for SSL connections)
+ filled = getEndPoint().fill(_requestBuffer);
+
+ // tell parser
+ if (filled < 0)
+ _parser.atEOF();
+ }
+ }
+
+ // Parse the buffer
+ if (_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer))
+ {
// The parser returned true, which indicates the channel is ready to handle a request.
// Call the channel and this will either handle the request/response to completion OR,
// if the request suspends, the request/response will be incomplete so the outer loop will exit.
- boolean handle=_channel.handle();
-
- // Return if suspended or upgraded
- if (!handle || getEndPoint().getConnection()!=this)
- return;
+ suspended = !_channel.handle();
}
- else if (BufferUtil.isEmpty(_requestBuffer))
- {
- if (_requestBuffer == null)
- _requestBuffer = _bufferPool.acquire(getInputBufferSize(), REQUEST_BUFFER_DIRECT);
-
- int filled = getEndPoint().fill(_requestBuffer);
- if (filled==0) // Do a retry on fill 0 (optimisation for SSL connections)
- filled = getEndPoint().fill(_requestBuffer);
-
- LOG.debug("{} filled {}", this, filled);
-
- // If we failed to fill
- if (filled == 0)
- {
- // Somebody wanted to read, we didn't so schedule another attempt
- releaseRequestBuffer();
- fillInterested();
- return;
- }
- else if (filled < 0)
- {
- _parser.shutdownInput();
- // We were only filling if fully consumed, so if we have
- // read -1 then we have nothing to parse and thus nothing that
- // will generate a response. If we had a suspended request pending
- // a response or a request waiting in the buffer, we would not be here.
- if (getEndPoint().isOutputShutdown())
- getEndPoint().close();
- else
- getEndPoint().shutdownOutput();
- // buffer must be empty and the channel must be idle, so we can release.
- releaseRequestBuffer();
- return;
- }
- }
- else
- {
- // TODO work out how we can get here and a better way to handle it
- LOG.warn("Unexpected state: "+this+ " "+_channel+" "+_channel.getRequest());
- if (!_channel.getState().isSuspended())
- getEndPoint().close();
- return;
- }
+
}
}
catch (EofException e)
@@ -307,10 +244,22 @@
close();
}
finally
- {
+ {
setCurrentConnection(null);
+ if (!suspended && getEndPoint().isOpen() && getEndPoint().getConnection()==this)
+ {
+ fillInterested();
+ }
}
}
+
+
+ @Override
+ protected void onFillInterestedFailed(Throwable cause)
+ {
+ _parser.close();
+ super.onFillInterestedFailed(cause);
+ }
@Override
public void onOpen()
@@ -325,32 +274,6 @@
onFillable();
}
- @Override
- public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent) throws IOException
- {
- try
- {
- if (info==null)
- new ContentCallback(content,lastContent,_writeBlocker).iterate();
- else
- {
- // If we are still expecting a 100 continues
- if (_channel.isExpecting100Continue())
- // then we can't be persistent
- _generator.setPersistent(false);
- new CommitCallback(info,content,lastContent,_writeBlocker).iterate();
- }
- _writeBlocker.block();
- }
- catch (ClosedChannelException e)
- {
- throw new EofException(e);
- }
- catch (IOException e)
- {
- throw e;
- }
- }
@Override
public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
@@ -376,11 +299,6 @@
@Override
public void completed()
{
- // Finish consuming the request
- if (_parser.isInContent() && _generator.isPersistent() && !_channel.isExpecting100Continue())
- // Complete reading the request
- _channel.getRequest().getHttpInput().consumeAll();
-
// Handle connection upgrades
if (_channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
{
@@ -391,34 +309,58 @@
onClose();
getEndPoint().setConnection(connection);
connection.onOpen();
- reset();
+ _channel.reset();
+ _parser.reset();
+ _generator.reset();
+ releaseRequestBuffer();
return;
}
}
+
+ // Finish consuming the request
+ // If we are still expecting
+ if (_channel.isExpecting100Continue())
+ // close to seek EOF
+ _parser.close();
+ else if (_parser.inContentState() && _generator.isPersistent())
+ // Complete reading the request
+ _channel.getRequest().getHttpInput().consumeAll();
- reset();
+ // Reset the channel, parsers and generator
+ _channel.reset();
+ if (_generator.isPersistent() && !_parser.isClosed())
+ _parser.reset();
+ else
+ _parser.close();
+ releaseRequestBuffer();
+ if (_chunk!=null)
+ _bufferPool.release(_chunk);
+ _chunk=null;
+ _generator.reset();
// if we are not called from the onfillable thread, schedule completion
if (getCurrentConnection()!=this)
{
+ // If we are looking for the next request
if (_parser.isStart())
{
- // it wants to eat more
+ // if the buffer is empty
if (_requestBuffer == null)
{
+ // look for more data
fillInterested();
}
- else if (getConnector().isStarted())
+ // else if we are still running
+ else if (getConnector().isRunning())
{
- LOG.debug("{} pipelined", this);
-
+ // Dispatched to handle a pipelined request
try
{
getExecutor().execute(this);
}
catch (RejectedExecutionException e)
{
- if (getConnector().isStarted())
+ if (getConnector().isRunning())
LOG.warn(e);
else
LOG.ignore(e);
@@ -430,131 +372,9 @@
getEndPoint().close();
}
}
- }
- }
-
- public ByteBuffer getRequestBuffer()
- {
- return _requestBuffer;
- }
-
- protected class Input extends ByteBufferHttpInput
- {
- @Override
- protected void blockForContent() throws IOException
- {
- /* We extend the blockForContent method to replace the
- default implementation of a blocking queue with an implementation
- that uses the calling thread to block on a readable callback and
- then to do the parsing before before attempting the read.
- */
- while (!_parser.isComplete())
- {
- // Can the parser progress (even with an empty buffer)
- boolean event=_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer);
-
- // If there is more content to parse, loop so we can queue all content from this buffer now without the
- // need to call blockForContent again
- while (!event && BufferUtil.hasContent(_requestBuffer) && _parser.inContentState())
- event=_parser.parseNext(_requestBuffer);
-
- // If we have content, return
- if (_parser.isComplete() || available()>0)
- return;
-
- // Do we have content ready to parse?
- if (BufferUtil.isEmpty(_requestBuffer))
- {
- // If no more input
- if (getEndPoint().isInputShutdown())
- {
- _parser.shutdownInput();
- shutdown();
- return;
- }
-
- // Wait until we can read
- block(_readBlocker);
- LOG.debug("{} block readable on {}",this,_readBlocker);
- _readBlocker.block();
-
- // We will need a buffer to read into
- if (_requestBuffer==null)
- {
- long content_length=_channel.getRequest().getContentLength();
- int size=getInputBufferSize();
- if (size<content_length)
- size=size*4; // TODO tune this
- _requestBuffer=_bufferPool.acquire(size,REQUEST_BUFFER_DIRECT);
- }
-
- // read some data
- int filled=getEndPoint().fill(_requestBuffer);
- LOG.debug("{} block filled {}",this,filled);
- if (filled<0)
- {
- _parser.shutdownInput();
- return;
- }
- }
- }
- }
-
- @Override
- protected void onContentQueued(ByteBuffer ref)
- {
- /* This callback could be used to tell the connection
- * that the request did contain content and thus the request
- * buffer needs to be held until a call to #onAllContentConsumed
- *
- * However it turns out that nothing is needed here because either a
- * request will have content, in which case the request buffer will be
- * released by a call to onAllContentConsumed; or it will not have content.
- * If it does not have content, either it will complete quickly and the
- * buffers will be released in completed() or it will be suspended and
- * onReadable() contains explicit handling to release if it is suspended.
- *
- * We extend this method anyway, to turn off the notify done by the
- * default implementation as this is not needed by our implementation
- * of blockForContent
- */
- }
-
- @Override
- public void earlyEOF()
- {
- synchronized (lock())
- {
- _inputEOF=true;
- _earlyEOF = true;
- LOG.debug("{} early EOF", this);
- }
- }
-
- @Override
- public void shutdown()
- {
- synchronized (lock())
- {
- _inputEOF=true;
- LOG.debug("{} shutdown", this);
- }
- }
-
- @Override
- protected void onAllContentConsumed()
- {
- /* This callback tells the connection that all content that has
- * been parsed has been consumed. Thus the request buffer may be
- * released if it is empty.
- */
- releaseRequestBuffer();
- }
-
- @Override
- public String toString()
- {
- return super.toString()+"{"+_channel+","+HttpConnection.this+"}";
+ // else the parser must be closed, so seek the EOF if we are still open
+ else if (getEndPoint().isOpen())
+ fillInterested();
}
}
@@ -564,6 +384,23 @@
{
super(connector,config,endPoint,transport,input);
}
+
+ @Override
+ public void earlyEOF()
+ {
+ // If we have no request yet, just close
+ if (getRequest().getMethod()==null)
+ close();
+ else
+ super.earlyEOF();
+ }
+
+ @Override
+ public boolean content(ByteBuffer item)
+ {
+ super.content(item);
+ return true;
+ }
@Override
public void badMessage(int status, String reason)
@@ -629,7 +466,7 @@
}
}
- private class CommitCallback extends IteratingCallback
+ private class CommitCallback extends IteratingNestedCallback
{
final ByteBuffer _content;
final boolean _lastContent;
@@ -751,7 +588,7 @@
}
}
- private class ContentCallback extends IteratingCallback
+ private class ContentCallback extends IteratingNestedCallback
{
final ByteBuffer _content;
final boolean _lastContent;
@@ -832,4 +669,5 @@
}
}
}
+
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
index 488bab9..9aaaef7 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
@@ -19,13 +19,12 @@
package org.eclipse.jetty.server;
import java.io.IOException;
-import java.io.InterruptedIOException;
+import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.io.RuntimeIOException;
-import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -39,54 +38,108 @@
* {@link #onContentConsumed(T)} and {@link #onAllContentConsumed()} that can be implemented so that the
* caller will know when buffers are queued and consumed.</p>
*/
-public abstract class HttpInput<T> extends ServletInputStream
+/**
+ * @author gregw
+ *
+ * @param <T>
+ */
+/**
+ * @author gregw
+ *
+ * @param <T>
+ */
+public abstract class HttpInput<T> extends ServletInputStream implements Runnable
{
private final static Logger LOG = Log.getLogger(HttpInput.class);
- private final ArrayQueue<T> _inputQ = new ArrayQueue<>();
- protected boolean _earlyEOF;
- protected boolean _inputEOF;
- public Object lock()
+ private final byte[] _oneByteBuffer = new byte[1];
+ private HttpChannelState _channelState;
+ private Throwable _onError;
+ private ReadListener _listener;
+ private boolean _notReady;
+
+ protected State _state = BLOCKING;
+ private State _eof=null;
+ private final Object _lock;
+ private long _contentRead;
+
+ protected HttpInput()
{
- return _inputQ.lock();
+ this(null);
+ }
+
+ protected HttpInput(Object lock)
+ {
+ _lock=lock==null?this:lock;
+ }
+
+ public final Object lock()
+ {
+ return _lock;
}
public void recycle()
{
synchronized (lock())
{
- T item = _inputQ.peekUnsafe();
- while (item != null)
- {
- _inputQ.pollUnsafe();
- onContentConsumed(item);
-
- item = _inputQ.peekUnsafe();
- if (item == null)
- onAllContentConsumed();
- }
- _inputEOF = false;
- _earlyEOF = false;
+ _state = BLOCKING;
+ _eof=null;
+ _onError=null;
+ _contentRead=0;
}
}
+ /**
+ * Access the next content to be consumed from. Returning the next item does not consume it
+ * and it may be returned multiple times until it is consumed. Calls to {@link #get(Object, byte[], int, int)}
+ * or {@link #consume(Object, int)} are required to consume data from the content.
+ * @return Content or null if none available.
+ * @throws IOException
+ */
+ protected abstract T nextContent() throws IOException;
+
+ /**
+ * A convenience method to call nextContent and to check the return value, which if null then the
+ * a check is made for EOF and the state changed accordingly.
+ * @see #nextContent()
+ * @return Content or null if none available.
+ * @throws IOException
+ */
+ protected T getNextContent() throws IOException
+ {
+ T content=nextContent();
+
+ if (content==null && _eof!=null)
+ {
+ LOG.debug("{} eof {}",this,_eof);
+ _state=_eof;
+ _eof=null;
+ }
+
+ return content;
+ }
+
@Override
public int read() throws IOException
{
- byte[] bytes = new byte[1];
- int read = read(bytes, 0, 1);
- return read < 0 ? -1 : 0xff & bytes[0];
+ int read = read(_oneByteBuffer, 0, 1);
+ return read < 0 ? -1 : 0xff & _oneByteBuffer[0];
}
@Override
public int available()
{
- synchronized (lock())
+ try
{
- T item = _inputQ.peekUnsafe();
- if (item == null)
- return 0;
- return remaining(item);
+ synchronized (lock())
+ {
+ T item = getNextContent();
+ return item==null?0:remaining(item);
+ }
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeIOException(e);
}
}
@@ -94,126 +147,60 @@
public int read(byte[] b, int off, int len) throws IOException
{
T item = null;
+ int l;
synchronized (lock())
{
+ // System.err.printf("read s=%s q=%d e=%s%n",_state,_inputQ.size(),_eof);
+
// Get the current head of the input Q
- item = _inputQ.peekUnsafe();
-
- // Skip empty items at the head of the queue
- while (item != null && remaining(item) == 0)
- {
- _inputQ.pollUnsafe();
- onContentConsumed(item);
- LOG.debug("{} consumed {}", this, item);
- item = _inputQ.peekUnsafe();
-
- // If that was the last item then notify
- if (item==null)
- onAllContentConsumed();
- }
+ item = getNextContent();
// If we have no item
if (item == null)
{
- // Was it unexpectedly EOF'd?
- if (isEarlyEOF())
- throw new EofException();
-
- // check for EOF
- if (isShutdown())
- {
- onEOF();
- return -1;
- }
-
- // OK then block for some more content
- blockForContent();
-
- // If still not content, we must be closed
- item = _inputQ.peekUnsafe();
+ _state.waitForContent(this);
+ item=getNextContent();
if (item==null)
- {
- if (isEarlyEOF())
- throw new EofException();
-
- // blockForContent will only return with no
- // content if it is closed.
- if (!isShutdown())
- LOG.warn("Unexpected !EOF: "+this);
-
- onEOF();
- return -1;
- }
+ return _state.noContent();
}
+
+ l=get(item, b, off, len);
+ _contentRead+=l;
+
}
- return get(item, b, off, len);
+ return l;
}
+
protected abstract int remaining(T item);
protected abstract int get(T item, byte[] buffer, int offset, int length);
- protected abstract void onContentConsumed(T item);
+ protected abstract void consume(T item, int length);
- protected void blockForContent() throws IOException
+ protected abstract void blockForContent() throws IOException;
+
+ protected boolean onAsyncRead()
+ {
+ if (_listener==null)
+ return false;
+ _channelState.onReadPossible();
+ return true;
+ }
+
+ public long getContentRead()
{
synchronized (lock())
{
- while (_inputQ.isEmpty() && !isShutdown() && !isEarlyEOF())
- {
- try
- {
- LOG.debug("{} waiting for content", this);
- lock().wait();
- }
- catch (InterruptedException e)
- {
- throw (IOException)new InterruptedIOException().initCause(e);
- }
- }
+ return _contentRead;
}
}
-
- /* ------------------------------------------------------------ */
- /** Called by this HttpInput to signal new content has been queued
- * @param item
- */
- protected void onContentQueued(T item)
- {
- lock().notify();
- }
-
- /* ------------------------------------------------------------ */
- /** Called by this HttpInput to signal all available content has been consumed
- */
- protected void onAllContentConsumed()
- {
- }
-
- /* ------------------------------------------------------------ */
- /** Called by this HttpInput to signal it has reached EOF
- */
- protected void onEOF()
- {
- }
-
- /* ------------------------------------------------------------ */
+
/** Add some content to the input stream
* @param item
*/
- public void content(T item)
- {
- synchronized (lock())
- {
- // The buffer is not copied here. This relies on the caller not recycling the buffer
- // until the it is consumed. The onAllContentConsumed() callback is the signal to the
- // caller that the buffers can be recycled.
- _inputQ.add(item);
- onContentQueued(item);
- LOG.debug("{} queued {}", this, item);
- }
- }
+ public abstract void content(T item);
- /* ------------------------------------------------------------ */
+
/** This method should be called to signal to the HttpInput
* that an EOF has arrived before all the expected content.
* Typically this will result in an EOFException being thrown
@@ -223,73 +210,245 @@
{
synchronized (lock())
{
- _earlyEOF = true;
- _inputEOF = true;
- lock().notify();
- LOG.debug("{} early EOF", this);
+ if (_eof==null || !_eof.isEOF())
+ {
+ LOG.debug("{} early EOF", this);
+ _eof=EARLY_EOF;
+ if (_listener!=null)
+ _channelState.onReadPossible();
+ }
}
}
- /* ------------------------------------------------------------ */
- public boolean isEarlyEOF()
+ public void messageComplete()
{
synchronized (lock())
{
- return _earlyEOF;
+ if (_eof==null || !_eof.isEOF())
+ {
+ LOG.debug("{} EOF", this);
+ _eof=EOF;
+ if (_listener!=null)
+ _channelState.onReadPossible();
+ }
}
}
- /* ------------------------------------------------------------ */
- public void shutdown()
- {
- synchronized (lock())
- {
- _inputEOF = true;
- lock().notify();
- LOG.debug("{} shutdown", this);
- }
- }
-
- /* ------------------------------------------------------------ */
- public boolean isShutdown()
- {
- synchronized (lock())
- {
- return _inputEOF;
- }
- }
-
- /* ------------------------------------------------------------ */
public void consumeAll()
{
synchronized (lock())
{
- T item = _inputQ.peekUnsafe();
- loop: while (!isShutdown() && !isEarlyEOF())
+ try
{
- while (item != null)
+ while (!isFinished())
{
- _inputQ.pollUnsafe();
- onContentConsumed(item);
-
- item = _inputQ.peekUnsafe();
- if (item == null)
- onAllContentConsumed();
- }
-
- try
- {
- blockForContent();
- item = _inputQ.peekUnsafe();
+ T item = getNextContent();
if (item==null)
- break;
+ _state.waitForContent(this);
+ else
+ consume(item,remaining(item));
}
- catch (IOException e)
- {
- LOG.debug(e);
- break loop;
- }
+ }
+ catch (IOException e)
+ {
+ LOG.debug(e);
}
}
}
+
+ @Override
+ public boolean isFinished()
+ {
+ synchronized (lock())
+ {
+ return _state.isEOF();
+ }
+ }
+
+ @Override
+ public boolean isReady()
+ {
+ synchronized (lock())
+ {
+ if (_listener==null)
+ return true;
+ int available = available();
+ if (available>0)
+ return true;
+ if (!_notReady)
+ {
+ _notReady=true;
+ if (_state.isEOF())
+ _channelState.onReadPossible();
+ else
+ unready();
+ }
+ return false;
+ }
+ }
+
+ protected void unready()
+ {
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener)
+ {
+ if (readListener==null)
+ throw new NullPointerException("readListener==null");
+ synchronized (lock())
+ {
+ if (_state!=BLOCKING)
+ throw new IllegalStateException("state="+_state);
+ _state=ASYNC;
+ _listener=readListener;
+ _notReady=true;
+
+ _channelState.onReadPossible();
+ }
+ }
+
+ public void failed(Throwable x)
+ {
+ synchronized (lock())
+ {
+ if (_onError==null)
+ LOG.warn(x);
+ else
+ _onError=x;
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ final boolean available;
+ final boolean eof;
+ final Throwable x;
+
+ synchronized (lock())
+ {
+ if (!_notReady || _listener==null)
+ return;
+
+ x=_onError;
+ T item;
+ try
+ {
+ item = getNextContent();
+ }
+ catch(Exception e)
+ {
+ item=null;
+ failed(e);
+ }
+ available= item!=null && remaining(item)>0;
+
+ eof = !available && _state.isEOF();
+ _notReady=!available&&!eof;
+ }
+
+ try
+ {
+ if (x!=null)
+ _listener.onError(x);
+ else if (available)
+ _listener.onDataAvailable();
+ else if (eof)
+ _listener.onAllDataRead();
+ else
+ unready();
+ }
+ catch(Throwable e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ _listener.onError(e);
+ }
+ }
+
+ protected static class State
+ {
+ public void waitForContent(HttpInput<?> in) throws IOException
+ {
+ }
+
+ public int noContent() throws IOException
+ {
+ return -1;
+ }
+
+ public boolean isEOF()
+ {
+ return false;
+ }
+ }
+
+ protected static final State BLOCKING= new State()
+ {
+ @Override
+ public void waitForContent(HttpInput<?> in) throws IOException
+ {
+ in.blockForContent();
+ }
+ public String toString()
+ {
+ return "OPEN";
+ }
+ };
+
+ protected static final State ASYNC= new State()
+ {
+ @Override
+ public int noContent() throws IOException
+ {
+ return 0;
+ }
+ @Override
+ public String toString()
+ {
+ return "ASYNC";
+ }
+ };
+
+ protected static final State EARLY_EOF= new State()
+ {
+ @Override
+ public int noContent() throws IOException
+ {
+ throw new EofException();
+ }
+ @Override
+ public boolean isEOF()
+ {
+ return true;
+ }
+ public String toString()
+ {
+ return "EARLY_EOF";
+ }
+ };
+
+ protected static final State EOF= new State()
+ {
+ @Override
+ public boolean isEOF()
+ {
+ return true;
+ }
+
+ public String toString()
+ {
+ return "EOF";
+ }
+ };
+
+ public void init(HttpChannelState state)
+ {
+ synchronized (lock())
+ {
+ _channelState=state;
+ }
+ }
+
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java
new file mode 100644
index 0000000..fd7933f
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java
@@ -0,0 +1,168 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BlockingCallback;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HttpInputOverHTTP extends HttpInput<ByteBuffer> implements Callback
+{
+ private static final Logger LOG = Log.getLogger(HttpInputOverHTTP.class);
+ private final BlockingCallback _readBlocker = new BlockingCallback();
+ private final HttpConnection _httpConnection;
+ private ByteBuffer _content;
+
+ /**
+ * @param httpConnection
+ */
+ public HttpInputOverHTTP(HttpConnection httpConnection)
+ {
+ _httpConnection = httpConnection;
+ }
+
+ @Override
+ public void recycle()
+ {
+ synchronized (lock())
+ {
+ super.recycle();
+ _content=null;
+ }
+ }
+
+ @Override
+ protected void blockForContent() throws IOException
+ {
+ while(true)
+ {
+ _httpConnection.fillInterested(_readBlocker);
+ LOG.debug("{} block readable on {}",this,_readBlocker);
+ _readBlocker.block();
+
+ Object content=getNextContent();
+ if (content!=null || isFinished())
+ break;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x",getClass().getSimpleName(),hashCode());
+ }
+
+ @Override
+ protected ByteBuffer nextContent() throws IOException
+ {
+ // If we have some content available, return it
+ if (BufferUtil.hasContent(_content))
+ return _content;
+
+ // No - then we are going to need to parse some more content
+ _content=null;
+ ByteBuffer requestBuffer = _httpConnection.getRequestBuffer();
+
+ while (!_httpConnection.getParser().isComplete())
+ {
+ // Can the parser progress (even with an empty buffer)
+ _httpConnection.getParser().parseNext(requestBuffer==null?BufferUtil.EMPTY_BUFFER:requestBuffer);
+
+ // If we got some content, that will do for now!
+ if (BufferUtil.hasContent(_content))
+ return _content;
+
+ // No, we can we try reading some content?
+ if (BufferUtil.isEmpty(requestBuffer) && _httpConnection.getEndPoint().isInputShutdown())
+ {
+ _httpConnection.getParser().atEOF();
+ continue;
+ }
+
+ // OK lets read some data
+ int filled=_httpConnection.getEndPoint().fill(requestBuffer);
+ if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+ LOG.debug("{} filled {}",this,filled);
+ if (filled<=0)
+ {
+ if (filled<0)
+ {
+ _httpConnection.getParser().atEOF();
+ continue;
+ }
+ return null;
+ }
+ }
+
+ return null;
+
+ }
+
+ @Override
+ protected int remaining(ByteBuffer item)
+ {
+ return item.remaining();
+ }
+
+ @Override
+ protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
+ {
+ int l = Math.min(item.remaining(), length);
+ item.get(buffer, offset, l);
+ return l;
+ }
+
+ @Override
+ protected void consume(ByteBuffer item, int length)
+ {
+ item.position(item.position()+length);
+ }
+
+ @Override
+ public void content(ByteBuffer item)
+ {
+ if (BufferUtil.hasContent(_content))
+ throw new IllegalStateException();
+ _content=item;
+ }
+
+ @Override
+ protected void unready()
+ {
+ _httpConnection.fillInterested(this);
+ }
+
+ @Override
+ public void succeeded()
+ {
+ _httpConnection.getHttpChannel().getState().onReadPossible();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ super.failed(x);
+ _httpConnection.getHttpChannel().getState().onReadPossible();
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
index 5f17d02..eee5c23 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
@@ -18,26 +18,28 @@
package org.eclipse.jetty.server;
-import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritePendingException;
+import java.util.concurrent.atomic.AtomicReference;
+
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
import org.eclipse.jetty.http.HttpContent;
-import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.BlockingCallback;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.IteratingNestedCallback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.Resource;
/**
* <p>{@link HttpOutput} implements {@link ServletOutputStream}
@@ -49,19 +51,34 @@
* via {@link RequestDispatcher#include(ServletRequest, ServletResponse)} to
* close the stream, to be reopened after the inclusion ends.</p>
*/
-public class HttpOutput extends ServletOutputStream
+public class HttpOutput extends ServletOutputStream implements Runnable
{
private static Logger LOG = Log.getLogger(HttpOutput.class);
private final HttpChannel<?> _channel;
- private boolean _closed;
private long _written;
private ByteBuffer _aggregate;
private int _bufferSize;
+ private int _commitSize;
+ private WriteListener _writeListener;
+ private volatile Throwable _onError;
+
+ /*
+ ACTION OPEN ASYNC READY PENDING UNREADY
+ -------------------------------------------------------------------------------
+ setWriteListener() READY->owp ise ise ise ise
+ write() OPEN ise PENDING wpe wpe
+ flush() OPEN ise PENDING wpe wpe
+ isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false
+ write completed - - - ASYNC READY->owp
+ */
+ enum State { OPEN, ASYNC, READY, PENDING, UNREADY, CLOSED }
+ private final AtomicReference<State> _state=new AtomicReference<>(State.OPEN);
public HttpOutput(HttpChannel<?> channel)
{
_channel = channel;
_bufferSize = _channel.getHttpConfiguration().getOutputBufferSize();
+ _commitSize=_bufferSize/4;
}
public boolean isWritten()
@@ -82,49 +99,63 @@
public void reopen()
{
- _closed = false;
+ _state.set(State.OPEN);
}
- /** Called by the HttpChannel if the output was closed
- * externally (eg by a 500 exception handling).
- */
- void closed()
+ public boolean isAllContentWritten()
{
- if (!_closed)
- {
- _closed = true;
- try
- {
- _channel.getResponse().closeOutput();
- }
- catch(IOException e)
- {
- _channel.failed();
- LOG.ignore(e);
- }
- releaseBuffer();
- }
+ return _channel.getResponse().isAllContentWritten(_written);
}
@Override
public void close()
{
- if (!isClosed())
+ State state=_state.get();
+ while(state!=State.CLOSED)
{
- try
+ if (_state.compareAndSet(state,State.CLOSED))
{
- if (BufferUtil.hasContent(_aggregate))
- _channel.write(_aggregate, !_channel.getResponse().isIncluding());
- else
- _channel.write(BufferUtil.EMPTY_BUFFER, !_channel.getResponse().isIncluding());
+ try
+ {
+ if (BufferUtil.hasContent(_aggregate))
+ _channel.write(_aggregate, !_channel.getResponse().isIncluding());
+ else
+ _channel.write(BufferUtil.EMPTY_BUFFER, !_channel.getResponse().isIncluding());
+ }
+ catch(IOException e)
+ {
+ LOG.debug(e);
+ _channel.failed();
+ }
+ releaseBuffer();
+ return;
}
- catch(IOException e)
- {
- _channel.failed();
- LOG.ignore(e);
- }
+ state=_state.get();
}
- closed();
+ }
+
+ /* Called to indicated that the output is already closed and the state needs to be updated to match */
+ void closed()
+ {
+ State state=_state.get();
+ while(state!=State.CLOSED)
+ {
+ if (_state.compareAndSet(state,State.CLOSED))
+ {
+ try
+ {
+ _channel.getResponse().closeOutput();
+ }
+ catch(IOException e)
+ {
+ LOG.debug(e);
+ _channel.failed();
+ }
+ releaseBuffer();
+ return;
+ }
+ state=_state.get();
+ }
}
private void releaseBuffer()
@@ -138,44 +169,109 @@
public boolean isClosed()
{
- return _closed;
+ return _state.get()==State.CLOSED;
}
@Override
public void flush() throws IOException
{
- if (isClosed())
- return;
+ while(true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ if (BufferUtil.hasContent(_aggregate))
+ _channel.write(_aggregate, false);
+ else
+ _channel.write(BufferUtil.EMPTY_BUFFER, false);
+ return;
- if (BufferUtil.hasContent(_aggregate))
- _channel.write(_aggregate, false);
- else
- _channel.write(BufferUtil.EMPTY_BUFFER, false);
+ case ASYNC:
+ throw new IllegalStateException("isReady() not called");
+
+ case READY:
+ if (!_state.compareAndSet(State.READY, State.PENDING))
+ continue;
+ new AsyncFlush().process();
+ return;
+
+ case PENDING:
+ case UNREADY:
+ throw new WritePendingException();
+
+ case CLOSED:
+ return;
+ }
+ break;
+ }
}
- public boolean isAllContentWritten()
- {
- Response response=_channel.getResponse();
- return response.isAllContentWritten(_written);
- }
-
- public void closeOutput() throws IOException
- {
- _channel.getResponse().closeOutput();
- }
@Override
public void write(byte[] b, int off, int len) throws IOException
- {
- if (isClosed())
- throw new EofException("Closed");
-
+ {
_written+=len;
boolean complete=_channel.getResponse().isAllContentWritten(_written);
- int capacity = getBufferSize();
+
+ // Async or Blocking ?
+ while(true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ // process blocking below
+ break;
+
+ case ASYNC:
+ throw new IllegalStateException("isReady() not called");
+
+ case READY:
+ if (!_state.compareAndSet(State.READY, State.PENDING))
+ continue;
+
+ // Should we aggregate?
+ int capacity = getBufferSize();
+ if (!complete && len<=_commitSize)
+ {
+ if (_aggregate == null)
+ _aggregate = _channel.getByteBufferPool().acquire(capacity, false);
+
+ // YES - fill the aggregate with content from the buffer
+ int filled = BufferUtil.fill(_aggregate, b, off, len);
+
+ // return if we are not complete, not full and filled all the content
+ if (filled==len && !BufferUtil.isFull(_aggregate))
+ {
+ if (!_state.compareAndSet(State.PENDING, State.ASYNC))
+ throw new IllegalStateException();
+ return;
+ }
+
+ // adjust offset/length
+ off+=filled;
+ len-=filled;
+ }
+
+ // Do the asynchronous writing from the callback
+ new AsyncWrite(b,off,len,complete).process();
+ return;
+
+ case PENDING:
+ case UNREADY:
+ throw new WritePendingException();
+
+ case CLOSED:
+ throw new EofException("Closed");
+ }
+ break;
+ }
+
+
+ // handle blocking write
// Should we aggregate?
- if (!complete && len<=capacity/4)
+ int capacity = getBufferSize();
+ if (!complete && len<=_commitSize)
{
if (_aggregate == null)
_aggregate = _channel.getByteBufferPool().acquire(capacity, false);
@@ -184,12 +280,12 @@
int filled = BufferUtil.fill(_aggregate, b, off, len);
// return if we are not complete, not full and filled all the content
- if (!complete && filled == len && !BufferUtil.isFull(_aggregate))
+ if (filled==len && !BufferUtil.isFull(_aggregate))
return;
// adjust offset/length
- off += filled;
- len -= filled;
+ off+=filled;
+ len-=filled;
}
// flush any content from the aggregate
@@ -198,7 +294,7 @@
_channel.write(_aggregate, complete && len==0);
// should we fill aggregate again from the buffer?
- if (len>0 && !complete && len<=_aggregate.capacity()/4)
+ if (len>0 && !complete && len<=_commitSize)
{
BufferUtil.append(_aggregate, b, off, len);
return;
@@ -213,37 +309,128 @@
_channel.write(BufferUtil.EMPTY_BUFFER,complete);
if (complete)
+ {
closed();
+ }
+
}
+ public void write(ByteBuffer buffer) throws IOException
+ {
+ _written+=buffer.remaining();
+ boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+ // Async or Blocking ?
+ while(true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ // process blocking below
+ break;
+
+ case ASYNC:
+ throw new IllegalStateException("isReady() not called");
+
+ case READY:
+ if (!_state.compareAndSet(State.READY, State.PENDING))
+ continue;
+
+ // Do the asynchronous writing from the callback
+ new AsyncWrite(buffer,complete).process();
+ return;
+
+ case PENDING:
+ case UNREADY:
+ throw new WritePendingException();
+
+ case CLOSED:
+ throw new EofException("Closed");
+ }
+ break;
+ }
+
+
+ // handle blocking write
+ int len=BufferUtil.length(buffer);
+
+ // flush any content from the aggregate
+ if (BufferUtil.hasContent(_aggregate))
+ _channel.write(_aggregate, complete && len==0);
+
+ // write any remaining content in the buffer directly
+ if (len>0)
+ _channel.write(buffer, complete);
+ else if (complete)
+ _channel.write(BufferUtil.EMPTY_BUFFER,complete);
+
+ if (complete)
+ closed();
+ }
@Override
public void write(int b) throws IOException
{
- if (isClosed())
- throw new EOFException("Closed");
-
- // Allocate an aggregate buffer.
- // Never direct as it is slow to do little writes to a direct buffer.
- if (_aggregate == null)
- _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
-
- BufferUtil.append(_aggregate, (byte)b);
- _written++;
-
+ _written+=1;
boolean complete=_channel.getResponse().isAllContentWritten(_written);
-
- // Check if all written or full
- if (complete || BufferUtil.isFull(_aggregate))
+
+ // Async or Blocking ?
+ while(true)
{
- BlockingCallback callback = _channel.getWriteBlockingCallback();
- _channel.write(_aggregate, false, callback);
- callback.block();
- if (complete)
- closed();
+ switch(_state.get())
+ {
+ case OPEN:
+ if (_aggregate == null)
+ _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+ BufferUtil.append(_aggregate, (byte)b);
+
+ // Check if all written or full
+ if (complete || BufferUtil.isFull(_aggregate))
+ {
+ BlockingCallback callback = _channel.getWriteBlockingCallback();
+ _channel.write(_aggregate, complete, callback);
+ callback.block();
+ if (complete)
+ closed();
+ }
+ break;
+
+ case ASYNC:
+ throw new IllegalStateException("isReady() not called");
+
+ case READY:
+ if (!_state.compareAndSet(State.READY, State.PENDING))
+ continue;
+
+ if (_aggregate == null)
+ _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+ BufferUtil.append(_aggregate, (byte)b);
+
+ // Check if all written or full
+ if (!complete && !BufferUtil.isFull(_aggregate))
+ {
+ if (!_state.compareAndSet(State.PENDING, State.ASYNC))
+ throw new IllegalStateException();
+ return;
+ }
+
+ // Do the asynchronous writing from the callback
+ new AsyncFlush().process();
+ return;
+
+ case PENDING:
+ case UNREADY:
+ throw new WritePendingException();
+
+ case CLOSED:
+ throw new EofException("Closed");
+ }
+ break;
}
}
+
+
@Override
public void print(String s) throws IOException
{
@@ -254,51 +441,6 @@
}
/* ------------------------------------------------------------ */
- /** Set headers and send content.
- * @deprecated Use {@link Response#setHeaders(HttpContent)} and {@link #sendContent(HttpContent)} instead.
- * @param content
- * @throws IOException
- */
- @Deprecated
- public void sendContent(Object content) throws IOException
- {
- final BlockingCallback callback =_channel.getWriteBlockingCallback();
-
- if (content instanceof HttpContent)
- {
- _channel.getResponse().setHeaders((HttpContent)content);
- sendContent((HttpContent)content,callback);
- }
- else if (content instanceof Resource)
- {
- Resource resource = (Resource)content;
- _channel.getResponse().getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, resource.lastModified());
-
- ReadableByteChannel in=((Resource)content).getReadableByteChannel();
- if (in!=null)
- sendContent(in,callback);
- else
- sendContent(resource.getInputStream(),callback);
- }
- else if (content instanceof ByteBuffer)
- {
- sendContent((ByteBuffer)content,callback);
- }
- else if (content instanceof ReadableByteChannel)
- {
- sendContent((ReadableByteChannel)content,callback);
- }
- else if (content instanceof InputStream)
- {
- sendContent((InputStream)content,callback);
- }
- else
- callback.failed(new IllegalArgumentException("unknown content type "+content.getClass()));
-
- callback.block();
- }
-
- /* ------------------------------------------------------------ */
/** Blocking send of content.
* @param content The content to send.
* @throws IOException
@@ -335,7 +477,7 @@
new ReadableByteChannelWritingCB(in,callback).iterate();
callback.block();
}
-
+
/* ------------------------------------------------------------ */
/** Blocking send of content.
@@ -348,7 +490,7 @@
sendContent(content,callback);
callback.block();
}
-
+
/* ------------------------------------------------------------ */
/** Asynchronous send of content.
* @param content The content to send
@@ -371,13 +513,13 @@
public void failed(Throwable x)
{
callback.failed(x);
- }
+ }
});
}
/* ------------------------------------------------------------ */
/** Asynchronous send of content.
- * @param in The content to send as a stream. The stream will be closed
+ * @param in The content to send as a stream. The stream will be closed
* after reading all content.
* @param callback The callback to use to notify success or failure
*/
@@ -388,7 +530,7 @@
/* ------------------------------------------------------------ */
/** Asynchronous send of content.
- * @param in The content to send as a channel. The channel will be closed
+ * @param in The content to send as a channel. The channel will be closed
* after reading all content.
* @param callback The callback to use to notify success or failure
*/
@@ -404,23 +546,36 @@
*/
public void sendContent(HttpContent httpContent, Callback callback) throws IOException
{
- if (isClosed())
- throw new IOException("Closed");
if (BufferUtil.hasContent(_aggregate))
throw new IOException("written");
if (_channel.isCommitted())
throw new IOException("committed");
-
+
+ while (true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ if (!_state.compareAndSet(State.OPEN, State.PENDING))
+ continue;
+ break;
+ case CLOSED:
+ throw new EofException("Closed");
+ default:
+ throw new IllegalStateException();
+ }
+ break;
+ }
ByteBuffer buffer= _channel.useDirectBuffers()?httpContent.getDirectBuffer():null;
if (buffer == null)
buffer = httpContent.getIndirectBuffer();
-
+
if (buffer!=null)
{
sendContent(buffer,callback);
return;
}
-
+
ReadableByteChannel rbc=httpContent.getReadableByteChannel();
if (rbc!=null)
{
@@ -428,7 +583,7 @@
sendContent(rbc,callback);
return;
}
-
+
InputStream in = httpContent.getInputStream();
if ( in!=null )
{
@@ -446,7 +601,8 @@
public void setBufferSize(int size)
{
- this._bufferSize = size;
+ _bufferSize = size;
+ _commitSize = size;
}
public void resetBuffer()
@@ -454,24 +610,223 @@
if (BufferUtil.hasContent(_aggregate))
BufferUtil.clear(_aggregate);
}
-
-
- /* ------------------------------------------------------------ */
- /** An iterating callback that will take content from an
- * InputStream and write it to the associated {@link HttpChannel}.
- * A non direct buffer of size {@link HttpOutput#getBufferSize()} is used.
- * This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
- * be notified as each buffer is written and only once all the input is consumed will the
- * wrapped {@link Callback#succeeded()} method be called.
+
+ @Override
+ public void setWriteListener(WriteListener writeListener)
+ {
+ if (!_channel.getState().isAsync())
+ throw new IllegalStateException("!ASYNC");
+
+ if (_state.compareAndSet(State.OPEN, State.READY))
+ {
+ _writeListener = writeListener;
+ _channel.getState().onWritePossible();
+ }
+ else
+ throw new IllegalStateException();
+ }
+
+ /**
+ * @see javax.servlet.ServletOutputStream#isReady()
*/
- private class InputStreamWritingCB extends IteratingCallback
+ @Override
+ public boolean isReady()
+ {
+ while (true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ return true;
+ case ASYNC:
+ if (!_state.compareAndSet(State.ASYNC, State.READY))
+ continue;
+ return true;
+ case READY:
+ return true;
+ case PENDING:
+ if (!_state.compareAndSet(State.PENDING, State.UNREADY))
+ continue;
+ return false;
+ case UNREADY:
+ return false;
+ case CLOSED:
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ if(_onError!=null)
+ {
+ Throwable th=_onError;
+ _onError=null;
+ _writeListener.onError(th);
+ close();
+ }
+ if (_state.get()==State.READY)
+ {
+ try
+ {
+ _writeListener.onWritePossible();
+ }
+ catch (Throwable e)
+ {
+ _writeListener.onError(e);
+ close();
+ }
+ }
+ }
+
+ private class AsyncWrite extends AsyncFlush
+ {
+ private final ByteBuffer _buffer;
+ private final boolean _complete;
+ private final int _len;
+
+ public AsyncWrite(byte[] b, int off, int len, boolean complete)
+ {
+ _buffer=ByteBuffer.wrap(b, off, len);
+ _complete=complete;
+ _len=len;
+ }
+
+ public AsyncWrite(ByteBuffer buffer, boolean complete)
+ {
+ _buffer=buffer;
+ _complete=complete;
+ _len=buffer.remaining();
+ }
+
+ @Override
+ protected boolean process()
+ {
+ // flush any content from the aggregate
+ if (BufferUtil.hasContent(_aggregate))
+ {
+ _channel.write(_aggregate, _complete && _len==0, this);
+ return false;
+ }
+
+ if (!_complete && _len<BufferUtil.space(_aggregate) && _len<_commitSize)
+ {
+ BufferUtil.put(_buffer,_aggregate);
+ }
+ else if (_len>0 && !_flushed)
+ {
+ _flushed=true;
+ _channel.write(_buffer, _complete, this);
+ return false;
+ }
+ else if (_len==0 && !_flushed)
+ {
+ _flushed=true;
+ _channel.write(BufferUtil.EMPTY_BUFFER, _complete, this);
+ return false;
+ }
+
+ if (_complete)
+ closed();
+ return true;
+ }
+ }
+
+ private class AsyncFlush extends IteratingCallback
+ {
+ protected boolean _flushed;
+
+ public AsyncFlush()
+ {
+ }
+
+ @Override
+ protected boolean process()
+ {
+ if (BufferUtil.hasContent(_aggregate))
+ {
+ _flushed=true;
+ _channel.write(_aggregate, false, this);
+ return false;
+ }
+
+ if (!_flushed)
+ {
+ _flushed=true;
+ _channel.write(BufferUtil.EMPTY_BUFFER,false,this);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ protected void completed()
+ {
+ try
+ {
+ while(true)
+ {
+ State last=_state.get();
+ switch(last)
+ {
+ case PENDING:
+ if (!_state.compareAndSet(State.PENDING, State.ASYNC))
+ continue;
+ break;
+
+ case UNREADY:
+ if (!_state.compareAndSet(State.UNREADY, State.READY))
+ continue;
+ _channel.getState().onWritePossible();
+ break;
+
+ case CLOSED:
+ _onError=new EofException("Closed");
+ break;
+
+ default:
+ throw new IllegalStateException();
+ }
+ break;
+ }
+ }
+ catch (Exception e)
+ {
+ _onError=e;
+ _channel.getState().onWritePossible();
+ }
+ }
+
+ @Override
+ public void failed(Throwable e)
+ {
+ super.failed(e);
+ _onError=e;
+ _channel.getState().onWritePossible();
+ }
+
+
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** An iterating callback that will take content from an
+ * InputStream and write it to the associated {@link HttpChannel}.
+ * A non direct buffer of size {@link HttpOutput#getBufferSize()} is used.
+ * This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
+ * be notified as each buffer is written and only once all the input is consumed will the
+ * wrapped {@link Callback#succeeded()} method be called.
+ */
+ private class InputStreamWritingCB extends IteratingNestedCallback
{
private final InputStream _in;
private final ByteBuffer _buffer;
private boolean _eof;
-
+
public InputStreamWritingCB(InputStream in, Callback callback)
- {
+ {
super(callback);
_in=in;
_buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false);
@@ -506,6 +861,7 @@
_buffer.position(0);
_buffer.limit(len);
_channel.write(_buffer,_eof,this);
+
return false;
}
@@ -523,26 +879,26 @@
LOG.ignore(e);
}
}
-
+
}
/* ------------------------------------------------------------ */
- /** An iterating callback that will take content from a
+ /** An iterating callback that will take content from a
* ReadableByteChannel and write it to the {@link HttpChannel}.
* A {@link ByteBuffer} of size {@link HttpOutput#getBufferSize()} is used that will be direct if
* {@link HttpChannel#useDirectBuffers()} is true.
* This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
- * be notified as each buffer is written and only once all the input is consumed will the
- * wrapped {@link Callback#succeeded()} method be called.
+ * be notified as each buffer is written and only once all the input is consumed will the
+ * wrapped {@link Callback#succeeded()} method be called.
*/
- private class ReadableByteChannelWritingCB extends IteratingCallback
+ private class ReadableByteChannelWritingCB extends IteratingNestedCallback
{
private final ReadableByteChannel _in;
private final ByteBuffer _buffer;
private boolean _eof;
-
+
public ReadableByteChannelWritingCB(ReadableByteChannel in, Callback callback)
- {
+ {
super(callback);
_in=in;
_buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers());
@@ -565,10 +921,11 @@
_buffer.clear();
while (_buffer.hasRemaining() && !_eof)
_eof = (_in.read(_buffer)) < 0;
-
+
// write what we have
_buffer.flip();
_channel.write(_buffer,_eof,this);
+
return false;
}
@@ -587,4 +944,5 @@
}
}
}
+
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java
index ef62bdc..0ab12b2 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.server;
-import java.io.IOException;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpGenerator;
@@ -26,9 +25,6 @@
public interface HttpTransport
{
- @Deprecated
- void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent) throws IOException;
-
void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback);
void send(ByteBuffer content, boolean lastContent, Callback callback);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Iso88591HttpWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Iso88591HttpWriter.java
index 83335c4..0f4abd9 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Iso88591HttpWriter.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Iso88591HttpWriter.java
@@ -35,10 +35,10 @@
public void write (char[] s,int offset, int length) throws IOException
{
HttpOutput out = _out;
- if (length==0)
+ if (length==0 && out.isAllContentWritten())
{
- if (_out.isAllContentWritten())
- close();
+ close();
+ return;
}
if (length==1)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java
index ffdb5b9..3dc634b 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java
@@ -31,7 +31,6 @@
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Scheduler;
@@ -48,22 +47,22 @@
public LocalConnector(Server server)
{
- this(server, null, null, null, 0, new HttpConnectionFactory());
+ this(server, null, null, null, -1, new HttpConnectionFactory());
}
public LocalConnector(Server server, SslContextFactory sslContextFactory)
{
- this(server, null, null, null, 0,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+ this(server, null, null, null, -1,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
}
public LocalConnector(Server server, ConnectionFactory connectionFactory)
{
- this(server, null, null, null, 0, connectionFactory);
+ this(server, null, null, null, -1, connectionFactory);
}
public LocalConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
{
- this(server, null, null, null, 0, AbstractConnectionFactory.getFactories(sslContextFactory,connectionFactory));
+ this(server, null, null, null, -1, AbstractConnectionFactory.getFactories(sslContextFactory,connectionFactory));
}
@Override
@@ -173,7 +172,6 @@
Connection connection = getDefaultConnectionFactory().newConnection(this, endPoint);
endPoint.setConnection(connection);
-// connectionOpened(connection);
connection.onOpen();
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/QueuedHttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/QueuedHttpInput.java
new file mode 100644
index 0000000..19e0994
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/QueuedHttpInput.java
@@ -0,0 +1,161 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+import javax.servlet.ServletInputStream;
+
+import org.eclipse.jetty.util.ArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>{@link QueuedHttpInput} provides an implementation of {@link ServletInputStream} for {@link HttpChannel}.</p>
+ * <p>{@link QueuedHttpInput} holds a queue of items passed to it by calls to {@link #content(Object)}.</p>
+ * <p>{@link QueuedHttpInput} stores the items directly; if the items contain byte buffers, it does not copy them
+ * but simply holds references to the item, thus the caller must organize for those buffers to valid while
+ * held by this class.</p>
+ * <p>To assist the caller, subclasses may override methods {@link #onAsyncRead()},
+ * {@link #onContentConsumed(Object)} and {@link #onAllContentConsumed()} that can be implemented so that the
+ * caller will know when buffers are queued and consumed.</p>
+ */
+public abstract class QueuedHttpInput<T> extends HttpInput<T>
+{
+ private final static Logger LOG = Log.getLogger(QueuedHttpInput.class);
+
+ private final ArrayQueue<T> _inputQ = new ArrayQueue<>(lock());
+
+ public QueuedHttpInput()
+ {}
+
+ public void recycle()
+ {
+ synchronized (lock())
+ {
+ T item = _inputQ.peekUnsafe();
+ while (item != null)
+ {
+ _inputQ.pollUnsafe();
+ onContentConsumed(item);
+
+ item = _inputQ.peekUnsafe();
+ if (item == null)
+ onAllContentConsumed();
+ }
+ super.recycle();
+ }
+ }
+
+ @Override
+ protected T nextContent()
+ {
+ T item = _inputQ.peekUnsafe();
+
+ // Skip empty items at the head of the queue
+ while (item != null && remaining(item) == 0)
+ {
+ _inputQ.pollUnsafe();
+ onContentConsumed(item);
+ LOG.debug("{} consumed {}", this, item);
+ item = _inputQ.peekUnsafe();
+
+ // If that was the last item then notify
+ if (item==null)
+ onAllContentConsumed();
+ }
+ return item;
+ }
+
+ protected abstract void onContentConsumed(T item);
+
+ protected void blockForContent() throws IOException
+ {
+ synchronized (lock())
+ {
+ while (_inputQ.isEmpty() && !_state.isEOF())
+ {
+ try
+ {
+ LOG.debug("{} waiting for content", this);
+ lock().wait();
+ }
+ catch (InterruptedException e)
+ {
+ throw (IOException)new InterruptedIOException().initCause(e);
+ }
+ }
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Called by this HttpInput to signal all available content has been consumed
+ */
+ protected void onAllContentConsumed()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add some content to the input stream
+ * @param item
+ */
+ public void content(T item)
+ {
+ // The buffer is not copied here. This relies on the caller not recycling the buffer
+ // until the it is consumed. The onContentConsumed and onAllContentConsumed() callbacks are
+ // the signals to the caller that the buffers can be recycled.
+
+ synchronized (lock())
+ {
+ boolean empty=_inputQ.isEmpty();
+
+ _inputQ.add(item);
+
+ if (empty)
+ {
+ if (!onAsyncRead())
+ lock().notify();
+ }
+
+ LOG.debug("{} queued {}", this, item);
+ }
+ }
+
+
+ public void earlyEOF()
+ {
+ synchronized (lock())
+ {
+ super.earlyEOF();
+ lock().notify();
+ }
+ }
+
+ public void messageComplete()
+ {
+ synchronized (lock())
+ {
+ super.messageComplete();
+ lock().notify();
+ }
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
index a963069..24677ed 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
@@ -40,6 +40,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+
import javax.servlet.AsyncContext;
import javax.servlet.AsyncListener;
import javax.servlet.DispatcherType;
@@ -58,6 +59,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
import javax.servlet.http.Part;
import org.eclipse.jetty.http.HttpCookie;
@@ -409,12 +411,32 @@
}
/* ------------------------------------------------------------ */
- /*
+ /**
+ * Get Request Attribute.
+ * <p>Also supports jetty specific attributes to gain access to Jetty APIs:
+ * <dl>
+ * <dt>org.eclipse.jetty.server.Server</dt><dd>The Jetty Server instance</dd>
+ * <dt>org.eclipse.jetty.server.HttpChannel</dt><dd>The HttpChannel for this request</dd>
+ * <dt>org.eclipse.jetty.server.HttpConnection</dt><dd>The HttpConnection or null if another transport is used</dd>
+ * </dl>
+ * While these attributes may look like security problems, they are exposing nothing that is not already
+ * available via reflection from a Request instance.
+ * </p>
* @see javax.servlet.ServletRequest#getAttribute(java.lang.String)
*/
@Override
public Object getAttribute(String name)
{
+ if (name.startsWith("org.eclipse.jetty"))
+ {
+ if ("org.eclipse.jetty.server.Server".equals(name))
+ return _channel.getServer();
+ if ("org.eclipse.jetty.server.HttpChannel".equals(name))
+ return _channel;
+ if ("org.eclipse.jetty.server.HttpConnection".equals(name) &&
+ _channel.getHttpTransport() instanceof HttpConnection)
+ return _channel.getHttpTransport();
+ }
return (_attributes == null)?null:_attributes.getAttribute(name);
}
@@ -498,6 +520,22 @@
/* ------------------------------------------------------------ */
/*
+ * @see javax.servlet.ServletRequest.getContentLengthLong()
+ */
+ @Override
+ public long getContentLengthLong()
+ {
+ return _fields.getLongField(HttpHeader.CONTENT_LENGTH.toString());
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getContentRead()
+ {
+ return _input.getContentRead();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
* @see javax.servlet.ServletRequest#getContentType()
*/
@Override
@@ -533,7 +571,12 @@
public Cookie[] getCookies()
{
if (_cookiesExtracted)
- return _cookies == null?null:_cookies.getCookies();
+ {
+ if (_cookies == null || _cookies.getCookies().length == 0)
+ return null;
+
+ return _cookies.getCookies();
+ }
_cookiesExtracted = true;
@@ -552,7 +595,11 @@
}
}
- return _cookies == null?null:_cookies.getCookies();
+ //Javadoc for Request.getCookies() stipulates null for no cookies
+ if (_cookies == null || _cookies.getCookies().length == 0)
+ return null;
+
+ return _cookies.getCookies();
}
/* ------------------------------------------------------------ */
@@ -1613,9 +1660,7 @@
/* ------------------------------------------------------------ */
/*
* Set a request attribute. if the attribute name is "org.eclipse.jetty.server.server.Request.queryEncoding" then the value is also passed in a call to
- * {@link #setQueryEncoding}. <p> if the attribute name is "org.eclipse.jetty.server.server.ResponseBuffer", then the response buffer is flushed with @{link
- * #flushResponseBuffer} <p> if the attribute name is "org.eclipse.jetty.io.EndPoint.maxIdleTime", then the value is passed to the associated {@link
- * EndPoint#setIdleTimeout}.
+ * {@link #setQueryEncoding}.
*
* @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object)
*/
@@ -1624,35 +1669,11 @@
{
Object old_value = _attributes == null?null:_attributes.getAttribute(name);
- if (name.startsWith("org.eclipse.jetty."))
- {
- if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name))
- setQueryEncoding(value == null?null:value.toString());
- else if ("org.eclipse.jetty.server.sendContent".equals(name))
- {
- try
- {
- ((HttpOutput)getServletResponse().getOutputStream()).sendContent(value);
- }
- catch (IOException e)
- {
- throw new RuntimeException(e);
- }
- }
- else if ("org.eclipse.jetty.server.ResponseBuffer".equals(name))
- {
- try
- {
- throw new IOException("not implemented");
- //((HttpChannel.Output)getServletResponse().getOutputStream()).sendResponse(byteBuffer);
- }
- catch (IOException e)
- {
- throw new RuntimeException(e);
- }
- }
- }
-
+ if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name))
+ setQueryEncoding(value == null?null:value.toString());
+ else if ("org.eclipse.jetty.server.sendContent".equals(name))
+ LOG.warn("Deprecated: org.eclipse.jetty.server.sendContent");
+
if (_attributes == null)
_attributes = new AttributesMap();
_attributes.setAttribute(name,value);
@@ -2198,4 +2219,32 @@
setParameters(parameters);
setQueryString(query);
}
+
+
+
+ /**
+ * @see javax.servlet.http.HttpServletRequest#upgrade(java.lang.Class)
+ */
+ @Override
+ public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
+ {
+ if (getContext() == null)
+ throw new ServletException ("Unable to instantiate "+handlerClass);
+
+ try
+ {
+ //Instantiate an instance and inject it
+ T h = getContext().createInstance(handlerClass);
+
+ //TODO handle the rest of the upgrade process
+
+ return h;
+ }
+ catch (Exception e)
+ {
+ if (e instanceof ServletException)
+ throw (ServletException)e;
+ throw new ServletException(e);
+ }
+ }
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
index c41ee2b..fb7cf4f 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
@@ -31,9 +31,9 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpContent.ResourceAsHttpContent;
-import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
@@ -284,26 +284,9 @@
{
try
{
- int len=(int)resource.length();
- if (len<0)
- {
- LOG.warn("invalid resource: "+String.valueOf(resource)+" "+len);
- return null;
- }
- ByteBuffer buffer = BufferUtil.allocate(len);
- int pos=BufferUtil.flipToFill(buffer);
- if (resource.getFile()!=null)
- BufferUtil.readFrom(resource.getFile(),buffer);
- else
- {
- InputStream is = resource.getInputStream();
- BufferUtil.readFrom(is,len,buffer);
- is.close();
- }
- BufferUtil.flipToFlush(buffer,pos);
- return buffer;
+ return BufferUtil.toBuffer(resource,true);
}
- catch(IOException e)
+ catch(IOException|IllegalArgumentException e)
{
LOG.warn(e);
return null;
@@ -316,30 +299,11 @@
try
{
if (_useFileMappedBuffer && resource.getFile()!=null)
- return BufferUtil.toBuffer(resource.getFile());
+ return BufferUtil.toMappedBuffer(resource.getFile());
- int len=(int)resource.length();
- if (len<0)
- {
- LOG.warn("invalid resource: "+String.valueOf(resource)+" "+len);
- return null;
- }
- ByteBuffer buffer = BufferUtil.allocateDirect(len);
-
- int pos=BufferUtil.flipToFill(buffer);
- if (resource.getFile()!=null)
- BufferUtil.readFrom(resource.getFile(),buffer);
- else
- {
- InputStream is = resource.getInputStream();
- BufferUtil.readFrom(is,len,buffer);
- is.close();
- }
- BufferUtil.flipToFlush(buffer,pos);
-
- return buffer;
+ return BufferUtil.toBuffer(resource,true);
}
- catch(IOException e)
+ catch(IOException|IllegalArgumentException e)
{
LOG.warn(e);
return null;
@@ -381,7 +345,7 @@
_contentType=(mimeType==null?null:BufferUtil.toBuffer(mimeType));
boolean exists=resource.exists();
_lastModified=exists?resource.lastModified():-1;
- _lastModifiedBytes=_lastModified<0?null:BufferUtil.toBuffer(HttpFields.formatDate(_lastModified));
+ _lastModifiedBytes=_lastModified<0?null:BufferUtil.toBuffer(DateGenerator.formatDate(_lastModified));
_length=exists?(int)resource.length():0;
_cachedSize.addAndGet(_length);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
index ffd49cf..490d886 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
@@ -18,24 +18,26 @@
package org.eclipse.jetty.server;
+import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted;
+
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.channels.IllegalSelectorException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.EnumSet;
import java.util.Enumeration;
+import java.util.Iterator;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletOutputStream;
-import javax.servlet.SessionTrackingMode;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpField;
@@ -44,6 +46,7 @@
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
@@ -53,6 +56,7 @@
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
@@ -63,7 +67,19 @@
*/
public class Response implements HttpServletResponse
{
- private static final Logger LOG = Log.getLogger(Response.class);
+ private static final Logger LOG = Log.getLogger(Response.class);
+ private static final String __COOKIE_DELIM="\",;\\ \t";
+ private final static String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
+
+ // Cookie building buffer. Reduce garbage for cookie using applications
+ private static final ThreadLocal<StringBuilder> __cookieBuilder = new ThreadLocal<StringBuilder>()
+ {
+ @Override
+ protected StringBuilder initialValue()
+ {
+ return new StringBuilder(128);
+ }
+ };
/* ------------------------------------------------------------ */
public static Response getResponse(HttpServletResponse response)
@@ -101,6 +117,7 @@
private Locale _locale;
private MimeTypes.Type _mimeType;
private String _characterEncoding;
+ private boolean _explicitEncoding;
private String _contentType;
private OutputType _outputType = OutputType.NONE;
private ResponseWriter _writer;
@@ -130,6 +147,7 @@
_contentLength = -1;
_out.reset();
_fields.clear();
+ _explicitEncoding=false;
}
public void setHeaders(HttpContent httpContent)
@@ -175,12 +193,25 @@
public void included()
{
_include.decrementAndGet();
+ if (_outputType == OutputType.WRITER)
+ {
+ _writer.reopen();
+ }
_out.reopen();
}
public void addCookie(HttpCookie cookie)
{
- _fields.addSetCookie(cookie);
+ addSetCookie(
+ cookie.getName(),
+ cookie.getValue(),
+ cookie.getDomain(),
+ cookie.getPath(),
+ cookie.getMaxAge(),
+ cookie.getComment(),
+ cookie.isSecure(),
+ cookie.isHttpOnly(),
+ cookie.getVersion());;
}
@Override
@@ -200,7 +231,7 @@
comment = null;
}
}
- _fields.addSetCookie(cookie.getName(),
+ addSetCookie(cookie.getName(),
cookie.getValue(),
cookie.getDomain(),
cookie.getPath(),
@@ -211,6 +242,176 @@
cookie.getVersion());
}
+
+ /**
+ * Format a set cookie value
+ *
+ * @param name the name
+ * @param value the value
+ * @param domain the domain
+ * @param path the path
+ * @param maxAge the maximum age
+ * @param comment the comment (only present on versions > 0)
+ * @param isSecure true if secure cookie
+ * @param isHttpOnly true if for http only
+ * @param version version of cookie logic to use (0 == default behavior)
+ */
+ public void addSetCookie(
+ final String name,
+ final String value,
+ final String domain,
+ final String path,
+ final long maxAge,
+ final String comment,
+ final boolean isSecure,
+ final boolean isHttpOnly,
+ int version)
+ {
+ // Check arguments
+ if (name == null || name.length() == 0)
+ throw new IllegalArgumentException("Bad cookie name");
+
+ // Format value and params
+ StringBuilder buf = __cookieBuilder.get();
+ buf.setLength(0);
+
+ // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
+ boolean quote_name=isQuoteNeededForCookie(name);
+ quoteOnlyOrAppend(buf,name,quote_name);
+
+ buf.append('=');
+
+ // Remember name= part to look for other matching set-cookie
+ String name_equals=buf.toString();
+
+ // Append the value
+ boolean quote_value=isQuoteNeededForCookie(value);
+ quoteOnlyOrAppend(buf,value,quote_value);
+
+ // Look for domain and path fields and check if they need to be quoted
+ boolean has_domain = domain!=null && domain.length()>0;
+ boolean quote_domain = has_domain && isQuoteNeededForCookie(domain);
+ boolean has_path = path!=null && path.length()>0;
+ boolean quote_path = has_path && isQuoteNeededForCookie(path);
+
+ // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
+ if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path || isQuoted(name) || isQuoted(value) || isQuoted(path) || isQuoted(domain)))
+ version=1;
+
+ // Append version
+ if (version==1)
+ buf.append (";Version=1");
+ else if (version>1)
+ buf.append (";Version=").append(version);
+
+ // Append path
+ if (has_path)
+ {
+ buf.append(";Path=");
+ quoteOnlyOrAppend(buf,path,quote_path);
+ }
+
+ // Append domain
+ if (has_domain)
+ {
+ buf.append(";Domain=");
+ quoteOnlyOrAppend(buf,domain,quote_domain);
+ }
+
+ // Handle max-age and/or expires
+ if (maxAge >= 0)
+ {
+ // Always use expires
+ // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
+ buf.append(";Expires=");
+ if (maxAge == 0)
+ buf.append(__01Jan1970_COOKIE);
+ else
+ DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
+
+ // for v1 cookies, also send max-age
+ if (version>=1)
+ {
+ buf.append(";Max-Age=");
+ buf.append(maxAge);
+ }
+ }
+
+ // add the other fields
+ if (isSecure)
+ buf.append(";Secure");
+ if (isHttpOnly)
+ buf.append(";HttpOnly");
+ if (comment != null)
+ {
+ buf.append(";Comment=");
+ quoteOnlyOrAppend(buf,comment,isQuoteNeededForCookie(comment));
+ }
+
+ // remove any existing set-cookie fields of same name
+ Iterator<HttpField> i=_fields.iterator();
+ while (i.hasNext())
+ {
+ HttpField field=i.next();
+ if (field.getHeader()==HttpHeader.SET_COOKIE)
+ {
+ String val = field.getValue();
+ if (val!=null && val.startsWith(name_equals))
+ {
+ //existing cookie has same name, does it also match domain and path?
+ if (((!has_domain && !val.contains("Domain")) || (has_domain && val.contains(domain))) &&
+ ((!has_path && !val.contains("Path")) || (has_path && val.contains(path))))
+ {
+ i.remove();
+ }
+ }
+ }
+ }
+
+ // add the set cookie
+ _fields.add(HttpHeader.SET_COOKIE.toString(), buf.toString());
+
+ // Expire responses with set-cookie headers so they do not get cached.
+ _fields.put(HttpHeader.EXPIRES.toString(), DateGenerator.__01Jan1970);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Does a cookie value need to be quoted?
+ * @param s value string
+ * @return true if quoted;
+ * @throws IllegalArgumentException If there a control characters in the string
+ */
+ private static boolean isQuoteNeededForCookie(String s)
+ {
+ if (s==null || s.length()==0)
+ return true;
+
+ if (QuotedStringTokenizer.isQuoted(s))
+ return false;
+
+ for (int i=0;i<s.length();i++)
+ {
+ char c = s.charAt(i);
+ if (__COOKIE_DELIM.indexOf(c)>=0)
+ return true;
+
+ if (c<0x20 || c>=0x7f)
+ throw new IllegalArgumentException("Illegal character in cookie value");
+ }
+
+ return false;
+ }
+
+
+ private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
+ {
+ if (quote)
+ QuotedStringTokenizer.quoteOnly(buf,s);
+ else
+ buf.append(s);
+ }
+
@Override
public boolean containsHeader(String name)
{
@@ -392,43 +593,45 @@
{
setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString());
- ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);
- if (message != null)
+ try (ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);)
{
- message= StringUtil.replace(message, "&", "&");
- message= StringUtil.replace(message, "<", "<");
- message= StringUtil.replace(message, ">", ">");
- }
- String uri= request.getRequestURI();
- if (uri!=null)
- {
- uri= StringUtil.replace(uri, "&", "&");
- uri= StringUtil.replace(uri, "<", "<");
- uri= StringUtil.replace(uri, ">", ">");
- }
+ if (message != null)
+ {
+ message= StringUtil.replace(message, "&", "&");
+ message= StringUtil.replace(message, "<", "<");
+ message= StringUtil.replace(message, ">", ">");
+ }
+ String uri= request.getRequestURI();
+ if (uri!=null)
+ {
+ uri= StringUtil.replace(uri, "&", "&");
+ uri= StringUtil.replace(uri, "<", "<");
+ uri= StringUtil.replace(uri, ">", ">");
+ }
- writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
- writer.write("<title>Error ");
- writer.write(Integer.toString(code));
- writer.write(' ');
- if (message==null)
- writer.write(message);
- writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
- writer.write(Integer.toString(code));
- writer.write("</h2>\n<p>Problem accessing ");
- writer.write(uri);
- writer.write(". Reason:\n<pre> ");
- writer.write(message);
- writer.write("</pre>");
- writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
- writer.write("\n</body>\n</html>\n");
+ writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
+ writer.write("<title>Error ");
+ writer.write(Integer.toString(code));
+ writer.write(' ');
+ if (message==null)
+ writer.write(message);
+ writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
+ writer.write(Integer.toString(code));
+ writer.write("</h2>\n<p>Problem accessing ");
+ writer.write(uri);
+ writer.write(". Reason:\n<pre> ");
+ writer.write(message);
+ writer.write("</pre>");
+ writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
+ writer.write("\n</body>\n</html>\n");
- writer.flush();
- setContentLength(writer.size());
- try (ServletOutputStream outputStream = getOutputStream())
- {
- writer.writeTo(outputStream);
- writer.destroy();
+ writer.flush();
+ setContentLength(writer.size());
+ try (ServletOutputStream outputStream = getOutputStream())
+ {
+ writer.writeTo(outputStream);
+ writer.destroy();
+ }
}
}
}
@@ -459,10 +662,18 @@
_channel.sendResponse(HttpGenerator.PROGRESS_102_INFO, null, true);
}
}
-
- @Override
- public void sendRedirect(String location) throws IOException
+
+ /**
+ * Sends a response with one of the 300 series redirection codes.
+ * @param code
+ * @param location
+ * @throws IOException
+ */
+ public void sendRedirect(int code, String location) throws IOException
{
+ if ((code < HttpServletResponse.SC_MULTIPLE_CHOICES) || (code >= HttpServletResponse.SC_BAD_REQUEST))
+ throw new IllegalArgumentException("Not a 3xx redirect code");
+
if (isIncluding())
return;
@@ -472,7 +683,15 @@
if (!URIUtil.hasScheme(location))
{
StringBuilder buf = _channel.getRequest().getRootURL();
- if (location.startsWith("/"))
+
+ if (location.startsWith("//"))
+ {
+ buf.delete(0, buf.length());
+ buf.append(_channel.getRequest().getScheme());
+ buf.append(":");
+ buf.append(location);
+ }
+ else if (location.startsWith("/"))
buf.append(location);
else
{
@@ -520,11 +739,17 @@
resetBuffer();
setHeader(HttpHeader.LOCATION, location);
- setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+ setStatus(code);
closeOutput();
}
@Override
+ public void sendRedirect(String location) throws IOException
+ {
+ sendRedirect(HttpServletResponse.SC_MOVED_TEMPORARILY, location);
+ }
+
+ @Override
public void setDateHeader(String name, long date)
{
if (!isIncluding())
@@ -601,7 +826,7 @@
public Collection<String> getHeaders(String name)
{
final HttpFields fields = _fields;
- Collection<String> i = fields.getValuesCollection(name);
+ Collection<String> i = fields.getValuesList(name);
if (i == null)
return Collections.emptyList();
return i;
@@ -728,7 +953,7 @@
encoding = MimeTypes.inferCharsetFromContentType(_contentType);
if (encoding == null)
encoding = StringUtil.__ISO_8859_1;
- setCharacterEncoding(encoding);
+ setCharacterEncoding(encoding,false);
}
if (_writer != null && _writer.isFor(encoding))
@@ -792,6 +1017,8 @@
{
case WRITER:
_writer.close();
+ if (!_out.isClosed())
+ _out.close();
break;
case STREAM:
getOutputStream().close();
@@ -816,10 +1043,21 @@
_contentLength = len;
_fields.putLongField(HttpHeader.CONTENT_LENGTH.toString(), len);
}
+
+ @Override
+ public void setContentLengthLong(long length)
+ {
+ setLongContentLength(length);
+ }
@Override
public void setCharacterEncoding(String encoding)
{
+ setCharacterEncoding(encoding,true);
+ }
+
+ private void setCharacterEncoding(String encoding, boolean explicit)
+ {
if (isIncluding())
return;
@@ -827,6 +1065,8 @@
{
if (encoding == null)
{
+ _explicitEncoding=false;
+
// Clear any encoding.
if (_characterEncoding != null)
{
@@ -834,7 +1074,7 @@
if (_contentType != null)
{
_contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
- HttpField field = HttpField.CONTENT_TYPE.get(_contentType);
+ HttpField field = HttpParser.CONTENT_TYPE.get(_contentType);
if (field!=null)
_fields.put(field);
else
@@ -845,11 +1085,12 @@
else
{
// No, so just add this one to the mimetype
+ _explicitEncoding=explicit;
_characterEncoding = StringUtil.normalizeCharset(encoding);
if (_contentType != null)
{
_contentType = MimeTypes.getContentTypeWithoutCharset(_contentType) + ";charset=" + _characterEncoding;
- HttpField field = HttpField.CONTENT_TYPE.get(_contentType);
+ HttpField field = HttpParser.CONTENT_TYPE.get(_contentType);
if (field!=null)
_fields.put(field);
else
@@ -905,9 +1146,10 @@
else
{
_characterEncoding = charset;
+ _explicitEncoding = true;
}
- HttpField field = HttpField.CONTENT_TYPE.get(_contentType);
+ HttpField field = HttpParser.CONTENT_TYPE.get(_contentType);
if (field!=null)
_fields.put(field);
else
@@ -1045,8 +1287,8 @@
String charset = _channel.getRequest().getContext().getContextHandler().getLocaleEncoding(locale);
- if (charset != null && charset.length() > 0 && _characterEncoding == null)
- setCharacterEncoding(charset);
+ if (charset != null && charset.length() > 0 && !_explicitEncoding)
+ setCharacterEncoding(charset,false);
}
@Override
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
index 0fbc89f..f1ca0a2 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
@@ -28,17 +28,17 @@
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
-import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.http.HttpField;
-import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
@@ -48,7 +48,6 @@
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap;
-import org.eclipse.jetty.util.DateCache;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.URIUtil;
@@ -62,6 +61,7 @@
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ShutdownThread;
import org.eclipse.jetty.util.thread.ThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
/* ------------------------------------------------------------ */
/** Jetty HTTP Servlet Server.
@@ -82,9 +82,9 @@
private boolean _stopAtShutdown;
private boolean _dumpAfterStart=false;
private boolean _dumpBeforeStop=false;
- private HttpField _dateField;
-
-
+
+ private volatile DateField _dateField;
+
/* ------------------------------------------------------------ */
public Server()
{
@@ -263,11 +263,27 @@
_dumpBeforeStop = dumpBeforeStop;
}
-
/* ------------------------------------------------------------ */
public HttpField getDateField()
{
- return _dateField;
+ long now=System.currentTimeMillis();
+ long seconds = now/1000;
+ DateField df = _dateField;
+
+ if (df==null || df._seconds!=seconds)
+ {
+ synchronized (this) // Trade some contention for less garbage
+ {
+ df = _dateField;
+ if (df==null || df._seconds!=seconds)
+ {
+ HttpField field=new HttpGenerator.CachedHttpField(HttpHeader.DATE,DateGenerator.formatDate(now));
+ _dateField=new DateField(seconds,field);
+ return field;
+ }
+ }
+ }
+ return df._dateField;
}
/* ------------------------------------------------------------ */
@@ -285,6 +301,24 @@
HttpGenerator.setJettyVersion(HttpConfiguration.SERVER_VERSION);
MultiException mex=new MultiException();
+ // check size of thread pool
+ SizedThreadPool pool = getBean(SizedThreadPool.class);
+ int max=pool==null?-1:pool.getMaxThreads();
+ int needed=1;
+ if (mex.size()==0)
+ {
+ for (Connector connector : _connectors)
+ {
+ if (connector instanceof AbstractConnector)
+ needed+=((AbstractConnector)connector).getAcceptors();
+ if (connector instanceof ServerConnector)
+ needed+=((ServerConnector)connector).getSelectorManager().getSelectorCount();
+ }
+ }
+
+ if (max>0 && needed>max)
+ throw new IllegalStateException("Insufficient max threads in ThreadPool: max="+max+" < needed="+needed);
+
try
{
super.doStart();
@@ -294,43 +328,22 @@
mex.add(e);
}
- if (mex.size()==0)
+ // start connectors last
+ for (Connector connector : _connectors)
{
- for (Connector _connector : _connectors)
+ try
+ {
+ connector.start();
+ }
+ catch(Throwable e)
{
- try
- {
- _connector.start();
- }
- catch (Throwable e)
- {
- mex.add(e);
- }
+ mex.add(e);
}
}
-
+
if (isDumpAfterStart())
dumpStdErr();
-
- // use DateCache timer for Date field reformat
- final HttpFields.DateGenerator date = new HttpFields.DateGenerator();
- long now=System.currentTimeMillis();
- long tick=1000*((now/1000)+1)-now;
- _dateField=new HttpField.CachedHttpField(HttpHeader.DATE,date.formatDate(now));
- DateCache.getTimer().scheduleAtFixedRate(new TimerTask()
- {
- @Override
- public void run()
- {
- long now=System.currentTimeMillis();
- _dateField=new HttpField.CachedHttpField(HttpHeader.DATE,date.formatDate(now));
- if (!isRunning())
- this.cancel();
- }
- },tick,1000);
-
-
mex.ifExceptionThrow();
}
@@ -536,9 +549,9 @@
@Override
public void clearAttributes()
{
- Enumeration names = _attributes.getAttributeNames();
+ Enumeration<String> names = _attributes.getAttributeNames();
while (names.hasMoreElements())
- removeBean(_attributes.getAttribute((String)names.nextElement()));
+ removeBean(_attributes.getAttribute(names.nextElement()));
_attributes.clearAttributes();
}
@@ -647,4 +660,19 @@
{
System.err.println(getVersion());
}
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private static class DateField
+ {
+ final long _seconds;
+ final HttpField _dateField;
+ public DateField(long seconds, HttpField dateField)
+ {
+ super();
+ _seconds = seconds;
+ _dateField = dateField;
+ }
+
+ }
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
index 889e2ff..c6d3977 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
@@ -67,7 +67,7 @@
* which are implemented to each use a NIO {@link Selector} instance to asynchronously
* schedule a set of accepted connections. It is the selector thread that will call the
* {@link Callback} instances passed in the {@link EndPoint#fillInterested(Callback)} or
- * {@link EndPoint#write(Object, Callback, java.nio.ByteBuffer...)} methods. It is expected
+ * {@link EndPoint#write(Callback, java.nio.ByteBuffer...)} methods. It is expected
* that these callbacks may do some non-blocking IO work, but will always dispatch to the
* {@link Executor} service any blocking, long running or application tasks.
* <p>
@@ -96,7 +96,7 @@
public ServerConnector(
@Name("server") Server server)
{
- this(server,null,null,null,0,0,new HttpConnectionFactory());
+ this(server,null,null,null,-1,-1,new HttpConnectionFactory());
}
/* ------------------------------------------------------------ */
@@ -104,9 +104,10 @@
* <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
* @param server The {@link Server} this connector will accept connection for.
* @param acceptors
- * the number of acceptor threads to use, or 0 for a default value. Acceptors accept new TCP/IP connections.
+ * the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections. If 0, then
+ * the selector threads are used to accept connections.
* @param selectors
- * the number of selector threads, or 0 for a default value. Selectors notice and schedule established connection that can make IO progress.
+ * the number of selector threads, or -1 for a default value. Selectors notice and schedule established connection that can make IO progress.
*/
public ServerConnector(
@Name("server") Server server,
@@ -126,7 +127,7 @@
@Name("server") Server server,
@Name("factories") ConnectionFactory... factories)
{
- this(server,null,null,null,0,0,factories);
+ this(server,null,null,null,-1,-1,factories);
}
/* ------------------------------------------------------------ */
@@ -140,7 +141,7 @@
@Name("server") Server server,
@Name("sslContextFactory") SslContextFactory sslContextFactory)
{
- this(server,null,null,null,0,0,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+ this(server,null,null,null,-1,-1,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
}
/* ------------------------------------------------------------ */
@@ -150,9 +151,10 @@
* @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the
* list of HTTP Connection Factory.
* @param acceptors
- * the number of acceptor threads to use, or 0 for a default value. Acceptors accept new TCP/IP connections.
+ * the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections. If 0, then
+ * the selector threads are used to accept connections.
* @param selectors
- * the number of selector threads, or 0 for a default value. Selectors notice and schedule established connection that can make IO progress.
+ * the number of selector threads, or -1 for a default value. Selectors notice and schedule established connection that can make IO progress.
*/
public ServerConnector(
@Name("server") Server server,
@@ -175,7 +177,7 @@
@Name("sslContextFactory") SslContextFactory sslContextFactory,
@Name("factories") ConnectionFactory... factories)
{
- this(server,null,null,null,0,0,AbstractConnectionFactory.getFactories(sslContextFactory,factories));
+ this(server,null,null,null,-1,-1,AbstractConnectionFactory.getFactories(sslContextFactory,factories));
}
/** Generic Server Connection.
@@ -189,9 +191,10 @@
* @param bufferPool
* A ByteBuffer pool used to allocate buffers. If null then create a private pool with default configuration.
* @param acceptors
- * the number of acceptor threads to use, or 0 for a default value. Acceptors accept new TCP/IP connections.
+ * the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections. If 0, then
+ * the selector threads are used to accept connections.
* @param selectors
- * the number of selector threads, or 0 for a default value. Selectors notice and schedule established connection that can make IO progress.
+ * the number of selector threads, or -1 for a default value. Selectors notice and schedule established connection that can make IO progress.
* @param factories
* Zero or more {@link ConnectionFactory} instances used to create and configure connections.
*/
@@ -205,11 +208,23 @@
@Name("factories") ConnectionFactory... factories)
{
super(server,executor,scheduler,bufferPool,acceptors,factories);
- _manager = new ServerConnectorManager(getExecutor(), getScheduler(), selectors > 0 ? selectors : Runtime.getRuntime().availableProcessors());
+ _manager = new ServerConnectorManager(getExecutor(), getScheduler(), selectors >= 0 ? selectors : Runtime.getRuntime().availableProcessors());
addBean(_manager, true);
}
@Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+
+ if (getAcceptors()==0)
+ {
+ _acceptChannel.configureBlocking(false);
+ _manager.acceptor(_acceptChannel);
+ }
+ }
+
+ @Override
public boolean isOpen()
{
ServerSocketChannel channel = _acceptChannel;
@@ -319,12 +334,17 @@
if (serverChannel != null && serverChannel.isOpen())
{
SocketChannel channel = serverChannel.accept();
- channel.configureBlocking(false);
- Socket socket = channel.socket();
- configure(socket);
- _manager.accept(channel);
+ accepted(channel);
}
}
+
+ private void accepted(SocketChannel channel) throws IOException
+ {
+ channel.configureBlocking(false);
+ Socket socket = channel.socket();
+ configure(socket);
+ _manager.accept(channel);
+ }
protected void configure(Socket socket)
{
@@ -427,6 +447,12 @@
}
@Override
+ protected void accepted(SocketChannel channel) throws IOException
+ {
+ ServerConnector.this.accepted(channel);
+ }
+
+ @Override
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
{
return ServerConnector.this.newEndPoint(channel, selectSet, selectionKey);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServletRequestHttpWrapper.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServletRequestHttpWrapper.java
index b131448..b0c6270 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ServletRequestHttpWrapper.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServletRequestHttpWrapper.java
@@ -31,10 +31,15 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
import javax.servlet.http.Part;
+
/* ------------------------------------------------------------ */
-/** Class to tunnel a ServletRequest via a HttpServletRequest
+/**
+ * ServletRequestHttpWrapper
+ *
+ * Class to tunnel a ServletRequest via a HttpServletRequest
*/
public class ServletRequestHttpWrapper extends ServletRequestWrapper implements HttpServletRequest
{
@@ -209,4 +214,25 @@
}
+ /**
+ * @see javax.servlet.http.HttpServletRequest#changeSessionId()
+ */
+ @Override
+ public String changeSessionId()
+ {
+ // TODO 3.1 Auto-generated method stub
+ return null;
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServletRequest#upgrade(java.lang.Class)
+ */
+ @Override
+ public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
+ {
+ // TODO 3.1 Auto-generated method stub
+ return null;
+ }
+
+
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServletResponseHttpWrapper.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServletResponseHttpWrapper.java
index 1f1ffb4..4c62b9e 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ServletResponseHttpWrapper.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServletResponseHttpWrapper.java
@@ -28,7 +28,10 @@
/* ------------------------------------------------------------ */
-/** Wrapper to tunnel a ServletResponse via a HttpServletResponse
+/**
+ * ServletResponseHttpWrapper
+ *
+ * Wrapper to tunnel a ServletResponse via a HttpServletResponse
*/
public class ServletResponseHttpWrapper extends ServletResponseWrapper implements HttpServletResponse
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
index 5e590dd..96e504e 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
@@ -28,7 +28,6 @@
import java.nio.charset.StandardCharsets;
import java.util.Properties;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.thread.ShutdownThread;
/**
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Utf8HttpWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Utf8HttpWriter.java
index 251e3d0..6028109 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Utf8HttpWriter.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Utf8HttpWriter.java
@@ -43,10 +43,10 @@
public void write (char[] s,int offset, int length) throws IOException
{
HttpOutput out = _out;
- if (length==0)
+ if (length==0 && out.isAllContentWritten())
{
- if (_out.isAllContentWritten())
- close();
+ close();
+ return;
}
while (length > 0)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
index 5cdf1e1..eec4a8d 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
@@ -54,7 +54,7 @@
{
// we can use the real path method to check the symlinks resolve to the alias
URI real = file.toPath().toRealPath().toUri();
- if (real.equals(resource.getAlias().toURI()))
+ if (real.equals(resource.getAlias()))
{
LOG.debug("Allow symlink {} --> {}",resource,real);
return true;
@@ -77,7 +77,7 @@
d=link.toFile().getAbsoluteFile().getCanonicalFile();
}
}
- if (resource.getAlias().toURI().equals(d.toURI()))
+ if (resource.getAlias().equals(d.toURI()))
{
LOG.debug("Allow symlink {} --> {}",resource,d);
return true;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
index baed72b..16344c9 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
@@ -22,6 +22,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
+import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
@@ -96,6 +97,8 @@
* <p>
* The maximum size of a form that can be processed by this context is controlled by the system properties org.eclipse.jetty.server.Request.maxFormKeys
* and org.eclipse.jetty.server.Request.maxFormContentSize. These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)}
+ * <p>
+ * This servers executore is made available via a context attributed "org.eclipse.jetty.server.Executor".
*
* @org.apache.xbean.XBean description="Creates a basic HTTP context"
*/
@@ -104,11 +107,17 @@
{
public final static int SERVLET_MAJOR_VERSION=3;
public final static int SERVLET_MINOR_VERSION=0;
+ public static final Class[] SERVLET_LISTENER_TYPES = new Class[] {ServletContextListener.class,
+ ServletContextAttributeListener.class,
+ ServletRequestListener.class,
+ ServletRequestAttributeListener.class};
+
+ public static final int DEFAULT_LISTENER_TYPE_INDEX = 1;
+ public static final int EXTENDED_LISTENER_TYPE_INDEX = 0;
+
final private static String __unimplmented="Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler";
-
-
private static final Logger LOG = Log.getLogger(ContextHandler.class);
private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();
@@ -550,7 +559,7 @@
_requestListeners.clear();
_requestAttributeListeners.clear();
_eventListeners.clear();
-
+
if (eventListeners!=null)
for (EventListener listener : eventListeners)
addEventListener(listener);
@@ -628,7 +637,7 @@
return _programmaticListeners.contains(listener);
}
-
+
/* ------------------------------------------------------------ */
/**
@@ -712,6 +721,8 @@
Thread current_thread = null;
Context old_context = null;
+ _attributes.setAttribute("org.eclipse.jetty.server.Executor",getServer().getThreadPool());
+
try
{
// Set the classloader
@@ -828,7 +839,7 @@
for (int i = _contextListeners.size(); i-->0;)
callContextDestroyed(_contextListeners.get(i),event);
}
-
+
//retain only durable listeners
setEventListeners(_durableListeners.toArray(new EventListener[_durableListeners.size()]));
_durableListeners.clear();
@@ -968,7 +979,9 @@
if (old_context != _scontext)
{
// check the target.
- if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch) || (DispatcherType.ERROR.equals(dispatch) && baseRequest.getHttpChannelState().isExpired()))
+ if (DispatcherType.REQUEST.equals(dispatch) ||
+ DispatcherType.ASYNC.equals(dispatch) ||
+ DispatcherType.ERROR.equals(dispatch) && baseRequest.getHttpChannelState().isAsync())
{
if (_compactPath)
target = URIUtil.compactPath(target);
@@ -1318,13 +1331,13 @@
LOG.warn(this+" contextPath ends with /");
contextPath=contextPath.substring(0,contextPath.length()-1);
}
-
+
if (contextPath.length()==0)
{
LOG.warn("Empty contextPath");
contextPath="/";
}
-
+
_contextPath = contextPath;
if (getServer() != null && (getServer().isStarting() || getServer().isStarted()))
@@ -1651,6 +1664,15 @@
{
return Resource.newResource(url);
}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
+ */
+ public Resource newResource(URI uri) throws IOException
+ {
+ return Resource.newResource(uri);
+ }
/* ------------------------------------------------------------ */
/**
@@ -1752,6 +1774,8 @@
public class Context extends NoContext
{
protected boolean _enabled = true; //whether or not the dynamic API is enabled for callers
+ protected boolean _extendedListenerTypes = false;
+
/* ------------------------------------------------------------ */
protected Context()
@@ -2156,6 +2180,7 @@
try
{
Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className);
+ checkListener(clazz);
addListener(clazz);
}
catch (ClassNotFoundException e)
@@ -2169,6 +2194,9 @@
{
if (!_enabled)
throw new UnsupportedOperationException();
+
+ checkListener(t.getClass());
+
ContextHandler.this.addEventListener(t);
ContextHandler.this.addProgrammaticListener(t);
}
@@ -2179,6 +2207,8 @@
if (!_enabled)
throw new UnsupportedOperationException();
+ checkListener(listenerClass);
+
try
{
EventListener e = createListener(listenerClass);
@@ -2196,18 +2226,42 @@
{
try
{
- return clazz.newInstance();
+ return createInstance(clazz);
}
- catch (InstantiationException e)
- {
- throw new ServletException(e);
- }
- catch (IllegalAccessException e)
+ catch (Exception e)
{
throw new ServletException(e);
}
}
+
+ public void checkListener (Class<? extends EventListener> listener) throws IllegalStateException
+ {
+ boolean ok = false;
+ int startIndex = (isExtendedListenerTypes()?EXTENDED_LISTENER_TYPE_INDEX:DEFAULT_LISTENER_TYPE_INDEX);
+ for (int i=startIndex;i<SERVLET_LISTENER_TYPES.length;i++)
+ {
+ if (SERVLET_LISTENER_TYPES[i].isAssignableFrom(listener))
+ {
+ ok = true;
+ break;
+ }
+ }
+ if (!ok)
+ throw new IllegalArgumentException("Inappropriate listener class "+listener.getName());
+ }
+
+ public void setExtendedListenerTypes (boolean extended)
+ {
+ _extendedListenerTypes = extended;
+ }
+
+ public boolean isExtendedListenerTypes()
+ {
+ return _extendedListenerTypes;
+ }
+
+
@Override
public ClassLoader getClassLoader()
{
@@ -2245,6 +2299,14 @@
{
return _enabled;
}
+
+
+
+ public <T> T createInstance (Class<T> clazz) throws Exception
+ {
+ T o = clazz.newInstance();
+ return o;
+ }
}
@@ -2585,6 +2647,16 @@
{
LOG.warn(__unimplmented);
}
+
+ /**
+ * @see javax.servlet.ServletContext#getVirtualServerName()
+ */
+ @Override
+ public String getVirtualServerName()
+ {
+ // TODO 3.1 Auto-generated method stub
+ return null;
+ }
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
index 09ac1d4..b068926 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
@@ -19,7 +19,6 @@
package org.eclipse.jetty.server.handler;
import java.io.IOException;
-import java.util.Arrays;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java
index a758c70..83c5c48 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java
@@ -117,7 +117,7 @@
private void print(String name,String message)
{
long now=System.currentTimeMillis();
- final String d=_date.format(now);
+ final String d=_date.formatNow(now);
final int ms=(int)(now%1000);
_print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+message);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java
index 4ed0c6d..b580cfc 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java
@@ -23,7 +23,6 @@
import java.io.StringWriter;
import java.io.Writer;
-import javax.activation.MimeType;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java
new file mode 100644
index 0000000..309dae3
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java
@@ -0,0 +1,134 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+
+/**
+ * Handler to adjust the idle timeout of requests while dispatched.
+ * Can be applied in jetty.xml with
+ * <pre>
+ * <Get id='handler' name='Handler'/>
+ * <Set name='Handler'>
+ * <New id='idleTimeoutHandler' class='org.eclipse.jetty.server.handler.IdleTimeoutHandler'>
+ * <Set name='Handler'><Ref id='handler'/></Set>
+ * <Set name='IdleTimeoutMs'>5000</Set>
+ * </New>
+ * </Set>
+ * </pre>
+ */
+public class IdleTimeoutHandler extends HandlerWrapper
+{
+ private long _idleTimeoutMs = 1000;
+ private boolean _applyToAsync = false;
+
+ public boolean isApplyToAsync()
+ {
+ return _applyToAsync;
+ }
+
+ /**
+ * Should the adjusted idle time be maintained for asynchronous requests
+ * @param applyToAsync true if alternate idle timeout is applied to asynchronous requests
+ */
+ public void setApplyToAsync(boolean applyToAsync)
+ {
+ _applyToAsync = applyToAsync;
+ }
+
+ public long getIdleTimeoutMs()
+ {
+ return _idleTimeoutMs;
+ }
+
+ /**
+ * @param idleTimeoutMs The idle timeout in MS to apply while dispatched or async
+ */
+ public void setIdleTimeoutMs(long idleTimeoutMs)
+ {
+ this._idleTimeoutMs = idleTimeoutMs;
+ }
+
+
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ HttpConnection connection = HttpConnection.getCurrentConnection();
+ final EndPoint endp = connection==null?null:connection.getEndPoint();
+
+ final long idle_timeout;
+ if (endp==null)
+ idle_timeout=-1;
+ else
+ {
+ idle_timeout=endp.getIdleTimeout();
+ endp.setIdleTimeout(_idleTimeoutMs);
+ }
+
+ try
+ {
+ super.handle(target,baseRequest,request,response);
+ }
+ finally
+ {
+ if (endp!=null)
+ {
+ if (_applyToAsync && request.isAsyncStarted())
+ {
+ request.getAsyncContext().addListener(new AsyncListener()
+ {
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException
+ {
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+ endp.setIdleTimeout(idle_timeout);
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
+ {
+ endp.setIdleTimeout(idle_timeout);
+ }
+ });
+ }
+ else
+ endp.setIdleTimeout(idle_timeout);
+ }
+ }
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java
index 07b289d..f5cddba 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java
@@ -533,7 +533,7 @@
resource.length()>_minMemoryMappedContentLength &&
resource instanceof FileResource)
{
- ByteBuffer buffer = BufferUtil.toBuffer(resource.getFile());
+ ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
((HttpOutput)out).sendContent(buffer,callback);
}
else // Do a blocking write of a channel (if available) or input stream
@@ -553,7 +553,7 @@
resource.length()>_minMemoryMappedContentLength &&
resource instanceof FileResource)
{
- ByteBuffer buffer = BufferUtil.toBuffer(resource.getFile());
+ ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
((HttpOutput)out).sendContent(buffer);
}
else // Do a blocking write of a channel (if available) or input stream
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ScopedHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ScopedHandler.java
index 45e3d16..bba4ddf 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ScopedHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ScopedHandler.java
@@ -31,15 +31,18 @@
/** ScopedHandler.
*
* A ScopedHandler is a HandlerWrapper where the wrapped handlers
- * each define a scope. When {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
+ * each define a scope.
+ *
+ * <p>When {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
* is called on the first ScopedHandler in a chain of HandlerWrappers,
* the {@link #doScope(String, Request, HttpServletRequest, HttpServletResponse)} method is
* called on all contained ScopedHandlers, before the
* {@link #doHandle(String, Request, HttpServletRequest, HttpServletResponse)} method
- * is called on all contained handlers.
+ * is called on all contained handlers.</p>
*
* <p>For example if Scoped handlers A, B & C were chained together, then
- * the calling order would be:<pre>
+ * the calling order would be:</p>
+ * <pre>
* A.handle(...)
* A.doScope(...)
* B.doScope(...)
@@ -47,10 +50,11 @@
* A.doHandle(...)
* B.doHandle(...)
* C.doHandle(...)
- * <pre>
+ * </pre>
*
* <p>If non scoped handler X was in the chained A, B, X & C, then
- * the calling order would be:<pre>
+ * the calling order would be:</p>
+ * <pre>
* A.handle(...)
* A.doScope(...)
* B.doScope(...)
@@ -60,9 +64,10 @@
* X.handle(...)
* C.handle(...)
* C.doHandle(...)
- * <pre>
+ * </pre>
*
- * <p>A typical usage pattern is:<pre>
+ * <p>A typical usage pattern is:</p>
+ * <pre>
* private static class MyHandler extends ScopedHandler
* {
* public void doScope(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
index bc93a65..3fbbddd 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
@@ -98,8 +98,7 @@
updateResponse(request);
- if (!state.isDispatched())
- _asyncWaitStats.decrement();
+ _asyncWaitStats.decrement();
// If we have no more dispatches, should we signal shutdown?
if (d==0)
@@ -152,9 +151,7 @@
{
// resumed request
start = System.currentTimeMillis();
- _asyncWaitStats.decrement();
- if (state.isDispatched())
- _asyncDispatches.incrementAndGet();
+ _asyncDispatches.incrementAndGet();
}
try
@@ -172,8 +169,10 @@
if (state.isSuspended())
{
if (state.isInitial())
+ {
state.addListener(_onCompletion);
- _asyncWaitStats.increment();
+ _asyncWaitStats.increment();
+ }
}
else if (state.isInitial())
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java
index db6b465..d3fb5ab 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java
@@ -51,8 +51,8 @@
{
final static Logger LOG = SessionHandler.LOG;
public final static String SESSION_KNOWN_ONLY_TO_AUTHENTICATED="org.eclipse.jetty.security.sessionKnownOnlytoAuthenticated";
- private String _clusterId; // ID unique within cluster
- private String _nodeId; // ID unique within node
+ private String _clusterId; // ID without any node (ie "worker") id appended
+ private String _nodeId; // ID of session with node(ie "worker") id appended
private final AbstractSessionManager _manager;
private final Map<String,Object> _attributes=new HashMap<String, Object>();
private boolean _idChanged;
@@ -185,6 +185,7 @@
@Override
public long getCreationTime() throws IllegalStateException
{
+ checkValid();
return _created;
}
@@ -225,7 +226,6 @@
@Override
public int getMaxInactiveInterval()
{
- checkValid();
return (int)(_maxIdleMs/1000);
}
@@ -365,6 +365,7 @@
@Override
public void invalidate() throws IllegalStateException
{
+ checkValid();
// remove session from context and invalidate other sessions with same ID.
_manager.removeSession(this,true);
doInvalidate();
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java
index 4801d16..b13419b 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java
@@ -38,6 +38,7 @@
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionContext;
import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
import javax.servlet.http.HttpSessionListener;
import org.eclipse.jetty.http.HttpCookie;
@@ -107,6 +108,7 @@
protected final List<HttpSessionAttributeListener> _sessionAttributeListeners = new CopyOnWriteArrayList<HttpSessionAttributeListener>();
protected final List<HttpSessionListener> _sessionListeners= new CopyOnWriteArrayList<HttpSessionListener>();
+ protected final List<HttpSessionIdListener> _sessionIdListeners = new CopyOnWriteArrayList<HttpSessionIdListener>();
protected ClassLoader _loader;
protected ContextHandler.Context _context;
@@ -191,6 +193,8 @@
_sessionAttributeListeners.add((HttpSessionAttributeListener)listener);
if (listener instanceof HttpSessionListener)
_sessionListeners.add((HttpSessionListener)listener);
+ if (listener instanceof HttpSessionIdListener)
+ _sessionIdListeners.add((HttpSessionIdListener)listener);
}
/* ------------------------------------------------------------ */
@@ -198,6 +202,7 @@
{
_sessionAttributeListeners.clear();
_sessionListeners.clear();
+ _sessionIdListeners.clear();
}
/* ------------------------------------------------------------ */
@@ -411,7 +416,6 @@
/* ------------------------------------------------------------ */
/**
- * @return if true, session cookie will be marked as secure only iff
* HTTPS request. Can be overridden by setting SessionCookieConfig.setSecure(true),
* in which case the session cookie will be marked as secure on both HTTPS and HTTP.
*/
@@ -1006,6 +1010,29 @@
{
_checkingRemoteSessionIdEncoding=remote;
}
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Tell the HttpSessionIdListeners the id changed.
+ * NOTE: this method must be called LAST in subclass overrides, after the session has been updated
+ * with the new id.
+ * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+ */
+ @Override
+ public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+ {
+ if (!_sessionIdListeners.isEmpty())
+ {
+ AbstractSession session = getSession(newClusterId);
+ HttpSessionEvent event = new HttpSessionEvent(session);
+ for (HttpSessionIdListener l:_sessionIdListeners)
+ {
+ l.sessionIdChanged(event, oldClusterId);
+ }
+ }
+
+ }
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java
index 2f7e1b4..65bcf1f 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java
@@ -83,7 +83,7 @@
/* ------------------------------------------------------------ */
/**
- * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStart()
+ * @see AbstractSessionManager#doStart()
*/
@Override
public void doStart() throws Exception
@@ -116,7 +116,7 @@
/* ------------------------------------------------------------ */
/**
- * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStop()
+ * @see AbstractSessionManager#doStop()
*/
@Override
public void doStop() throws Exception
@@ -440,6 +440,8 @@
session.setNodeId(newNodeId);
session.save(); //save updated session: TODO consider only saving file if idled
sessions.put(newClusterId, session);
+
+ super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
}
catch (Exception e)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
index 2552055..b6057a2 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
@@ -21,9 +21,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.io.InputStream;
-import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
@@ -647,6 +645,8 @@
LOG.warn(e);
}
}
+
+ super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
}
@@ -939,7 +939,7 @@
/**
* Insert a session into the database.
*
- * @param data
+ * @param session
* @throws Exception
*/
protected void storeSession (Session session)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java
index 0c8df26..44de981 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java
@@ -29,6 +29,9 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.server.Request;
@@ -46,6 +49,11 @@
final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
public final static EnumSet<SessionTrackingMode> DEFAULT_TRACKING = EnumSet.of(SessionTrackingMode.COOKIE,SessionTrackingMode.URL);
+
+ public static final Class[] SESSION_LISTENER_TYPES = new Class[] {HttpSessionAttributeListener.class,
+ HttpSessionIdListener.class,
+ HttpSessionListener.class};
+
/* -------------------------------------------------------------- */
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java
index 3a81922..bdb3403 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java
@@ -34,6 +34,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
@@ -58,8 +59,9 @@
public void setUp() throws Exception
{
server = new Server();
- connector = new ServerConnector(server);
+ connector = new ServerConnector(server,null,null,new ArrayByteBufferPool(64,2048,64*1024),1,1,new HttpConnectionFactory());
connector.setIdleTimeout(10000);
+
server.addConnector(connector);
httpParser = new SimpleHttpParser();
((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java
index f0ca3ac..83b0727 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java
@@ -33,26 +33,18 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
-import org.hamcrest.Matchers;
import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Assert;
import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Ignore;
import org.junit.Test;
public class AsyncRequestReadTest
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java
index ec929a3..9522d3b 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.server;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.io.InputStream;
@@ -39,8 +39,8 @@
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
-import org.eclipse.jetty.toolchain.test.annotation.Stress;
import org.eclipse.jetty.toolchain.test.PropertyFlag;
+import org.eclipse.jetty.toolchain.test.annotation.Stress;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java
index ceabcf0..2881ba2 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java
@@ -39,6 +39,7 @@
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
@@ -50,7 +51,7 @@
{
super(HttpVersion.HTTP_1_1.asString());
}
-
+
@Slow
@Test
public void testOpenClose() throws Exception
@@ -60,6 +61,56 @@
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
+ throw new IllegalStateException();
+ }
+ });
+ server.start();
+
+ final AtomicInteger callbacks = new AtomicInteger();
+ final CountDownLatch openLatch = new CountDownLatch(1);
+ final CountDownLatch closeLatch = new CountDownLatch(1);
+ connector.addBean(new Connection.Listener.Adapter()
+ {
+ @Override
+ public void onOpened(Connection connection)
+ {
+ callbacks.incrementAndGet();
+ openLatch.countDown();
+ }
+
+ @Override
+ public void onClosed(Connection connection)
+ {
+ callbacks.incrementAndGet();
+ closeLatch.countDown();
+ }
+ });
+
+ try (Socket socket = new Socket("localhost", connector.getLocalPort());)
+ {
+ socket.setSoTimeout((int)connector.getIdleTimeout());
+
+ Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS));
+ socket.shutdownOutput();
+ Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
+ String response=IO.toString(socket.getInputStream());
+ Assert.assertEquals(0,response.length());
+
+ // Wait some time to see if the callbacks are called too many times
+ TimeUnit.MILLISECONDS.sleep(200);
+ Assert.assertEquals(2, callbacks.get());
+ }
+ }
+
+ @Slow
+ @Test
+ public void testOpenRequestClose() throws Exception
+ {
+ server.setHandler(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
baseRequest.setHandled(true);
}
});
@@ -68,7 +119,7 @@
final AtomicInteger callbacks = new AtomicInteger();
final CountDownLatch openLatch = new CountDownLatch(1);
final CountDownLatch closeLatch = new CountDownLatch(1);
- connector.addBean(new Connection.Listener.Empty()
+ connector.addBean(new Connection.Listener.Adapter()
{
@Override
public void onOpened(Connection connection)
@@ -88,7 +139,7 @@
Socket socket = new Socket("localhost", connector.getLocalPort());
socket.setSoTimeout((int)connector.getIdleTimeout());
OutputStream output = socket.getOutputStream();
- output.write(("" +
+ output.write((
"GET / HTTP/1.1\r\n" +
"Host: localhost:" + connector.getLocalPort() + "\r\n" +
"Connection: close\r\n" +
@@ -113,7 +164,7 @@
@Slow
@Test
- public void testSSLOpenClose() throws Exception
+ public void testSSLOpenRequestClose() throws Exception
{
SslContextFactory sslContextFactory = new SslContextFactory();
File keystore = MavenTestingUtils.getTestResourceFile("keystore");
@@ -139,7 +190,7 @@
final AtomicInteger callbacks = new AtomicInteger();
final CountDownLatch openLatch = new CountDownLatch(1);
final CountDownLatch closeLatch = new CountDownLatch(1);
- connector.addBean(new Connection.Listener.Empty()
+ connector.addBean(new Connection.Listener.Adapter()
{
@Override
public void onOpened(Connection connection)
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java b/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java
index 69720d9..fa25e95 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java
@@ -35,7 +35,6 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -179,7 +178,7 @@
}
writer.write("</pre>\n<h3>Attributes:</h3>\n<pre>");
- Enumeration attributes=request.getAttributeNames();
+ Enumeration<String> attributes=request.getAttributeNames();
if (attributes!=null && attributes.hasMoreElements())
{
while(attributes.hasMoreElements())
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java
index 37021b5..ed86509 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java
@@ -27,12 +27,9 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseRaceTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseRaceTest.java
deleted file mode 100644
index 4d3714b..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseRaceTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-import java.net.Socket;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.junit.Test;
-
-public class HalfCloseRaceTest
-{
- @Test
- public void testHalfCloseRace() throws Exception
- {
- Server server = new Server();
- ServerConnector connector = new ServerConnector(server);
- connector.setPort(0);
- connector.setIdleTimeout(500);
- server.addConnector(connector);
- TestHandler handler = new TestHandler();
- server.setHandler(handler);
-
- server.start();
-
- Socket client = new Socket("localhost",connector.getLocalPort());
-
- int in = client.getInputStream().read();
- assertEquals(-1,in);
-
- client.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes());
-
- Thread.sleep(200);
- assertEquals(0,handler.getHandled());
-
- }
-
- public static class TestHandler extends AbstractHandler
- {
- transient int handled;
-
- public TestHandler()
- {
- }
-
- @Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
- {
- baseRequest.setHandled(true);
- handled++;
- response.setContentType("text/html;charset=utf-8");
- response.setStatus(HttpServletResponse.SC_OK);
- response.getWriter().println("<h1>Test</h1>");
- }
-
- public int getHandled()
- {
- return handled;
- }
- }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseTest.java
new file mode 100644
index 0000000..69078c4
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseTest.java
@@ -0,0 +1,214 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.IO;
+import org.junit.Test;
+
+public class HalfCloseTest
+{
+ @Test
+ public void testHalfCloseRace() throws Exception
+ {
+ Server server = new Server();
+ ServerConnector connector = new ServerConnector(server,1,1);
+ connector.setPort(0);
+ connector.setIdleTimeout(500);
+ server.addConnector(connector);
+ TestHandler handler = new TestHandler();
+ server.setHandler(handler);
+
+ server.start();
+
+ try(Socket client = new Socket("localhost",connector.getLocalPort());)
+ {
+ int in = client.getInputStream().read();
+ assertEquals(-1,in);
+
+ client.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes());
+
+ Thread.sleep(200);
+ assertEquals(0,handler.getHandled());
+ }
+
+ }
+
+ @Test
+ public void testCompleteClose() throws Exception
+ {
+ Server server = new Server();
+ ServerConnector connector = new ServerConnector(server,1,1);
+ connector.setPort(0);
+ connector.setIdleTimeout(5000);
+ final AtomicInteger opened = new AtomicInteger(0);
+ final CountDownLatch closed = new CountDownLatch(1);
+ connector.addBean(new Connection.Listener()
+ {
+ @Override
+ public void onOpened(Connection connection)
+ {
+ opened.incrementAndGet();
+ }
+
+ @Override
+ public void onClosed(Connection connection)
+ {
+ closed.countDown();
+ }
+
+ });
+ server.addConnector(connector);
+ TestHandler handler = new TestHandler();
+ server.setHandler(handler);
+
+ server.start();
+
+ try(Socket client = new Socket("localhost",connector.getLocalPort());)
+ {
+ client.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes());
+ IO.toString(client.getInputStream());
+ assertEquals(1,handler.getHandled());
+ assertEquals(1,opened.get());
+ }
+ assertEquals(true,closed.await(1,TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testAsyncClose() throws Exception
+ {
+ Server server = new Server();
+ ServerConnector connector = new ServerConnector(server,1,1);
+ connector.setPort(0);
+ connector.setIdleTimeout(5000);
+ final AtomicInteger opened = new AtomicInteger(0);
+ final CountDownLatch closed = new CountDownLatch(1);
+ connector.addBean(new Connection.Listener()
+ {
+ @Override
+ public void onOpened(Connection connection)
+ {
+ opened.incrementAndGet();
+ }
+
+ @Override
+ public void onClosed(Connection connection)
+ {
+ closed.countDown();
+ }
+
+ });
+ server.addConnector(connector);
+ AsyncHandler handler = new AsyncHandler();
+ server.setHandler(handler);
+
+ server.start();
+
+ try(Socket client = new Socket("localhost",connector.getLocalPort());)
+ {
+ client.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes());
+ IO.toString(client.getInputStream());
+ assertEquals(1,handler.getHandled());
+ assertEquals(1,opened.get());
+ }
+ assertEquals(true,closed.await(1,TimeUnit.SECONDS));
+ }
+
+ public static class TestHandler extends AbstractHandler
+ {
+ transient int handled;
+
+ public TestHandler()
+ {
+ }
+
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ handled++;
+ response.setContentType("text/html;charset=utf-8");
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.getWriter().println("<h1>Test</h1>");
+ }
+
+ public int getHandled()
+ {
+ return handled;
+ }
+ }
+
+ public static class AsyncHandler extends AbstractHandler
+ {
+ transient int handled;
+
+ public AsyncHandler()
+ {
+ }
+
+ @Override
+ public void handle(String target, Request baseRequest, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ handled++;
+
+ final AsyncContext async = request.startAsync();
+ new Thread()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ response.setContentType("text/html;charset=utf-8");
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.getWriter().println("<h1>Test</h1>");
+ }
+ catch (Exception ex)
+ {
+ System.err.println(ex);
+ }
+ finally
+ {
+ async.complete();
+ }
+ }
+ }.start();
+ }
+
+ public int getHandled()
+ {
+ return handled;
+ }
+ }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HostHeaderCustomizerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HostHeaderCustomizerTest.java
index 56bd076..83f7a18 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HostHeaderCustomizerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HostHeaderCustomizerTest.java
@@ -24,6 +24,7 @@
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
index 1adaf4a..1a7f0d8 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
@@ -201,7 +201,20 @@
}
@Test
- public void testEmpty() throws Exception
+ public void testSimple() throws Exception
+ {
+ String response=connector.getResponses("GET /R1 HTTP/1.1\n"+
+ "Host: localhost\n"+
+ "Connection: close\n"+
+ "\n");
+
+ int offset=0;
+ offset = checkContains(response,offset,"HTTP/1.1 200");
+ checkContains(response,offset,"/R1");
+ }
+
+ @Test
+ public void testEmptyChunk() throws Exception
{
String response=connector.getResponses("GET /R1 HTTP/1.1\n"+
"Host: localhost\n"+
@@ -440,7 +453,7 @@
}
@Test
- public void testUnconsumedError() throws Exception
+ public void testUnconsumedErrorRead() throws Exception
{
String response=null;
String requests=null;
@@ -474,6 +487,42 @@
offset = checkContains(response,offset,"encoding=UTF-8");
offset = checkContains(response,offset,"abcdefghij");
}
+
+ @Test
+ public void testUnconsumedErrorStream() throws Exception
+ {
+ String response=null;
+ String requests=null;
+ int offset=0;
+
+ offset=0;
+ requests=
+ "GET /R1?error=599 HTTP/1.1\n"+
+ "Host: localhost\n"+
+ "Transfer-Encoding: chunked\n"+
+ "Content-Type: application/data; charset=utf-8\n"+
+ "\015\012"+
+ "5;\015\012"+
+ "12345\015\012"+
+ "5;\015\012"+
+ "67890\015\012"+
+ "0;\015\012\015\012"+
+ "GET /R2 HTTP/1.1\n"+
+ "Host: localhost\n"+
+ "Content-Type: text/plain; charset=utf-8\n"+
+ "Content-Length: 10\n"+
+ "Connection: close\n"+
+ "\n"+
+ "abcdefghij\n";
+
+ response=connector.getResponses(requests);
+
+ offset = checkContains(response,offset,"HTTP/1.1 599");
+ offset = checkContains(response,offset,"HTTP/1.1 200");
+ offset = checkContains(response,offset,"/R2");
+ offset = checkContains(response,offset,"encoding=UTF-8");
+ offset = checkContains(response,offset,"abcdefghij");
+ }
@Test
public void testUnconsumedException() throws Exception
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java
index 65ac35d..8fcc49b 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java
@@ -29,15 +29,17 @@
import java.nio.channels.ReadableByteChannel;
import java.util.Arrays;
+import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
+import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.hamcrest.Matchers;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -142,9 +144,9 @@
assertThat(response,containsString("HTTP/1.1 200 OK"));
assertThat(response,containsString("Transfer-Encoding: chunked"));
+ assertThat(response,containsString("400\tThis is a big file"));
assertThat(response,containsString("\r\n0\r\n"));
}
-
@Test
public void testSendChannelSimple() throws Exception
@@ -164,9 +166,33 @@
String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response,containsString("HTTP/1.1 200 OK"));
assertThat(response,Matchers.not(containsString("Content-Length")));
+ assertThat(response,containsString("400\tThis is a big file"));
}
@Test
+ public void testSendBigDirect() throws Exception
+ {
+ Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,true);
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,containsString("Content-Length"));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
+ @Test
+ public void testSendBigInDirect() throws Exception
+ {
+ Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,false);
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,containsString("Content-Length"));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
+
+ @Test
public void testSendChannelBigChunked() throws Exception
{
Resource big = Resource.newClassPathResource("simple/big.txt");
@@ -214,8 +240,174 @@
assertThat(response,containsString("\r\n0\r\n"));
}
+
+ @Test
+ public void testWriteSmall() throws Exception
+ {
+ final Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,false);
+ _handler._bytes=new byte[8];
+
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
+ @Test
+ public void testWriteMed() throws Exception
+ {
+ final Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,false);
+ _handler._bytes=new byte[4000];
+
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
+ @Test
+ public void testWriteLarge() throws Exception
+ {
+ final Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,false);
+ _handler._bytes=new byte[8192];
+
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
+ @Test
+ public void testWriteBufferSmall() throws Exception
+ {
+ final Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,false);
+ _handler._buffer=BufferUtil.allocate(8);
+
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
+ @Test
+ public void testWriteBufferMed() throws Exception
+ {
+ final Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,false);
+ _handler._buffer=BufferUtil.allocate(4000);
+
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
+ @Test
+ public void testWriteBufferLarge() throws Exception
+ {
+ final Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,false);
+ _handler._buffer=BufferUtil.allocate(8192);
+
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
+ @Test
+ public void testAsyncWriteSmall() throws Exception
+ {
+ final Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,false);
+ _handler._bytes=new byte[8];
+ _handler._async=true;
+
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
+ @Test
+ public void testAsyncWriteMed() throws Exception
+ {
+ final Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,false);
+ _handler._bytes=new byte[4000];
+ _handler._async=true;
+
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
+ @Test
+ public void testAsyncWriteLarge() throws Exception
+ {
+ final Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,false);
+ _handler._bytes=new byte[8192];
+ _handler._async=true;
+
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
+ @Test
+ public void testAsyncWriteBufferSmall() throws Exception
+ {
+ final Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,false);
+ _handler._buffer=BufferUtil.allocate(8);
+ _handler._async=true;
+
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
+ @Test
+ public void testAsyncWriteBufferMed() throws Exception
+ {
+ final Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,false);
+ _handler._buffer=BufferUtil.allocate(4000);
+ _handler._async=true;
+
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
+ @Test
+ public void testAsyncWriteBufferLarge() throws Exception
+ {
+ final Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._content=BufferUtil.toBuffer(big,false);
+ _handler._buffer=BufferUtil.allocate(8192);
+ _handler._async=true;
+
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ assertThat(response,containsString("400\tThis is a big file"));
+ }
+
static class ContentHandler extends AbstractHandler
{
+ boolean _async;
+ ByteBuffer _buffer;
+ byte[] _bytes;
InputStream _contentInputStream;
ReadableByteChannel _contentChannel;
ByteBuffer _content;
@@ -226,18 +418,8 @@
baseRequest.setHandled(true);
response.setContentType("text/plain");
- HttpOutput out = (HttpOutput) response.getOutputStream();
+ final HttpOutput out = (HttpOutput) response.getOutputStream();
- if (_content!=null)
- {
- response.setContentLength(_content.remaining());
- if (_content.hasArray())
- out.write(_content.array(),_content.arrayOffset()+_content.position(),_content.remaining());
- else
- out.sendContent(_content);
- _content=null;
- return;
- }
if (_contentInputStream!=null)
{
@@ -253,6 +435,116 @@
return;
}
+ if (_bytes!=null)
+ {
+ if (_async)
+ {
+ final AsyncContext async = request.startAsync();
+ out.setWriteListener(new WriteListener()
+ {
+ @Override
+ public void onWritePossible() throws IOException
+ {
+ while (out.isReady())
+ {
+ int len=_content.remaining();
+ if (len>_bytes.length)
+ len=_bytes.length;
+ if (len==0)
+ {
+ async.complete();
+ break;
+ }
+
+ _content.get(_bytes,0,len);
+ out.write(_bytes,0,len);
+ }
+ }
+
+ @Override
+ public void onError(Throwable t)
+ {
+ t.printStackTrace();
+ async.complete();
+ }
+ });
+
+ return;
+ }
+
+
+ while(BufferUtil.hasContent(_content))
+ {
+ int len=_content.remaining();
+ if (len>_bytes.length)
+ len=_bytes.length;
+ _content.get(_bytes,0,len);
+ out.write(_bytes,0,len);
+ }
+
+ return;
+ }
+
+ if (_buffer!=null)
+ {
+ if (_async)
+ {
+ final AsyncContext async = request.startAsync();
+ out.setWriteListener(new WriteListener()
+ {
+ @Override
+ public void onWritePossible() throws IOException
+ {
+ while (out.isReady())
+ {
+ if(BufferUtil.isEmpty(_content))
+ {
+ async.complete();
+ break;
+ }
+
+ BufferUtil.clearToFill(_buffer);
+ BufferUtil.put(_content,_buffer);
+ BufferUtil.flipToFlush(_buffer,0);
+ out.write(_buffer);
+ }
+ }
+
+ @Override
+ public void onError(Throwable t)
+ {
+ t.printStackTrace();
+ async.complete();
+ }
+ });
+
+ return;
+ }
+
+
+ while(BufferUtil.hasContent(_content))
+ {
+ BufferUtil.clearToFill(_buffer);
+ BufferUtil.put(_content,_buffer);
+ BufferUtil.flipToFlush(_buffer,0);
+ out.write(_buffer);
+ }
+
+ return;
+ }
+
+ if (_content!=null)
+ {
+ response.setContentLength(_content.remaining());
+ if (_content.hasArray())
+ out.write(_content.array(),_content.arrayOffset()+_content.position(),_content.remaining());
+ else
+ out.sendContent(_content);
+ _content=null;
+ return;
+ }
+
+
}
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
index 5f511b1..6e350ca 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
@@ -21,6 +21,7 @@
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -35,14 +36,10 @@
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.net.Socket;
-import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
-import java.util.Random;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Exchanger;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.ServletException;
@@ -54,14 +51,13 @@
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StdErrLog;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
-import org.junit.matchers.JUnitMatchers;
/**
*
*/
@@ -146,6 +142,7 @@
try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
{
+ client.setSoTimeout(10000);
((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true);
((StdErrLog)Log.getLogger(HttpConnection.class)).info("expect request is too large, then ISE extra data ...");
OutputStream os = client.getOutputStream();
@@ -374,17 +371,19 @@
"Connection: close\015\012" +
"\015\012").getBytes());
os.flush();
- Thread.sleep(PAUSE);
- os.write(("5\015\012").getBytes());
+ Thread.sleep(1000);
+ os.write(("5").getBytes());
+ Thread.sleep(1000);
+ os.write(("\015\012").getBytes());
os.flush();
- Thread.sleep(PAUSE);
+ Thread.sleep(1000);
os.write(("ABCDE\015\012" +
"0;\015\012\015\012").getBytes());
os.flush();
// Read the response.
String response = readResponse(client);
- assertTrue(response.indexOf("200") > 0);
+ assertThat(response,containsString("200"));
}
}
@@ -474,59 +473,53 @@
}
}
+
@Test
- public void testRequest2Fragments() throws Exception
+ @Slow
+ public void testRequest2Sliced2() throws Exception
{
configureServer(new EchoHandler());
byte[] bytes = REQUEST2.getBytes();
- final int pointCount = 2;
- // TODO random unit tests suck!
- Random random = new Random(System.currentTimeMillis());
- for (int i = 0; i < LOOPS; i++)
- {
- int[] points = new int[pointCount];
- StringBuilder message = new StringBuilder();
-
- message.append("iteration #").append(i + 1);
-
- // Pick fragment points at random
- for (int j = 0; j < points.length; ++j)
- points[j] = random.nextInt(bytes.length);
-
- // Sort the list
- Arrays.sort(points);
-
- try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
- {
- OutputStream os = client.getOutputStream();
-
- writeFragments(bytes, points, message, os);
-
- // Read the response
- String response = readResponse(client);
-
- // Check the response
- assertEquals("response for " + i + " " + message.toString(), RESPONSE2, response);
- }
- }
- }
-
- @Test
- public void testRequest2Iterate() throws Exception
- {
- configureServer(new EchoHandler());
-
- byte[] bytes = REQUEST2.getBytes();
- for (int i = 0; i < bytes.length; i += 3)
+ int splits = bytes.length-REQUEST2_CONTENT.length()+5;
+ for (int i = 0; i < splits; i += 1)
{
int[] points = new int[]{i};
StringBuilder message = new StringBuilder();
message.append("iteration #").append(i + 1);
- // Sort the list
- Arrays.sort(points);
+ try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
+ {
+ OutputStream os = client.getOutputStream();
+
+ writeFragments(bytes, points, message, os);
+
+ // Read the response
+ String response = readResponse(client);
+
+ // Check the response
+ assertEquals("response for " + i + " " + message.toString(), RESPONSE2, response);
+
+ Thread.sleep(10);
+ }
+ }
+ }
+
+ @Test
+ @Slow
+ public void testRequest2Sliced3() throws Exception
+ {
+ configureServer(new EchoHandler());
+
+ byte[] bytes = REQUEST2.getBytes();
+ int splits = bytes.length-REQUEST2_CONTENT.length()+5;
+ for (int i = 0; i < splits; i += 1)
+ {
+ int[] points = new int[]{i,i+1};
+ StringBuilder message = new StringBuilder();
+
+ message.append("iteration #").append(i + 1);
try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
{
@@ -539,9 +532,14 @@
// Check the response
assertEquals("response for " + i + " " + message.toString(), RESPONSE2, response);
+
+ Thread.sleep(10);
}
}
}
+
+
+
@Test
public void testFlush() throws Exception
@@ -1015,33 +1013,28 @@
"\015\012" +
"123456789\n" +
- "HEAD /R1 HTTP/1.1\015\012" +
+ "HEAD /R2 HTTP/1.1\015\012" +
"Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\015\012" +
"content-type: text/plain; charset=utf-8\r\n" +
"content-length: 10\r\n" +
"\015\012" +
- "123456789\n" +
+ "ABCDEFGHI\n" +
- "POST /R1 HTTP/1.1\015\012" +
+ "POST /R3 HTTP/1.1\015\012" +
"Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\015\012" +
"content-type: text/plain; charset=utf-8\r\n" +
"content-length: 10\r\n" +
"Connection: close\015\012" +
"\015\012" +
- "123456789\n"
+ "abcdefghi\n"
).getBytes("iso-8859-1"));
String in = IO.toString(is);
- // System.err.println(in);
-
- int index = in.indexOf("123456789");
- assertTrue(index > 0);
- index = in.indexOf("123456789", index + 1);
- assertTrue(index > 0);
- index = in.indexOf("123456789", index + 1);
- assertTrue(index == -1);
+ Assert.assertThat(in,containsString("123456789"));
+ Assert.assertThat(in,not(containsString("ABCDEFGHI")));
+ Assert.assertThat(in,containsString("abcdefghi"));
}
}
@@ -1381,7 +1374,7 @@
}
}
- private void writeFragments(byte[] bytes, int[] points, StringBuilder message, OutputStream os) throws IOException, InterruptedException
+ protected void writeFragments(byte[] bytes, int[] points, StringBuilder message, OutputStream os) throws IOException, InterruptedException
{
int last = 0;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java
index 9924928..32fe7eb 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java
@@ -23,7 +23,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
-import java.io.Writer;
import java.net.Socket;
import java.net.URI;
@@ -93,14 +92,14 @@
protected static class EchoHandler extends AbstractHandler
{
- boolean musthavecontent=true;
+ boolean _musthavecontent=true;
public EchoHandler()
{}
public EchoHandler(boolean content)
{
- musthavecontent=false;
+ _musthavecontent=false;
}
@Override
@@ -134,7 +133,7 @@
if (count==0)
{
- if (musthavecontent)
+ if (_musthavecontent)
throw new IllegalStateException("no input recieved");
writer.println("No content");
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java
index bcb92ca..fe41b1a 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java
@@ -32,7 +32,6 @@
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.Utf8Appendable;
import org.junit.Assert;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java
index fea92eb..d24d0a0 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java
@@ -44,7 +44,7 @@
_bytes = BufferUtil.allocate(2048);
final ByteBufferPool bufferPool = new MappedByteBufferPool();
- HttpChannel<?> channel = new HttpChannel<ByteBuffer>(null,new HttpConfiguration(),null,null,null)
+ HttpChannel<?> channel = new HttpChannel<ByteBuffer>(null,new HttpConfiguration(),null,null,new ByteBufferQueuedHttpInput())
{
@Override
public ByteBufferPool getByteBufferPool()
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/LocalConnectorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/LocalConnectorTest.java
index e7715bd..2fa30a9 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/LocalConnectorTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/LocalConnectorTest.java
@@ -59,7 +59,7 @@
{
final CountDownLatch openLatch = new CountDownLatch(1);
final CountDownLatch closeLatch = new CountDownLatch(1);
- _connector.addBean(new Connection.Listener.Empty()
+ _connector.addBean(new Connection.Listener.Adapter()
{
@Override
public void onOpened(Connection connection)
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java
index b3695ba..de245c6 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java
@@ -28,7 +28,6 @@
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.TimerScheduler;
import org.junit.After;
@@ -90,7 +89,7 @@
for (int i=0;i<100;i++)
{
- _threadPool.dispatch(new Runnable()
+ _threadPool.execute(new Runnable()
{
@Override
public void run()
@@ -193,6 +192,6 @@
Thread.sleep(1200);
Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
Assert.assertEquals(-1,socket1.getInputStream().read());
-
+
}
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java
index dbd9976..73a951c 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java
@@ -41,7 +41,6 @@
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector;
import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
import org.junit.After;
import org.junit.Ignore;
import org.junit.Test;
@@ -83,7 +82,7 @@
final CountDownLatch openedLatch = new CountDownLatch(1);
final CountDownLatch closedLatch = new CountDownLatch(1);
- connector.addNetworkTrafficListener(new NetworkTrafficListener.Empty()
+ connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
{
public volatile Socket socket;
@@ -126,7 +125,7 @@
final CountDownLatch incomingLatch = new CountDownLatch(1);
final AtomicReference<String> outgoingData = new AtomicReference<String>("");
final CountDownLatch outgoingLatch = new CountDownLatch(1);
- connector.addNetworkTrafficListener(new NetworkTrafficListener.Empty()
+ connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
{
@Override
public void incoming(Socket socket, ByteBuffer bytes)
@@ -191,7 +190,7 @@
final CountDownLatch incomingLatch = new CountDownLatch(1);
final AtomicReference<String> outgoingData = new AtomicReference<String>("");
final CountDownLatch outgoingLatch = new CountDownLatch(2);
- connector.addNetworkTrafficListener(new NetworkTrafficListener.Empty()
+ connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
{
public void incoming(Socket socket, ByteBuffer bytes)
{
@@ -258,7 +257,7 @@
final CountDownLatch incomingLatch = new CountDownLatch(1);
final AtomicReference<String> outgoingData = new AtomicReference<String>("");
final CountDownLatch outgoingLatch = new CountDownLatch(4);
- connector.addNetworkTrafficListener(new NetworkTrafficListener.Empty()
+ connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
{
public void incoming(Socket socket, ByteBuffer bytes)
{
@@ -324,7 +323,7 @@
final CountDownLatch incomingLatch = new CountDownLatch(1);
final AtomicReference<String> outgoingData = new AtomicReference<String>("");
final CountDownLatch outgoingLatch = new CountDownLatch(1);
- connector.addNetworkTrafficListener(new NetworkTrafficListener.Empty()
+ connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
{
public void incoming(Socket socket, ByteBuffer bytes)
{
@@ -397,7 +396,7 @@
final AtomicReference<String> incomingData = new AtomicReference<String>("");
final AtomicReference<String> outgoingData = new AtomicReference<String>("");
final CountDownLatch outgoingLatch = new CountDownLatch(1);
- connector.addNetworkTrafficListener(new NetworkTrafficListener.Empty()
+ connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
{
public void incoming(Socket socket, ByteBuffer bytes)
{
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
index ba1f065..46f0fe3 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
@@ -18,6 +18,15 @@
package org.eclipse.jetty.server;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
@@ -30,9 +39,11 @@
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequestEvent;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
@@ -44,26 +55,15 @@
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiPartInputStreamParser;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8Appendable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StdErrLog;
import org.hamcrest.Matchers;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
public class RequestTest
{
private static final Logger LOG = Log.getLogger(RequestTest.class);
@@ -140,7 +140,7 @@
assertNull(request.getCharacterEncoding());
assertEquals(0,request.getQueryString().length());
assertEquals(-1,request.getContentLength());
- assertEquals(0,request.getCookies().length);
+ assertNull(request.getCookies());
assertNull(request.getHeader("Name"));
assertFalse(request.getHeaders("Name").hasMoreElements());
assertEquals(-1,request.getDateHeader("Name"));
@@ -209,17 +209,18 @@
String responses=_connector.getResponses(request);
assertTrue(responses.startsWith("HTTP/1.1 200"));
}
-
-
+
+
@Test
public void testMultiPart() throws Exception
- {
- final File tmpDir = new File (System.getProperty("java.io.tmpdir"));
- final File testTmpDir = new File (tmpDir, "reqtest");
+ {
+ final File testTmpDir = File.createTempFile("reqtest", null);
+ if (testTmpDir.exists())
+ testTmpDir.delete();
+ testTmpDir.mkdir();
testTmpDir.deleteOnExit();
- assertTrue(testTmpDir.mkdirs());
assertTrue(testTmpDir.list().length == 0);
-
+
ContextHandler contextHandler = new ContextHandler();
contextHandler.setContextPath("/foo");
contextHandler.setResourceBase(".");
@@ -241,12 +242,12 @@
String[] files = testTmpDir.list();
assertTrue(files.length == 0);
}
-
+
});
_server.stop();
_server.setHandler(contextHandler);
_server.start();
-
+
String multipart = "--AaB03x\r\n"+
"content-disposition: form-data; name=\"field1\"\r\n"+
"\r\n"+
@@ -257,7 +258,7 @@
"\r\n"+
"000000000000000000000000000000000000000000000000000\r\n"+
"--AaB03x--\r\n";
-
+
String request="GET /foo/x.html HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: multipart/form-data; boundary=\"AaB03x\"\r\n"+
@@ -400,8 +401,8 @@
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
assertEquals("80",results.get(i++));
-
-
+
+
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
@@ -414,8 +415,8 @@
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
assertEquals("8888",results.get(i++));
-
-
+
+
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
@@ -429,8 +430,8 @@
assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++));
assertEquals("80",results.get(i++));
-
-
+
+
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
@@ -443,8 +444,8 @@
assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++));
assertEquals("8888",results.get(i++));
-
-
+
+
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
@@ -457,8 +458,8 @@
assertEquals("0.0.0.0",results.get(i++));
assertEquals("::1",results.get(i++));
assertEquals("80",results.get(i++));
-
-
+
+
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
@@ -471,8 +472,8 @@
assertEquals("0.0.0.0",results.get(i++));
assertEquals("::1",results.get(i++));
assertEquals("8888",results.get(i++));
-
-
+
+
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
@@ -487,8 +488,8 @@
assertEquals("remote",results.get(i++));
assertEquals("::1",results.get(i++));
assertEquals("443",results.get(i++));
-
-
+
+
results.clear();
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
@@ -508,15 +509,25 @@
@Test
public void testContent() throws Exception
{
- final int[] length=new int[1];
+ final AtomicInteger length=new AtomicInteger();
_handler._checker = new RequestTester()
{
@Override
- public boolean check(HttpServletRequest request,HttpServletResponse response)
+ public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
{
- //assertEquals(request.getContentLength(), ((Request)request).getContentRead());
- length[0]=request.getContentLength();
+ int len=request.getContentLength();
+ ServletInputStream in = request.getInputStream();
+ for (int i=0;i<len;i++)
+ {
+ int b=in.read();
+ if (b<0)
+ return false;
+ }
+ if (in.read()>0)
+ return false;
+
+ length.set(len);
return true;
}
};
@@ -524,11 +535,11 @@
String content="";
- for (int l=0;l<1025;l++)
+ for (int l=0;l<1024;l++)
{
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
- "Content-Type: text/test\r\n"+
+ "Content-Type: multipart/form-data-test\r\n"+
"Content-Length: "+l+"\r\n"+
"Connection: close\r\n"+
"\r\n"+
@@ -536,9 +547,8 @@
Log.getRootLogger().debug("test l={}",l);
String response = _connector.getResponses(request);
Log.getRootLogger().debug(response);
- assertEquals(l,length[0]);
- if (l>0)
- assertEquals(l,_handler._content.length());
+ assertThat(response,Matchers.containsString(" 200 OK"));
+ assertEquals(l,length.get());
content+="x";
}
}
@@ -690,7 +700,7 @@
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: whatever\n"+
- "\n",
+ "\n",
200, TimeUnit.MILLISECONDS
);
assertTrue(response.indexOf("200")>0);
@@ -1107,30 +1117,30 @@
response.sendError(500);
}
}
-
+
private class MultiPartRequestHandler extends AbstractHandler
{
File tmpDir;
-
+
public MultiPartRequestHandler(File tmpDir)
{
this.tmpDir = tmpDir;
}
-
-
+
+
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
((Request)request).setHandled(true);
try
- {
+ {
MultipartConfigElement mpce = new MultipartConfigElement(tmpDir.getAbsolutePath(),-1, -1, 2);
request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
-
+
String field1 = request.getParameter("field1");
assertNotNull(field1);
-
+
Part foo = request.getPart("stuff");
assertNotNull(foo);
assertTrue(foo.getSize() > 0);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
index 245fb5c..80543ff 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
@@ -18,6 +18,13 @@
package org.eclipse.jetty.server;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
@@ -29,7 +36,6 @@
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Locale;
-import java.util.concurrent.TimeoutException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
@@ -37,7 +43,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpURI;
@@ -48,7 +54,6 @@
import org.eclipse.jetty.server.session.HashSessionIdManager;
import org.eclipse.jetty.server.session.HashSessionManager;
import org.eclipse.jetty.server.session.HashedSession;
-import org.eclipse.jetty.util.BlockingCallback;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.TimerScheduler;
@@ -58,12 +63,6 @@
import org.junit.Before;
import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
public class ResponseTest
{
private Server _server;
@@ -81,18 +80,10 @@
_server.start();
AbstractEndPoint endp = new ByteArrayEndPoint(_scheduler, 5000);
- ByteBufferHttpInput input = new ByteBufferHttpInput();
- _channel = new HttpChannel<>(connector, new HttpConfiguration(), endp, new HttpTransport()
+ ByteBufferQueuedHttpInput input = new ByteBufferQueuedHttpInput();
+ _channel = new HttpChannel<ByteBuffer>(connector, new HttpConfiguration(), endp, new HttpTransport()
{
@Override
- public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent) throws IOException
- {
- BlockingCallback cb = new BlockingCallback();
- send(info,content,lastContent,cb);
- cb.block();
- }
-
- @Override
public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
{
callback.succeeded();
@@ -653,6 +644,126 @@
output.flush();
}
+
+ @Test
+ public void testSetCookie() throws Exception
+ {
+ Response response = _channel.getResponse();
+ HttpFields fields = response.getHttpFields();
+
+ response.addSetCookie("null",null,null,null,-1,null,false,false,-1);
+ assertEquals("null=",fields.getStringField("Set-Cookie"));
+
+ fields.clear();
+
+ response.addSetCookie("minimal","value",null,null,-1,null,false,false,-1);
+ assertEquals("minimal=value",fields.getStringField("Set-Cookie"));
+
+ fields.clear();
+ //test cookies with same name, domain and path, only 1 allowed
+ response.addSetCookie("everything","wrong","domain","path",0,"to be replaced",true,true,0);
+ response.addSetCookie("everything","value","domain","path",0,"comment",true,true,0);
+ assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",fields.getStringField("Set-Cookie"));
+ Enumeration<String> e =fields.getValues("Set-Cookie");
+ assertTrue(e.hasMoreElements());
+ assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+ assertFalse(e.hasMoreElements());
+ assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.getStringField("Expires"));
+ assertFalse(e.hasMoreElements());
+
+ //test cookies with same name, different domain
+ fields.clear();
+ response.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
+ response.addSetCookie("everything","value","domain2","path",0,"comment",true,true,0);
+ e =fields.getValues("Set-Cookie");
+ assertTrue(e.hasMoreElements());
+ assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
+ assertTrue(e.hasMoreElements());
+ assertEquals("everything=value;Version=1;Path=path;Domain=domain2;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+ assertFalse(e.hasMoreElements());
+
+ //test cookies with same name, same path, one with domain, one without
+ fields.clear();
+ response.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
+ response.addSetCookie("everything","value","","path",0,"comment",true,true,0);
+ e =fields.getValues("Set-Cookie");
+ assertTrue(e.hasMoreElements());
+ assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
+ assertTrue(e.hasMoreElements());
+ assertEquals("everything=value;Version=1;Path=path;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+ assertFalse(e.hasMoreElements());
+
+
+ //test cookies with same name, different path
+ fields.clear();
+ response.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
+ response.addSetCookie("everything","value","domain1","path2",0,"comment",true,true,0);
+ e =fields.getValues("Set-Cookie");
+ assertTrue(e.hasMoreElements());
+ assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
+ assertTrue(e.hasMoreElements());
+ assertEquals("everything=value;Version=1;Path=path2;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+ assertFalse(e.hasMoreElements());
+
+ //test cookies with same name, same domain, one with path, one without
+ fields.clear();
+ response.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
+ response.addSetCookie("everything","value","domain1","",0,"comment",true,true,0);
+ e =fields.getValues("Set-Cookie");
+ assertTrue(e.hasMoreElements());
+ assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
+ assertTrue(e.hasMoreElements());
+ assertEquals("everything=value;Version=1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+ assertFalse(e.hasMoreElements());
+
+ //test cookies same name only, no path, no domain
+ fields.clear();
+ response.addSetCookie("everything","other","","",0,"blah",true,true,0);
+ response.addSetCookie("everything","value","","",0,"comment",true,true,0);
+ e =fields.getValues("Set-Cookie");
+ assertTrue(e.hasMoreElements());
+ assertEquals("everything=value;Version=1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+ assertFalse(e.hasMoreElements());
+
+ fields.clear();
+ response.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,1);
+ String setCookie=fields.getStringField("Set-Cookie");
+ assertThat(setCookie,Matchers.startsWith("\"ev erything\"=\"va lue\";Version=1;Path=\"pa th\";Domain=\"do main\";Expires="));
+ assertThat(setCookie,Matchers.endsWith(" GMT;Max-Age=1;Secure;HttpOnly;Comment=\"co mment\""));
+
+ fields.clear();
+ response.addSetCookie("name","value",null,null,-1,null,false,false,0);
+ setCookie=fields.getStringField("Set-Cookie");
+ assertEquals(-1,setCookie.indexOf("Version="));
+ fields.clear();
+ response.addSetCookie("name","v a l u e",null,null,-1,null,false,false,0);
+ setCookie=fields.getStringField("Set-Cookie");
+
+ fields.clear();
+ response.addSetCookie("json","{\"services\":[\"cwa\", \"aa\"]}",null,null,-1,null,false,false,-1);
+ assertEquals("json=\"{\\\"services\\\":[\\\"cwa\\\", \\\"aa\\\"]}\"",fields.getStringField("Set-Cookie"));
+
+ fields.clear();
+ response.addSetCookie("name","value","domain",null,-1,null,false,false,-1);
+ response.addSetCookie("name","other","domain",null,-1,null,false,false,-1);
+ assertEquals("name=other;Domain=domain",fields.getStringField("Set-Cookie"));
+ response.addSetCookie("name","more","domain",null,-1,null,false,false,-1);
+ assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie"));
+ response.addSetCookie("foo","bar","domain",null,-1,null,false,false,-1);
+ response.addSetCookie("foo","bob","domain",null,-1,null,false,false,-1);
+ assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie"));
+
+ e=fields.getValues("Set-Cookie");
+ assertEquals("name=more;Domain=domain",e.nextElement());
+ assertEquals("foo=bob;Domain=domain",e.nextElement());
+
+ fields.clear();
+ response.addSetCookie("name","value%=",null,null,-1,null,false,false,0);
+ setCookie=fields.getStringField("Set-Cookie");
+ assertEquals("name=value%=",setCookie);
+
+ }
+
private Response newResponse()
{
_channel.reset();
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java
index 396a60d..b23c355 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java
@@ -28,6 +28,7 @@
@Before
public void init() throws Exception
{
- startServer(new ServerConnector(_server,1,1));
+ // Run this test with 0 acceptors. Other tests already check the acceptors >0
+ startServer(new ServerConnector(_server,0,1));
}
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java
index 596f9cc..685b434 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java
@@ -19,7 +19,6 @@
package org.eclipse.jetty.server;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.InputStreamReader;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java
index 93dae66..157bc47 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java
@@ -38,7 +38,6 @@
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.After;
import org.junit.Assert;
-import org.junit.Ignore;
import org.junit.Test;
public class SlowClientWithPipelinedRequestTest
@@ -83,9 +82,7 @@
}
}
- // TODO merged from jetty-8 - not working???
@Test
- @Ignore
public void testSlowClientWithPipelinedRequest() throws Exception
{
final int contentLength = 512 * 1024;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java
index 269fce5..b5cbe41 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java
@@ -37,7 +37,6 @@
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.annotation.Stress;
-import org.eclipse.jetty.toolchain.test.PropertyFlag;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java
index a287dcd..029b937 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java
@@ -23,8 +23,6 @@
import static org.junit.Assert.assertTrue;
import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -34,7 +32,6 @@
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.util.ArrayUtil;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java
index a0817aa..1fb29fc 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java
@@ -19,10 +19,10 @@
package org.eclipse.jetty.server.handler;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
@@ -332,7 +332,7 @@
Resource resource=context.getResource(path);
assertNotNull(resource);
- assertEquals(context.getResource("/subdir/TextFile.Long.txt").getURL(),resource.getAlias());
+ assertEquals(context.getResource("/subdir/TextFile.Long.txt").getURI(),resource.getAlias());
URL url=context.getServletContext().getResource(path);
assertNotNull(url);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java
index 9cced27..0b67a16 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java
@@ -399,6 +399,7 @@
public void testAttributes() throws Exception
{
ContextHandler handler = new ContextHandler();
+ handler.setServer(new Server());
handler.setAttribute("aaa","111");
assertEquals("111",handler.getServletContext().getAttribute("aaa"));
assertEquals(null,handler.getAttribute("bbb"));
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerRangeTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerRangeTest.java
index 15593c7..7289658 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerRangeTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerRangeTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.server.handler;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.io.File;
import java.io.FileWriter;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java
index 8e65b66..489961f 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java
@@ -48,6 +48,7 @@
*/
public class ResourceHandlerTest
{
+ private static final String LN = System.getProperty("line.separator");
private static Server _server;
private static HttpConfiguration _config;
private static ServerConnector _connector;
@@ -124,7 +125,7 @@
SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
String response=sr.getString("/resource/big.txt");
Assert.assertThat(response,Matchers.startsWith(" 1\tThis is a big file"));
- Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file\n"));
+ Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file"+LN));
}
@Test
@@ -134,7 +135,7 @@
SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
String response=sr.getString("/resource/big.txt");
Assert.assertThat(response,Matchers.startsWith(" 1\tThis is a big file"));
- Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file\n"));
+ Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file"+LN));
}
@Test
@@ -144,7 +145,7 @@
SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
String response=sr.getString("/resource/big.txt");
Assert.assertThat(response,Matchers.startsWith(" 1\tThis is a big file"));
- Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file\n"));
+ Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file"+LN));
}
@Test
@@ -156,8 +157,8 @@
Thread.sleep(1000);
String response = IO.toString(socket.getInputStream());
Assert.assertThat(response,Matchers.startsWith("HTTP/1.1 200 OK"));
- Assert.assertThat(response,Matchers.containsString(" 400\tThis is a big file\n 1\tThis is a big file"));
- Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file\n"));
+ Assert.assertThat(response,Matchers.containsString(" 400\tThis is a big file"+LN+" 1\tThis is a big file"));
+ Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file"+LN));
}
}
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
index 2a59567..28b5b00 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
@@ -18,11 +18,18 @@
package org.eclipse.jetty.server.handler;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
+
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
@@ -38,12 +45,6 @@
import org.junit.Before;
import org.junit.Test;
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
public class StatisticsHandlerTest
{
private Server _server;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java
index 6c37535..bf2824f 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java
@@ -36,8 +36,10 @@
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.server.HttpServerTestBase;
import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -61,6 +63,9 @@
@Override
public void testFullMethod() throws Exception
{
+ // Don't run on Windows (buggy JVM)
+ Assume.assumeTrue(!OS.IS_WINDOWS);
+
try
{
super.testFullMethod();
@@ -74,6 +79,8 @@
@Override
public void testFullURI() throws Exception
{
+ // Don't run on Windows (buggy JVM)
+ Assume.assumeTrue(!OS.IS_WINDOWS);
try
{
super.testFullURI();
diff --git a/jetty-servlet/pom.xml b/jetty-servlet/pom.xml
index 233bd98..5fe9f28 100644
--- a/jetty-servlet/pom.xml
+++ b/jetty-servlet/pom.xml
@@ -3,7 +3,7 @@
<parent>
<artifactId>jetty-project</artifactId>
<groupId>org.eclipse.jetty</groupId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-servlet</artifactId>
@@ -26,7 +26,7 @@
</goals>
<configuration>
<instructions>
- <Import-Package>javax.servlet.*;version="2.6.0",org.eclipse.jetty.jmx.*;version="9.0";resolution:=optional,*</Import-Package>
+ <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.eclipse.jetty.jmx.*;version="9.1";resolution:=optional,*</Import-Package>
<_nouses>true</_nouses>
</instructions>
</configuration>
@@ -54,6 +54,23 @@
</executions>
</plugin>
<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <descriptorRef>config</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
diff --git a/jetty-servlet/src/main/config/modules/servlet.mod b/jetty-servlet/src/main/config/modules/servlet.mod
new file mode 100644
index 0000000..fdb65c5
--- /dev/null
+++ b/jetty-servlet/src/main/config/modules/servlet.mod
@@ -0,0 +1,9 @@
+#
+# Jetty Servlet Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-servlet-${jetty.version}.jar
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java
index 729ecf9..9d75ee6 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java
@@ -23,11 +23,11 @@
import java.util.List;
import java.util.Map;
-import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
+import org.eclipse.jetty.server.Dispatcher;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
@@ -50,35 +50,36 @@
public ErrorPageErrorHandler()
{}
+ /* ------------------------------------------------------------ */
@Override
public String getErrorPage(HttpServletRequest request)
{
String error_page= null;
- Class<?> exClass= (Class<?>)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE);
- if (ServletException.class.equals(exClass))
+ Throwable th= (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
+
+ // Walk the cause hierarchy
+ while (error_page == null && th != null )
{
+ Class<?> exClass=th.getClass();
error_page= (String)_errorPages.get(exClass.getName());
- if (error_page == null)
+
+ // walk the inheritance hierarchy
+ while (error_page == null)
{
- Throwable th= (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
- while (th instanceof ServletException)
- th= ((ServletException)th).getRootCause();
- if (th != null)
- exClass= th.getClass();
+ exClass= exClass.getSuperclass();
+ if (exClass==null)
+ break;
+ error_page= (String)_errorPages.get(exClass.getName());
}
- }
- while (error_page == null && exClass != null )
- {
- error_page= (String)_errorPages.get(exClass.getName());
- exClass= exClass.getSuperclass();
+ th=(th instanceof ServletException)?((ServletException)th).getRootCause():null;
}
if (error_page == null)
{
// look for an exact code match
- Integer code=(Integer)request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
+ Integer code=(Integer)request.getAttribute(Dispatcher.ERROR_STATUS_CODE);
if (code!=null)
{
error_page= (String)_errorPages.get(Integer.toString(code));
@@ -100,15 +101,14 @@
}
}
- //try new servlet 3.0 global error page
+ //try servlet 3.x global error page
if (error_page == null)
- {
error_page = _errorPages.get(GLOBAL_ERROR_PAGE);
- }
-
+
return error_page;
}
+
/* ------------------------------------------------------------ */
/**
* @return Returns the errorPages.
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java
index b63e40a..52b5397 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java
@@ -18,6 +18,7 @@
package org.eclipse.jetty.servlet;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -32,6 +33,7 @@
import javax.servlet.ServletException;
import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -193,6 +195,16 @@
{
return getName();
}
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ super.dump(out, indent);
+ if(_filter instanceof Dumpable) {
+ ((Dumpable) _filter).dump(out, indent);
+ }
+ }
/* ------------------------------------------------------------ */
public FilterRegistration.Dynamic getRegistration()
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java
index fd8f43e..21af0d8 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java
@@ -27,8 +27,6 @@
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.Dispatcher;
-import org.eclipse.jetty.server.HttpConnection;
-import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java
index 991ff84..91666ea 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java
@@ -274,10 +274,10 @@
Decorator decorator = _decorators.get(i);
if (_servletHandler.getFilters()!=null)
for (FilterHolder holder:_servletHandler.getFilters())
- decorator.decorateFilterHolder(holder);
+ decorator.decorate(holder);
if(_servletHandler.getServlets()!=null)
for (ServletHolder holder:_servletHandler.getServlets())
- decorator.decorateServletHolder(holder);
+ decorator.decorate(holder);
}
}
@@ -574,14 +574,14 @@
void destroyServlet(Servlet servlet)
{
for (Decorator decorator : _decorators)
- decorator.destroyServletInstance(servlet);
+ decorator.destroy(servlet);
}
/* ------------------------------------------------------------ */
void destroyFilter(Filter filter)
{
for (Decorator decorator : _decorators)
- decorator.destroyFilterInstance(filter);
+ decorator.destroy(filter);
}
/* ------------------------------------------------------------ */
@@ -896,6 +896,9 @@
{
if (isStarted())
throw new IllegalStateException();
+
+ if (filterName == null || "".equals(filterName.trim()))
+ throw new IllegalStateException("Missing filter name");
if (!_enabled)
throw new UnsupportedOperationException();
@@ -930,6 +933,9 @@
{
if (isStarted())
throw new IllegalStateException();
+
+ if (filterName == null || "".equals(filterName.trim()))
+ throw new IllegalStateException("Missing filter name");
if (!_enabled)
throw new UnsupportedOperationException();
@@ -966,6 +972,9 @@
if (isStarted())
throw new IllegalStateException();
+ if (filterName == null || "".equals(filterName.trim()))
+ throw new IllegalStateException("Missing filter name");
+
if (!_enabled)
throw new UnsupportedOperationException();
@@ -1001,6 +1010,9 @@
if (!isStarting())
throw new IllegalStateException();
+ if (servletName == null || "".equals(servletName.trim()))
+ throw new IllegalStateException("Missing servlet name");
+
if (!_enabled)
throw new UnsupportedOperationException();
@@ -1036,6 +1048,9 @@
if (!isStarting())
throw new IllegalStateException();
+ if (servletName == null || "".equals(servletName.trim()))
+ throw new IllegalStateException("Missing servlet name");
+
if (!_enabled)
throw new UnsupportedOperationException();
@@ -1071,7 +1086,10 @@
{
if (!isStarting())
throw new IllegalStateException();
-
+
+ if (servletName == null || "".equals(servletName.trim()))
+ throw new IllegalStateException("Missing servlet name");
+
if (!_enabled)
throw new UnsupportedOperationException();
@@ -1115,19 +1133,10 @@
{
try
{
- T f = c.newInstance();
- for (int i=_decorators.size()-1; i>=0; i--)
- {
- Decorator decorator = _decorators.get(i);
- f=decorator.decorateFilterInstance(f);
- }
+ T f = createInstance(c);
return f;
}
- catch (InstantiationException e)
- {
- throw new ServletException(e);
- }
- catch (IllegalAccessException e)
+ catch (Exception e)
{
throw new ServletException(e);
}
@@ -1139,23 +1148,29 @@
{
try
{
- T s = c.newInstance();
- for (int i=_decorators.size()-1; i>=0; i--)
- {
- Decorator decorator = _decorators.get(i);
- s=decorator.decorateServletInstance(s);
- }
+ T s = createInstance(c);
return s;
}
- catch (InstantiationException e)
- {
- throw new ServletException(e);
- }
- catch (IllegalAccessException e)
+ catch (Exception e)
{
throw new ServletException(e);
}
}
+
+
+
+ public <T> T createInstance (Class<T> c) throws Exception
+ {
+ T o = super.createInstance(c);
+ for (int i=_decorators.size()-1; i>=0; i--)
+ {
+ Decorator decorator = _decorators.get(i);
+ o=decorator.decorate(o);
+ }
+ return o;
+ }
+
+
@Override
public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
@@ -1286,20 +1301,10 @@
{
try
{
- T l = super.createListener(clazz);
-
- for (int i=_decorators.size()-1; i>=0; i--)
- {
- Decorator decorator = _decorators.get(i);
- l=decorator.decorateListenerInstance(l);
- }
+ T l = createInstance(clazz);
return l;
- }
- catch(ServletException e)
- {
- throw e;
- }
- catch(Exception e)
+ }
+ catch (Exception e)
{
throw new ServletException(e);
}
@@ -1340,15 +1345,7 @@
*/
public interface Decorator
{
- <T extends Filter> T decorateFilterInstance(T filter) throws ServletException;
- <T extends Servlet> T decorateServletInstance(T servlet) throws ServletException;
- <T extends EventListener> T decorateListenerInstance(T listener) throws ServletException;
-
- void decorateFilterHolder(FilterHolder filter) throws ServletException;
- void decorateServletHolder(ServletHolder servlet) throws ServletException;
-
- void destroyServletInstance(Servlet s);
- void destroyFilterInstance(Filter f);
- void destroyListenerInstance(EventListener f);
+ <T> T decorate (T o);
+ void destroy (Object o);
}
}
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
index 4904ffc..4b990f2 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
@@ -24,6 +24,7 @@
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
@@ -45,6 +46,7 @@
import javax.servlet.ServletResponse;
import javax.servlet.ServletSecurityElement;
import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -109,6 +111,7 @@
private ServletHolder[] _servlets=new ServletHolder[0];
private ServletMapping[] _servletMappings;
+ private Map<String,ServletMapping> _servletPathMappings = new HashMap<String,ServletMapping>();
private final Map<String,FilterHolder> _filterNameMap= new HashMap<>();
private List<FilterMapping> _filterPathMappings;
@@ -145,7 +148,15 @@
}
updateNameMappings();
- updateMappings();
+ updateMappings();
+
+ if (getServletMapping("/")==null)
+ {
+ LOG.debug("Adding Default404Servlet to {}",this);
+ addServletWithMapping(Default404Servlet.class,"/");
+ updateMappings();
+ getServletMapping("/").setDefault(true);
+ }
if(_filterChainsCached)
{
@@ -278,6 +289,7 @@
_filterPathMappings=null;
_filterNameMappings=null;
_servletPathMap=null;
+ _servletPathMappings=null;
}
/* ------------------------------------------------------------ */
@@ -343,31 +355,26 @@
return _servletMappings;
}
+
+
/* ------------------------------------------------------------ */
/**
- * @return Returns the servletMappings.
+ * Get the ServletMapping matching the path
+ *
+ * @param pathSpec
+ * @return
*/
- public ServletMapping getServletMapping(String pattern)
+ public ServletMapping getServletMapping(String pathSpec)
{
- ServletMapping theMapping = null;
- if (_servletMappings!=null)
- {
- for (ServletMapping m:_servletMappings)
- {
- String[] paths=m.getPathSpecs();
- if (paths!=null)
- {
- for (String path:paths)
- {
- if (pattern.equals(path))
- theMapping = m;
- }
- }
- }
- }
- return theMapping;
+ if (pathSpec == null || _servletPathMappings == null)
+ return null;
+
+ return _servletPathMappings.get(pathSpec);
}
+
+
+
/* ------------------------------------------------------------ */
/** Get Servlets.
* @return Array of defined servlets
@@ -550,13 +557,6 @@
}
else
LOG.warn(th);
- while (th instanceof ServletException)
- {
- Throwable cause=((ServletException)th).getRootCause();
- if (cause==null)
- break;
- th=cause;
- }
}
else if (th instanceof EofException)
{
@@ -586,7 +586,7 @@
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
else
- LOG.debug("Response already committed for handling "+th);
+ LOG.debug("Response already committed",th);
}
catch(Error e)
{
@@ -915,9 +915,12 @@
{
setServletMappings(ArrayUtil.addToArray(getServletMappings(), mapping, ServletMapping.class));
}
-
- public Set<String> setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement) {
- if (_contextHandler != null) {
+
+ /* ------------------------------------------------------------ */
+ public Set<String> setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement)
+ {
+ if (_contextHandler != null)
+ {
return _contextHandler.setServletSecurity(registration, servletSecurityElement);
}
return Collections.emptySet();
@@ -1323,23 +1326,75 @@
else
{
PathMap<ServletHolder> pm = new PathMap<>();
-
- // update the maps
- for (ServletMapping servletmapping : _servletMappings)
+ Map<String,ServletMapping> servletPathMappings = new HashMap<String,ServletMapping>();
+
+ //create a map of paths to set of ServletMappings that define that mapping
+ HashMap<String, Set<ServletMapping>> sms = new HashMap<String, Set<ServletMapping>>();
+ for (ServletMapping servletMapping : _servletMappings)
{
- ServletHolder servlet_holder = _servletNameMap.get(servletmapping.getServletName());
- if (servlet_holder == null)
- throw new IllegalStateException("No such servlet: " + servletmapping.getServletName());
- else if (servlet_holder.isEnabled() && servletmapping.getPathSpecs() != null)
+ String[] pathSpecs = servletMapping.getPathSpecs();
+ if (pathSpecs != null)
{
- String[] pathSpecs = servletmapping.getPathSpecs();
for (String pathSpec : pathSpecs)
- if (pathSpec != null)
- pm.put(pathSpec, servlet_holder);
+ {
+ Set<ServletMapping> mappings = sms.get(pathSpec);
+ if (mappings == null)
+ {
+ mappings = new HashSet<ServletMapping>();
+ sms.put(pathSpec, mappings);
+ }
+ mappings.add(servletMapping);
+ }
}
}
+
+ //evaluate path to servlet map based on servlet mappings
+ for (String pathSpec : sms.keySet())
+ {
+ //for each path, look at the mappings where it is referenced
+ //if a mapping is for a servlet that is not enabled, skip it
+ Set<ServletMapping> mappings = sms.get(pathSpec);
+
+
+
+ ServletMapping finalMapping = null;
+ for (ServletMapping mapping : mappings)
+ {
+ //Get servlet associated with the mapping and check it is enabled
+ ServletHolder servlet_holder = _servletNameMap.get(mapping.getServletName());
+ if (servlet_holder == null)
+ throw new IllegalStateException("No such servlet: " + mapping.getServletName());
+ //if the servlet related to the mapping is not enabled, skip it from consideration
+ if (!servlet_holder.isEnabled())
+ continue;
+ //only accept a default mapping if we don't have any other
+ if (finalMapping == null)
+ finalMapping = mapping;
+ else
+ {
+ //already have a candidate - only accept another one if the candidate is a default
+ if (finalMapping.isDefault())
+ finalMapping = mapping;
+ else
+ {
+ //existing candidate isn't a default, if the one we're looking at isn't a default either, then its an error
+ if (!mapping.isDefault())
+ throw new IllegalStateException("Multiple servlets map to path: "+pathSpec);
+ }
+ }
+ }
+ if (finalMapping == null)
+ throw new IllegalStateException ("No acceptable servlet mappings for "+pathSpec);
+
+ if (LOG.isDebugEnabled()) LOG.debug("Chose path={} mapped to servlet={} from default={}", pathSpec, finalMapping.getServletName(), finalMapping.isDefault());
+
+ servletPathMappings.put(pathSpec, finalMapping);
+ pm.put(pathSpec,_servletNameMap.get(finalMapping.getServletName()));
+ }
+
_servletPathMap=pm;
+ _servletPathMappings=servletPathMappings;
}
// flush filter chain cache
@@ -1652,4 +1707,15 @@
_contextHandler.destroyFilter(filter);
}
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class Default404Servlet extends HttpServlet
+ {
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException
+ {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ }
}
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java
index c997399..2626da7 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java
@@ -98,7 +98,7 @@
/* ------------------------------------------------------------ */
/**
- * @param default1
+ * @param fromDefault
*/
public void setDefault(boolean fromDefault)
{
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java
index 8ffa98b..a1979fa 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java
@@ -60,7 +60,7 @@
//Get rid of references
purgeEntries(field);
- LOG.info("javax.el.BeanELResolver purged");
+ LOG.debug("javax.el.BeanELResolver purged");
}
catch (ClassNotFoundException e)
@@ -81,7 +81,7 @@
}
catch (NoSuchFieldException e)
{
- LOG.info("Not cleaning cached beans: no such field javax.el.BeanELResolver.properties");
+ LOG.debug("Not cleaning cached beans: no such field javax.el.BeanELResolver.properties");
}
}
@@ -113,14 +113,14 @@
while (itor.hasNext())
{
Class clazz = itor.next();
- LOG.info("Clazz: "+clazz+" loaded by "+clazz.getClassLoader());
+ LOG.debug("Clazz: "+clazz+" loaded by "+clazz.getClassLoader());
if (Thread.currentThread().getContextClassLoader().equals(clazz.getClassLoader()))
{
itor.remove();
- LOG.info("removed");
+ LOG.debug("removed");
}
else
- LOG.info("not removed: "+"contextclassloader="+Thread.currentThread().getContextClassLoader()+"clazz's classloader="+clazz.getClassLoader());
+ LOG.debug("not removed: "+"contextclassloader="+Thread.currentThread().getContextClassLoader()+"clazz's classloader="+clazz.getClassLoader());
}
}
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextListenersTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextListenersTest.java
index f0b4c5e..578a1a8 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextListenersTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextListenersTest.java
@@ -25,6 +25,7 @@
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicInteger;
+
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
index bcc6ae8..3aefeac 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
@@ -138,7 +138,7 @@
Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
Assert.assertEquals("error servlet","PathInfo= /IOE",br.readLine());
- Assert.assertEquals("error servlet","EXCEPTION: java.io.IOException: Test",br.readLine());
+ Assert.assertEquals("error servlet","EXCEPTION: org.eclipse.jetty.server.QuietServletException: java.io.IOException: Test",br.readLine());
}
@Test
@@ -159,7 +159,7 @@
br.readLine();// empty
Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
Assert.assertEquals("error servlet","PathInfo= /IOE",br.readLine());
- Assert.assertEquals("error servlet","EXCEPTION: java.io.IOException: Test",br.readLine());
+ Assert.assertEquals("error servlet","EXCEPTION: org.eclipse.jetty.server.QuietServletException: java.io.IOException: Test",br.readLine());
}
@Test
@@ -180,7 +180,7 @@
br.readLine();// empty
Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
Assert.assertEquals("error servlet","PathInfo= /IOE",br.readLine());
- Assert.assertEquals("error servlet","EXCEPTION: java.io.IOException: Test",br.readLine());
+ Assert.assertEquals("error servlet","EXCEPTION: org.eclipse.jetty.server.QuietServletException: java.io.IOException: Test",br.readLine());
}
@Test
@@ -415,7 +415,7 @@
Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
Assert.assertEquals("error servlet","PathInfo= /500",br.readLine());
- Assert.assertEquals("error servlet","EXCEPTION: java.io.IOException: TEST",br.readLine());
+ Assert.assertEquals("error servlet","EXCEPTION: java.lang.RuntimeException: TEST",br.readLine());
}
private class DispatchingRunnable implements Runnable
@@ -489,7 +489,7 @@
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
- throw new IOException("TEST");
+ throw new RuntimeException("TEST");
}
@Override
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java
new file mode 100644
index 0000000..62a48fd
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java
@@ -0,0 +1,260 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletException;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AsyncIOServletTest
+{
+ private Server server;
+ private ServerConnector connector;
+ private ServletContextHandler context;
+ private String path = "/path";
+
+ public void startServer(HttpServlet servlet) throws Exception
+ {
+ server = new Server();
+ connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ context = new ServletContextHandler(server, "", false, false);
+ ServletHolder holder = new ServletHolder(servlet);
+ holder.setAsyncSupported(true);
+ context.addServlet(holder, path);
+
+ server.start();
+ }
+
+ @After
+ public void stopServer() throws Exception
+ {
+ server.stop();
+ }
+
+ @Test
+ public void testAsyncReadThrowsException() throws Exception
+ {
+ testAsyncReadThrows(new NullPointerException("explicitly_thrown_by_test"));
+ }
+
+ @Test
+ public void testAsyncReadThrowsError() throws Exception
+ {
+ testAsyncReadThrows(new Error("explicitly_thrown_by_test"));
+ }
+
+ private void testAsyncReadThrows(final Throwable throwable) throws Exception
+ {
+ final CountDownLatch latch = new CountDownLatch(1);
+ startServer(new HttpServlet()
+ {
+ @Override
+ protected void service(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+ {
+ final AsyncContext asyncContext = request.startAsync(request, response);
+ request.getInputStream().setReadListener(new ReadListener()
+ {
+ @Override
+ public void onDataAvailable() throws IOException
+ {
+ if (throwable instanceof RuntimeException)
+ throw (RuntimeException)throwable;
+ if (throwable instanceof Error)
+ throw (Error)throwable;
+ throw new IOException(throwable);
+ }
+
+ @Override
+ public void onAllDataRead() throws IOException
+ {
+ }
+
+ @Override
+ public void onError(Throwable t)
+ {
+ Assert.assertSame(throwable, t);
+ latch.countDown();
+ response.setStatus(500);
+ asyncContext.complete();
+ }
+ });
+ }
+ });
+
+ String data = "0123456789";
+ String request = "GET " + path + " HTTP/1.1\r\n" +
+ "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+ "Content-Length: " + data.length() + "\r\n" +
+ "\r\n" +
+ data;
+
+ try (Socket client = new Socket("localhost", connector.getLocalPort()))
+ {
+ OutputStream output = client.getOutputStream();
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ SimpleHttpParser parser = new SimpleHttpParser();
+ SimpleHttpResponse response = parser.readResponse(new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")));
+
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+ Assert.assertEquals("500", response.getCode());
+ }
+ }
+
+ @Test
+ public void testOnErrorThrows() throws Exception
+ {
+ final AtomicInteger errors = new AtomicInteger();
+ startServer(new HttpServlet()
+ {
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ final AsyncContext asyncContext = request.startAsync(request, response);
+ request.getInputStream().setReadListener(new ReadListener()
+ {
+ @Override
+ public void onDataAvailable() throws IOException
+ {
+ throw new NullPointerException("explicitly_thrown_by_test_1");
+ }
+
+ @Override
+ public void onAllDataRead() throws IOException
+ {
+ }
+
+ @Override
+ public void onError(Throwable t)
+ {
+ errors.incrementAndGet();
+ throw new NullPointerException("explicitly_thrown_by_test_2");
+ }
+ });
+ }
+ });
+
+ String data = "0123456789";
+ String request = "GET " + path + " HTTP/1.1\r\n" +
+ "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+ "Content-Length: " + data.length() + "\r\n" +
+ "\r\n" +
+ data;
+
+ try (Socket client = new Socket("localhost", connector.getLocalPort()))
+ {
+ OutputStream output = client.getOutputStream();
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ SimpleHttpParser parser = new SimpleHttpParser();
+ SimpleHttpResponse response = parser.readResponse(new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")));
+
+ Assert.assertEquals("500", response.getCode());
+ Assert.assertEquals(1, errors.get());
+ }
+ }
+
+ @Test
+ public void testAsyncWriteThrowsException() throws Exception
+ {
+ testAsyncWriteThrows(new NullPointerException("explicitly_thrown_by_test"));
+ }
+
+ @Test
+ public void testAsyncWriteThrowsError() throws Exception
+ {
+ testAsyncWriteThrows(new Error("explicitly_thrown_by_test"));
+ }
+
+ private void testAsyncWriteThrows(final Throwable throwable) throws Exception
+ {
+ final CountDownLatch latch = new CountDownLatch(1);
+ startServer(new HttpServlet()
+ {
+ @Override
+ protected void service(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+ {
+ final AsyncContext asyncContext = request.startAsync(request, response);
+ response.getOutputStream().setWriteListener(new WriteListener()
+ {
+ @Override
+ public void onWritePossible() throws IOException
+ {
+ if (throwable instanceof RuntimeException)
+ throw (RuntimeException)throwable;
+ if (throwable instanceof Error)
+ throw (Error)throwable;
+ throw new IOException(throwable);
+ }
+
+ @Override
+ public void onError(Throwable t)
+ {
+ Assert.assertSame(throwable, t);
+ latch.countDown();
+ response.setStatus(500);
+ asyncContext.complete();
+ }
+ });
+ }
+ });
+
+ String request = "GET " + path + " HTTP/1.1\r\n" +
+ "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+ "\r\n";
+
+ try (Socket client = new Socket("localhost", connector.getLocalPort()))
+ {
+ OutputStream output = client.getOutputStream();
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ SimpleHttpParser parser = new SimpleHttpParser();
+ SimpleHttpResponse response = parser.readResponse(new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")));
+
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+ Assert.assertEquals("500", response.getCode());
+ }
+ }
+}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java
new file mode 100644
index 0000000..c6cd0a3
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java
@@ -0,0 +1,377 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+// TODO need these on SPDY as well!
+public class AsyncServletIOTest
+{
+ protected AsyncIOServlet _servlet=new AsyncIOServlet();
+ protected int _port;
+
+ protected Server _server = new Server();
+ protected ServletHandler _servletHandler;
+ protected ServerConnector _connector;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ HttpConfiguration http_config = new HttpConfiguration();
+ http_config.setOutputBufferSize(4096);
+ _connector = new ServerConnector(_server,new HttpConnectionFactory(http_config));
+
+ _server.setConnectors(new Connector[]{ _connector });
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SECURITY|ServletContextHandler.NO_SESSIONS);
+ context.setContextPath("/ctx");
+ _server.setHandler(context);
+ _servletHandler=context.getServletHandler();
+ ServletHolder holder=new ServletHolder(_servlet);
+ holder.setAsyncSupported(true);
+ _servletHandler.addServletWithMapping(holder,"/path/*");
+ _server.start();
+ _port=_connector.getLocalPort();
+
+ _owp.set(0);
+ _oda.set(0);
+ _read.set(0);
+ }
+
+ @After
+ public void tearDown() throws Exception
+ {
+ _server.stop();
+ }
+
+ @Test
+ public void testEmpty() throws Exception
+ {
+ process();
+ }
+
+ @Test
+ public void testWrite() throws Exception
+ {
+ process(10);
+ }
+
+ @Test
+ public void testWrites() throws Exception
+ {
+ process(10,1,20,10);
+ }
+
+ @Test
+ public void testWritesFlushWrites() throws Exception
+ {
+ process(10,1,0,20,10);
+ }
+
+ @Test
+ public void testBigWrite() throws Exception
+ {
+ process(102400);
+ }
+
+ @Test
+ public void testBigWrites() throws Exception
+ {
+ process(102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400);
+ Assert.assertThat(_owp.get(),Matchers.greaterThan(1));
+ }
+
+
+
+ @Test
+ public void testRead() throws Exception
+ {
+ process("Hello!!!\r\n");
+ }
+
+ @Test
+ public void testBigRead() throws Exception
+ {
+ process("Now is the time for all good men to come to the aid of the party. How now Brown Cow. The quick brown fox jumped over the lazy dog. The moon is blue to a fish in love.\r\n");
+ }
+
+ @Test
+ public void testReadWrite() throws Exception
+ {
+ process("Hello!!!\r\n",10);
+ }
+
+
+ protected void assertContains(String content,String response)
+ {
+ Assert.assertThat(response,Matchers.containsString(content));
+ }
+
+ protected void assertNotContains(String content,String response)
+ {
+ Assert.assertThat(response,Matchers.not(Matchers.containsString(content)));
+ }
+
+ public synchronized List<String> process(String content,int... writes) throws Exception
+ {
+ return process(content.getBytes("ISO-8859-1"),writes);
+ }
+
+ public synchronized List<String> process(int... writes) throws Exception
+ {
+ return process((byte[])null,writes);
+ }
+
+ public synchronized List<String> process(byte[] content, int... writes) throws Exception
+ {
+ StringBuilder request = new StringBuilder(512);
+ request.append("GET /ctx/path/info");
+ char s='?';
+ for (int w: writes)
+ {
+ request.append(s).append("w=").append(w);
+ s='&';
+ }
+
+ request.append(" HTTP/1.1\r\n")
+ .append("Host: localhost\r\n")
+ .append("Connection: close\r\n");
+
+ if (content!=null)
+ request.append("Content-Length: "+content.length+"\r\n")
+ .append("Content-Type: text/plain\r\n");
+
+ request.append("\r\n");
+
+ int port=_port;
+ List<String> list = new ArrayList<>();
+ try (Socket socket = new Socket("localhost",port);)
+ {
+ socket.setSoTimeout(1000000);
+ OutputStream out = socket.getOutputStream();
+ out.write(request.toString().getBytes("ISO-8859-1"));
+
+ if (content!=null && content.length>0)
+ {
+ Thread.sleep(100);
+ out.write(content[0]);
+ Thread.sleep(100);
+ int half=(content.length-1)/2;
+ out.write(content,1,half);
+ Thread.sleep(100);
+ out.write(content,1+half,content.length-half-1);
+ }
+
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()),102400);
+
+ // response line
+ String line = in.readLine();
+ //System.err.println("line: "+line);
+ Assert.assertThat(line,Matchers.startsWith("HTTP/1.1 200 OK"));
+
+ // Skip headers
+ while (line!=null)
+ {
+ line = in.readLine();
+ //System.err.println("line: "+line);
+ if (line.length()==0)
+ break;
+ }
+
+ // Get body slowly
+ while (true)
+ {
+ line = in.readLine();
+ if (line==null)
+ break;
+ //System.err.println("line: "+line.length()+"\t"+(line.length()>40?(line.substring(0,40)+"..."):line));
+ list.add(line);
+ }
+ }
+
+ // check lines
+ int w=0;
+ for (String line : list)
+ {
+ if ("-".equals(line))
+ continue;
+ assertEquals(writes[w],line.length());
+ assertEquals(line.charAt(0),'0'+(w%10));
+
+ w++;
+ if (w<writes.length && writes[w]<=0)
+ w++;
+ }
+
+ if (content!=null)
+ Assert.assertEquals(content.length,_read.get());
+
+ return list;
+ }
+
+ static AtomicInteger _owp = new AtomicInteger();
+ static AtomicInteger _oda = new AtomicInteger();
+ static AtomicInteger _read = new AtomicInteger();
+
+ private static class AsyncIOServlet extends HttpServlet
+ {
+ private static final long serialVersionUID = -8161977157098646562L;
+
+ public AsyncIOServlet()
+ {}
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+ {
+ final AsyncContext async = request.startAsync();
+ final AtomicInteger complete = new AtomicInteger(2);
+ final AtomicBoolean onDataAvailable = new AtomicBoolean(false);
+
+ // Asynchronous Read
+ if (request.getContentLength()>0)
+ {
+ // System.err.println("reading "+request.getContentLength());
+ final ServletInputStream in=request.getInputStream();
+ in.setReadListener(new ReadListener()
+ {
+ byte[] _buf=new byte[32];
+ @Override
+ public void onError(Throwable t)
+ {
+ if (complete.decrementAndGet()==0)
+ async.complete();
+ }
+
+ @Override
+ public void onDataAvailable() throws IOException
+ {
+ if (!onDataAvailable.compareAndSet(false,true))
+ throw new IllegalStateException();
+
+ // System.err.println("ODA");
+ while (in.isReady())
+ {
+ _oda.incrementAndGet();
+ int len=in.read(_buf);
+ // System.err.println("read "+len);
+ if (len>0)
+ _read.addAndGet(len);
+ }
+
+ if (!onDataAvailable.compareAndSet(true,false))
+ throw new IllegalStateException();
+
+ }
+
+ @Override
+ public void onAllDataRead() throws IOException
+ {
+ if (onDataAvailable.get())
+ {
+ System.err.println("OADR too early!");
+ _read.set(-1);
+ }
+
+ // System.err.println("OADR");
+ if (complete.decrementAndGet()==0)
+ async.complete();
+ }
+ });
+ }
+ else
+ complete.decrementAndGet();
+
+
+ // Asynchronous Write
+ final String[] writes = request.getParameterValues("w");
+ final ServletOutputStream out = response.getOutputStream();
+ out.setWriteListener(new WriteListener()
+ {
+ int _w=0;
+
+ @Override
+ public void onWritePossible() throws IOException
+ {
+ //System.err.println("OWP");
+ _owp.incrementAndGet();
+
+ while (writes!=null && _w< writes.length)
+ {
+ int write=Integer.valueOf(writes[_w++]);
+
+ if (write==0)
+ out.flush();
+ else
+ {
+ byte[] buf=new byte[write+1];
+ Arrays.fill(buf,(byte)('0'+((_w-1)%10)));
+ buf[write]='\n';
+ out.write(buf);
+ }
+
+ if (!out.isReady())
+ return;
+ }
+
+ if (complete.decrementAndGet()==0)
+ async.complete();
+ }
+
+ @Override
+ public void onError(Throwable t)
+ {
+ async.complete();
+ }
+ });
+ }
+ }
+}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletLongPollTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletLongPollTest.java
index 383d059..9f43eec 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletLongPollTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletLongPollTest.java
@@ -26,6 +26,7 @@
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
index 6b707fc..ed5348c 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.servlet;
+import static org.junit.Assert.assertEquals;
+
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
@@ -26,6 +28,7 @@
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
+
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
@@ -42,6 +45,7 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.hamcrest.Matchers;
@@ -49,10 +53,10 @@
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
+import org.junit.runner.RunWith;
+@RunWith(AdvancedRunner.class)
public class AsyncServletTest
{
protected AsyncServlet _servlet=new AsyncServlet();
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletRangesTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletRangesTest.java
index aa0c366..ca348b6 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletRangesTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletRangesTest.java
@@ -33,7 +33,6 @@
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java
index 7c2d2d1..21da492 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java
@@ -37,7 +37,7 @@
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
-import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
@@ -47,7 +47,6 @@
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
@@ -761,16 +760,16 @@
response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+last_modified+"\r\n\r\n");
assertResponseContains("304", response);
- response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+HttpFields.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n");
+ response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+DateGenerator.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n");
assertResponseContains("200", response);
- response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+HttpFields.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n");
+ response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+DateGenerator.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n");
assertResponseContains("304", response);
- response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+HttpFields.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n");
+ response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+DateGenerator.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n");
assertResponseContains("200", response);
- response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+HttpFields.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n");
+ response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+DateGenerator.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n");
assertResponseContains("412", response);
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java
index df2aa5f..5c48129 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java
@@ -18,10 +18,11 @@
package org.eclipse.jetty.servlet;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.Arrays;
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
index ba13937..59973ed 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.servlet;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.io.IOException;
@@ -36,7 +35,6 @@
import org.eclipse.jetty.util.log.StdErrLog;
import org.hamcrest.Matchers;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -102,8 +100,8 @@
assertThat(response,Matchers.containsString("HTTP/1.1 500 Server Error"));
assertThat(response,Matchers.containsString("ERROR_PAGE: /TestException"));
assertThat(response,Matchers.containsString("ERROR_CODE: 500"));
- assertThat(response,Matchers.containsString("ERROR_EXCEPTION: java.lang.IllegalStateException"));
- assertThat(response,Matchers.containsString("ERROR_EXCEPTION_TYPE: class java.lang.IllegalStateException"));
+ assertThat(response,Matchers.containsString("ERROR_EXCEPTION: javax.servlet.ServletException: java.lang.IllegalStateException"));
+ assertThat(response,Matchers.containsString("ERROR_EXCEPTION_TYPE: class javax.servlet.ServletException"));
assertThat(response,Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$FailServlet-"));
assertThat(response,Matchers.containsString("ERROR_REQUEST_URI: /fail/exception"));
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestHeadersTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestHeadersTest.java
index ae5250c..336bbd7 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestHeadersTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestHeadersTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.servlet;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.io.IOException;
import java.io.InputStream;
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java
index f717434..8e0f506 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java
@@ -27,7 +27,6 @@
import java.io.IOException;
import java.io.PrintWriter;
-import java.net.MalformedURLException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -48,9 +47,7 @@
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.server.session.SessionHandler;
-import org.eclipse.jetty.util.resource.Resource;
import org.hamcrest.Matchers;
-import org.hamcrest.core.IsEqual;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
diff --git a/jetty-servlet/src/test/resources/jetty-logging.properties b/jetty-servlet/src/test/resources/jetty-logging.properties
index 50680ef..3e0d7b7 100644
--- a/jetty-servlet/src/test/resources/jetty-logging.properties
+++ b/jetty-servlet/src/test/resources/jetty-logging.properties
@@ -1,3 +1,4 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG
+#org.eclipse.jetty.server.LEVEL=DEBUG
#org.eclipse.jetty.servlet.LEVEL=DEBUG
diff --git a/jetty-servlets/pom.xml b/jetty-servlets/pom.xml
index 011f652..44e69c3 100644
--- a/jetty-servlets/pom.xml
+++ b/jetty-servlets/pom.xml
@@ -3,7 +3,7 @@
<parent>
<artifactId>jetty-project</artifactId>
<groupId>org.eclipse.jetty</groupId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-servlets</artifactId>
@@ -26,7 +26,7 @@
</goals>
<configuration>
<instructions>
- <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
+ <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",*</Import-Package>
</instructions>
</configuration>
</execution>
@@ -45,6 +45,23 @@
</configuration>
</plugin>
<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <descriptorRef>config</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
@@ -76,16 +93,16 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-io</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jmx</artifactId>
<version>${project.version}</version>
diff --git a/jetty-servlets/src/main/config/modules/servlets.mod b/jetty-servlets/src/main/config/modules/servlets.mod
new file mode 100644
index 0000000..e8724b8
--- /dev/null
+++ b/jetty-servlets/src/main/config/modules/servlets.mod
@@ -0,0 +1,10 @@
+#
+# Jetty Servlets Module
+#
+
+[depend]
+servlet
+
+[lib]
+lib/jetty-servlets-${jetty.version}.jar
+
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java
index 1ef2cf3..8075646 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java
@@ -30,6 +30,7 @@
import java.util.Locale;
import java.util.Map;
+import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -304,7 +305,7 @@
LOG.debug("Environment: " + env.getExportString());
LOG.debug("Command: " + execCmd);
- Process p;
+ final Process p;
if (dir == null)
p = Runtime.getRuntime().exec(execCmd, env.getEnvArray());
else
@@ -316,13 +317,28 @@
else if (len > 0)
writeProcessInput(p, req.getInputStream(), len);
- IO.copyThread(p.getErrorStream(), System.err);
-
// hook processes output to browser's input (sync)
// if browser closes stream, we should detect it and kill process...
OutputStream os = null;
+ AsyncContext async=req.startAsync();
try
{
+ async.start(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ IO.copy(p.getErrorStream(), System.err);
+ }
+ catch (IOException e)
+ {
+ LOG.warn(e);
+ }
+ }
+ });
+
// read any headers off the top of our input stream
// NOTE: Multiline header items not supported!
String line = null;
@@ -398,6 +414,7 @@
}
p.destroy();
// LOG.debug("CGI: terminated!");
+ async.complete();
}
}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DataRateLimitedServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DataRateLimitedServlet.java
new file mode 100644
index 0000000..6a9855a
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DataRateLimitedServlet.java
@@ -0,0 +1,315 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel.MapMode;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.HttpOutput;
+
+/**
+ * A servlet that uses the Servlet 3.1 asynchronous IO API to server
+ * static content at a limited data rate.
+ * <p>
+ * Two implementations are supported: <ul>
+ * <li>The <code>StandardDataStream</code> impl uses only standard
+ * APIs, but produces more garbage due to the byte[] nature of the API.
+ * <li>the <code>JettyDataStream</code> impl uses a Jetty API to write a ByteBuffer
+ * and thus allow the efficient use of file mapped buffers without any
+ * temporary buffer copies (I did tell the JSR that this was a good idea to
+ * have in the standard!).
+ * </ul>
+ * <p>
+ * The data rate is controlled by setting init parameters:
+ * <dl>
+ * <dt>buffersize</dt><dd>The amount of data in bytes written per write</dd>
+ * <dt>pause</dt><dd>The period in ms to wait after a write before attempting another</dd>
+ * <dt>pool</dt><dd>The size of the thread pool used to service the writes (defaults to available processors)</dd>
+ * </dl>
+ * Thus if buffersize = 1024 and pause = 100, the data rate will be limited to 10KB per second.
+ */
+public class DataRateLimitedServlet extends HttpServlet
+{
+ private static final long serialVersionUID = -4771757707068097025L;
+ private int buffersize=8192;
+ private int pause=100;
+ ScheduledThreadPoolExecutor scheduler;
+ private final ConcurrentHashMap<String, ByteBuffer> cache=new ConcurrentHashMap<>();
+
+ @Override
+ public void init() throws ServletException
+ {
+ // read the init params
+ String tmp = getInitParameter("buffersize");
+ if (tmp!=null)
+ buffersize=Integer.parseInt(tmp);
+ tmp = getInitParameter("pause");
+ if (tmp!=null)
+ pause=Integer.parseInt(tmp);
+ tmp = getInitParameter("pool");
+ int pool=tmp==null?Runtime.getRuntime().availableProcessors():Integer.parseInt(tmp);
+
+ // Create and start a shared scheduler.
+ scheduler=new ScheduledThreadPoolExecutor(pool);
+ }
+
+ @Override
+ public void destroy()
+ {
+ scheduler.shutdown();
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ // Get the path of the static resource to serve.
+ String info=request.getPathInfo();
+
+ // We don't handle directories
+ if (info.endsWith("/"))
+ {
+ response.sendError(503,"directories not supported");
+ return;
+ }
+
+ // Set the mime type of the response
+ String content_type=getServletContext().getMimeType(info);
+ response.setContentType(content_type==null?"application/x-data":content_type);
+
+ // Look for a matching file path
+ String path = request.getPathTranslated();
+
+ // If we have a file path and this is a jetty response, we can use the JettyStream impl
+ ServletOutputStream out = response.getOutputStream();
+ if (path != null && out instanceof HttpOutput)
+ {
+ // If the file exists
+ File file = new File(path);
+ if (file.exists() && file.canRead())
+ {
+ // Set the content length
+ response.setContentLengthLong(file.length());
+
+ // Look for a file mapped buffer in the cache
+ ByteBuffer mapped=cache.get(path);
+
+ // Handle cache miss
+ if (mapped==null)
+ {
+ // TODO implement LRU cache flush
+ try (RandomAccessFile raf = new RandomAccessFile(file, "r"))
+ {
+ ByteBuffer buf = raf.getChannel().map(MapMode.READ_ONLY,0,raf.length());
+ mapped=cache.putIfAbsent(path,buf);
+ if (mapped==null)
+ mapped=buf;
+ }
+ }
+
+ // start async request handling
+ AsyncContext async=request.startAsync();
+
+ // Set a JettyStream as the write listener to write the content asynchronously.
+ out.setWriteListener(new JettyDataStream(mapped,async,out));
+ return;
+ }
+ }
+
+ // Jetty API was not used, so lets try the standards approach
+
+ // Can we find the content as an input stream
+ InputStream content = getServletContext().getResourceAsStream(info);
+ if (content==null)
+ {
+ response.sendError(404);
+ return;
+ }
+
+ // Set a StandardStream as he write listener to write the content asynchronously
+ out.setWriteListener(new StandardDataStream(content,request.startAsync(),out));
+ }
+
+ /**
+ * A standard API Stream writer
+ */
+ private final class StandardDataStream implements WriteListener, Runnable
+ {
+ private final InputStream content;
+ private final AsyncContext async;
+ private final ServletOutputStream out;
+
+ private StandardDataStream(InputStream content, AsyncContext async, ServletOutputStream out)
+ {
+ this.content = content;
+ this.async = async;
+ this.out = out;
+ }
+
+ @Override
+ public void onWritePossible() throws IOException
+ {
+ // If we are able to write
+ if(out.isReady())
+ {
+ // Allocated a copy buffer for each write, so as to not hold while paused
+ // TODO put these buffers into a pool
+ byte[] buffer = new byte[buffersize];
+
+ // read some content into the copy buffer
+ int len=content.read(buffer);
+
+ // If we are at EOF
+ if (len<0)
+ {
+ // complete the async lifecycle
+ async.complete();
+ return;
+ }
+
+ // write out the copy buffer. This will be an asynchronous write
+ // and will always return immediately without blocking. If a subsequent
+ // call to out.isReady() returns false, then this onWritePossible method
+ // will be called back when a write is possible.
+ out.write(buffer,0,len);
+
+ // Schedule a timer callback to pause writing. Because isReady() is not called,
+ // a onWritePossible callback is no scheduled.
+ scheduler.schedule(this,pause,TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ // When the pause timer wakes up, call onWritePossible. Either isReady() will return
+ // true and another chunk of content will be written, or it will return false and the
+ // onWritePossible() callback will be scheduled when a write is next possible.
+ onWritePossible();
+ }
+ catch(Exception e)
+ {
+ onError(e);
+ }
+ }
+
+ @Override
+ public void onError(Throwable t)
+ {
+ getServletContext().log("Async Error",t);
+ async.complete();
+ }
+ }
+
+
+ /**
+ * A Jetty API DataStream
+ *
+ */
+ private final class JettyDataStream implements WriteListener, Runnable
+ {
+ private final ByteBuffer content;
+ private final int limit;
+ private final AsyncContext async;
+ private final HttpOutput out;
+
+ private JettyDataStream(ByteBuffer content, AsyncContext async, ServletOutputStream out)
+ {
+ // Make a readonly copy of the passed buffer. This uses the same underlying content
+ // without a copy, but gives this instance its own position and limit.
+ this.content = content.asReadOnlyBuffer();
+ // remember the ultimate limit.
+ this.limit=this.content.limit();
+ this.async = async;
+ this.out = (HttpOutput)out;
+ }
+
+ @Override
+ public void onWritePossible() throws IOException
+ {
+ // If we are able to write
+ if(out.isReady())
+ {
+ // Position our buffers limit to allow only buffersize bytes to be written
+ int l=content.position()+buffersize;
+ // respect the ultimate limit
+ if (l>limit)
+ l=limit;
+ content.limit(l);
+
+ // if all content has been written
+ if (!content.hasRemaining())
+ {
+ // complete the async lifecycle
+ async.complete();
+ return;
+ }
+
+ // write our limited buffer. This will be an asynchronous write
+ // and will always return immediately without blocking. If a subsequent
+ // call to out.isReady() returns false, then this onWritePossible method
+ // will be called back when a write is possible.
+ out.write(content);
+
+ // Schedule a timer callback to pause writing. Because isReady() is not called,
+ // a onWritePossible callback is no scheduled.
+ scheduler.schedule(this,pause,TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ // When the pause timer wakes up, call onWritePossible. Either isReady() will return
+ // true and another chunk of content will be written, or it will return false and the
+ // onWritePossible() callback will be scheduled when a write is next possible.
+ onWritePossible();
+ }
+ catch(Exception e)
+ {
+ onError(e);
+ }
+ }
+
+ @Override
+ public void onError(Throwable t)
+ {
+ getServletContext().log("Async Error",t);
+ async.complete();
+ }
+ }
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
index 047f599..0c10dcd 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
@@ -31,6 +31,7 @@
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
@@ -56,7 +57,8 @@
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.Timeout;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
/**
* Denial of Service filter
@@ -184,12 +186,9 @@
private ContinuationListener[] _listeners;
private final ConcurrentHashMap<String, RateTracker> _rateTrackers = new ConcurrentHashMap<>();
private final List<String> _whitelist = new CopyOnWriteArrayList<>();
- private final Timeout _requestTimeoutQ = new Timeout();
- private final Timeout _trackerTimeoutQ = new Timeout();
- private Thread _timerThread;
- private volatile boolean _running;
+ private Scheduler _scheduler;
- public void init(FilterConfig filterConfig)
+ public void init(FilterConfig filterConfig) throws ServletException
{
_context = filterConfig.getServletContext();
@@ -275,47 +274,26 @@
parameter = filterConfig.getInitParameter(ENABLED_INIT_PARAM);
setEnabled(parameter == null || Boolean.parseBoolean(parameter));
- _requestTimeoutQ.setNow();
- _requestTimeoutQ.setDuration(_maxRequestMs);
-
- _trackerTimeoutQ.setNow();
- _trackerTimeoutQ.setDuration(_maxIdleTrackerMs);
-
- _running = true;
- _timerThread = (new Thread()
- {
- public void run()
- {
- try
- {
- while (_running)
- {
- long now = _requestTimeoutQ.setNow();
- _requestTimeoutQ.tick();
- _trackerTimeoutQ.setNow(now);
- _trackerTimeoutQ.tick();
- try
- {
- Thread.sleep(100);
- }
- catch (InterruptedException e)
- {
- LOG.ignore(e);
- }
- }
- }
- finally
- {
- LOG.debug("DoSFilter timer exited");
- }
- }
- });
- _timerThread.start();
+ _scheduler = startScheduler();
if (_context != null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM)))
_context.setAttribute(filterConfig.getFilterName(), this);
}
+ protected Scheduler startScheduler() throws ServletException
+ {
+ try
+ {
+ Scheduler result = new ScheduledExecutorScheduler();
+ result.start();
+ return result;
+ }
+ catch (Exception x)
+ {
+ throw new ServletException(x);
+ }
+ }
+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
{
doFilter((HttpServletRequest)request, (HttpServletResponse)response, filterChain);
@@ -329,8 +307,6 @@
return;
}
- final long now = _requestTimeoutQ.getNow();
-
// Look for the rate tracker for this request
RateTracker tracker = (RateTracker)request.getAttribute(__TRACKER);
@@ -342,7 +318,7 @@
tracker = getRateTracker(request);
// Calculate the rate and check it is over the allowed limit
- final boolean overRateLimit = tracker.isRateExceeded(now);
+ final boolean overRateLimit = tracker.isRateExceeded(System.currentTimeMillis());
// pass it through if we are not currently over the rate limit
if (!overRateLimit)
@@ -466,23 +442,23 @@
protected void doFilterChain(FilterChain chain, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException
{
final Thread thread = Thread.currentThread();
-
- final Timeout.Task requestTimeout = new Timeout.Task()
+ Runnable requestTimeout = new Runnable()
{
- public void expired()
+ @Override
+ public void run()
{
closeConnection(request, response, thread);
}
};
+ Scheduler.Task task = _scheduler.schedule(requestTimeout, getMaxRequestMs(), TimeUnit.MILLISECONDS);
try
{
- _requestTimeoutQ.schedule(requestTimeout);
chain.doFilter(request, response);
}
finally
{
- requestTimeout.cancel();
+ task.cancel();
}
}
@@ -573,14 +549,14 @@
}
else
{
- if (_trackSessions && session != null && !session.isNew())
+ if (isTrackSessions() && session != null && !session.isNew())
{
loadId = session.getId();
type = USER_SESSION;
}
else
{
- loadId = _remotePort ? (request.getRemoteAddr() + request.getRemotePort()) : request.getRemoteAddr();
+ loadId = isRemotePort() ? (request.getRemoteAddr() + request.getRemotePort()) : request.getRemoteAddr();
type = USER_IP;
}
}
@@ -590,16 +566,17 @@
if (tracker == null)
{
boolean allowed = checkWhitelist(_whitelist, request.getRemoteAddr());
- tracker = allowed ? new FixedRateTracker(loadId, type, _maxRequestsPerSec)
- : new RateTracker(loadId, type, _maxRequestsPerSec);
+ int maxRequestsPerSec = getMaxRequestsPerSec();
+ tracker = allowed ? new FixedRateTracker(loadId, type, maxRequestsPerSec)
+ : new RateTracker(loadId, type, maxRequestsPerSec);
RateTracker existing = _rateTrackers.putIfAbsent(loadId, tracker);
if (existing != null)
tracker = existing;
if (type == USER_IP)
{
- // USER_IP expiration from _rateTrackers is handled by the _trackerTimeoutQ
- _trackerTimeoutQ.schedule(tracker);
+ // USER_IP expiration from _rateTrackers is handled by the _scheduler
+ _scheduler.schedule(tracker, getMaxIdleTrackerMs(), TimeUnit.MILLISECONDS);
}
else if (session != null)
{
@@ -722,14 +699,23 @@
public void destroy()
{
LOG.debug("Destroy {}",this);
- _running = false;
- _timerThread.interrupt();
- _requestTimeoutQ.cancelAll();
- _trackerTimeoutQ.cancelAll();
+ stopScheduler();
_rateTrackers.clear();
_whitelist.clear();
}
+ protected void stopScheduler()
+ {
+ try
+ {
+ _scheduler.stop();
+ }
+ catch (Exception x)
+ {
+ LOG.ignore(x);
+ }
+ }
+
/**
* Returns the user id, used to track this connection.
* This SHOULD be overridden by subclasses.
@@ -1055,7 +1041,7 @@
*
* @param address the address to remove
* @return whether the address was removed from the list
- * @see #addWhitelistAddress(List, String)
+ * @see #addWhitelistAddress(String)
*/
@ManagedOperation("removes an IP address that will not be rate limited")
public boolean removeWhitelistAddress(@Name("address") String address)
@@ -1067,7 +1053,7 @@
* A RateTracker is associated with a connection, and stores request rate
* data.
*/
- class RateTracker extends Timeout.Task implements HttpSessionBindingListener, HttpSessionActivationListener, Serializable
+ class RateTracker implements Runnable, HttpSessionBindingListener, HttpSessionActivationListener, Serializable
{
private static final long serialVersionUID = 3534663738034577872L;
@@ -1138,15 +1124,15 @@
LOG.warn("Unexpected session activation");
}
- public void expired()
+ @Override
+ public void run()
{
- long now = _trackerTimeoutQ.getNow();
int latestIndex = _next == 0 ? (_timestamps.length - 1) : (_next - 1);
long last = _timestamps[latestIndex];
- boolean hasRecentRequest = last != 0 && (now - last) < 1000L;
+ boolean hasRecentRequest = last != 0 && (System.currentTimeMillis() - last) < 1000L;
if (hasRecentRequest)
- reschedule();
+ _scheduler.schedule(this, getMaxIdleTrackerMs(), TimeUnit.MILLISECONDS);
else
_rateTrackers.remove(_id);
}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java
index 5693dd1..6e15fa1 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java
@@ -352,7 +352,6 @@
{
if (request.isAsyncStarted())
{
-
request.getAsyncContext().addListener(new FinishOnCompleteListener(wrappedResponse));
}
else if (exceptional && !response.isCommitted())
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java
index fe805de..643246b 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java
@@ -34,7 +34,6 @@
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.UncheckedPrintWriter;
-import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.servlets.gzip.AbstractCompressedStream;
import org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper;
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
index 8501373..1a057cb 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
@@ -47,13 +47,11 @@
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.Part;
-import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.MultiPartInputStreamParser;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/AbstractCompressedStream.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/AbstractCompressedStream.java
index c27a272..56590d6 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/AbstractCompressedStream.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/AbstractCompressedStream.java
@@ -26,6 +26,7 @@
import java.util.zip.DeflaterOutputStream;
import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -342,17 +343,11 @@
}
}
- /**
- * @see org.eclipse.jetty.servlets.gzip.CompressedStream#getOutputStream()
- */
public OutputStream getOutputStream()
{
return _out;
}
- /**
- * @see org.eclipse.jetty.http.gzip.CompressedStream#isClosed()
- */
public boolean isClosed()
{
return _closed;
@@ -376,6 +371,21 @@
_response.setHeader(name, value);
}
+ @Override
+ public void setWriteListener(WriteListener writeListener)
+ {
+ // TODO 3.1 Auto-generated method stub
+
+ }
+
+
+ @Override
+ public boolean isReady()
+ {
+ // TODO 3.1 Auto-generated method stub
+ return false;
+ }
+
/**
* Create the stream fitting to the underlying compression type.
*
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java
index 4c4e9cd..516c690 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java
@@ -24,6 +24,8 @@
import static org.junit.Assert.assertTrue;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.EnumSet;
@@ -113,35 +115,37 @@
private String doRequests(String loopRequests, int loops, long pauseBetweenLoops, long pauseBeforeLast, String lastRequest) throws Exception
{
- Socket socket = new Socket(_host, _port);
- socket.setSoTimeout(30000);
-
- for (int i=loops;i-->0;)
+ try (Socket socket = new Socket(_host,_port))
{
- socket.getOutputStream().write(loopRequests.getBytes(StandardCharsets.UTF_8));
- socket.getOutputStream().flush();
- if (i>0 && pauseBetweenLoops>0)
- Thread.sleep(pauseBetweenLoops);
- }
- if (pauseBeforeLast>0)
- Thread.sleep(pauseBeforeLast);
- socket.getOutputStream().write(lastRequest.getBytes(StandardCharsets.UTF_8));
- socket.getOutputStream().flush();
+ socket.setSoTimeout(30000);
+ OutputStream out = socket.getOutputStream();
- String response;
- if (loopRequests.contains("/unresponsive"))
- {
- // don't read in anything, forcing the request to time out
- Thread.sleep(_requestMaxTime * 2);
- response = IO.toString(socket.getInputStream(),StandardCharsets.UTF_8);
+ for (int i = loops; i-- > 0;)
+ {
+ out.write(loopRequests.getBytes(StandardCharsets.UTF_8));
+ out.flush();
+ if (i > 0 && pauseBetweenLoops > 0)
+ {
+ Thread.sleep(pauseBetweenLoops);
+ }
+ }
+ if (pauseBeforeLast > 0)
+ {
+ Thread.sleep(pauseBeforeLast);
+ }
+ out.write(lastRequest.getBytes(StandardCharsets.UTF_8));
+ out.flush();
+
+ InputStream in = socket.getInputStream();
+ if (loopRequests.contains("/unresponsive"))
+ {
+ // don't read in anything, forcing the request to time out
+ Thread.sleep(_requestMaxTime * 2);
+ }
+ String response = IO.toString(in,StandardCharsets.UTF_8);
+ return response;
}
- else
- {
- response = IO.toString(socket.getInputStream(),StandardCharsets.UTF_8);
- }
- socket.close();
- return response;
}
private int count(String responses,String substring)
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DataRateLimitedServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DataRateLimitedServletTest.java
new file mode 100644
index 0000000..c3d7d24
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DataRateLimitedServletTest.java
@@ -0,0 +1,109 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertThat;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.util.resource.Resource;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class DataRateLimitedServletTest
+{
+ public static final int BUFFER=8192;
+ public static final int PAUSE=10;
+
+ @Rule
+ public TestingDir testdir = new TestingDir();
+
+ private Server server;
+ private LocalConnector connector;
+ private ServletContextHandler context;
+
+ @Before
+ public void init() throws Exception
+ {
+ server = new Server();
+
+ connector = new LocalConnector(server);
+ connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
+
+ context = new ServletContextHandler();
+
+ context.setContextPath("/context");
+ context.setWelcomeFiles(new String[]{"index.html", "index.jsp", "index.htm"});
+ context.setBaseResource(Resource.newResource(testdir.getEmptyDir()));
+
+ ServletHolder holder =context.addServlet(DataRateLimitedServlet.class,"/stream/*");
+ holder.setInitParameter("buffersize",""+BUFFER);
+ holder.setInitParameter("pause",""+PAUSE);
+ server.setHandler(context);
+ server.addConnector(connector);
+
+ server.start();
+ }
+
+ @After
+ public void destroy() throws Exception
+ {
+ server.stop();
+ server.join();
+ }
+
+ @Test
+ public void testStream() throws Exception
+ {
+ File content = testdir.getFile("content.txt");
+ try(OutputStream out = new FileOutputStream(content);)
+ {
+ byte[] b= new byte[1024];
+
+ for (int i=1024;i-->0;)
+ {
+ Arrays.fill(b,(byte)('0'+(i%10)));
+ out.write(b);
+ out.write('\n');
+ }
+ }
+
+ long start=System.currentTimeMillis();
+ String response = connector.getResponses("GET /context/stream/content.txt HTTP/1.0\r\n\r\n");
+ long duration=System.currentTimeMillis()-start;
+
+ assertThat(response.length(),greaterThan(1024*1024));
+ assertThat(response,containsString("200 OK"));
+ assertThat(duration,greaterThan(PAUSE*1024L*1024/BUFFER));
+
+ }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterJMXTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterJMXTest.java
index 14a6157..f785ac0 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterJMXTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterJMXTest.java
@@ -21,6 +21,7 @@
import java.lang.management.ManagementFactory;
import java.util.EnumSet;
import java.util.Set;
+
import javax.management.Attribute;
import javax.management.MBeanServer;
import javax.management.ObjectName;
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java
index 31c00d4..dcac74c 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java
@@ -18,8 +18,12 @@
package org.eclipse.jetty.servlets;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import java.util.ArrayList;
import java.util.List;
+
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -30,9 +34,6 @@
import org.junit.BeforeClass;
import org.junit.Test;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
public class DoSFilterTest extends AbstractDoSFilterTest
{
private static final Logger LOG = Log.getLogger(DoSFilterTest.class);
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/EventSourceServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/EventSourceServletTest.java
index 75fc4b7..78f0649 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/EventSourceServletTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/EventSourceServletTest.java
@@ -28,6 +28,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
+
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.NetworkConnector;
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
index ba43429..8dcac2c 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
@@ -41,12 +41,10 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletTester;
import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java
index 1cf6f10..b4e9554 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java
@@ -18,8 +18,13 @@
package org.eclipse.jetty.servlets.gzip;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -42,7 +47,7 @@
import javax.servlet.Servlet;
import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.servlet.FilterHolder;
@@ -98,7 +103,7 @@
request.setHeader("Host","tester");
request.setHeader("Accept-Encoding",compressionType);
if (ifmodifiedsince>0)
- request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),HttpFields.formatDate(ifmodifiedsince));
+ request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),DateGenerator.formatDate(ifmodifiedsince));
if (this.userAgent != null)
request.setHeader("User-Agent", this.userAgent);
request.setURI("/context/" + requestedFilename);
@@ -176,7 +181,7 @@
request.setHeader("Host","tester");
request.setHeader("Accept-Encoding",compressionType);
if (ifmodifiedsince>0)
- request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),HttpFields.formatDate(ifmodifiedsince));
+ request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),DateGenerator.formatDate(ifmodifiedsince));
if (this.userAgent != null)
request.setHeader("User-Agent", this.userAgent);
request.setURI("/context/" + requestedFilename);
diff --git a/jetty-spdy/pom.xml b/jetty-spdy/pom.xml
index eb05b15..13eb126 100644
--- a/jetty-spdy/pom.xml
+++ b/jetty-spdy/pom.xml
@@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -17,7 +17,9 @@
<module>spdy-core</module>
<module>spdy-client</module>
<module>spdy-server</module>
+ <module>spdy-http-common</module>
<module>spdy-http-server</module>
+ <module>spdy-http-client-transport</module>
<module>spdy-example-webapp</module>
</modules>
@@ -74,7 +76,7 @@
</goals>
<configuration>
<instructions>
- <Export-Package>org.eclipse.jetty.spdy.*;version="9.0"</Export-Package>
+ <Export-Package>org.eclipse.jetty.spdy.*;version="9.1"</Export-Package>
<Import-Package>org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
<_nouses>true</_nouses>
</instructions>
diff --git a/jetty-spdy/spdy-client/pom.xml b/jetty-spdy/spdy-client/pom.xml
index 3545363..56e0d0d 100644
--- a/jetty-spdy/spdy-client/pom.xml
+++ b/jetty-spdy/spdy-client/pom.xml
@@ -3,12 +3,12 @@
<parent>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spdy-client</artifactId>
- <name>Jetty :: SPDY :: Jetty Client Binding</name>
+ <name>Jetty :: SPDY :: Client Binding</name>
<properties>
<bundle-symbolic-name>${project.groupId}.client</bundle-symbolic-name>
@@ -58,7 +58,7 @@
</goals>
<configuration>
<instructions>
- <Export-Package>org.eclipse.jetty.spdy.client;version="9.0"</Export-Package>
+ <Export-Package>org.eclipse.jetty.spdy.client;version="9.1"</Export-Package>
<Import-Package>!org.eclipse.jetty.npn,org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
</instructions>
</configuration>
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnection.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnection.java
new file mode 100644
index 0000000..7374d57
--- /dev/null
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnection.java
@@ -0,0 +1,140 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.npn.NextProtoNego;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class NPNClientConnection extends AbstractConnection implements NextProtoNego.ClientProvider
+{
+ private final Logger LOG = Log.getLogger(getClass());
+ private final SPDYClient client;
+ private final ClientConnectionFactory connectionFactory;
+ private final SSLEngine engine;
+ private final Map<String, Object> context;
+ private volatile boolean completed;
+
+ public NPNClientConnection(EndPoint endPoint, SPDYClient client, ClientConnectionFactory connectionFactory, SSLEngine sslEngine, Map<String, Object> context)
+ {
+ super(endPoint, client.getFactory().getExecutor());
+ this.client = client;
+ this.connectionFactory = connectionFactory;
+ this.engine = sslEngine;
+ this.context = context;
+ NextProtoNego.put(engine, this);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ try
+ {
+ getEndPoint().flush(BufferUtil.EMPTY_BUFFER);
+ if (completed)
+ replaceConnection();
+ else
+ fillInterested();
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+ @Override
+ public void onFillable()
+ {
+ while (true)
+ {
+ int filled = fill();
+ if (filled == 0 && !completed)
+ fillInterested();
+ if (filled <= 0 || completed)
+ break;
+ }
+ if (completed)
+ replaceConnection();
+ }
+
+ private int fill()
+ {
+ try
+ {
+ return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
+ }
+ catch (IOException x)
+ {
+ LOG.debug(x);
+ NextProtoNego.remove(engine);
+ close();
+ return -1;
+ }
+ }
+
+ @Override
+ public boolean supports()
+ {
+ return true;
+ }
+
+ @Override
+ public void unsupported()
+ {
+ NextProtoNego.remove(engine);
+ completed = true;
+ }
+
+ @Override
+ public String selectProtocol(List<String> protocols)
+ {
+ NextProtoNego.remove(engine);
+ completed = true;
+ return client.selectProtocol(protocols);
+ }
+
+ private void replaceConnection()
+ {
+ EndPoint endPoint = getEndPoint();
+ try
+ {
+ Connection oldConnection = endPoint.getConnection();
+ Connection newConnection = connectionFactory.newConnection(endPoint, context);
+ ClientConnectionFactory.Helper.replaceConnection(oldConnection, newConnection);
+ }
+ catch (Throwable x)
+ {
+ LOG.debug(x);
+ close();
+ }
+ }
+}
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnectionFactory.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnectionFactory.java
new file mode 100644
index 0000000..b0e7b45
--- /dev/null
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnectionFactory.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+
+public class NPNClientConnectionFactory implements ClientConnectionFactory
+{
+ private final SPDYClient client;
+ private final ClientConnectionFactory connectionFactory;
+
+ public NPNClientConnectionFactory(SPDYClient client, ClientConnectionFactory connectionFactory)
+ {
+ this.client = client;
+ this.connectionFactory = connectionFactory;
+ }
+
+ @Override
+ public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+ {
+ return new NPNClientConnection(endPoint, client, connectionFactory,
+ (SSLEngine)context.get(SslClientConnectionFactory.SSL_ENGINE_CONTEXT_KEY), context);
+ }
+}
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NextProtoNegoClientConnection.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NextProtoNegoClientConnection.java
deleted file mode 100644
index cd745f3..0000000
--- a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NextProtoNegoClientConnection.java
+++ /dev/null
@@ -1,135 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.spdy.client;
-
-import java.io.IOException;
-import java.nio.channels.SocketChannel;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-import javax.net.ssl.SSLEngine;
-
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.RuntimeIOException;
-import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
-import org.eclipse.jetty.npn.NextProtoNego;
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-public class NextProtoNegoClientConnection extends AbstractConnection implements NextProtoNego.ClientProvider
-{
- private final Logger LOG = Log.getLogger(getClass());
- private final SocketChannel channel;
- private final Object attachment;
- private final SPDYClient client;
- private final SSLEngine engine;
- private volatile boolean completed;
-
- public NextProtoNegoClientConnection(SocketChannel channel, DecryptedEndPoint endPoint, Object attachment, Executor executor, SPDYClient client)
- {
- super(endPoint, executor);
- this.channel = channel;
- this.attachment = attachment;
- this.client = client;
- this.engine = endPoint.getSslConnection().getSSLEngine();
- NextProtoNego.put(engine, this);
- }
-
- @Override
- public void onOpen()
- {
- super.onOpen();
- try
- {
- getEndPoint().flush(BufferUtil.EMPTY_BUFFER);
- if (completed)
- replaceConnection();
- else
- fillInterested();
- }
- catch(IOException e)
- {
- throw new RuntimeIOException(e);
- }
- }
-
- @Override
- public void onFillable()
- {
- while (true)
- {
- int filled = fill();
- if (filled == 0 && !completed)
- fillInterested();
- if (filled <= 0 || completed)
- break;
- }
- if (completed)
- replaceConnection();
- }
-
- private int fill()
- {
- try
- {
- return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
- }
- catch (IOException x)
- {
- LOG.debug(x);
- NextProtoNego.remove(engine);
- getEndPoint().close();
- return -1;
- }
- }
-
- @Override
- public boolean supports()
- {
- return true;
- }
-
- @Override
- public void unsupported()
- {
- NextProtoNego.remove(engine);
- completed = true;
- }
-
- @Override
- public String selectProtocol(List<String> protocols)
- {
- NextProtoNego.remove(engine);
- completed = true;
- String protocol = client.selectProtocol(protocols);
- return protocol == null ? null : protocol;
- }
-
- private void replaceConnection()
- {
- EndPoint endPoint = getEndPoint();
- Connection connection = client.getConnectionFactory().newConnection(channel, endPoint, attachment);
- endPoint.getConnection().onClose();
- endPoint.setConnection(connection);
- connection.onOpen();
- }
-}
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
index c4360a2..974b32a 100644
--- a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
@@ -25,48 +25,160 @@
import java.nio.channels.SocketChannel;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
-import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.io.SelectorManager;
-import org.eclipse.jetty.io.ssl.SslConnection;
-import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.spdy.FlowControlStrategy;
import org.eclipse.jetty.spdy.api.GoAwayInfo;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.SessionFrameListener;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FuturePromise;
+import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
+/**
+ * A {@link SPDYClient} allows applications to connect to one or more SPDY servers,
+ * obtaining {@link Session} objects that can be used to send/receive SPDY frames.
+ * <p/>
+ * {@link SPDYClient} instances are created through a {@link Factory}:
+ * <pre>
+ * SPDYClient.Factory factory = new SPDYClient.Factory();
+ * SPDYClient client = factory.newSPDYClient(SPDY.V3);
+ * </pre>
+ * and then used to connect to the server:
+ * <pre>
+ * FuturePromise<Session> promise = new FuturePromise<>();
+ * client.connect("server.com", null, promise);
+ * Session session = promise.get();
+ * </pre>
+ */
public class SPDYClient
{
- private final SPDYClientConnectionFactory connectionFactory = new SPDYClientConnectionFactory();
- final short version;
- final Factory factory;
+ private final short version;
+ private final Factory factory;
private volatile SocketAddress bindAddress;
private volatile long idleTimeout = -1;
private volatile int initialWindowSize;
- private volatile boolean executeOnFillable;
+ private volatile boolean dispatchIO;
+ private volatile ClientConnectionFactory connectionFactory;
protected SPDYClient(short version, Factory factory)
{
this.version = version;
this.factory = factory;
setInitialWindowSize(65536);
+ setDispatchIO(true);
+ ClientConnectionFactory connectionFactory = new SPDYClientConnectionFactory();
+ if (factory.sslContextFactory != null)
+ connectionFactory = new SslClientConnectionFactory(factory.getSslContextFactory(), factory.getByteBufferPool(), factory.getExecutor(), new NPNClientConnectionFactory(this, connectionFactory));
+ setClientConnectionFactory(connectionFactory);
+ }
+
+ public short getVersion()
+ {
+ return version;
+ }
+
+ public Factory getFactory()
+ {
+ return factory;
+ }
+
+ /**
+ * Equivalent to:
+ * <pre>
+ * Future<Session> promise = new FuturePromise<>();
+ * connect(address, listener, promise);
+ * </pre>
+ *
+ * @param address the address to connect to
+ * @param listener the session listener that will be notified of session events
+ * @return a {@link Session} when connected
+ */
+ public Session connect(SocketAddress address, SessionFrameListener listener) throws ExecutionException, InterruptedException
+ {
+ FuturePromise<Session> promise = new FuturePromise<>();
+ connect(address, listener, promise);
+ return promise.get();
+ }
+
+ /**
+ * Equivalent to:
+ * <pre>
+ * connect(address, listener, promise, null);
+ * </pre>
+ *
+ * @param address the address to connect to
+ * @param listener the session listener that will be notified of session events
+ * @param promise the promise notified of connection success/failure
+ */
+ public void connect(SocketAddress address, SessionFrameListener listener, Promise<Session> promise)
+ {
+ connect(address, listener, promise, new HashMap<String, Object>());
+ }
+
+ /**
+ * Connects to the given {@code address}, binding the given {@code listener} to session events,
+ * and notified the given {@code promise} of the connect result.
+ * <p/>
+ * If the connect operation is successful, the {@code promise} will be invoked with the {@link Session}
+ * object that applications can use to perform SPDY requests.
+ *
+ * @param address the address to connect to
+ * @param listener the session listener that will be notified of session events
+ * @param promise the promise notified of connection success/failure
+ * @param context a context object passed to the {@link #getClientConnectionFactory() ConnectionFactory}
+ * for the creation of the connection
+ */
+ public void connect(final SocketAddress address, final SessionFrameListener listener, final Promise<Session> promise, Map<String, Object> context)
+ {
+ if (!factory.isStarted())
+ throw new IllegalStateException(Factory.class.getSimpleName() + " is not started");
+
+ try
+ {
+ SocketChannel channel = SocketChannel.open();
+ if (bindAddress != null)
+ channel.bind(bindAddress);
+ configure(channel);
+ channel.configureBlocking(false);
+ channel.connect(address);
+
+ context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, ((InetSocketAddress)address).getHostString());
+ context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, ((InetSocketAddress)address).getPort());
+ context.put(SPDYClientConnectionFactory.SPDY_CLIENT_CONTEXT_KEY, this);
+ context.put(SPDYClientConnectionFactory.SPDY_SESSION_LISTENER_CONTEXT_KEY, listener);
+ context.put(SPDYClientConnectionFactory.SPDY_SESSION_PROMISE_CONTEXT_KEY, promise);
+
+ factory.selector.connect(channel, context);
+ }
+ catch (IOException x)
+ {
+ promise.failed(x);
+ }
+ }
+
+ protected void configure(SocketChannel channel) throws IOException
+ {
+ channel.socket().setTcpNoDelay(true);
}
/**
@@ -87,25 +199,6 @@
this.bindAddress = bindAddress;
}
- public Future<Session> connect(InetSocketAddress address, SessionFrameListener listener) throws IOException
- {
- if (!factory.isStarted())
- throw new IllegalStateException(Factory.class.getSimpleName() + " is not started");
-
- SocketChannel channel = SocketChannel.open();
- if (bindAddress != null)
- channel.bind(bindAddress);
- channel.socket().setTcpNoDelay(true);
- channel.configureBlocking(false);
-
- SessionPromise result = new SessionPromise(channel, this, listener);
-
- channel.connect(address);
- factory.selector.connect(channel, result);
-
- return result;
- }
-
public long getIdleTimeout()
{
return idleTimeout;
@@ -126,14 +219,24 @@
this.initialWindowSize = initialWindowSize;
}
- public boolean isExecuteOnFillable()
+ public boolean isDispatchIO()
{
- return executeOnFillable;
+ return dispatchIO;
}
- public void setExecuteOnFillable(boolean executeOnFillable)
+ public void setDispatchIO(boolean dispatchIO)
{
- this.executeOnFillable = executeOnFillable;
+ this.dispatchIO = dispatchIO;
+ }
+
+ public ClientConnectionFactory getClientConnectionFactory()
+ {
+ return connectionFactory;
+ }
+
+ public void setClientConnectionFactory(ClientConnectionFactory connectionFactory)
+ {
+ this.connectionFactory = connectionFactory;
}
protected String selectProtocol(List<String> serverProtocols)
@@ -147,20 +250,6 @@
return null;
}
- public SPDYClientConnectionFactory getConnectionFactory()
- {
- return connectionFactory;
- }
-
- protected SSLEngine newSSLEngine(SslContextFactory sslContextFactory, SocketChannel channel)
- {
- String peerHost = channel.socket().getInetAddress().getHostName();
- int peerPort = channel.socket().getPort();
- SSLEngine engine = sslContextFactory.newSSLEngine(peerHost, peerPort);
- engine.setUseClientMode(true);
- return engine;
- }
-
protected FlowControlStrategy newFlowControlStrategy()
{
return FlowControlStrategyFactory.newFlowControlStrategy(version);
@@ -175,7 +264,7 @@
private final SslContextFactory sslContextFactory;
private final SelectorManager selector;
private final long idleTimeout;
- private long connectTimeout = 15000;
+ private long connectTimeout;
public Factory()
{
@@ -205,6 +294,7 @@
public Factory(Executor executor, Scheduler scheduler, SslContextFactory sslContextFactory, long idleTimeout)
{
this.idleTimeout = idleTimeout;
+ setConnectTimeout(15000);
if (executor == null)
executor = new QueuedThreadPool();
@@ -240,6 +330,11 @@
return executor;
}
+ public SslContextFactory getSslContextFactory()
+ {
+ return sslContextFactory;
+ }
+
public long getConnectTimeout()
{
return connectTimeout;
@@ -297,72 +392,33 @@
@Override
protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
{
- SessionPromise attachment = (SessionPromise)key.attachment();
-
- long clientIdleTimeout = attachment.client.getIdleTimeout();
+ @SuppressWarnings("unchecked")
+ Map<String, Object> context = (Map<String, Object>)key.attachment();
+ SPDYClient client = (SPDYClient)context.get(SPDYClientConnectionFactory.SPDY_CLIENT_CONTEXT_KEY);
+ long clientIdleTimeout = client.getIdleTimeout();
if (clientIdleTimeout < 0)
clientIdleTimeout = idleTimeout;
-
return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), clientIdleTimeout);
}
@Override
- public Connection newConnection(final SocketChannel channel, EndPoint endPoint, final Object attachment)
+ public Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
{
- SessionPromise sessionPromise = (SessionPromise)attachment;
- final SPDYClient client = sessionPromise.client;
-
+ @SuppressWarnings("unchecked")
+ Map<String, Object> context = (Map<String, Object>)attachment;
try
{
- if (sslContextFactory != null)
- {
- final SSLEngine engine = client.newSSLEngine(sslContextFactory, channel);
- SslConnection sslConnection = new SslConnection(bufferPool, getExecutor(), endPoint, engine);
- sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
- DecryptedEndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
- NextProtoNegoClientConnection connection = new NextProtoNegoClientConnection(channel, sslEndPoint, attachment, getExecutor(), client);
- sslEndPoint.setConnection(connection);
- return sslConnection;
- }
-
- SPDYClientConnectionFactory connectionFactory = new SPDYClientConnectionFactory();
- return connectionFactory.newConnection(channel, endPoint, attachment);
+ SPDYClient client = (SPDYClient)context.get(SPDYClientConnectionFactory.SPDY_CLIENT_CONTEXT_KEY);
+ return client.getClientConnectionFactory().newConnection(endPoint, context);
}
- catch (RuntimeException x)
+ catch (Throwable x)
{
- sessionPromise.failed(x);
+ @SuppressWarnings("unchecked")
+ Promise<Session> promise = (Promise<Session>)context.get(SPDYClientConnectionFactory.SPDY_SESSION_PROMISE_CONTEXT_KEY);
+ promise.failed(x);
throw x;
}
}
}
}
-
- static class SessionPromise extends FuturePromise<Session>
- {
- private final SocketChannel channel;
- final SPDYClient client;
- final SessionFrameListener listener;
-
- private SessionPromise(SocketChannel channel, SPDYClient client, SessionFrameListener listener)
- {
- this.channel = channel;
- this.client = client;
- this.listener = listener;
- }
-
- @Override
- public boolean cancel(boolean mayInterruptIfRunning)
- {
- try
- {
- super.cancel(mayInterruptIfRunning);
- channel.close();
- return true;
- }
- catch (IOException x)
- {
- return true;
- }
- }
- }
}
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClientConnectionFactory.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClientConnectionFactory.java
index 18244e8..b84382d 100644
--- a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClientConnectionFactory.java
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClientConnectionFactory.java
@@ -18,46 +18,55 @@
package org.eclipse.jetty.spdy.client;
-import java.nio.channels.SocketChannel;
+import java.io.IOException;
+import java.util.Map;
import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.spdy.CompressionFactory;
import org.eclipse.jetty.spdy.FlowControlStrategy;
import org.eclipse.jetty.spdy.StandardCompressionFactory;
import org.eclipse.jetty.spdy.StandardSession;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
import org.eclipse.jetty.spdy.client.SPDYClient.Factory;
-import org.eclipse.jetty.spdy.client.SPDYClient.SessionPromise;
import org.eclipse.jetty.spdy.generator.Generator;
import org.eclipse.jetty.spdy.parser.Parser;
+import org.eclipse.jetty.util.Promise;
-public class SPDYClientConnectionFactory
+public class SPDYClientConnectionFactory implements ClientConnectionFactory
{
- public Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment)
- {
- SessionPromise sessionPromise = (SessionPromise)attachment;
- SPDYClient client = sessionPromise.client;
- Factory factory = client.factory;
- ByteBufferPool bufferPool = factory.getByteBufferPool();
+ public static final String SPDY_CLIENT_CONTEXT_KEY = "spdy.client";
+ public static final String SPDY_SESSION_LISTENER_CONTEXT_KEY = "spdy.session.listener";
+ public static final String SPDY_SESSION_PROMISE_CONTEXT_KEY = "spdy.session.promise";
+ @Override
+ public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+ {
+ SPDYClient client = (SPDYClient)context.get(SPDY_CLIENT_CONTEXT_KEY);
+ SPDYClient.Factory factory = client.getFactory();
+ ByteBufferPool byteBufferPool = factory.getByteBufferPool();
CompressionFactory compressionFactory = new StandardCompressionFactory();
Parser parser = new Parser(compressionFactory.newDecompressor());
- Generator generator = new Generator(bufferPool, compressionFactory.newCompressor());
+ Generator generator = new Generator(byteBufferPool, compressionFactory.newCompressor());
- SPDYConnection connection = new ClientSPDYConnection(endPoint, bufferPool, parser, factory, client.isExecuteOnFillable());
+ SPDYConnection connection = new ClientSPDYConnection(endPoint, byteBufferPool, parser, factory, client.isDispatchIO());
FlowControlStrategy flowControlStrategy = client.newFlowControlStrategy();
- StandardSession session = new StandardSession(client.version, bufferPool, factory.getExecutor(),
- factory.getScheduler(), connection, endPoint, connection, 1, sessionPromise.listener, generator,
- flowControlStrategy);
+ SessionFrameListener listener = (SessionFrameListener)context.get(SPDY_SESSION_LISTENER_CONTEXT_KEY);
+ StandardSession session = new StandardSession(client.getVersion(), byteBufferPool,
+ factory.getScheduler(), connection, endPoint, connection, 1, listener, generator, flowControlStrategy);
+
session.setWindowSize(client.getInitialWindowSize());
parser.addListener(session);
- sessionPromise.succeeded(session);
connection.setSession(session);
- factory.sessionOpened(session);
+ @SuppressWarnings("unchecked")
+ Promise<Session> promise = (Promise<Session>)context.get(SPDY_SESSION_PROMISE_CONTEXT_KEY);
+ promise.succeeded(session);
return connection;
}
@@ -66,14 +75,20 @@
{
private final Factory factory;
- public ClientSPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Factory factory,
- boolean executeOnFillable)
+ public ClientSPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Factory factory, boolean dispatchIO)
{
- super(endPoint, bufferPool, parser, factory.getExecutor(), executeOnFillable);
+ super(endPoint, bufferPool, parser, factory.getExecutor(), dispatchIO);
this.factory = factory;
}
@Override
+ public void onOpen()
+ {
+ super.onOpen();
+ factory.sessionOpened(getSession());
+ }
+
+ @Override
public void onClose()
{
super.onClose();
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
index 372d169..b66edba 100644
--- a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
@@ -44,26 +44,20 @@
private volatile ISession session;
private volatile boolean idle = false;
- public SPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Executor executor,
- boolean executeOnFillable)
+ public SPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Executor executor, boolean dispatchIO)
{
- this(endPoint, bufferPool, parser, executor, executeOnFillable, 8192);
+ this(endPoint, bufferPool, parser, executor, dispatchIO, 8192);
}
- public SPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Executor executor,
- boolean executeOnFillable, int bufferSize)
+ public SPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Executor executor, boolean dispatchIO, int bufferSize)
{
// Since SPDY is multiplexed, onFillable() must never block while calling application code. In fact,
// the SPDY code always dispatches to a new thread when calling application code,
- // so here we can safely pass false as last parameter, and avoid to dispatch to onFillable(). The IO
- // operation (read, parse, etc.) will not block and will be fast in almost all cases. Big uploads to a server
- // however might block the Selector thread for a long time and therefore block other connections to be read.
- // This might be a good reason to set executeOnFillable to true.
- //
- // Due to a jvm bug we've had a Selector thread being stuck at
- // sun.nio.ch.FileDispatcherImpl.preClose0(Native Method). That's why we now default executeOnFillable to
- // true even if for most use cases it is faster to not dispatch the IO events.
- super(endPoint, executor, executeOnFillable);
+ // so here we can safely pass false as last parameter, and avoid to dispatch to onFillable().
+ // The IO operation (read, parse, etc.) will not block and will be fast in almost all cases.
+ // Big uploads to a server, however, might occupy the Selector thread for a long time and
+ // therefore starve other connections, so by default dispatchIO is true.
+ super(endPoint, executor, dispatchIO);
this.bufferPool = bufferPool;
this.parser = parser;
onIdle(true);
@@ -93,7 +87,8 @@
while (true)
{
int filled = fill(endPoint, buffer);
- LOG.debug("Read {} bytes", filled);
+ if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+ LOG.debug("Read {} bytes", filled);
if (filled == 0)
{
return 0;
diff --git a/jetty-spdy/spdy-core/pom.xml b/jetty-spdy/spdy-core/pom.xml
index 64a5a64..574e225 100644
--- a/jetty-spdy/spdy-core/pom.xml
+++ b/jetty-spdy/spdy-core/pom.xml
@@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -14,7 +14,6 @@
<bundle-symbolic-name>${project.groupId}.core</bundle-symbolic-name>
</properties>
- <url>http://www.eclipse.org/jetty</url>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Flusher.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Flusher.java
new file mode 100644
index 0000000..d092534
--- /dev/null
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Flusher.java
@@ -0,0 +1,249 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jetty.spdy.api.SPDYException;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class Flusher
+{
+ private static final Logger LOG = Log.getLogger(Flusher.class);
+
+ private final IteratingCallback iteratingCallback = new SessionIteratingCallback();
+ private final Controller controller;
+ private final LinkedList<StandardSession.FrameBytes> queue = new LinkedList<>();
+ private Throwable failure;
+ private StandardSession.FrameBytes active;
+
+ private boolean flushing;
+
+ public Flusher(Controller controller)
+ {
+ this.controller = controller;
+ }
+
+ void removeFrameBytesFromQueue(Stream stream)
+ {
+ synchronized (queue)
+ {
+ for (StandardSession.FrameBytes frameBytes : queue)
+ if (frameBytes.getStream() == stream)
+ queue.remove(frameBytes);
+ }
+ }
+
+ void append(StandardSession.FrameBytes frameBytes)
+ {
+ Throwable failure;
+ synchronized (queue)
+ {
+ failure = this.failure;
+ if (failure == null)
+ {
+ // Frames containing headers must be send in the order the headers have been generated. We don't need
+ // to do this check in StandardSession.prepend() as no frames containing headers will be prepended.
+ if (frameBytes instanceof StandardSession.ControlFrameBytes)
+ queue.addLast(frameBytes);
+ else
+ {
+ int index = queue.size();
+ while (index > 0)
+ {
+ StandardSession.FrameBytes element = queue.get(index - 1);
+ if (element.compareTo(frameBytes) >= 0)
+ break;
+ --index;
+ }
+ queue.add(index, frameBytes);
+ }
+ }
+ }
+ if (failure == null)
+ iteratingCallback.iterate();
+ else
+ frameBytes.failed(new SPDYException(failure));
+ }
+
+ void prepend(StandardSession.FrameBytes frameBytes)
+ {
+ Throwable failure;
+ synchronized (queue)
+ {
+ failure = this.failure;
+ if (failure == null)
+ {
+ int index = 0;
+ while (index < queue.size())
+ {
+ StandardSession.FrameBytes element = queue.get(index);
+ if (element.compareTo(frameBytes) <= 0)
+ break;
+ ++index;
+ }
+ queue.add(index, frameBytes);
+ }
+ }
+
+ if (failure == null)
+ iteratingCallback.iterate();
+ else
+ frameBytes.failed(new SPDYException(failure));
+ }
+
+ void flush()
+ {
+ StandardSession.FrameBytes frameBytes = null;
+ ByteBuffer buffer = null;
+ boolean failFrameBytes = false;
+ synchronized (queue)
+ {
+ if (flushing || queue.isEmpty())
+ return;
+
+ Set<IStream> stalledStreams = null;
+ for (int i = 0; i < queue.size(); ++i)
+ {
+ frameBytes = queue.get(i);
+
+ IStream stream = frameBytes.getStream();
+ if (stream != null && stalledStreams != null && stalledStreams.contains(stream))
+ continue;
+
+ buffer = frameBytes.getByteBuffer();
+ if (buffer != null)
+ {
+ queue.remove(i);
+ if (stream != null && stream.isReset() && !(frameBytes instanceof StandardSession
+ .ControlFrameBytes))
+ failFrameBytes = true;
+ break;
+ }
+
+ if (stalledStreams == null)
+ stalledStreams = new HashSet<>();
+ if (stream != null)
+ stalledStreams.add(stream);
+
+ LOG.debug("Flush stalled for {}, {} frame(s) in queue", frameBytes, queue.size());
+ }
+
+ if (buffer == null)
+ return;
+
+ if (!failFrameBytes)
+ {
+ flushing = true;
+ LOG.debug("Flushing {}, {} frame(s) in queue", frameBytes, queue.size());
+ }
+ }
+ if (failFrameBytes)
+ {
+ frameBytes.failed(new StreamException(frameBytes.getStream().getId(), StreamStatus.INVALID_STREAM,
+ "Stream: " + frameBytes.getStream() + " is reset!"));
+ }
+ else
+ {
+ write(buffer, frameBytes);
+ }
+ }
+
+ private void write(ByteBuffer buffer, StandardSession.FrameBytes frameBytes)
+ {
+ active = frameBytes;
+ if (controller != null)
+ {
+ LOG.debug("Writing {} frame bytes of {}", buffer.remaining(), buffer.limit());
+ controller.write(buffer, iteratingCallback);
+ }
+ }
+
+ public int getQueueSize()
+ {
+ return queue.size();
+ }
+
+ private class SessionIteratingCallback extends IteratingCallback
+ {
+ @Override
+ protected boolean process() throws Exception
+ {
+ flush();
+ return false;
+ }
+
+ @Override
+ protected void completed()
+ {
+ // will never be called as process always returns false!
+ }
+
+ @Override
+ public void succeeded()
+ {
+ if (LOG.isDebugEnabled())
+ {
+ synchronized (queue)
+ {
+ LOG.debug("Completed write of {}, {} frame(s) in queue", active, queue.size());
+ }
+ }
+ active.succeeded();
+ synchronized (queue)
+ {
+ flushing = false;
+ }
+ super.succeeded();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ List<StandardSession.FrameBytes> frameBytesToFail = new ArrayList<>();
+
+ synchronized (queue)
+ {
+ failure = x;
+ if (LOG.isDebugEnabled())
+ {
+ String logMessage = String.format("Failed write of %s, failing all %d frame(s) in queue", this, queue.size());
+ LOG.debug(logMessage, x);
+ }
+ frameBytesToFail.addAll(queue);
+ queue.clear();
+ }
+
+ active.failed(x);
+ for (StandardSession.FrameBytes fb : frameBytesToFail)
+ fb.failed(x);
+ super.failed(x);
+ }
+ }
+
+}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/ISession.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/ISession.java
index 9340f5d..5a46ad6 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/ISession.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/ISession.java
@@ -27,13 +27,6 @@
public interface ISession extends Session
{
- /**
- * <p>Initiates the flush of data to the other peer.</p>
- * <p>Note that the flush may do nothing if, for example, there is nothing to flush, or
- * if the data to be flushed belong to streams that have their flow-control stalled.</p>
- */
- public void flush();
-
public void control(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Callback callback);
public void data(IStream stream, DataInfo dataInfo, long timeout, TimeUnit unit, Callback callback);
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
index e50f98e..b2e4bb4 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
@@ -22,10 +22,8 @@
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.InterruptedByTimeoutException;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -34,7 +32,6 @@
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -77,7 +74,6 @@
import org.eclipse.jetty.util.Atomics;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.util.ForkInvoker;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Promise;
@@ -91,13 +87,11 @@
{
private static final Logger LOG = Log.getLogger(Session.class);
- private final ForkInvoker<Callback> invoker = new SessionInvoker();
+ private final Flusher flusher;
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
private final ConcurrentMap<Integer, IStream> streams = new ConcurrentHashMap<>();
- private final LinkedList<FrameBytes> queue = new LinkedList<>();
private final ByteBufferPool bufferPool;
- private final Executor threadPool;
private final Scheduler scheduler;
private final short version;
private final Controller controller;
@@ -113,17 +107,14 @@
private final AtomicInteger localStreamCount = new AtomicInteger(0);
private final FlowControlStrategy flowControlStrategy;
private volatile int maxConcurrentLocalStreams = -1;
- private boolean flushing;
- private Throwable failure;
- public StandardSession(short version, ByteBufferPool bufferPool, Executor threadPool, Scheduler scheduler,
+ public StandardSession(short version, ByteBufferPool bufferPool, Scheduler scheduler,
Controller controller, EndPoint endPoint, IdleListener idleListener, int initialStreamId,
SessionFrameListener listener, Generator generator, FlowControlStrategy flowControlStrategy)
{
// TODO this should probably be an aggregate lifecycle
this.version = version;
this.bufferPool = bufferPool;
- this.threadPool = threadPool;
this.scheduler = scheduler;
this.controller = controller;
this.endPoint = endPoint;
@@ -133,6 +124,7 @@
this.listener = listener;
this.generator = generator;
this.flowControlStrategy = flowControlStrategy;
+ this.flusher = new Flusher(controller);
}
@Override
@@ -187,7 +179,6 @@
return;
generateAndEnqueueControlFrame(stream, synStream, synInfo.getTimeout(), synInfo.getUnit(), stream);
}
- flush();
}
@Override
@@ -218,22 +209,12 @@
if (stream != null)
{
stream.process(frame);
- removeFrameBytesFromQueue(stream);
+ flusher.removeFrameBytesFromQueue(stream);
removeStream(stream);
}
}
}
- private void removeFrameBytesFromQueue(Stream stream)
- {
- synchronized (queue)
- {
- for (FrameBytes frameBytes : queue)
- if (frameBytes.getStream() == stream)
- queue.remove(frameBytes);
- }
- }
-
@Override
public void settings(SettingsInfo settingsInfo) throws ExecutionException, InterruptedException, TimeoutException
{
@@ -489,7 +470,8 @@
@Override
public void onStreamException(StreamException x)
{
- notifyOnException(listener, x);
+ // TODO: rename to onFailure
+ notifyOnException(listener, x); //TODO: notify StreamFrameListener if exists?
rst(new RstInfo(x.getStreamId(), x.getStreamStatus()), new Callback.Adapter());
}
@@ -532,7 +514,6 @@
streamListener = notifyOnSyn(listener, stream, synInfo);
}
stream.setStreamFrameListener(streamListener);
- flush();
// The onSyn() listener may have sent a frame that closed the stream
if (stream.isClosed())
removeStream(stream);
@@ -541,7 +522,9 @@
private IStream createStream(SynStreamFrame frame, StreamFrameListener listener, boolean local, Promise<Stream> promise)
{
IStream associatedStream = streams.get(frame.getAssociatedStreamId());
- IStream stream = new StandardStream(frame.getStreamId(), frame.getPriority(), this, associatedStream, promise);
+ IStream stream = new StandardStream(frame.getStreamId(), frame.getPriority(), this, associatedStream,
+ scheduler, promise);
+ stream.setIdleTimeout(endPoint.getIdleTimeout());
flowControlStrategy.onNewStream(this, stream);
stream.updateCloseState(frame.isClose(), local);
@@ -695,7 +678,6 @@
RstInfo rstInfo = new RstInfo(frame.getStreamId(), StreamStatus.from(frame.getVersion(), frame.getStatusCode()));
notifyOnRst(listener, rstInfo);
- flush();
if (stream != null)
removeStream(stream);
@@ -719,7 +701,6 @@
}
SettingsInfo settingsInfo = new SettingsInfo(frame.getSettings(), frame.isClearPersisted());
notifyOnSettings(listener, settingsInfo);
- flush();
}
private void onPing(PingFrame frame)
@@ -729,7 +710,6 @@
{
PingResultInfo pingResultInfo = new PingResultInfo(frame.getPingId());
notifyOnPing(listener, pingResultInfo);
- flush();
}
else
{
@@ -744,7 +724,6 @@
//TODO: Find a better name for GoAwayResultInfo
GoAwayResultInfo goAwayResultInfo = new GoAwayResultInfo(frame.getLastStreamId(), SessionStatus.from(frame.getStatusCode()));
notifyOnGoAway(listener, goAwayResultInfo);
- flush();
// SPDY does not require to send back a response to a GO_AWAY.
// We notified the application of the last good stream id and
// tried our best to flush remaining data.
@@ -779,13 +758,12 @@
int streamId = frame.getStreamId();
IStream stream = streams.get(streamId);
flowControlStrategy.onWindowUpdate(this, stream, frame.getWindowDelta());
- flush();
+ flusher.flush();
}
private void onCredential(CredentialFrame frame)
{
LOG.warn("{} frame not yet supported", frame.getType());
- flush();
}
protected void close()
@@ -802,7 +780,7 @@
if (listener != null)
{
LOG.debug("Invoking callback with {} on listener {}", x, listener);
- listener.onException(x);
+ listener.onFailure(this, x);
}
}
catch (Exception xx)
@@ -942,12 +920,10 @@
}
}
-
@Override
public void control(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Callback callback)
{
generateAndEnqueueControlFrame(stream, frame, timeout, unit, callback);
- flush();
}
private void generateAndEnqueueControlFrame(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Callback callback)
@@ -967,9 +943,9 @@
// Special handling for PING frames, they must be sent as soon as possible
if (ControlFrameType.PING == frame.getType())
- prepend(frameBytes);
+ flusher.prepend(frameBytes);
else
- append(frameBytes);
+ flusher.append(frameBytes);
}
}
catch (Exception x)
@@ -992,151 +968,19 @@
DataFrameBytes frameBytes = new DataFrameBytes(stream, callback, dataInfo);
if (timeout > 0)
frameBytes.task = scheduler.schedule(frameBytes, timeout, unit);
- append(frameBytes);
- flush();
+ flusher.append(frameBytes);
}
@Override
public void shutdown()
{
FrameBytes frameBytes = new CloseFrameBytes();
- append(frameBytes);
- flush();
- }
-
- private void execute(Runnable task)
- {
- threadPool.execute(task);
- }
-
- @Override
- public void flush()
- {
- FrameBytes frameBytes = null;
- ByteBuffer buffer = null;
- boolean failFrameBytes = false;
- synchronized (queue)
- {
- if (flushing || queue.isEmpty())
- return;
-
- Set<IStream> stalledStreams = null;
- for (int i = 0; i < queue.size(); ++i)
- {
- frameBytes = queue.get(i);
-
- IStream stream = frameBytes.getStream();
- if (stream != null && stalledStreams != null && stalledStreams.contains(stream))
- continue;
-
- buffer = frameBytes.getByteBuffer();
- if (buffer != null)
- {
- queue.remove(i);
- if (stream != null && stream.isReset() && !(frameBytes instanceof ControlFrameBytes))
- failFrameBytes = true;
- break;
- }
-
- if (stalledStreams == null)
- stalledStreams = new HashSet<>();
- if (stream != null)
- stalledStreams.add(stream);
-
- LOG.debug("Flush stalled for {}, {} frame(s) in queue", frameBytes, queue.size());
- }
-
- if (buffer == null)
- return;
-
- if (!failFrameBytes)
- {
- flushing = true;
- LOG.debug("Flushing {}, {} frame(s) in queue", frameBytes, queue.size());
- }
- }
- if (failFrameBytes)
- {
- frameBytes.fail(new StreamException(frameBytes.getStream().getId(), StreamStatus.INVALID_STREAM,
- "Stream: " + frameBytes.getStream() + " is reset!"));
- }
- else
- {
- write(buffer, frameBytes);
- }
- }
-
- void append(FrameBytes frameBytes)
- {
- Throwable failure;
- synchronized (queue)
- {
- failure = this.failure;
- if (failure == null)
- {
- // Frames containing headers must be send in the order the headers have been generated. We don't need
- // to do this check in StandardSession.prepend() as no frames containing headers will be prepended.
- if (frameBytes instanceof ControlFrameBytes)
- queue.addLast(frameBytes);
- else
- {
- int index = queue.size();
- while (index > 0)
- {
- FrameBytes element = queue.get(index - 1);
- if (element.compareTo(frameBytes) >= 0)
- break;
- --index;
- }
- queue.add(index, frameBytes);
- }
- }
- }
-
- if (failure != null)
- frameBytes.fail(new SPDYException(failure));
- }
-
- private void prepend(FrameBytes frameBytes)
- {
- Throwable failure;
- synchronized (queue)
- {
- failure = this.failure;
- if (failure == null)
- {
- int index = 0;
- while (index < queue.size())
- {
- FrameBytes element = queue.get(index);
- if (element.compareTo(frameBytes) <= 0)
- break;
- ++index;
- }
- queue.add(index, frameBytes);
- }
- }
-
- if (failure != null)
- frameBytes.fail(new SPDYException(failure));
- }
-
- protected void write(ByteBuffer buffer, Callback callback)
- {
- if (controller != null)
- {
- LOG.debug("Writing {} frame bytes of {}", buffer.remaining(), buffer.limit());
- controller.write(buffer, callback);
- }
+ flusher.append(frameBytes);
}
private void complete(final Callback callback)
{
- // Applications may send and queue up a lot of frames and
- // if we call Callback.completed() only synchronously we risk
- // starvation (for the last frames sent) and stack overflow.
- // Therefore every some invocation, we dispatch to a new thread
- invoker.invoke(callback);
+ callback.succeeded();
}
private void notifyCallbackFailed(Callback callback, Throwable x)
@@ -1171,7 +1015,7 @@
public String toString()
{
return String.format("%s@%x{v%d,queueSize=%d,windowSize=%d,streams=%d}", getClass().getSimpleName(),
- hashCode(), version, queue.size(), getWindowSize(), streams.size());
+ hashCode(), version, flusher.getQueueSize(), getWindowSize(), streams.size());
}
@Override
@@ -1187,44 +1031,11 @@
ContainerLifeCycle.dump(out, indent, Collections.singletonList(controller), streams.values());
}
- private class SessionInvoker extends ForkInvoker<Callback>
- {
- private SessionInvoker()
- {
- super(4);
- }
-
- @Override
- public void fork(final Callback callback)
- {
- execute(new Runnable()
- {
- @Override
- public void run()
- {
- callback.succeeded();
- flush();
- }
- });
- }
-
- @Override
- public void call(Callback callback)
- {
- callback.succeeded();
- flush();
- }
- }
-
public interface FrameBytes extends Comparable<FrameBytes>, Callback
{
public IStream getStream();
public abstract ByteBuffer getByteBuffer();
-
- public abstract void complete();
-
- public abstract void fail(Throwable throwable);
}
abstract class AbstractFrameBytes implements FrameBytes, Runnable
@@ -1260,21 +1071,6 @@
return thatStream.getPriority() - thisStream.getPriority();
}
- @Override
- public void complete()
- {
- cancelTask();
- StandardSession.this.complete(callback);
- }
-
- @Override
- public void fail(Throwable x)
- {
- cancelTask();
- notifyCallbackFailed(callback, x);
- StandardSession.this.flush();
- }
-
private void cancelTask()
{
Scheduler.Task task = this.task;
@@ -1286,46 +1082,25 @@
public void run()
{
close();
- fail(new InterruptedByTimeoutException());
+ failed(new InterruptedByTimeoutException());
}
@Override
public void succeeded()
{
- synchronized (queue)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Completed write of {}, {} frame(s) in queue", this, queue.size());
- flushing = false;
- }
- complete();
+ cancelTask();
+ StandardSession.this.complete(callback);
}
@Override
public void failed(Throwable x)
{
- List<FrameBytes> frameBytesToFail = new ArrayList<>();
- frameBytesToFail.add(this);
-
- synchronized (queue)
- {
- failure = x;
- if (LOG.isDebugEnabled())
- {
- String logMessage = String.format("Failed write of %s, failing all %d frame(s) in queue", this, queue.size());
- LOG.debug(logMessage, x);
- }
- frameBytesToFail.addAll(queue);
- queue.clear();
- flushing = false;
- }
-
- for (FrameBytes fb : frameBytesToFail)
- fb.fail(x);
+ cancelTask();
+ notifyCallbackFailed(callback, x);
}
}
- private class ControlFrameBytes extends AbstractFrameBytes
+ class ControlFrameBytes extends AbstractFrameBytes
{
private final ControlFrame frame;
private final ByteBuffer buffer;
@@ -1344,11 +1119,11 @@
}
@Override
- public void complete()
+ public void succeeded()
{
bufferPool.release(buffer);
- super.complete();
+ super.succeeded();
if (frame.getType() == ControlFrameType.GO_AWAY)
{
@@ -1399,13 +1174,13 @@
}
catch (Throwable x)
{
- fail(x);
+ failed(x);
return null;
}
}
@Override
- public void complete()
+ public void succeeded()
{
bufferPool.release(buffer);
IStream stream = getStream();
@@ -1416,12 +1191,11 @@
// We have written a frame out of this DataInfo, but there is more to write.
// We need to keep the correct ordering of frames, to avoid that another
// DataInfo for the same stream is written before this one is finished.
- prepend(this);
- flush();
+ flusher.prepend(this);
}
else
{
- super.complete();
+ super.succeeded();
stream.updateCloseState(dataInfo.isClose(), true);
if (stream.isClosed())
removeStream(stream);
@@ -1449,9 +1223,9 @@
}
@Override
- public void complete()
+ public void succeeded()
{
- super.complete();
+ super.succeeded();
close();
}
}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java
index e0b65c7..3fe6559 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java
@@ -26,6 +26,7 @@
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
+import org.eclipse.jetty.io.IdleTimeout;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.HeadersInfo;
import org.eclipse.jetty.spdy.api.PushInfo;
@@ -43,8 +44,9 @@
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
-public class StandardStream implements IStream
+public class StandardStream extends IdleTimeout implements IStream
{
private static final Logger LOG = Log.getLogger(Stream.class);
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
@@ -60,8 +62,9 @@
private volatile CloseState closeState = CloseState.OPENED;
private volatile boolean reset = false;
- public StandardStream(int id, byte priority, ISession session, IStream associatedStream, Promise<Stream> promise)
+ public StandardStream(int id, byte priority, ISession session, IStream associatedStream, Scheduler scheduler, Promise<Stream> promise)
{
+ super(scheduler);
this.id = id;
this.priority = priority;
this.session = session;
@@ -106,6 +109,20 @@
}
@Override
+ protected void onIdleExpired(TimeoutException timeout)
+ {
+ StreamFrameListener listener = this.listener;
+ if (listener != null)
+ listener.onFailure(this, timeout);
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return !isClosed();
+ }
+
+ @Override
public int getWindowSize()
{
return windowSize.get();
@@ -194,6 +211,7 @@
@Override
public void process(ControlFrame frame)
{
+ notIdle();
switch (frame.getType())
{
case SYN_STREAM:
@@ -228,12 +246,12 @@
throw new IllegalStateException();
}
}
- session.flush();
}
@Override
public void process(DataInfo dataInfo)
{
+ notIdle();
// TODO: in v3 we need to send a rst instead of just ignoring
// ignore data frame if this stream is remotelyClosed already
if (isRemotelyClosed())
@@ -251,7 +269,6 @@
updateCloseState(dataInfo.isClose(), false);
notifyOnData(dataInfo);
- session.flush();
}
@Override
@@ -349,6 +366,7 @@
@Override
public void push(PushInfo pushInfo, Promise<Stream> promise)
{
+ notIdle();
if (isClosed() || isReset())
{
promise.failed(new StreamException(getId(), StreamStatus.STREAM_ALREADY_CLOSED,
@@ -373,6 +391,7 @@
@Override
public void reply(ReplyInfo replyInfo, Callback callback)
{
+ notIdle();
if (isUnidirectional())
throw new IllegalStateException("Protocol violation: cannot send SYN_REPLY frames in unidirectional streams");
openState = OpenState.REPLY_SENT;
@@ -395,6 +414,7 @@
@Override
public void data(DataInfo dataInfo, Callback callback)
{
+ notIdle();
if (!canSend())
{
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new Adapter());
@@ -425,6 +445,7 @@
@Override
public void headers(HeadersInfo headersInfo, Callback callback)
{
+ notIdle();
if (!canSend())
{
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new Adapter());
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Session.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Session.java
index f25dc1b..07577dc 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Session.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Session.java
@@ -77,7 +77,7 @@
* @param synInfo the metadata to send on stream creation
* @param listener the listener to invoke when events happen on the stream just created
* @return the stream that will be created
- * @see #syn(SynInfo, StreamFrameListener, Promise
+ * @see #syn(SynInfo, StreamFrameListener, Promise)
*/
public Stream syn(SynInfo synInfo, StreamFrameListener listener) throws ExecutionException, InterruptedException, TimeoutException;
@@ -98,7 +98,6 @@
* <p>Sends synchronously a RST_STREAM to abort a stream.</p>
*
* @param rstInfo the metadata to reset the stream
- * @return the RstInfo belonging to the reset to be sent
* @see #rst(RstInfo, Callback)
*/
public void rst(RstInfo rstInfo) throws InterruptedException, ExecutionException, TimeoutException;
@@ -137,7 +136,7 @@
/**
* <p>Sends synchronously a PING, normally to measure round-trip time.</p>
*
- * @see #ping(PingInfo, Promise
+ * @see #ping(PingInfo, Promise)
* @param pingInfo
*/
public PingResultInfo ping(PingInfo pingInfo) throws ExecutionException, InterruptedException, TimeoutException;
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SessionFrameListener.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SessionFrameListener.java
index ffff51f..3ad1771 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SessionFrameListener.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SessionFrameListener.java
@@ -115,9 +115,10 @@
* SPDY session.</p>
* <p>Examples of such conditions are invalid frames received, corrupted headers compression state, etc.</p>
*
+ * @param session the session
* @param x the exception that caused the event processing failure
*/
- public void onException(Throwable x);
+ public void onFailure(Session session, Throwable x);
/**
@@ -154,7 +155,7 @@
}
@Override
- public void onException(Throwable x)
+ public void onFailure(Session session, Throwable x)
{
logger.info("", x);
}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Stream.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Stream.java
index abc1b88..0bb21a2 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Stream.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Stream.java
@@ -91,7 +91,7 @@
*
* @param pushInfo the metadata to send on stream creation
* @return a future containing the stream once it got established
- * @see #push(PushInfo, Promise
+ * @see #push(PushInfo, Promise)
*/
public Stream push(PushInfo pushInfo) throws InterruptedException, ExecutionException, TimeoutException;
@@ -110,7 +110,6 @@
* future to wait for the reply to be actually sent.</p>
*
* @param replyInfo the metadata to send
- * @return a future to wait for the reply to be sent
* @see #reply(ReplyInfo, Callback)
* @see SessionFrameListener#onSyn(Stream, SynInfo)
*/
@@ -131,7 +130,6 @@
* frame.</p> <p>Callers may use the returned future to wait for the data to be actually sent.</p>
*
* @param dataInfo the metadata to send
- * @return a future to wait for the data to be sent
* @see #data(DataInfo, Callback)
* @see #reply(ReplyInfo)
*/
@@ -153,8 +151,7 @@
* SYN_REPLY frame.</p> <p>Callers may use the returned future to wait for the headers to be actually sent.</p>
*
* @param headersInfo the metadata to send
- * @return a future to wait for the headers to be sent
- * @see #headers(HeadersInfo, Callback
+ * @see #headers(HeadersInfo, Callback)
* @see #reply(ReplyInfo)
*/
public void headers(HeadersInfo headersInfo) throws InterruptedException, ExecutionException, TimeoutException;
@@ -225,4 +222,16 @@
*/
public Set<Stream> getPushedStreams();
+ /**
+ * Get the idle timeout set for this particular stream
+ * @return the idle timeout
+ */
+ public long getIdleTimeout();
+
+ /**
+ * Set an idle timeout for this stream
+ * @param timeout
+ */
+ public void setIdleTimeout(long timeout);
+
}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StreamFrameListener.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StreamFrameListener.java
index 0a4248a..e3d9cd8 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StreamFrameListener.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StreamFrameListener.java
@@ -54,7 +54,7 @@
* <p>Callback invoked when a push syn has been received on a stream.</p>
*
* @param stream the push stream just created
- * @param pushInfo
+ * @param pushInfo the push metadata
* @return a listener for stream events or null if there is no interest in being notified of stream events
*/
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo);
@@ -70,6 +70,13 @@
public void onData(Stream stream, DataInfo dataInfo);
/**
+ * <p>Callback invoked on errors.</p>
+ * @param stream the stream
+ * @param x the failure
+ */
+ public void onFailure(Stream stream, Throwable x);
+
+ /**
* <p>Empty implementation of {@link StreamFrameListener}</p>
*/
public static class Adapter implements StreamFrameListener
@@ -94,5 +101,10 @@
public void onData(Stream stream, DataInfo dataInfo)
{
}
+
+ @Override
+ public void onFailure(Stream stream, Throwable x)
+ {
+ }
}
}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java
index 4504523..d4d5758 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java
@@ -20,7 +20,9 @@
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import java.util.List;
import java.util.Locale;
import org.eclipse.jetty.spdy.CompressionDictionary;
@@ -41,24 +43,25 @@
public ByteBuffer generate(short version, Fields headers)
{
// TODO: ByteArrayOutputStream is quite inefficient, but grows on demand; optimize using ByteBuffer ?
- ByteArrayOutputStream buffer = new ByteArrayOutputStream(headers.size() * 64);
- writeCount(version, buffer, headers.size());
+ final Charset iso1 = StandardCharsets.ISO_8859_1;
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream(headers.getSize() * 64);
+ writeCount(version, buffer, headers.getSize());
for (Fields.Field header : headers)
{
- String name = header.name().toLowerCase(Locale.ENGLISH);
- byte[] nameBytes = name.getBytes(StandardCharsets.ISO_8859_1);
+ String name = header.getName().toLowerCase(Locale.ENGLISH);
+ byte[] nameBytes = name.getBytes(iso1);
writeNameLength(version, buffer, nameBytes.length);
buffer.write(nameBytes, 0, nameBytes.length);
// Most common path first
- String value = header.value();
- byte[] valueBytes = value.getBytes(StandardCharsets.ISO_8859_1);
+ String value = header.getValue();
+ byte[] valueBytes = value.getBytes(iso1);
if (header.hasMultipleValues())
{
- String[] values = header.values();
- for (int i = 1; i < values.length; ++i)
+ List<String> values = header.getValues();
+ for (int i = 1; i < values.size(); ++i)
{
- byte[] moreValueBytes = values[i].getBytes(StandardCharsets.ISO_8859_1);
+ byte[] moreValueBytes = values.get(i).getBytes(iso1);
byte[] newValueBytes = new byte[valueBytes.length + 1 + moreValueBytes.length];
System.arraycopy(valueBytes, 0, newValueBytes, 0, valueBytes.length);
newValueBytes[valueBytes.length] = 0;
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameParser.java
index a253a19..c86d921 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameParser.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameParser.java
@@ -35,7 +35,8 @@
private short type;
private byte flags;
private int length;
- private ControlFrameBodyParser parser;
+ private ControlFrameBodyParser bodyParser;
+ private int bytesToSkip = 0;
public ControlFrameParser(CompressionFactory.Decompressor decompressor)
{
@@ -66,6 +67,12 @@
return length;
}
+ public void skip(int bytesToSkip)
+ {
+ state = State.SKIP;
+ this.bytesToSkip = bytesToSkip;
+ }
+
public boolean parse(ByteBuffer buffer)
{
while (buffer.hasRemaining())
@@ -140,9 +147,9 @@
// SPEC v3, 2.2.1: unrecognized control frames must be ignored
if (controlFrameType == null)
- parser = unknownParser;
+ bodyParser = unknownParser;
else
- parser = parsers.get(controlFrameType);
+ bodyParser = parsers.get(controlFrameType);
state = State.BODY;
@@ -153,13 +160,29 @@
}
case BODY:
{
- if (parser.parse(buffer))
+ if (bodyParser.parse(buffer))
{
reset();
return true;
}
break;
}
+ case SKIP:
+ {
+ int remaining = buffer.remaining();
+ if (remaining >= bytesToSkip)
+ {
+ buffer.position(buffer.position() + bytesToSkip);
+ reset();
+ return true;
+ }
+ else
+ {
+ buffer.position(buffer.limit());
+ bytesToSkip = bytesToSkip - remaining;
+ return false;
+ }
+ }
default:
{
throw new IllegalStateException();
@@ -169,7 +192,7 @@
return false;
}
- private void reset()
+ void reset()
{
state = State.VERSION;
cursor = 0;
@@ -177,13 +200,14 @@
type = 0;
flags = 0;
length = 0;
- parser = null;
+ bodyParser = null;
+ bytesToSkip = 0;
}
protected abstract void onControlFrame(ControlFrame frame);
private enum State
{
- VERSION, VERSION_BYTES, TYPE, TYPE_BYTES, FLAGS, LENGTH, BODY
+ VERSION, VERSION_BYTES, TYPE, TYPE_BYTES, FLAGS, LENGTH, BODY, SKIP
}
}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java
index 7dda076..f3b28b6 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.spdy.parser;
import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.zip.ZipException;
@@ -57,6 +58,7 @@
data = null;
ByteBuffer decompressedHeaders = decompress(version, compressedHeaders);
+ Charset iso1 = StandardCharsets.ISO_8859_1;
// We know the decoded bytes contain the full headers,
// so optimize instead of looping byte by byte
int count = readCount(version, decompressedHeaders);
@@ -67,14 +69,14 @@
throw new StreamException(streamId, StreamStatus.PROTOCOL_ERROR, "Invalid header name length");
byte[] nameBytes = new byte[nameLength];
decompressedHeaders.get(nameBytes);
- String name = new String(nameBytes, StandardCharsets.ISO_8859_1);
+ String name = new String(nameBytes, iso1);
int valueLength = readValueLength(version, decompressedHeaders);
if (valueLength == 0)
throw new StreamException(streamId, StreamStatus.PROTOCOL_ERROR, "Invalid header value length");
byte[] valueBytes = new byte[valueLength];
decompressedHeaders.get(valueBytes);
- String value = new String(valueBytes, StandardCharsets.ISO_8859_1);
+ String value = new String(valueBytes, iso1);
// Multi valued headers are separate by NUL
String[] values = value.split("\u0000");
// Check if there are multiple NULs (section 2.6.9)
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/Parser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/Parser.java
index 2bc95ac..341e2e3 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/Parser.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/Parser.java
@@ -108,7 +108,14 @@
{
for (Listener listener : listeners)
{
- listener.onStreamException(x);
+ try
+ {
+ listener.onStreamException(x);
+ }
+ catch (Exception xx)
+ {
+ logger.debug("Could not notify listener " + listener, xx);
+ }
}
}
@@ -130,38 +137,45 @@
public void parse(ByteBuffer buffer)
{
+ logger.debug("Parsing {} bytes", buffer.remaining());
try
{
- logger.debug("Parsing {} bytes", buffer.remaining());
while (buffer.hasRemaining())
{
- switch (state)
+ try
{
- case CONTROL_BIT:
+ switch (state)
{
- // We must only peek the first byte and not advance the buffer
- // because the 7 least significant bits may be relevant in data frames
- int currByte = buffer.get(buffer.position());
- boolean isControlFrame = (currByte & 0x80) == 0x80;
- state = isControlFrame ? State.CONTROL_FRAME : State.DATA_FRAME;
- break;
+ case CONTROL_BIT:
+ {
+ // We must only peek the first byte and not advance the buffer
+ // because the 7 least significant bits may be relevant in data frames
+ int currByte = buffer.get(buffer.position());
+ boolean isControlFrame = (currByte & 0x80) == 0x80;
+ state = isControlFrame ? State.CONTROL_FRAME : State.DATA_FRAME;
+ break;
+ }
+ case CONTROL_FRAME:
+ {
+ if (controlFrameParser.parse(buffer))
+ reset();
+ break;
+ }
+ case DATA_FRAME:
+ {
+ if (dataFrameParser.parse(buffer))
+ reset();
+ break;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
}
- case CONTROL_FRAME:
- {
- if (controlFrameParser.parse(buffer))
- reset();
- break;
- }
- case DATA_FRAME:
- {
- if (dataFrameParser.parse(buffer))
- reset();
- break;
- }
- default:
- {
- throw new IllegalStateException();
- }
+ }
+ catch (StreamException x)
+ {
+ notifyStreamException(x);
}
}
}
@@ -169,10 +183,6 @@
{
notifySessionException(x);
}
- catch (StreamException x)
- {
- notifyStreamException(x);
- }
catch (Throwable x)
{
notifySessionException(new SessionException(SessionStatus.PROTOCOL_ERROR, x));
@@ -227,5 +237,4 @@
{
CONTROL_BIT, CONTROL_FRAME, DATA_FRAME
}
-
}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynStreamBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynStreamBodyParser.java
index 30deb7c..644ffd8 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynStreamBodyParser.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynStreamBodyParser.java
@@ -85,8 +85,31 @@
{
// Now we know the streamId, we can do the version check
// and if it is wrong, issue a RST_STREAM
- checkVersion(controlFrameParser.getVersion(), streamId);
-
+ try
+ {
+ checkVersion(controlFrameParser.getVersion(), streamId);
+ }
+ catch (StreamException e)
+ {
+ // We've already read 4 bytes of the streamId which are part of controlFrameParser.getLength
+ // so we need to substract those from the bytesToSkip.
+ int bytesToSkip = controlFrameParser.getLength() - 4;
+ int remaining = buffer.remaining();
+ if (remaining >= bytesToSkip)
+ {
+ buffer.position(buffer.position() + bytesToSkip);
+ controlFrameParser.reset();
+ reset();
+ }
+ else
+ {
+ int bytesToSkipInNextBuffer = bytesToSkip - remaining;
+ buffer.position(buffer.limit());
+ controlFrameParser.skip(bytesToSkipInNextBuffer);
+ reset();
+ }
+ throw e;
+ }
if (buffer.remaining() >= 4)
{
associatedStreamId = buffer.getInt() & 0x7F_FF_FF_FF;
@@ -122,8 +145,6 @@
else
{
slot = (short)(currByte & 0xFF);
- if (slot < 0)
- throw new StreamException(streamId, StreamStatus.INVALID_CREDENTIALS);
cursor = 0;
state = State.HEADERS;
}
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/AsyncTimeoutTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/AsyncTimeoutTest.java
index 1d198a8..72ed326 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/AsyncTimeoutTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/AsyncTimeoutTest.java
@@ -24,7 +24,9 @@
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import org.eclipse.jetty.io.ByteArrayEndPoint;
import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.SPDYException;
@@ -41,12 +43,17 @@
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.TimerScheduler;
import org.junit.Assert;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AdvancedRunner.class)
+//TODO: Uncomment comment lines and reimplement tests to fit new design
+@Ignore("Doesn't work with new Flusher class, needs to be rewritten")
public class AsyncTimeoutTest
{
+ EndPoint endPoint = new ByteArrayEndPoint();
+
@Slow
@Test
public void testAsyncTimeoutInControlFrames() throws Exception
@@ -59,16 +66,16 @@
Scheduler scheduler = new TimerScheduler();
scheduler.start(); // TODO need to use jetty lifecycles better here
Generator generator = new Generator(bufferPool, new StandardCompressionFactory.StandardCompressor());
- Session session = new StandardSession(SPDY.V2, bufferPool, threadPool, scheduler, new TestController(),
- null, null, 1, null, generator, new FlowControlStrategy.None())
+ Session session = new StandardSession(SPDY.V2, bufferPool, scheduler, new TestController(),
+ endPoint, null, 1, null, generator, new FlowControlStrategy.None())
{
- @Override
+// @Override
public void flush()
{
try
{
unit.sleep(2 * timeout);
- super.flush();
+// super.flush();
}
catch (InterruptedException x)
{
@@ -102,10 +109,10 @@
Scheduler scheduler = new TimerScheduler();
scheduler.start();
Generator generator = new Generator(bufferPool, new StandardCompressionFactory.StandardCompressor());
- Session session = new StandardSession(SPDY.V2, bufferPool, threadPool, scheduler, new TestController(),
- null, null, 1, null, generator, new FlowControlStrategy.None())
+ Session session = new StandardSession(SPDY.V2, bufferPool, scheduler, new TestController(),
+ endPoint, null, 1, null, generator, new FlowControlStrategy.None())
{
- @Override
+// @Override
protected void write(ByteBuffer buffer, Callback callback)
{
try
@@ -113,7 +120,7 @@
// Wait if we're writing the data frame (control frame's first byte is 0x80)
if (buffer.get(0) == 0)
unit.sleep(2 * timeout);
- super.write(buffer, callback);
+// super.write(buffer, callback);
}
catch (InterruptedException x)
{
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java
index 813fb0c..793c779 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java
@@ -18,6 +18,16 @@
package org.eclipse.jetty.spdy;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.HashSet;
@@ -30,6 +40,7 @@
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
@@ -67,15 +78,6 @@
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
@RunWith(MockitoJUnitRunner.class)
public class StandardSessionTest
{
@@ -84,6 +86,10 @@
@Mock
private Controller controller;
+
+ @Mock
+ private EndPoint endPoint;
+
private ExecutorService threadPool;
private StandardSession session;
private Scheduler scheduler;
@@ -97,8 +103,9 @@
threadPool = Executors.newCachedThreadPool();
scheduler = new TimerScheduler();
scheduler.start();
- session = new StandardSession(VERSION, bufferPool, threadPool, scheduler, controller, null, null, 1, null,
+ session = new StandardSession(VERSION, bufferPool, scheduler, controller, endPoint, null, 1, null,
generator, new FlowControlStrategy.None());
+ when(endPoint.getIdleTimeout()).thenReturn(30000L);
headers = new Fields();
}
@@ -428,7 +435,7 @@
final CountDownLatch failedCalledLatch = new CountDownLatch(2);
SynStreamFrame synStreamFrame = new SynStreamFrame(VERSION, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, null);
- IStream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null, null);
+ IStream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null, null, null);
stream.updateWindowSize(8192);
Callback.Adapter callback = new Callback.Adapter()
{
@@ -501,8 +508,8 @@
private void testHeaderFramesAreSentInOrder(final byte priority0, final byte priority1, final byte priority2) throws InterruptedException, ExecutionException
{
- final StandardSession testLocalSession = new StandardSession(VERSION, bufferPool, threadPool, scheduler,
- new ControllerMock(), null, null, 1, null, generator, new FlowControlStrategy.None());
+ final StandardSession testLocalSession = new StandardSession(VERSION, bufferPool, scheduler,
+ new ControllerMock(), endPoint, null, 1, null, generator, new FlowControlStrategy.None());
HashSet<Future> tasks = new HashSet<>();
int numberOfTasksToRun = 128;
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardStreamTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardStreamTest.java
index 57bdd98..bda7f0e 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardStreamTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardStreamTest.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.spdy;
import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
@@ -43,9 +44,12 @@
import org.eclipse.jetty.spdy.api.StringDataInfo;
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
@@ -55,11 +59,18 @@
@RunWith(MockitoJUnitRunner.class)
public class StandardStreamTest
{
+ private final ScheduledExecutorScheduler scheduler = new ScheduledExecutorScheduler();
@Mock
private ISession session;
@Mock
private SynStreamFrame synStreamFrame;
+ @Before
+ public void setUp() throws Exception
+ {
+ scheduler.start();
+ }
+
/**
* Test method for {@link Stream#push(org.eclipse.jetty.spdy.api.PushInfo)}.
*/
@@ -67,7 +78,7 @@
@Test
public void testSyn()
{
- Stream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null, null);
+ Stream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null, null, null);
Set<Stream> streams = new HashSet<>();
streams.add(stream);
when(synStreamFrame.isClose()).thenReturn(false);
@@ -100,7 +111,8 @@
@Test
public void testSynOnClosedStream()
{
- IStream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null, null);
+ IStream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session,
+ null, null , null);
stream.updateCloseState(true, true);
stream.updateCloseState(true, false);
assertThat("stream expected to be closed", stream.isClosed(), is(true));
@@ -121,11 +133,57 @@
public void testSendDataOnHalfClosedStream() throws InterruptedException, ExecutionException, TimeoutException
{
SynStreamFrame synStreamFrame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, null);
- IStream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null, null);
+ IStream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session,
+ null, scheduler, null);
stream.updateWindowSize(8192);
stream.updateCloseState(synStreamFrame.isClose(), true);
assertThat("stream is half closed", stream.isHalfClosed(), is(true));
stream.data(new StringDataInfo("data on half closed stream", true));
verify(session, never()).data(any(IStream.class), any(DataInfo.class), anyInt(), any(TimeUnit.class), any(Callback.class));
}
+
+ @Test
+ @Slow
+ public void testIdleTimeout() throws InterruptedException, ExecutionException, TimeoutException
+ {
+ final CountDownLatch onFailCalledLatch = new CountDownLatch(1);
+ IStream stream = new StandardStream(1, (byte)0, session, null, scheduler, null);
+ stream.setIdleTimeout(500);
+ stream.setStreamFrameListener(new StreamFrameListener.Adapter()
+ {
+ @Override
+ public void onFailure(Stream stream, Throwable x)
+ {
+ assertThat("exception is a TimeoutException", x, is(instanceOf(TimeoutException.class)));
+ onFailCalledLatch.countDown();
+ }
+ });
+ stream.process(new StringDataInfo("string", false));
+ Thread.sleep(1000);
+ assertThat("onFailure has been called", onFailCalledLatch.await(5, TimeUnit.SECONDS), is(true));
+ }
+
+ @Test
+ @Slow
+ public void testIdleTimeoutIsInterruptedWhenReceiving() throws InterruptedException, ExecutionException,
+ TimeoutException
+ {
+ final CountDownLatch onFailCalledLatch = new CountDownLatch(1);
+ IStream stream = new StandardStream(1, (byte)0, session, null, scheduler, null);
+ stream.setStreamFrameListener(new StreamFrameListener.Adapter()
+ {
+ @Override
+ public void onFailure(Stream stream, Throwable x)
+ {
+ assertThat("exception is a TimeoutException", x, is(instanceOf(TimeoutException.class)));
+ onFailCalledLatch.countDown();
+ }
+ });
+ stream.process(new StringDataInfo("string", false));
+ Thread.sleep(500);
+ stream.process(new StringDataInfo("string", false));
+ Thread.sleep(500);
+ assertThat("onFailure has been called", onFailCalledLatch.await(1, TimeUnit.SECONDS), is(false));
+ }
+
}
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ClientUsageTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ClientUsageTest.java
index d3aa8d6..1565109 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ClientUsageTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ClientUsageTest.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.spdy.api;
-import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@@ -37,7 +36,7 @@
@Test
public void testClientRequestResponseNoBody() throws Exception
{
- Session session = new StandardSession(SPDY.V2, null, null, null, null, null, null, 1, null, null, null);
+ Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
session.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
{
@@ -63,7 +62,7 @@
@Test
public void testClientReceivesPush1() throws InterruptedException, ExecutionException, TimeoutException
{
- Session session = new StandardSession(SPDY.V2, null, null, null, null, null, null, 1, null, null, null);
+ Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
session.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
{
@@ -100,7 +99,7 @@
@Test
public void testClientReceivesPush2() throws InterruptedException, ExecutionException, TimeoutException
{
- Session session = new StandardSession(SPDY.V2, null, null, null, null, null, null, 1, new SessionFrameListener.Adapter()
+ Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, new SessionFrameListener.Adapter()
{
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
@@ -138,7 +137,7 @@
@Test
public void testClientRequestWithBodyResponseNoBody() throws Exception
{
- Session session = new StandardSession(SPDY.V2, null, null, null, null, null, null, 1, null, null, null);
+ Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0),
new StreamFrameListener.Adapter()
@@ -167,7 +166,7 @@
@Test
public void testAsyncClientRequestWithBodyResponseNoBody() throws Exception
{
- Session session = new StandardSession(SPDY.V2, null, null, null, null, null, null, 1, null, null, null);
+ Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
final String context = "context";
session.syn(new SynInfo(new Fields(), false), new StreamFrameListener.Adapter()
@@ -210,7 +209,7 @@
@Test
public void testAsyncClientRequestWithBodyAndResponseWithBody() throws Exception
{
- Session session = new StandardSession(SPDY.V2, null, null, null, null, null, null, 1, null, null, null);
+ Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
session.syn(new SynInfo(new Fields(), false), new StreamFrameListener.Adapter()
{
@@ -223,7 +222,7 @@
{
// Do something with the response
Fields headers = replyInfo.getHeaders();
- int contentLength = headers.get("content-length").valueAsInt();
+ int contentLength = headers.get("content-length").getValueAsInt();
stream.setAttribute("content-length", contentLength);
if (!replyInfo.isClose())
stream.setAttribute("builder", new StringBuilder());
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/HeadersGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/HeadersGenerateParseTest.java
index b7c7d20..f137d9d 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/HeadersGenerateParseTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/HeadersGenerateParseTest.java
@@ -85,7 +85,7 @@
parser.parse(createHeadersFrameBuffer(headers));
HeadersFrame parsedHeadersFrame = assertExpectationsAreMet(headers);
Fields.Field viaHeader = parsedHeadersFrame.getHeaders().get("via");
- assertThat("Via Header name is lowercase", viaHeader.name(), is("via"));
+ assertThat("Via Header name is lowercase", viaHeader.getName(), is("via"));
}
private HeadersFrame assertExpectationsAreMet(Fields headers)
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/generator/DataFrameGeneratorTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/generator/DataFrameGeneratorTest.java
index e87b286..49391b7 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/generator/DataFrameGeneratorTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/generator/DataFrameGeneratorTest.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.spdy.generator;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
import java.nio.ByteBuffer;
import java.util.concurrent.ThreadLocalRandom;
@@ -31,9 +34,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-
@RunWith(JUnit4.class)
public class DataFrameGeneratorTest
{
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/BrokenFrameTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/BrokenFrameTest.java
new file mode 100644
index 0000000..25a1085
--- /dev/null
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/BrokenFrameTest.java
@@ -0,0 +1,287 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.parser;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.ZipException;
+
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.spdy.CompressionFactory;
+import org.eclipse.jetty.spdy.StreamException;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.frames.ControlFrame;
+import org.eclipse.jetty.spdy.frames.SynStreamFrame;
+import org.eclipse.jetty.spdy.generator.Generator;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Fields;
+import org.junit.Test;
+
+public class BrokenFrameTest
+{
+
+ @Test
+ public void testInvalidHeaderNameLength() throws Exception
+ {
+ Fields headers = new Fields();
+ headers.add("broken", "header");
+ SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, headers);
+ Generator generator = new Generator(new MappedByteBufferPool(), new NoCompressionCompressionFactory.NoCompressionCompressor());
+
+ ByteBuffer bufferWithBrokenHeaderNameLength = generator.control(frame);
+ // Break the header name length to provoke the Parser to throw a StreamException
+ bufferWithBrokenHeaderNameLength.put(21, (byte)0);
+
+ ByteBuffer bufferWithValidSynStreamFrame = generator.control(frame);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ outputStream.write(BufferUtil.toArray(bufferWithBrokenHeaderNameLength));
+ outputStream.write(BufferUtil.toArray(bufferWithValidSynStreamFrame));
+
+ byte concatenatedFramesByteArray[] = outputStream.toByteArray();
+ ByteBuffer concatenatedBuffer = BufferUtil.toBuffer(concatenatedFramesByteArray);
+
+ final CountDownLatch latch = new CountDownLatch(2);
+ Parser parser = new Parser(new NoCompressionCompressionFactory.NoCompressionDecompressor());
+ parser.addListener(new Parser.Listener.Adapter()
+ {
+ @Override
+ public void onControlFrame(ControlFrame frame)
+ {
+ latch.countDown();
+ }
+
+ @Override
+ public void onStreamException(StreamException x)
+ {
+ latch.countDown();
+ }
+ });
+ parser.parse(concatenatedBuffer);
+
+ assertThat(latch.await(5, TimeUnit.SECONDS), is(true));
+ }
+
+ @Test
+ public void testInvalidVersion() throws Exception
+ {
+ Fields headers = new Fields();
+ headers.add("good", "header");
+ headers.add("another","header");
+ SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, headers);
+ Generator generator = new Generator(new MappedByteBufferPool(), new NoCompressionCompressionFactory.NoCompressionCompressor());
+
+ ByteBuffer bufferWithBrokenVersion = generator.control(frame);
+ // Break the header name length to provoke the Parser to throw a StreamException
+ bufferWithBrokenVersion.put(1, (byte)4);
+
+ ByteBuffer bufferWithValidSynStreamFrame = generator.control(frame);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ outputStream.write(BufferUtil.toArray(bufferWithBrokenVersion));
+ outputStream.write(BufferUtil.toArray(bufferWithValidSynStreamFrame));
+
+ byte concatenatedFramesByteArray[] = outputStream.toByteArray();
+ ByteBuffer concatenatedBuffer = BufferUtil.toBuffer(concatenatedFramesByteArray);
+
+ final CountDownLatch latch = new CountDownLatch(2);
+ Parser parser = new Parser(new NoCompressionCompressionFactory.NoCompressionDecompressor());
+ parser.addListener(new Parser.Listener.Adapter()
+ {
+ @Override
+ public void onControlFrame(ControlFrame frame)
+ {
+ latch.countDown();
+ }
+
+ @Override
+ public void onStreamException(StreamException x)
+ {
+ latch.countDown();
+ }
+ });
+ parser.parse(concatenatedBuffer);
+
+ assertThat(latch.await(5, TimeUnit.SECONDS), is(true));
+ }
+
+ @Test
+ public void testInvalidVersionWithSplitBuffer() throws Exception
+ {
+ Fields headers = new Fields();
+ headers.add("good", "header");
+ headers.add("another","header");
+ SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, headers);
+ Generator generator = new Generator(new MappedByteBufferPool(), new NoCompressionCompressionFactory.NoCompressionCompressor());
+
+ ByteBuffer bufferWithBrokenVersion = generator.control(frame);
+ // Break the header name length to provoke the Parser to throw a StreamException
+ bufferWithBrokenVersion.put(1, (byte)4);
+
+ ByteBuffer bufferWithValidSynStreamFrame = generator.control(frame);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ outputStream.write(BufferUtil.toArray(bufferWithBrokenVersion));
+ outputStream.write(BufferUtil.toArray(bufferWithValidSynStreamFrame));
+
+ byte concatenatedFramesByteArray[] = outputStream.toByteArray();
+ ByteBuffer concatenatedBuffer1 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,0,20));
+ ByteBuffer concatenatedBuffer2 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,20,
+ concatenatedFramesByteArray.length));
+
+ final CountDownLatch latch = new CountDownLatch(2);
+ Parser parser = new Parser(new NoCompressionCompressionFactory.NoCompressionDecompressor());
+ parser.addListener(new Parser.Listener.Adapter()
+ {
+ @Override
+ public void onControlFrame(ControlFrame frame)
+ {
+ latch.countDown();
+ }
+
+ @Override
+ public void onStreamException(StreamException x)
+ {
+ latch.countDown();
+ }
+ });
+ parser.parse(concatenatedBuffer1);
+ parser.parse(concatenatedBuffer2);
+
+ assertThat(latch.await(5, TimeUnit.SECONDS), is(true));
+ }
+
+ @Test
+ public void testInvalidVersionAndGoodFrameSplitInThreeBuffers() throws Exception
+ {
+ Fields headers = new Fields();
+ headers.add("good", "header");
+ headers.add("another","header");
+ SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, headers);
+ Generator generator = new Generator(new MappedByteBufferPool(), new NoCompressionCompressionFactory.NoCompressionCompressor());
+
+ ByteBuffer bufferWithBrokenVersion = generator.control(frame);
+ // Break the header name length to provoke the Parser to throw a StreamException
+ bufferWithBrokenVersion.put(1, (byte)4);
+
+ ByteBuffer bufferWithValidSynStreamFrame = generator.control(frame);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ outputStream.write(BufferUtil.toArray(bufferWithBrokenVersion));
+ outputStream.write(BufferUtil.toArray(bufferWithValidSynStreamFrame));
+
+ byte concatenatedFramesByteArray[] = outputStream.toByteArray();
+ ByteBuffer concatenatedBuffer1 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,0,20));
+ ByteBuffer concatenatedBuffer2 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,20, 30));
+ ByteBuffer concatenatedBuffer3 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,30,
+ concatenatedFramesByteArray.length));
+
+ final CountDownLatch latch = new CountDownLatch(2);
+ Parser parser = new Parser(new NoCompressionCompressionFactory.NoCompressionDecompressor());
+ parser.addListener(new Parser.Listener.Adapter()
+ {
+ @Override
+ public void onControlFrame(ControlFrame frame)
+ {
+ latch.countDown();
+ }
+
+ @Override
+ public void onStreamException(StreamException x)
+ {
+ latch.countDown();
+ }
+ });
+ parser.parse(concatenatedBuffer1);
+ parser.parse(concatenatedBuffer2);
+ parser.parse(concatenatedBuffer3);
+
+ assertThat(latch.await(5, TimeUnit.SECONDS), is(true));
+ }
+
+ private static class NoCompressionCompressionFactory implements CompressionFactory
+ {
+
+ @Override
+ public Compressor newCompressor()
+ {
+ return null;
+ }
+
+ @Override
+ public Decompressor newDecompressor()
+ {
+ return null;
+ }
+
+ public static class NoCompressionCompressor implements Compressor
+ {
+
+ private byte[] input;
+
+ @Override
+ public void setInput(byte[] input)
+ {
+ this.input = input;
+ }
+
+ @Override
+ public void setDictionary(byte[] dictionary)
+ {
+ }
+
+ @Override
+ public int compress(byte[] output)
+ {
+ System.arraycopy(input, 0, output, 0, input.length);
+ return input.length;
+ }
+ }
+
+ public static class NoCompressionDecompressor implements Decompressor
+ {
+ private byte[] input;
+
+ @Override
+ public void setDictionary(byte[] dictionary)
+ {
+ }
+
+ @Override
+ public void setInput(byte[] input)
+ {
+ this.input = input;
+ }
+
+ @Override
+ public int decompress(byte[] output) throws ZipException
+ {
+ System.arraycopy(input, 0, output, 0, input.length);
+ return input.length;
+ }
+ }
+ }
+}
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/ParseVersusCacheBenchmarkTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/ParseVersusCacheBenchmarkTest.java
index 143a510..afd02b3 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/ParseVersusCacheBenchmarkTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/ParseVersusCacheBenchmarkTest.java
@@ -19,7 +19,6 @@
package org.eclipse.jetty.spdy.parser;
import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
diff --git a/jetty-spdy/spdy-example-webapp/pom.xml b/jetty-spdy/spdy-example-webapp/pom.xml
index b9170a2..46104e2 100644
--- a/jetty-spdy/spdy-example-webapp/pom.xml
+++ b/jetty-spdy/spdy-example-webapp/pom.xml
@@ -3,13 +3,13 @@
<parent>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spdy-example-webapp</artifactId>
<packaging>war</packaging>
- <name>Jetty :: SPDY :: Jetty HTTP Web Application</name>
- <url>http://www.eclipse.org/jetty</url>
+ <name>Jetty :: SPDY :: HTTP Web Application</name>
+
<build>
<plugins>
<plugin>
diff --git a/jetty-spdy/spdy-http-client-transport/pom.xml b/jetty-spdy/spdy-http-client-transport/pom.xml
new file mode 100644
index 0000000..a6a9385
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/pom.xml
@@ -0,0 +1,101 @@
+<?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/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.eclipse.jetty.spdy</groupId>
+ <artifactId>spdy-parent</artifactId>
+ <version>9.1.1-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>spdy-http-client-transport</artifactId>
+ <name>Jetty :: SPDY :: HTTP Client Transport</name>
+
+ <properties>
+ <bundle-symbolic-name>${project.groupId}.client.http</bundle-symbolic-name>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.mortbay.jetty.npn</groupId>
+ <artifactId>npn-boot</artifactId>
+ <version>${npn.version}</version>
+ <type>jar</type>
+ <overWrite>false</overWrite>
+ <outputDirectory>${project.build.directory}/npn</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <argLine>-Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar</argLine>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ <configuration>
+ <instructions>
+ <Export-Package>org.eclipse.jetty.spdy.client.http;version="9.1"</Export-Package>
+ <Import-Package>!org.eclipse.jetty.npn,org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
+ </instructions>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.spdy</groupId>
+ <artifactId>spdy-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.spdy</groupId>
+ <artifactId>spdy-http-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.spdy</groupId>
+ <artifactId>spdy-http-server</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpChannelOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpChannelOverSPDY.java
new file mode 100644
index 0000000..992f2a5
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpChannelOverSPDY.java
@@ -0,0 +1,75 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import org.eclipse.jetty.client.HttpChannel;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.spdy.api.Session;
+
+public class HttpChannelOverSPDY extends HttpChannel
+{
+ private final Session session;
+ private final HttpSenderOverSPDY sender;
+ private final HttpReceiverOverSPDY receiver;
+
+ public HttpChannelOverSPDY(HttpDestination destination, Session session)
+ {
+ super(destination);
+ this.session = session;
+ this.sender = new HttpSenderOverSPDY(this);
+ this.receiver = new HttpReceiverOverSPDY(this);
+ }
+
+ public Session getSession()
+ {
+ return session;
+ }
+
+ public HttpSenderOverSPDY getHttpSender()
+ {
+ return sender;
+ }
+
+ public HttpReceiverOverSPDY getHttpReceiver()
+ {
+ return receiver;
+ }
+
+ @Override
+ public void send()
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange != null)
+ sender.send(exchange);
+ }
+
+ @Override
+ public void proceed(HttpExchange exchange, boolean proceed)
+ {
+ sender.proceed(exchange, proceed);
+ }
+
+ @Override
+ public boolean abort(Throwable cause)
+ {
+ sender.abort(cause);
+ return receiver.abort(cause);
+ }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpClientTransportOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpClientTransportOverSPDY.java
new file mode 100644
index 0000000..813f550
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpClientTransportOverSPDY.java
@@ -0,0 +1,107 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.util.Map;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpClientTransport;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.util.Promise;
+
+public class HttpClientTransportOverSPDY implements HttpClientTransport
+{
+ private final SPDYClient client;
+ private final ClientConnectionFactory connectionFactory;
+ private HttpClient httpClient;
+
+ public HttpClientTransportOverSPDY(SPDYClient client)
+ {
+ this.client = client;
+ this.connectionFactory = client.getClientConnectionFactory();
+ client.setClientConnectionFactory(new ClientConnectionFactory()
+ {
+ @Override
+ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+ {
+ HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+ return destination.getClientConnectionFactory().newConnection(endPoint, context);
+ }
+ });
+ }
+
+ @Override
+ public void setHttpClient(HttpClient client)
+ {
+ httpClient = client;
+ }
+
+ @Override
+ public HttpDestination newHttpDestination(Origin origin)
+ {
+ return new HttpDestinationOverSPDY(httpClient, origin);
+ }
+
+ @Override
+ public void connect(SocketAddress address, Map<String, Object> context)
+ {
+ final HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+ @SuppressWarnings("unchecked")
+ final Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+
+ SessionFrameListener.Adapter listener = new SessionFrameListener.Adapter()
+ {
+ @Override
+ public void onFailure(Session session, Throwable x)
+ {
+ destination.abort(x);
+ }
+ };
+
+ client.connect(address, listener, new Promise<Session>()
+ {
+ @Override
+ public void succeeded(Session session)
+ {
+ promise.succeeded(new HttpConnectionOverSPDY(destination, session));
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ promise.failed(x);
+ }
+ }, context);
+ }
+
+ @Override
+ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+ {
+ return connectionFactory.newConnection(endPoint, context);
+ }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpConnectionOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpConnectionOverSPDY.java
new file mode 100644
index 0000000..850088c
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpConnectionOverSPDY.java
@@ -0,0 +1,54 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import org.eclipse.jetty.client.HttpChannel;
+import org.eclipse.jetty.client.HttpConnection;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.util.Callback;
+
+public class HttpConnectionOverSPDY extends HttpConnection
+{
+ private final Session session;
+
+ public HttpConnectionOverSPDY(HttpDestination destination, Session session)
+ {
+ super(destination);
+ this.session = session;
+ }
+
+ @Override
+ protected void send(HttpExchange exchange)
+ {
+ normalizeRequest(exchange.getRequest());
+ // One connection maps to N channels, so for each exchange we create a new channel
+ HttpChannel channel = new HttpChannelOverSPDY(getHttpDestination(), session);
+ channel.associate(exchange);
+ channel.send();
+ }
+
+ @Override
+ public void close()
+ {
+ session.goAway(new GoAwayInfo(), new Callback.Adapter());
+ }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpDestinationOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpDestinationOverSPDY.java
new file mode 100644
index 0000000..10ff922
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpDestinationOverSPDY.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.MultiplexHttpDestination;
+import org.eclipse.jetty.client.Origin;
+
+public class HttpDestinationOverSPDY extends MultiplexHttpDestination<HttpConnectionOverSPDY>
+{
+ public HttpDestinationOverSPDY(HttpClient client, Origin origin)
+ {
+ super(client, origin);
+ }
+
+ @Override
+ protected void send(HttpConnectionOverSPDY connection, HttpExchange exchange)
+ {
+ connection.send(exchange);
+ }
+
+ @Override
+ public void abort(Throwable cause)
+ {
+ // TODO: in case of connection failure, we need to abort also
+ // TODO: all pending exchanges, so we need to track them.
+ super.abort(cause);
+ }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java
new file mode 100644
index 0000000..2d5f8d6
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java
@@ -0,0 +1,151 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.HttpReceiver;
+import org.eclipse.jetty.client.HttpResponse;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.HeadersInfo;
+import org.eclipse.jetty.spdy.api.PushInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+
+public class HttpReceiverOverSPDY extends HttpReceiver implements StreamFrameListener
+{
+ public HttpReceiverOverSPDY(HttpChannelOverSPDY channel)
+ {
+ super(channel);
+ }
+
+ @Override
+ public HttpChannelOverSPDY getHttpChannel()
+ {
+ return (HttpChannelOverSPDY)super.getHttpChannel();
+ }
+
+ @Override
+ public void onReply(Stream stream, ReplyInfo replyInfo)
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange == null)
+ return;
+
+ try
+ {
+ HttpResponse response = exchange.getResponse();
+
+ Fields fields = replyInfo.getHeaders();
+ short spdy = stream.getSession().getVersion();
+ HttpVersion version = HttpVersion.fromString(fields.get(HTTPSPDYHeader.VERSION.name(spdy)).getValue());
+ response.version(version);
+ String[] status = fields.get(HTTPSPDYHeader.STATUS.name(spdy)).getValue().split(" ", 2);
+
+ Integer code = Integer.parseInt(status[0]);
+ response.status(code);
+ String reason = status.length < 2 ? HttpStatus.getMessage(code) : status[1];
+ response.reason(reason);
+
+ if (responseBegin(exchange))
+ {
+ for (Fields.Field field : fields)
+ {
+ String name = field.getName();
+ if (HTTPSPDYHeader.from(spdy, name) != null)
+ continue;
+ // TODO: handle multiple values properly
+ HttpField httpField = new HttpField(name, field.getValue());
+ responseHeader(exchange, httpField);
+ }
+
+ if (responseHeaders(exchange))
+ {
+ if (replyInfo.isClose())
+ {
+ responseSuccess(exchange);
+ }
+ }
+ }
+ }
+ catch (Exception x)
+ {
+ responseFailure(x);
+ }
+ }
+
+ @Override
+ public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+ {
+ // SPDY push not supported
+ getHttpChannel().getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), new Callback.Adapter());
+ return null;
+ }
+
+ @Override
+ public void onHeaders(Stream stream, HeadersInfo headersInfo)
+ {
+ // TODO: see above handling of headers
+ }
+
+ @Override
+ public void onData(Stream stream, DataInfo dataInfo)
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange == null)
+ return;
+
+ try
+ {
+ int length = dataInfo.length();
+ // TODO: avoid data copy here
+ boolean process = responseContent(exchange, dataInfo.asByteBuffer(false));
+ dataInfo.consume(length);
+
+ if (process)
+ {
+ if (dataInfo.isClose())
+ {
+ responseSuccess(exchange);
+ }
+ }
+ }
+ catch (Exception x)
+ {
+ responseFailure(x);
+ }
+ }
+
+ @Override
+ public void onFailure(Stream stream, Throwable x)
+ {
+ HttpExchange exchange = getHttpExchange();
+ if (exchange == null)
+ return;
+ exchange.getRequest().abort(x);
+ }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpSenderOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpSenderOverSPDY.java
new file mode 100644
index 0000000..9dcf17f
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpSenderOverSPDY.java
@@ -0,0 +1,118 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import org.eclipse.jetty.client.HttpContent;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.HttpSender;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+
+public class HttpSenderOverSPDY extends HttpSender
+{
+ private volatile Stream stream;
+
+ public HttpSenderOverSPDY(HttpChannelOverSPDY channel)
+ {
+ super(channel);
+ }
+
+ @Override
+ public HttpChannelOverSPDY getHttpChannel()
+ {
+ return (HttpChannelOverSPDY)super.getHttpChannel();
+ }
+
+ @Override
+ protected void sendHeaders(HttpExchange exchange, final HttpContent content, final Callback callback)
+ {
+ final Request request = exchange.getRequest();
+ final long idleTimeout = request.getIdleTimeout();
+ short spdyVersion = getHttpChannel().getSession().getVersion();
+ Fields fields = new Fields();
+ HttpField hostHeader = null;
+ for (HttpField header : request.getHeaders())
+ {
+ String name = header.getName();
+ // The host header needs a special treatment
+ if (HTTPSPDYHeader.from(spdyVersion, name) != HTTPSPDYHeader.HOST)
+ fields.add(name, header.getValue());
+ else
+ hostHeader = header;
+ }
+
+ // Add special SPDY headers
+ fields.put(HTTPSPDYHeader.METHOD.name(spdyVersion), request.getMethod());
+ String path = request.getPath();
+ String query = request.getQuery();
+ if (query != null)
+ path += "?" + query;
+ fields.put(HTTPSPDYHeader.URI.name(spdyVersion), path);
+ fields.put(HTTPSPDYHeader.VERSION.name(spdyVersion), request.getVersion().asString());
+ if (hostHeader != null)
+ fields.put(HTTPSPDYHeader.HOST.name(spdyVersion), hostHeader.getValue());
+
+ SynInfo synInfo = new SynInfo(fields, !content.hasContent());
+ getHttpChannel().getSession().syn(synInfo, getHttpChannel().getHttpReceiver(), new Promise<Stream>()
+ {
+ @Override
+ public void succeeded(Stream stream)
+ {
+ stream.setIdleTimeout(idleTimeout);
+ if (content.hasContent())
+ HttpSenderOverSPDY.this.stream = stream;
+ callback.succeeded();
+ }
+
+ @Override
+ public void failed(Throwable failure)
+ {
+ callback.failed(failure);
+ }
+ });
+ }
+
+ @Override
+ protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback)
+ {
+ if (content.isConsumed())
+ {
+ callback.succeeded();
+ }
+ else
+ {
+ ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(content.getByteBuffer(), content.isLast());
+ stream.data(dataInfo, callback);
+ }
+ }
+
+ @Override
+ protected void reset()
+ {
+ super.reset();
+ stream = null;
+ }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/AbstractHttpClientServerTest.java b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/AbstractHttpClientServerTest.java
new file mode 100644
index 0000000..775913f
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/AbstractHttpClientServerTest.java
@@ -0,0 +1,107 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public abstract class AbstractHttpClientServerTest
+{
+ @Parameterized.Parameters
+ public static Collection<SslContextFactory[]> parameters()
+ {
+ return Arrays.asList(new SslContextFactory[]{null}, new SslContextFactory[]{new SslContextFactory()});
+ }
+
+ @Rule
+ public final TestTracker tracker = new TestTracker();
+
+ protected SslContextFactory sslContextFactory;
+ protected String scheme;
+ protected Server server;
+ protected ServerConnector connector;
+ protected SPDYClient.Factory factory;
+ protected HttpClient client;
+
+ public AbstractHttpClientServerTest(SslContextFactory sslContextFactory)
+ {
+ this.sslContextFactory = sslContextFactory;
+ this.scheme = (sslContextFactory == null ? HttpScheme.HTTP : HttpScheme.HTTPS).asString();
+ }
+
+ public void start(Handler handler) throws Exception
+ {
+ short version = SPDY.V3;
+
+ HTTPSPDYServerConnectionFactory httpSPDY = new HTTPSPDYServerConnectionFactory(version, new HttpConfiguration());
+ if (sslContextFactory != null)
+ {
+ sslContextFactory.setEndpointIdentificationAlgorithm("");
+ sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+ sslContextFactory.setKeyStorePassword("storepwd");
+ sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
+ sslContextFactory.setTrustStorePassword("storepwd");
+ }
+
+ server = new Server();
+ connector = new ServerConnector(server, AbstractConnectionFactory.getFactories(sslContextFactory, httpSPDY));
+ server.addConnector(connector);
+ server.setHandler(handler);
+ server.start();
+
+ QueuedThreadPool executor = new QueuedThreadPool();
+ executor.setName(executor.getName() + "-client");
+
+ factory = new SPDYClient.Factory(executor);
+ factory.start();
+ client = new HttpClient(new HttpClientTransportOverSPDY(factory.newSPDYClient(version)), sslContextFactory);
+ client.setExecutor(executor);
+ client.start();
+ }
+
+ @After
+ public void dispose() throws Exception
+ {
+ if (client != null)
+ client.stop();
+ if (factory != null)
+ factory.stop();
+ if (server != null)
+ server.stop();
+ }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/EmptyServerHandler.java b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/EmptyServerHandler.java
new file mode 100644
index 0000000..5658efd
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/EmptyServerHandler.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+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 void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientCustomProxyTest.java b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientCustomProxyTest.java
new file mode 100644
index 0000000..4560174
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientCustomProxyTest.java
@@ -0,0 +1,262 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpClientTransport;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.ProxyConfiguration;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.http.PushStrategy;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientCustomProxyTest
+{
+ public static final byte[] CAFE_BABE = new byte[]{(byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE};
+
+ private Server server;
+ private ServerConnector connector;
+ private SPDYClient.Factory factory;
+ private HttpClient httpClient;
+
+ public void prepare(Handler handler) throws Exception
+ {
+ server = new Server();
+ connector = new ServerConnector(server, new CAFEBABEServerConnectionFactory(new HTTPSPDYServerConnectionFactory(SPDY.V3, new HttpConfiguration(), new PushStrategy.None())));
+ server.addConnector(connector);
+ server.setHandler(handler);
+ server.start();
+
+ QueuedThreadPool executor = new QueuedThreadPool();
+ executor.setName(executor.getName() + "-client");
+
+ factory = new SPDYClient.Factory(executor);
+ factory.start();
+
+ httpClient = new HttpClient(new HttpClientTransportOverSPDY(factory.newSPDYClient(SPDY.V3)), null);
+ httpClient.setExecutor(executor);
+ httpClient.start();
+ }
+
+ @After
+ public void dispose() throws Exception
+ {
+ if (httpClient != null)
+ httpClient.stop();
+ if (factory != null)
+ factory.stop();
+ if (server != null)
+ server.stop();
+ }
+
+ @Test
+ public void testCustomProxy() throws Exception
+ {
+ final String serverHost = "server";
+ final int status = HttpStatus.NO_CONTENT_204;
+ prepare(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ if (!URI.create(baseRequest.getUri().toString()).isAbsolute())
+ response.setStatus(HttpServletResponse.SC_USE_PROXY);
+ else if (serverHost.equals(request.getServerName()))
+ response.setStatus(status);
+ else
+ response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
+ }
+ });
+
+ // Setup the custom proxy
+ int proxyPort = connector.getLocalPort();
+ int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
+ httpClient.getProxyConfiguration().getProxies().add(new CAFEBABEProxy(new Origin.Address("localhost", proxyPort), false));
+
+ ContentResponse response = httpClient.newRequest(serverHost, serverPort)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertEquals(status, response.getStatus());
+ }
+
+ private class CAFEBABEProxy extends ProxyConfiguration.Proxy
+ {
+ private CAFEBABEProxy(Origin.Address address, boolean secure)
+ {
+ super(address, secure);
+ }
+
+ @Override
+ public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
+ {
+ return new CAFEBABEClientConnectionFactory(connectionFactory);
+ }
+ }
+
+ private static class CAFEBABEClientConnectionFactory implements ClientConnectionFactory
+ {
+ private final ClientConnectionFactory connectionFactory;
+
+ private CAFEBABEClientConnectionFactory(ClientConnectionFactory connectionFactory)
+ {
+ this.connectionFactory = connectionFactory;
+ }
+
+ @Override
+ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+ {
+ HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+ Executor executor = destination.getHttpClient().getExecutor();
+ return new CAFEBABEConnection(endPoint, executor, connectionFactory, context);
+ }
+ }
+
+ private static class CAFEBABEConnection extends AbstractConnection
+ {
+ private final ClientConnectionFactory connectionFactory;
+ private final Map<String, Object> context;
+
+ public CAFEBABEConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, Map<String, Object> context)
+ {
+ super(endPoint, executor);
+ this.connectionFactory = connectionFactory;
+ this.context = context;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ fillInterested();
+ getEndPoint().write(new Callback.Adapter(), ByteBuffer.wrap(CAFE_BABE));
+ }
+
+ @Override
+ public void onFillable()
+ {
+ try
+ {
+ ByteBuffer buffer = BufferUtil.allocate(4);
+ int filled = getEndPoint().fill(buffer);
+ Assert.assertEquals(4, filled);
+ Assert.assertArrayEquals(CAFE_BABE, buffer.array());
+
+ // We are good, upgrade the connection
+ ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(getEndPoint(), context));
+ }
+ catch (Throwable x)
+ {
+ close();
+ @SuppressWarnings("unchecked")
+ Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+ promise.failed(x);
+ }
+ }
+ }
+
+ private class CAFEBABEServerConnectionFactory extends AbstractConnectionFactory
+ {
+ private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
+
+ private CAFEBABEServerConnectionFactory(org.eclipse.jetty.server.ConnectionFactory connectionFactory)
+ {
+ super("cafebabe");
+ this.connectionFactory = connectionFactory;
+ }
+
+ @Override
+ public org.eclipse.jetty.io.Connection newConnection(Connector connector, EndPoint endPoint)
+ {
+ return new CAFEBABEServerConnection(connector, endPoint, connectionFactory);
+ }
+ }
+
+ private class CAFEBABEServerConnection extends AbstractConnection
+ {
+ private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
+
+ public CAFEBABEServerConnection(Connector connector, EndPoint endPoint, org.eclipse.jetty.server.ConnectionFactory connectionFactory)
+ {
+ super(endPoint, connector.getExecutor());
+ this.connectionFactory = connectionFactory;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ try
+ {
+ ByteBuffer buffer = BufferUtil.allocate(4);
+ int filled = getEndPoint().fill(buffer);
+ Assert.assertEquals(4, filled);
+ Assert.assertArrayEquals(CAFE_BABE, buffer.array());
+ getEndPoint().write(new Callback.Adapter(), buffer);
+
+ // We are good, upgrade the connection
+ ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(connector, getEndPoint()));
+ }
+ catch (Throwable x)
+ {
+ close();
+ }
+ }
+ }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientTest.java b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientTest.java
new file mode 100644
index 0000000..7aa961d
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientTest.java
@@ -0,0 +1,428 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.zip.GZIPOutputStream;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientTest extends AbstractHttpClientServerTest
+{
+ public HttpClientTest(SslContextFactory sslContextFactory)
+ {
+ super(sslContextFactory);
+ }
+
+ @Test
+ public void test_GET_ResponseWithoutContent() throws Exception
+ {
+ start(new EmptyServerHandler());
+
+ Response response = client.GET(scheme + "://localhost:" + connector.getLocalPort());
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ }
+
+ @Test
+ public void test_GET_ResponseWithContent() throws Exception
+ {
+ final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ response.getOutputStream().write(data);
+ baseRequest.setHandled(true);
+ }
+ });
+
+ ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort());
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ byte[] content = response.getContent();
+ Assert.assertArrayEquals(data, content);
+ }
+
+ @Test
+ public void test_GET_WithParameters_ResponseWithContent() throws Exception
+ {
+ final String paramName1 = "a";
+ final String paramName2 = "b";
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ response.setCharacterEncoding("UTF-8");
+ ServletOutputStream output = response.getOutputStream();
+ String paramValue1 = request.getParameter(paramName1);
+ output.write(paramValue1.getBytes("UTF-8"));
+ String paramValue2 = request.getParameter(paramName2);
+ Assert.assertEquals("", paramValue2);
+ output.write("empty".getBytes("UTF-8"));
+ baseRequest.setHandled(true);
+ }
+ });
+
+ String value1 = "\u20AC";
+ String paramValue1 = URLEncoder.encode(value1, "UTF-8");
+ String query = paramName1 + "=" + paramValue1 + "&" + paramName2;
+ ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ String content = new String(response.getContent(), "UTF-8");
+ Assert.assertEquals(value1 + "empty", content);
+ }
+
+ @Test
+ public void test_GET_WithParametersMultiValued_ResponseWithContent() throws Exception
+ {
+ final String paramName1 = "a";
+ final String paramName2 = "b";
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ response.setCharacterEncoding("UTF-8");
+ ServletOutputStream output = response.getOutputStream();
+ String[] paramValues1 = request.getParameterValues(paramName1);
+ for (String paramValue : paramValues1)
+ output.write(paramValue.getBytes("UTF-8"));
+ String paramValue2 = request.getParameter(paramName2);
+ output.write(paramValue2.getBytes("UTF-8"));
+ baseRequest.setHandled(true);
+ }
+ });
+
+ String value11 = "\u20AC";
+ String value12 = "\u20AA";
+ String value2 = "&";
+ String paramValue11 = URLEncoder.encode(value11, "UTF-8");
+ String paramValue12 = URLEncoder.encode(value12, "UTF-8");
+ String paramValue2 = URLEncoder.encode(value2, "UTF-8");
+ String query = paramName1 + "=" + paramValue11 + "&" + paramName1 + "=" + paramValue12 + "&" + paramName2 + "=" + paramValue2;
+ ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ String content = new String(response.getContent(), "UTF-8");
+ Assert.assertEquals(value11 + value12 + value2, content);
+ }
+
+ @Test
+ public void test_POST_WithParameters() throws Exception
+ {
+ final String paramName = "a";
+ final String paramValue = "\u20AC";
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ String value = request.getParameter(paramName);
+ if (paramValue.equals(value))
+ {
+ response.setCharacterEncoding("UTF-8");
+ response.setContentType("text/plain");
+ response.getOutputStream().print(value);
+ }
+ }
+ });
+
+ ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
+ .param(paramName, paramValue)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
+ }
+
+ @Test
+ public void test_PUT_WithParameters() throws Exception
+ {
+ final String paramName = "a";
+ final String paramValue = "\u20AC";
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ String value = request.getParameter(paramName);
+ if (paramValue.equals(value))
+ {
+ response.setCharacterEncoding("UTF-8");
+ response.setContentType("text/plain");
+ response.getOutputStream().print(value);
+ }
+ }
+ });
+
+ URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort() + "/path?" + paramName + "=" + paramValue);
+ ContentResponse response = client.newRequest(uri)
+ .method(HttpMethod.PUT)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
+ }
+
+ @Test
+ public void test_POST_WithParameters_WithContent() throws Exception
+ {
+ final byte[] content = {0, 1, 2, 3};
+ final String paramName = "a";
+ final String paramValue = "\u20AC";
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ String value = request.getParameter(paramName);
+ if (paramValue.equals(value))
+ {
+ response.setCharacterEncoding("UTF-8");
+ response.setContentType("application/octet-stream");
+ response.getOutputStream().write(content);
+ }
+ }
+ });
+
+ ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort() + "/?b=1")
+ .param(paramName, paramValue)
+ .content(new BytesContentProvider(content))
+ .timeout(555, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ Assert.assertArrayEquals(content, response.getContent());
+ }
+
+ @Test
+ public void test_POST_WithContent_NotifiesRequestContentListener() throws Exception
+ {
+ final byte[] content = {0, 1, 2, 3};
+ start(new EmptyServerHandler());
+
+ ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
+ .onRequestContent(new Request.ContentListener()
+ {
+ @Override
+ public void onContent(Request request, ByteBuffer buffer)
+ {
+ byte[] bytes = new byte[buffer.remaining()];
+ buffer.get(bytes);
+ if (!Arrays.equals(content, bytes))
+ request.abort(new Exception());
+ }
+ })
+ .content(new BytesContentProvider(content))
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ }
+
+ @Test
+ public void test_POST_WithContent_TracksProgress() throws Exception
+ {
+ start(new EmptyServerHandler());
+
+ final AtomicInteger progress = new AtomicInteger();
+ ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
+ .onRequestContent(new Request.ContentListener()
+ {
+ @Override
+ public void onContent(Request request, ByteBuffer buffer)
+ {
+ byte[] bytes = new byte[buffer.remaining()];
+ Assert.assertEquals(1, bytes.length);
+ buffer.get(bytes);
+ Assert.assertEquals(bytes[0], progress.getAndIncrement());
+ }
+ })
+ .content(new BytesContentProvider(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4}))
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ Assert.assertEquals(5, progress.get());
+ }
+
+ @Test
+ public void test_GZIP_ContentEncoding() throws Exception
+ {
+ final byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ response.setHeader("Content-Encoding", "gzip");
+ GZIPOutputStream gzipOutput = new GZIPOutputStream(response.getOutputStream());
+ gzipOutput.write(data);
+ gzipOutput.finish();
+ }
+ });
+
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertEquals(200, response.getStatus());
+ Assert.assertArrayEquals(data, response.getContent());
+ }
+
+ @Slow
+ @Test
+ public void test_Request_IdleTimeout() throws Exception
+ {
+ final long idleTimeout = 1000;
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ try
+ {
+ baseRequest.setHandled(true);
+ TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+ }
+ catch (InterruptedException x)
+ {
+ throw new ServletException(x);
+ }
+ }
+ });
+
+ final String host = "localhost";
+ final int port = connector.getLocalPort();
+ try
+ {
+ client.newRequest(host, port)
+ .scheme(scheme)
+ .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
+ .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
+ .send();
+ Assert.fail();
+ }
+ catch (ExecutionException expected)
+ {
+ Assert.assertTrue(expected.getCause() instanceof TimeoutException);
+ }
+
+ // Make another request without specifying the idle timeout, should not fail
+ ContentResponse response = client.newRequest(host, port)
+ .scheme(scheme)
+ .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
+ .send();
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ }
+
+ @Test
+ public void testSendToIPv6Address() throws Exception
+ {
+ start(new EmptyServerHandler());
+
+ ContentResponse response = client.newRequest("[::1]", connector.getLocalPort())
+ .scheme(scheme)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ }
+
+ @Test
+ public void test_HEAD_With_ResponseContentLength() throws Exception
+ {
+ final int length = 1024;
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ response.getOutputStream().write(new byte[length]);
+ }
+ });
+
+ // HEAD requests receive a Content-Length header, but do not
+ // receive the content so they must handle this case properly
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .method(HttpMethod.HEAD)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ Assert.assertEquals(0, response.getContent().length);
+
+ // Perform a normal GET request to be sure the content is now read
+ response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ Assert.assertEquals(length, response.getContent().length);
+ }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/test/resources/jetty-logging.properties b/jetty-spdy/spdy-http-client-transport/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..8163013
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/test/resources/jetty-logging.properties
@@ -0,0 +1,4 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.LEVEL=DEBUG
+#org.eclipse.jetty.client.LEVEL=DEBUG
+#org.eclipse.jetty.spdy.LEVEL=DEBUG
diff --git a/jetty-spdy/spdy-http-client-transport/src/test/resources/keystore.jks b/jetty-spdy/spdy-http-client-transport/src/test/resources/keystore.jks
new file mode 100644
index 0000000..428ba54
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/test/resources/keystore.jks
Binary files differ
diff --git a/jetty-spdy/spdy-http-client-transport/src/test/resources/truststore.jks b/jetty-spdy/spdy-http-client-transport/src/test/resources/truststore.jks
new file mode 100644
index 0000000..839cb8c
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/test/resources/truststore.jks
Binary files differ
diff --git a/jetty-spdy/spdy-http-common/pom.xml b/jetty-spdy/spdy-http-common/pom.xml
new file mode 100644
index 0000000..45e773b
--- /dev/null
+++ b/jetty-spdy/spdy-http-common/pom.xml
@@ -0,0 +1,48 @@
+<?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/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.eclipse.jetty.spdy</groupId>
+ <artifactId>spdy-parent</artifactId>
+ <version>9.1.1-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>spdy-http-common</artifactId>
+ <name>Jetty :: SPDY :: HTTP Common</name>
+
+ <properties>
+ <bundle-symbolic-name>${project.groupId}.http.common</bundle-symbolic-name>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ <configuration>
+ <instructions>
+ <Export-Package>org.eclipse.jetty.spdy.http;version="9.1"</Export-Package>
+ <Import-Package>!org.eclipse.jetty.npn,org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
+ </instructions>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.jetty.spdy</groupId>
+ <artifactId>spdy-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/jetty-spdy/spdy-http-common/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java b/jetty-spdy/spdy-http-common/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java
new file mode 100644
index 0000000..18634d0
--- /dev/null
+++ b/jetty-spdy/spdy-http-common/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java
@@ -0,0 +1,82 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.http;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.spdy.api.SPDY;
+
+/**
+ * <p>{@link HTTPSPDYHeader} defines the SPDY headers that are not also HTTP headers,
+ * such as <tt>method</tt>, <tt>version</tt>, etc. or that are treated differently
+ * by the SPDY protocol, such as <tt>host</tt>.</p>
+ */
+public enum HTTPSPDYHeader
+{
+ METHOD("method", ":method"),
+ URI("url", ":path"),
+ VERSION("version", ":version"),
+ SCHEME("scheme", ":scheme"),
+ HOST("host", ":host"),
+ STATUS("status", ":status");
+
+ public static HTTPSPDYHeader from(short version, String name)
+ {
+ switch (version)
+ {
+ case SPDY.V2:
+ return Names.v2Names.get(name);
+ case SPDY.V3:
+ return Names.v3Names.get(name);
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ private final String v2Name;
+ private final String v3Name;
+
+ private HTTPSPDYHeader(String v2Name, String v3Name)
+ {
+ this.v2Name = v2Name;
+ Names.v2Names.put(v2Name, this);
+ this.v3Name = v3Name;
+ Names.v3Names.put(v3Name, this);
+ }
+
+ public String name(short version)
+ {
+ switch (version)
+ {
+ case SPDY.V2:
+ return v2Name;
+ case SPDY.V3:
+ return v3Name;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ private static class Names
+ {
+ private static final Map<String, HTTPSPDYHeader> v2Names = new HashMap<>();
+ private static final Map<String, HTTPSPDYHeader> v3Names = new HashMap<>();
+ }
+}
diff --git a/jetty-spdy/spdy-http-server/pom.xml b/jetty-spdy/spdy-http-server/pom.xml
index 3fc1615..ae8c7c4 100644
--- a/jetty-spdy/spdy-http-server/pom.xml
+++ b/jetty-spdy/spdy-http-server/pom.xml
@@ -3,11 +3,11 @@
<parent>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spdy-http-server</artifactId>
- <name>Jetty :: SPDY :: Jetty Server HTTP Layer</name>
+ <name>Jetty :: SPDY :: HTTP Server</name>
<properties>
<bundle-symbolic-name>${project.groupId}.http.server</bundle-symbolic-name>
@@ -73,8 +73,8 @@
</goals>
<configuration>
<instructions>
- <Export-Package>org.eclipse.jetty.spdy.server.http;version="9.0",
- org.eclipse.jetty.spdy.server.proxy;version="9.0"
+ <Export-Package>org.eclipse.jetty.spdy.server.http;version="9.1",
+ org.eclipse.jetty.spdy.server.proxy;version="9.1"
</Export-Package>
<Import-Package>!org.eclipse.jetty.npn,org.eclipse.jetty.*;version="[9.0,10.0)",*
</Import-Package>
@@ -90,6 +90,11 @@
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.spdy</groupId>
+ <artifactId>spdy-http-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-server</artifactId>
<version>${project.version}</version>
</dependency>
diff --git a/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy.xml b/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy.xml
index 973ab9f..41bfb86 100644
--- a/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy.xml
+++ b/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy.xml
@@ -113,7 +113,7 @@
</Arg>
<!-- Set the initial window size for this SPDY connector. -->
<!-- See: http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3#TOC-2.6.8-WINDOW_UPDATE -->
- <Set name="initialWindowSize">65536</Set>
+ <Set name="initialWindowSize"><Property name="spdy.initialWindowSize" default="65536"/></Set>
<!-- Uncomment to enable ReferrerPushStrategy -->
<!--<Arg name="pushStrategy"><Ref refid="pushStrategy"/></Arg>-->
</New>
@@ -128,7 +128,7 @@
</Arg>
<!-- Set the initial window size for this SPDY connector. -->
<!-- See: http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3#TOC-2.6.8-WINDOW_UPDATE -->
- <Set name="initialWindowSize">65536</Set>
+ <Set name="initialWindowSize"><Property name="spdy.initialWindowSize" default="65536"/></Set>
</New>
</Item>
@@ -143,13 +143,9 @@
</Array>
</Arg>
- <Set name="host">
- <Property name="jetty.host"/>
- </Set>
- <Set name="port">
- <Property name="jetty.spdy.port" default="8443"/>
- </Set>
- <Set name="idleTimeout">30000</Set>
+ <Set name="host"><Property name="jetty.host"/></Set>
+ <Set name="port"><Property name="spdy.port" default="443"/></Set>
+ <Set name="idleTimeout"><Property name="spdy.timeout" default="30000"/></Set>
</New>
</Arg>
</Call>
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/npn.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/npn.mod
new file mode 100644
index 0000000..783f5d8
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/npn.mod
@@ -0,0 +1,26 @@
+#
+# NPN Module
+#
+
+[files]
+http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar:lib/npn/npn-boot-1.1.6.v20130911.jar
+
+[ini-template]
+# NPN Configuration
+# NPN boot jar for JRE 1.7.0_45
+--exec
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.6.v20130911.jar
+
+# For other versions of JRE, an appropriate npn-boot jar must be downloaded
+#
+# 1.7.0 - 1.7.0u2 - 1.7.0u3 http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.0.0.v20120402/npn-boot-1.0.0.v20120402.jar
+# 1.7.0u4 - 1.7.0u5 http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.0.v20120525/npn-boot-1.1.0.v20120525.jar
+# 1.7.0u6 - 1.7.0u7 http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.1.v20121030/npn-boot-1.1.1.v20121030.jar
+# 1.7.0u9 - 1.7.0u10 - 1.7.0u11 http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.3.v20130313/npn-boot-1.1.3.v20130313.jar
+# 1.7.0u13 http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.4.v20130313/npn-boot-1.1.4.v20130313.jar
+# 1.7.0u15 - 1.7.0u17 - 1.7.0u21 - 1.7.0u25 http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar
+# 1.7.0u40 http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar
+# 1.7.0u45 http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar
+#
+# Then edit the -Xbootclasspath line above with the correct version
+
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/spdy.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/spdy.mod
new file mode 100644
index 0000000..eab614b
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/spdy.mod
@@ -0,0 +1,23 @@
+#
+# SPDY Support Module
+#
+
+[depend]
+ssl
+npn
+
+[lib]
+lib/spdy/*.jar
+
+[xml]
+etc/jetty-ssl.xml
+etc/jetty-spdy.xml
+
+[ini-template]
+## SPDY Configuration
+# Port for SPDY connections
+spdy.port=8443
+# SPDY idle timeout in milliseconds
+spdy.timeout=30000
+# Initial Window Size for SPDY
+#spdy.initialWindowSize=65536
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYHeader.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYHeader.java
deleted file mode 100644
index 8954fe2..0000000
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYHeader.java
+++ /dev/null
@@ -1,82 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.spdy.server.http;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.eclipse.jetty.spdy.api.SPDY;
-
-/**
- * <p>{@link HTTPSPDYHeader} defines the SPDY headers that are not also HTTP headers,
- * such as <tt>method</tt>, <tt>version</tt>, etc. or that are treated differently
- * by the SPDY protocol, such as <tt>host</tt>.</p>
- */
-public enum HTTPSPDYHeader
-{
- METHOD("method", ":method"),
- URI("url", ":path"),
- VERSION("version", ":version"),
- SCHEME("scheme", ":scheme"),
- HOST("host", ":host"),
- STATUS("status", ":status");
-
- public static HTTPSPDYHeader from(short version, String name)
- {
- switch (version)
- {
- case SPDY.V2:
- return Names.v2Names.get(name);
- case SPDY.V3:
- return Names.v3Names.get(name);
- default:
- throw new IllegalStateException();
- }
- }
-
- private final String v2Name;
- private final String v3Name;
-
- private HTTPSPDYHeader(String v2Name, String v3Name)
- {
- this.v2Name = v2Name;
- Names.v2Names.put(v2Name, this);
- this.v3Name = v3Name;
- Names.v3Names.put(v3Name, this);
- }
-
- public String name(short version)
- {
- switch (version)
- {
- case SPDY.V2:
- return v2Name;
- case SPDY.V3:
- return v3Name;
- default:
- throw new IllegalStateException();
- }
- }
-
- private static class Names
- {
- private static final Map<String, HTTPSPDYHeader> v2Names = new HashMap<>();
- private static final Map<String, HTTPSPDYHeader> v3Names = new HashMap<>();
- }
-}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnectionFactory.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnectionFactory.java
index 45d867a..63828bd 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnectionFactory.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnectionFactory.java
@@ -39,7 +39,7 @@
public class HTTPSPDYServerConnectionFactory extends SPDYServerConnectionFactory implements HttpConfiguration.ConnectionFactory
{
private static final String CHANNEL_ATTRIBUTE = "org.eclipse.jetty.spdy.server.http.HTTPChannelOverSPDY";
- private static final Logger logger = Log.getLogger(HTTPSPDYServerConnectionFactory.class);
+ private static final Logger LOG = Log.getLogger(HTTPSPDYServerConnectionFactory.class);
private final PushStrategy pushStrategy;
private final HttpConfiguration httpConfiguration;
@@ -94,7 +94,7 @@
// can arrive on the same connection, so we need to create an
// HttpChannel for each SYN in order to run concurrently.
- logger.debug("Received {} on {}", synInfo, stream);
+ LOG.debug("Received {} on {}", synInfo, stream);
Fields headers = synInfo.getHeaders();
// According to SPDY/3 spec section 3.2.1 user-agents MUST support gzip compression. Firefox omits the
@@ -102,7 +102,7 @@
// if clients have to accept it.
// So we inject the accept-encoding header here, even if not set by the client. This will enforce SPDY
// clients to follow the spec and enable gzip compression if GzipFilter or the like is enabled.
- if (!(headers.get("accept-encoding") != null && headers.get("accept-encoding").value().contains
+ if (!(headers.get("accept-encoding") != null && headers.get("accept-encoding").getValue().contains
("gzip")))
headers.add("accept-encoding", "gzip");
HttpTransportOverSPDY transport = new HttpTransportOverSPDY(connector, httpConfiguration, endPoint,
@@ -136,7 +136,7 @@
@Override
public void onHeaders(Stream stream, HeadersInfo headersInfo)
{
- logger.debug("Received {} on {}", headersInfo, stream);
+ LOG.debug("Received {} on {}", headersInfo, stream);
HttpChannelOverSPDY channel = (HttpChannelOverSPDY)stream.getAttribute(CHANNEL_ATTRIBUTE);
channel.requestHeaders(headersInfo.getHeaders(), headersInfo.isClose());
}
@@ -150,9 +150,15 @@
@Override
public void onData(Stream stream, final DataInfo dataInfo)
{
- logger.debug("Received {} on {}", dataInfo, stream);
+ LOG.debug("Received {} on {}", dataInfo, stream);
HttpChannelOverSPDY channel = (HttpChannelOverSPDY)stream.getAttribute(CHANNEL_ATTRIBUTE);
channel.requestContent(dataInfo, dataInfo.isClose());
}
+
+ @Override
+ public void onFailure(Stream stream, Throwable x)
+ {
+ LOG.debug(x);
+ }
}
}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpChannelOverSPDY.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpChannelOverSPDY.java
index fe2b9ac..d9eb54f 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpChannelOverSPDY.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpChannelOverSPDY.java
@@ -19,8 +19,6 @@
package org.eclipse.jetty.spdy.server.http;
import java.nio.ByteBuffer;
-import java.util.LinkedList;
-import java.util.Queue;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpMethod;
@@ -33,6 +31,7 @@
import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.log.Log;
@@ -42,9 +41,9 @@
{
private static final Logger LOG = Log.getLogger(HttpChannelOverSPDY.class);
- private final Queue<Runnable> tasks = new LinkedList<>();
private final Stream stream;
private boolean dispatched; // Guarded by synchronization on tasks
+ private boolean redispatch; // Guarded by synchronization on tasks
private boolean headersComplete;
public HttpChannelOverSPDY(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInputOverSPDY input, Stream stream)
@@ -60,44 +59,47 @@
return super.headerComplete();
}
- private void post(Runnable task)
- {
- synchronized (tasks)
- {
- LOG.debug("Posting task {}", task);
- tasks.offer(task);
- dispatch();
- }
- }
-
private void dispatch()
{
- synchronized (tasks)
+ synchronized (this)
{
if (dispatched)
- return;
-
- final Runnable task = tasks.poll();
- if (task != null)
+ redispatch=true;
+ else
{
- dispatched = true;
- LOG.debug("Dispatching task {}", task);
- execute(new Runnable()
- {
- @Override
- public void run()
- {
- LOG.debug("Executing task {}", task);
- task.run();
- LOG.debug("Completing task {}", task);
- dispatched = false;
- dispatch();
- }
- });
+ LOG.debug("Dispatch {}", this);
+ dispatched=true;
+ execute(this);
}
}
}
+ @Override
+ public void run()
+ {
+ boolean execute=true;
+
+ while(execute)
+ {
+ try
+ {
+ LOG.debug("Executing {}",this);
+ super.run();
+ }
+ finally
+ {
+ LOG.debug("Completing {}", this);
+ synchronized (this)
+ {
+ dispatched = redispatch;
+ redispatch=false;
+ execute=dispatched;
+ }
+ }
+ }
+ }
+
+
public void requestStart(final Fields headers, final boolean endRequest)
{
if (!headers.isEmpty())
@@ -114,20 +116,19 @@
if (endRequest)
{
- if (headerComplete())
- post(this);
+ boolean dispatch = headerComplete();
if (messageComplete())
- post(this);
+ dispatch=true;
+ if (dispatch)
+ dispatch();
}
}
public void requestContent(final DataInfo dataInfo, boolean endRequest)
{
- if (!headersComplete)
- {
- if (headerComplete())
- post(this);
- }
+ boolean dispatch=false;
+ if (!headersComplete && headerComplete())
+ dispatch=true;
LOG.debug("HTTP > {} bytes of content", dataInfo.length());
@@ -147,13 +148,13 @@
LOG.debug("Queuing last={} content {}", endRequest, copyDataInfo);
if (content(copyDataInfo))
- post(this);
+ dispatch=true;
- if (endRequest)
- {
- if (messageComplete())
- post(this);
- }
+ if (endRequest && messageComplete())
+ dispatch=true;
+
+ if (dispatch)
+ dispatch();
}
private boolean performBeginRequest(Fields headers)
@@ -169,19 +170,19 @@
return false;
}
- HttpMethod httpMethod = HttpMethod.fromString(methodHeader.value());
- HttpVersion httpVersion = HttpVersion.fromString(versionHeader.value());
-
+ HttpMethod httpMethod = HttpMethod.fromString(methodHeader.getValue());
+ HttpVersion httpVersion = HttpVersion.fromString(versionHeader.getValue());
+
// TODO should handle URI as byte buffer as some bad clients send WRONG encodings in query string
// that we have to deal with
- ByteBuffer uri = BufferUtil.toBuffer(uriHeader.value());
+ ByteBuffer uri = BufferUtil.toBuffer(uriHeader.getValue());
- LOG.debug("HTTP > {} {} {}", httpMethod, uriHeader.value(), httpVersion);
+ LOG.debug("HTTP > {} {} {}", httpMethod, uriHeader.getValue(), httpVersion);
startRequest(httpMethod, httpMethod.asString(), uri, httpVersion);
Fields.Field schemeHeader = headers.get(HTTPSPDYHeader.SCHEME.name(version));
if (schemeHeader != null)
- getRequest().setScheme(schemeHeader.value());
+ getRequest().setScheme(schemeHeader.getValue());
return true;
}
@@ -189,7 +190,7 @@
{
for (Fields.Field header : headers)
{
- String name = header.name();
+ String name = header.getName();
// Skip special SPDY headers, unless it's the "host" header
HTTPSPDYHeader specialHeader = HTTPSPDYHeader.from(stream.getSession().getVersion(), name);
@@ -214,7 +215,7 @@
default:
{
// Spec says headers must be single valued
- String value = header.value();
+ String value = header.getValue();
LOG.debug("HTTP > {}: {}", name, value);
parsedHeader(new HttpField(name,value));
break;
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpInputOverSPDY.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpInputOverSPDY.java
index eff52a4..326e6bd 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpInputOverSPDY.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpInputOverSPDY.java
@@ -18,10 +18,10 @@
package org.eclipse.jetty.spdy.server.http;
-import org.eclipse.jetty.server.HttpInput;
+import org.eclipse.jetty.server.QueuedHttpInput;
import org.eclipse.jetty.spdy.api.DataInfo;
-public class HttpInputOverSPDY extends HttpInput<DataInfo>
+public class HttpInputOverSPDY extends QueuedHttpInput<DataInfo>
{
@Override
protected int remaining(DataInfo item)
@@ -34,6 +34,12 @@
{
return item.readInto(buffer, offset, length);
}
+
+ @Override
+ protected void consume(DataInfo item, int length)
+ {
+ item.consume(length);
+ }
@Override
protected void onContentConsumed(DataInfo dataInfo)
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java
index 0c352e8..b554bec 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.spdy.server.http;
-import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Queue;
import java.util.Set;
@@ -46,6 +45,7 @@
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.BlockingCallback;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
@@ -121,7 +121,7 @@
// info!=null content!=null lastContent==false reply, commit with content
// info!=null content!=null lastContent==true reply, commit with content and complete
- boolean isHeadRequest = HttpMethod.HEAD.name().equalsIgnoreCase(requestHeaders.get(HTTPSPDYHeader.METHOD.name(version)).value());
+ boolean isHeadRequest = HttpMethod.HEAD.name().equalsIgnoreCase(requestHeaders.get(HTTPSPDYHeader.METHOD.name(version)).getValue());
boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest;
boolean close = !hasContent && lastContent;
@@ -132,7 +132,7 @@
StreamException exception = new StreamException(stream.getId(), StreamStatus.PROTOCOL_ERROR,
"Stream already committed!");
callback.failed(exception);
- LOG.warn("Committed response twice.", exception);
+ LOG.debug("Committed response twice.", exception);
return;
}
sendReply(info, !hasContent ? callback : new Callback.Adapter()
@@ -209,20 +209,6 @@
}
@Override
- public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent) throws IOException
- {
- send(info, content, lastContent, streamBlocker);
- try
- {
- streamBlocker.block();
- }
- catch (Exception e)
- {
- LOG.debug(e);
- }
- }
-
- @Override
public void completed()
{
LOG.debug("Completed {}", this);
@@ -236,7 +222,7 @@
stream.headers(new HeadersInfo(replyInfo.getHeaders(), replyInfo.isClose()), callback);
Fields responseHeaders = replyInfo.getHeaders();
- if (responseHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().startsWith("200") && !stream.isClosed())
+ if (responseHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().startsWith("200") && !stream.isClosed())
{
Set<String> pushResources = pushStrategy.apply(stream, requestHeaders, responseHeaders);
if (pushResources.size() > 0)
@@ -367,7 +353,7 @@
newRequestHeaders.put(scheme);
newRequestHeaders.put(host);
newRequestHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath);
- String referrer = scheme.value() + "://" + host.value() + uri.value();
+ String referrer = scheme.getValue() + "://" + host.getValue() + uri.getValue();
newRequestHeaders.put("referer", referrer);
newRequestHeaders.put("x-spdy-push", "true");
return newRequestHeaders;
@@ -377,7 +363,7 @@
{
final Fields pushHeaders = new Fields();
if (version == SPDY.V2)
- pushHeaders.put(HTTPSPDYHeader.URI.name(version), scheme.value() + "://" + host.value() + pushResourcePath);
+ pushHeaders.put(HTTPSPDYHeader.URI.name(version), scheme.getValue() + "://" + host.getValue() + pushResourcePath);
else
{
pushHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath);
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java
index c76bc88..85a4a73 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java
@@ -33,6 +33,7 @@
import java.util.regex.Pattern;
import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -40,23 +41,23 @@
/**
* <p>A SPDY push strategy that auto-populates push metadata based on referrer URLs.<p>A typical request for a main
* resource such as {@code index.html} is immediately followed by a number of requests for associated resources.
- * Associated resource requests will have a {@code Referer} HTTP header that points to {@code index.html}, which is
- * used to link the associated resource to the main resource.<p>However, also following a hyperlink generates a
- * HTTP request with a {@code Referer} HTTP header that points to {@code index.html}; therefore a proper value for
- * {@link #setReferrerPushPeriod(int)} has to be set. If the referrerPushPeriod for a main resource has elapsed,
- * no more associated resources will be added for that main resource.<p>This class distinguishes associated main
- * resources by their URL path suffix and content type. CSS stylesheets, images and JavaScript files have
- * recognizable URL path suffixes that are classified as associated resources. The suffix regexs can be configured by
- * constructor argument</p>
+ * Associated resource requests will have a {@code Referer} HTTP header that points to {@code index.html}, which is used
+ * to link the associated resource to the main resource.<p>However, also following a hyperlink generates a HTTP request
+ * with a {@code Referer} HTTP header that points to {@code index.html}; therefore a proper value for {@link
+ * #setReferrerPushPeriod(int)} has to be set. If the referrerPushPeriod for a main resource has elapsed, no more
+ * associated resources will be added for that main resource.<p>This class distinguishes associated main resources by
+ * their URL path suffix and content type. CSS stylesheets, images and JavaScript files have recognizable URL path
+ * suffixes that are classified as associated resources. The suffix regexs can be configured by constructor argument</p>
* <p>When CSS stylesheets refer to images, the CSS image request will have the CSS stylesheet as referrer. This
* implementation will push also the CSS image.<p>The push metadata built by this implementation is limited by the
- * number of pages of the application itself, and by the {@link #setMaxAssociatedResources(int)} max associated resources}
- * parameter. This parameter limits the number of associated resources per each main resource, so that if a main
- * resource has hundreds of associated resources, only up to the number specified by this parameter will be pushed.
+ * number of pages of the application itself, and by the {@link #setMaxAssociatedResources(int)} max associated
+ * resources} parameter. This parameter limits the number of associated resources per each main resource, so that if a
+ * main resource has hundreds of associated resources, only up to the number specified by this parameter will be
+ * pushed.
*/
public class ReferrerPushStrategy implements PushStrategy
{
- private static final Logger logger = Log.getLogger(ReferrerPushStrategy.class);
+ private static final Logger LOG = Log.getLogger(ReferrerPushStrategy.class);
private final ConcurrentMap<String, MainResource> mainResources = new ConcurrentHashMap<>();
private final Set<Pattern> pushRegexps = new HashSet<>();
private final Set<String> pushContentTypes = new HashSet<>();
@@ -159,14 +160,14 @@
Set<String> result = Collections.<String>emptySet();
short version = stream.getSession().getVersion();
if (!isIfModifiedSinceHeaderPresent(requestHeaders) && isValidMethod(requestHeaders.get(HTTPSPDYHeader.METHOD
- .name(version)).value()) && !isUserAgentBlacklisted(requestHeaders))
+ .name(version)).getValue()) && !isUserAgentBlacklisted(requestHeaders))
{
- String scheme = requestHeaders.get(HTTPSPDYHeader.SCHEME.name(version)).value();
- String host = requestHeaders.get(HTTPSPDYHeader.HOST.name(version)).value();
+ String scheme = requestHeaders.get(HTTPSPDYHeader.SCHEME.name(version)).getValue();
+ String host = requestHeaders.get(HTTPSPDYHeader.HOST.name(version)).getValue();
String origin = scheme + "://" + host;
- String url = requestHeaders.get(HTTPSPDYHeader.URI.name(version)).value();
+ String url = requestHeaders.get(HTTPSPDYHeader.URI.name(version)).getValue();
String absoluteURL = origin + url;
- logger.debug("Applying push strategy for {}", absoluteURL);
+ LOG.debug("Applying push strategy for {}", absoluteURL);
if (isMainResource(url, responseHeaders))
{
MainResource mainResource = getOrCreateMainResource(absoluteURL);
@@ -177,7 +178,7 @@
Fields.Field referrerHeader = requestHeaders.get("referer");
if (referrerHeader != null)
{
- String referrer = referrerHeader.value();
+ String referrer = referrerHeader.getValue();
MainResource mainResource = mainResources.get(referrer);
if (mainResource == null)
mainResource = getOrCreateMainResource(referrer);
@@ -189,7 +190,7 @@
result = getPushResources(absoluteURL);
}
}
- logger.debug("Pushing {} resources for {}: {}", result.size(), absoluteURL, result);
+ LOG.debug("Pushing {} resources for {}: {}", result.size(), absoluteURL, result);
}
return result;
}
@@ -207,7 +208,7 @@
MainResource mainResource = mainResources.get(absoluteURL);
if (mainResource == null)
{
- logger.debug("Creating new main resource for {}", absoluteURL);
+ LOG.debug("Creating new main resource for {}", absoluteURL);
MainResource value = new MainResource(absoluteURL);
mainResource = mainResources.putIfAbsent(absoluteURL, value);
if (mainResource == null)
@@ -236,7 +237,7 @@
Fields.Field userAgentHeader = headers.get("user-agent");
if (userAgentHeader != null)
for (Pattern userAgentPattern : userAgentBlacklist)
- if (userAgentPattern.matcher(userAgentHeader.value()).matches())
+ if (userAgentPattern.matcher(userAgentHeader.getValue()).matches())
return true;
return false;
}
@@ -251,7 +252,7 @@
if (header == null)
return true;
- String contentType = header.value().toLowerCase(Locale.ENGLISH);
+ String contentType = header.getValue().toLowerCase(Locale.ENGLISH);
for (String pushContentType : pushContentTypes)
if (contentType.startsWith(pushContentType))
return true;
@@ -282,7 +283,7 @@
long delay = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - firstResourceAdded.get());
if (!referrer.startsWith(origin) && !isPushOriginAllowed(origin))
{
- logger.debug("Skipped store of push metadata {} for {}: Origin: {} doesn't match or origin not allowed",
+ LOG.debug("Skipped store of push metadata {} for {}: Origin: {} doesn't match or origin not allowed",
url, name, origin);
return false;
}
@@ -292,17 +293,18 @@
// although in rare cases few more resources will be stored
if (resources.size() >= maxAssociatedResources)
{
- logger.debug("Skipped store of push metadata {} for {}: max associated resources ({}) reached",
+ LOG.debug("Skipped store of push metadata {} for {}: max associated resources ({}) reached",
url, name, maxAssociatedResources);
return false;
}
if (delay > referrerPushPeriod)
{
- logger.debug("Delay: {}ms longer than referrerPushPeriod: {}ms. Not adding resource: {} for: {}", delay, referrerPushPeriod, url, name);
+ LOG.debug("Delay: {}ms longer than referrerPushPeriod ({}ms). Not adding resource: {} for: {}", delay,
+ referrerPushPeriod, url, name);
return false;
}
- logger.debug("Adding resource: {} for: {} with delay: {}ms.", url, name, delay);
+ LOG.debug("Adding: {} to: {} with delay: {}ms.", url, this, delay);
resources.add(url);
return true;
}
@@ -314,7 +316,12 @@
public String toString()
{
- return "MainResource: " + name + " associated resources:" + resources.size();
+ return String.format("%s@%x{name=%s,resources=%s}",
+ getClass().getSimpleName(),
+ hashCode(),
+ name,
+ resources
+ );
}
private boolean isPushOriginAllowed(String origin)
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPProxyEngine.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPProxyEngine.java
index b46d11c..4779ff5 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPProxyEngine.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPProxyEngine.java
@@ -40,7 +40,7 @@
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.StreamStatus;
import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.server.http.HTTPSPDYHeader;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
@@ -76,8 +76,8 @@
public StreamFrameListener proxy(final Stream clientStream, SynInfo clientSynInfo, ProxyEngineSelector.ProxyServerInfo proxyServerInfo)
{
short version = clientStream.getSession().getVersion();
- String method = clientSynInfo.getHeaders().get(HTTPSPDYHeader.METHOD.name(version)).value();
- String path = clientSynInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).value();
+ String method = clientSynInfo.getHeaders().get(HTTPSPDYHeader.METHOD.name(version)).getValue();
+ String path = clientSynInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).getValue();
Fields headers = new Fields(clientSynInfo.getHeaders(), false);
@@ -132,7 +132,7 @@
private void sendRequest(final Stream clientStream, Request request)
{
- request.send(new Response.Listener.Empty()
+ request.send(new Response.Listener.Adapter()
{
private volatile boolean committed;
@@ -249,8 +249,8 @@
private void addNonSpdyHeadersToRequest(short version, Fields headers, Request request)
{
for (Fields.Field header : headers)
- if (HTTPSPDYHeader.from(version, header.name()) == null)
- request.header(header.name(), header.value());
+ if (HTTPSPDYHeader.from(version, header.getName()) == null)
+ request.header(header.getName(), header.getValue());
}
static class LoggingCallback extends Callback.Adapter
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngine.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngine.java
index 0b980b5..c72f3a0 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngine.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngine.java
@@ -28,7 +28,7 @@
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.server.http.HTTPSPDYHeader;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Fields;
/**
@@ -98,7 +98,7 @@
addViaHeader(headers);
Fields.Field schemeField = headers.get(HTTPSPDYHeader.SCHEME.name(stream.getSession().getVersion()));
if(schemeField != null)
- headers.add("X-Forwarded-Proto", schemeField.value());
+ headers.add("X-Forwarded-Proto", schemeField.getValue());
InetSocketAddress address = stream.getSession().getRemoteAddress();
if (address != null)
{
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngineSelector.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngineSelector.java
index 452e32b..71b6fbe 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngineSelector.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngineSelector.java
@@ -32,7 +32,7 @@
import org.eclipse.jetty.spdy.api.StreamStatus;
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.spdy.server.http.HTTPSPDYHeader;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.log.Log;
@@ -71,7 +71,7 @@
return null;
}
- String host = hostHeader.value();
+ String host = hostHeader.getValue();
int colon = host.indexOf(':');
if (colon >= 0)
host = host.substring(0, colon);
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java
index 76ca8bc..e66e78e 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.spdy.server.proxy;
-import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -52,7 +51,7 @@
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.server.http.HTTPSPDYHeader;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
@@ -193,7 +192,7 @@
{
private HTTPSession(short version, Connector connector)
{
- super(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), null,
+ super(version, connector.getByteBufferPool(), connector.getScheduler(), null,
getEndPoint(), null, 1, proxyEngineSelector, null, null);
}
@@ -201,7 +200,7 @@
public void rst(RstInfo rstInfo, Callback handler)
{
HttpGenerator.ResponseInfo info = new HttpGenerator.ResponseInfo(HttpVersion.fromString(headers.get
- ("version").value()), null, 0, 502, "SPDY reset received from upstream server", false);
+ ("version").getValue()), null, 0, 502, "SPDY reset received from upstream server", false);
send(info, null, true, new Callback.Adapter());
}
@@ -222,7 +221,7 @@
private HTTPStream(int id, byte priority, ISession session, IStream associatedStream)
{
- super(id, priority, session, associatedStream, null);
+ super(id, priority, session, associatedStream, getHttpChannel().getScheduler(), null);
}
@Override
@@ -240,53 +239,52 @@
}
@Override
- public void reply(ReplyInfo replyInfo, Callback handler)
+ public void reply(ReplyInfo replyInfo, final Callback handler)
{
- try
- {
- Fields headers = new Fields(replyInfo.getHeaders(), false);
+ Fields headers = new Fields(replyInfo.getHeaders(), false);
addPersistenceHeader(headers);
- headers.remove(HTTPSPDYHeader.SCHEME.name(version));
+ headers.remove(HTTPSPDYHeader.SCHEME.name(version));
- String status = headers.remove(HTTPSPDYHeader.STATUS.name(version)).value();
- Matcher matcher = statusRegexp.matcher(status);
- matcher.matches();
- int code = Integer.parseInt(matcher.group(1));
- String reason = matcher.group(2).trim();
+ String status = headers.remove(HTTPSPDYHeader.STATUS.name(version)).getValue();
+ Matcher matcher = statusRegexp.matcher(status);
+ matcher.matches();
+ int code = Integer.parseInt(matcher.group(1));
+ String reason = matcher.group(2).trim();
- HttpVersion httpVersion = HttpVersion.fromString(headers.remove(HTTPSPDYHeader.VERSION.name(version)).value());
+ HttpVersion httpVersion = HttpVersion.fromString(headers.remove(HTTPSPDYHeader.VERSION.name(version)).getValue());
- // Convert the Host header from a SPDY special header to a normal header
- Fields.Field host = headers.remove(HTTPSPDYHeader.HOST.name(version));
- if (host != null)
- headers.put("host", host.value());
+ // Convert the Host header from a SPDY special header to a normal header
+ Fields.Field host = headers.remove(HTTPSPDYHeader.HOST.name(version));
+ if (host != null)
+ headers.put("host", host.getValue());
- HttpFields fields = new HttpFields();
- for (Fields.Field header : headers)
- {
- String name = camelize(header.name());
- fields.put(name, header.value());
- }
-
- // TODO: handle better the HEAD last parameter
- long contentLength = fields.getLongField(HttpHeader.CONTENT_LENGTH.asString());
- HttpGenerator.ResponseInfo info = new HttpGenerator.ResponseInfo(httpVersion, fields, contentLength, code,
- reason, false);
-
- // TODO use the async send
- send(info, null, replyInfo.isClose());
-
- if (replyInfo.isClose())
- completed();
-
- handler.succeeded();
- }
- catch (IOException x)
+ HttpFields fields = new HttpFields();
+ for (Fields.Field header : headers)
{
- handler.failed(x);
+ String name = camelize(header.getName());
+ fields.put(name, header.getValue());
}
+
+ // TODO: handle better the HEAD last parameter
+ long contentLength = fields.getLongField(HttpHeader.CONTENT_LENGTH.asString());
+ HttpGenerator.ResponseInfo info = new HttpGenerator.ResponseInfo(httpVersion, fields, contentLength, code,
+ reason, false);
+
+ send(info, null, replyInfo.isClose(), new Adapter()
+ {
+ @Override
+ public void failed(Throwable x)
+ {
+ handler.failed(x);
+ }
+ });
+
+ if (replyInfo.isClose())
+ completed();
+
+ handler.succeeded();
}
private String camelize(String name)
@@ -305,41 +303,40 @@
}
@Override
- public void data(DataInfo dataInfo, Callback handler)
+ public void data(DataInfo dataInfo, final Callback handler)
{
- try
+ // Data buffer must be copied, as the ByteBuffer is pooled
+ ByteBuffer byteBuffer = dataInfo.asByteBuffer(false);
+
+ send(null, byteBuffer, dataInfo.isClose(), new Adapter()
{
- // Data buffer must be copied, as the ByteBuffer is pooled
- ByteBuffer byteBuffer = dataInfo.asByteBuffer(false);
+ @Override
+ public void failed(Throwable x)
+ {
+ handler.failed(x);
+ }
+ });
- // TODO use the async send with callback!
- send(null, byteBuffer, dataInfo.isClose());
+ if (dataInfo.isClose())
+ completed();
- if (dataInfo.isClose())
- completed();
-
- handler.succeeded();
- }
- catch (IOException x)
- {
- handler.failed(x);
- }
+ handler.succeeded();
}
}
private void addPersistenceHeader(Fields headersToAddTo)
{
- HttpVersion httpVersion = HttpVersion.fromString(headers.get("version").value());
+ HttpVersion httpVersion = HttpVersion.fromString(headers.get("version").getValue());
boolean persistent = false;
switch (httpVersion)
{
case HTTP_1_0:
{
Fields.Field keepAliveHeader = headers.get(HttpHeader.KEEP_ALIVE.asString());
- if (keepAliveHeader != null)
- persistent = HttpHeaderValue.KEEP_ALIVE.asString().equals(keepAliveHeader.value());
+ if(keepAliveHeader!=null)
+ persistent = HttpHeaderValue.KEEP_ALIVE.asString().equals(keepAliveHeader.getValue());
if (!persistent)
- persistent = HttpMethod.CONNECT.is(headers.get("method").value());
+ persistent = HttpMethod.CONNECT.is(headers.get("method").getValue());
if (persistent)
headersToAddTo.add(HttpHeader.CONNECTION.asString(), HttpHeaderValue.KEEP_ALIVE.asString());
break;
@@ -347,12 +344,12 @@
case HTTP_1_1:
{
Fields.Field connectionHeader = headers.get(HttpHeader.CONNECTION.asString());
- if (connectionHeader != null)
- persistent = !HttpHeaderValue.CLOSE.asString().equals(connectionHeader.value());
+ if(connectionHeader != null)
+ persistent = !HttpHeaderValue.CLOSE.asString().equals(connectionHeader.getValue());
else
persistent = true;
if (!persistent)
- persistent = HttpMethod.CONNECT.is(headers.get("method").value());
+ persistent = HttpMethod.CONNECT.is(headers.get("method").getValue());
if (!persistent)
headersToAddTo.add(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
break;
@@ -368,7 +365,7 @@
{
private HTTPPushStream(int id, byte priority, ISession session, IStream associatedStream)
{
- super(id, priority, session, associatedStream, null);
+ super(id, priority, session, associatedStream, getHttpChannel().getScheduler(), null);
}
@Override
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/SPDYProxyEngine.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/SPDYProxyEngine.java
index 5fe7909..6f49ad0 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/SPDYProxyEngine.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/SPDYProxyEngine.java
@@ -43,7 +43,7 @@
import org.eclipse.jetty.spdy.api.StreamStatus;
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.client.SPDYClient;
-import org.eclipse.jetty.spdy.server.http.HTTPSPDYHeader;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Promise;
@@ -170,6 +170,12 @@
streamPromise.data(serverDataInfo);
}
+ @Override
+ public void onFailure(Stream stream, Throwable x)
+ {
+ LOG.debug(x);
+ }
+
private Session produceSession(String host, short version, InetSocketAddress address)
{
try
@@ -178,7 +184,7 @@
if (session == null)
{
SPDYClient client = factory.newSPDYClient(version);
- session = client.connect(address, sessionListener).get(getConnectTimeout(), TimeUnit.MILLISECONDS);
+ session = client.connect(address, sessionListener);
LOG.debug("Proxy session connected to {}", address);
Session existing = serverSessions.putIfAbsent(host, session);
if (existing != null)
@@ -206,7 +212,7 @@
if (header != null)
{
String toName = httpHeader.name(toVersion);
- for (String value : header.values())
+ for (String value : header.getValues())
headers.add(toName, value);
}
}
@@ -267,6 +273,12 @@
pushStreamPromise.data(clientDataInfo);
}
+
+ @Override
+ public void onFailure(Stream stream, Throwable x)
+ {
+ LOG.debug(x);
+ }
}
private class ProxyStreamFrameListener extends StreamFrameListener.Adapter
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java
index de96a4c..fe53286 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java
@@ -22,7 +22,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
@@ -76,15 +75,17 @@
protected InetSocketAddress startHTTPServer(Handler handler) throws Exception
{
- return startHTTPServer(SPDY.V2, handler);
+ return startHTTPServer(SPDY.V2, handler, 30000);
}
- protected InetSocketAddress startHTTPServer(short version, Handler handler) throws Exception
+ protected InetSocketAddress startHTTPServer(short version, Handler handler, long idleTimeout) throws Exception
{
- server = new Server();
+ QueuedThreadPool threadPool = new QueuedThreadPool(256);
+ threadPool.setName("serverQTP");
+ server = new Server(threadPool);
connector = newHTTPSPDYServerConnector(version);
connector.setPort(0);
- connector.setIdleTimeout(30000);
+ connector.setIdleTimeout(idleTimeout);
server.addConnector(connector);
server.setHandler(handler);
server.start();
@@ -120,7 +121,7 @@
clientFactory = newSPDYClientFactory(threadPool);
clientFactory.start();
}
- return clientFactory.newSPDYClient(version).connect(socketAddress, listener).get(5, TimeUnit.SECONDS);
+ return clientFactory.newSPDYClient(version).connect(socketAddress, listener);
}
protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ConcurrentStreamsTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ConcurrentStreamsTest.java
index e6816a3..50a5279 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ConcurrentStreamsTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ConcurrentStreamsTest.java
@@ -33,6 +33,7 @@
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Fields;
import org.junit.Assert;
import org.junit.Test;
@@ -79,7 +80,7 @@
throw new ServletException(x);
}
}
- }), null);
+ }, 30000), null);
// Perform slow request. This will wait on server side until the fast request wakes it up
Fields headers = createHeaders(slowPath);
@@ -90,7 +91,7 @@
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Fields replyHeaders = replyInfo.getHeaders();
- Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
slowClientLatch.countDown();
}
});
@@ -104,7 +105,7 @@
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Fields replyHeaders = replyInfo.getHeaders();
- Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
fastClientLatch.countDown();
}
});
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDYTest.java
index 9689a2d..145aacc 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDYTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDYTest.java
@@ -18,9 +18,18 @@
package org.eclipse.jetty.spdy.server.http;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpStatus;
@@ -33,11 +42,10 @@
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.StdErrLog;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,13 +53,6 @@
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
@RunWith(MockitoJUnitRunner.class)
public class HttpTransportOverSPDYTest
{
@@ -250,22 +251,29 @@
}
@Test
- public void testVerifyThatAStreamIsNotCommittedTwice() throws IOException
+ public void testVerifyThatAStreamIsNotCommittedTwice() throws IOException, InterruptedException
{
- ((StdErrLog)Log.getLogger(HttpTransportOverSPDY.class)).setHideStacks(true);
+ final CountDownLatch failedCalledLatch = new CountDownLatch(1);
ByteBuffer content = createRandomByteBuffer();
boolean lastContent = false;
- httpTransportOverSPDY.send(responseInfo,content,lastContent, callback);
+ httpTransportOverSPDY.send(responseInfo, content, lastContent, callback);
ArgumentCaptor<ReplyInfo> replyInfoCaptor = ArgumentCaptor.forClass(ReplyInfo.class);
verify(stream, times(1)).reply(replyInfoCaptor.capture(), any(Callback.class));
assertThat("ReplyInfo close is false", replyInfoCaptor.getValue().isClose(), is(false));
- httpTransportOverSPDY.send(HttpGenerator.RESPONSE_500_INFO, null,true);
+ httpTransportOverSPDY.send(HttpGenerator.RESPONSE_500_INFO, null, true, new Callback.Adapter()
+ {
+ @Override
+ public void failed(Throwable x)
+ {
+ failedCalledLatch.countDown();
+ }
+ });
verify(stream, times(1)).data(any(DataInfo.class), any(Callback.class));
- ((StdErrLog)Log.getLogger(HttpTransportOverSPDY.class)).setHideStacks(false);
+ assertThat("callback.failed has been called", failedCalledLatch.await(5, TimeUnit.SECONDS), is(true));
}
private ByteBuffer createRandomByteBuffer()
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/PushStrategyBenchmarkTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/PushStrategyBenchmarkTest.java
index 17f0e17..cc861ac 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/PushStrategyBenchmarkTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/PushStrategyBenchmarkTest.java
@@ -29,6 +29,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -51,6 +52,7 @@
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Fields;
import org.junit.Assert;
import org.junit.Ignore;
@@ -81,7 +83,7 @@
@Test
public void benchmarkPushStrategy() throws Exception
{
- InetSocketAddress address = startHTTPServer(version, new PushStrategyBenchmarkHandler());
+ InetSocketAddress address = startHTTPServer(version, new PushStrategyBenchmarkHandler(), 30000);
// Plain HTTP
ConnectionFactory factory = new HttpConnectionFactory(new HttpConfiguration());
@@ -366,7 +368,7 @@
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
{
- String path = synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).value();
+ String path = synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).getValue();
addPushedResource(path);
return new DataListener();
}
@@ -383,7 +385,7 @@
}
}
- private class TestListener extends Response.Listener.Empty
+ private class TestListener extends Response.Listener.Adapter
{
@Override
public void onComplete(Result result)
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java
index 82537fd..517f54a 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java
@@ -18,6 +18,10 @@
package org.eclipse.jetty.spdy.server.http;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
@@ -27,6 +31,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
@@ -51,6 +56,7 @@
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.StreamStatus;
import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
@@ -62,10 +68,6 @@
import org.junit.Before;
import org.junit.Test;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.junit.Assert.assertThat;
-
public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
{
private static final Logger LOG = Log.getLogger(ReferrerPushStrategyTest.class);
@@ -174,7 +176,7 @@
assertThat("Stream is unidirectional", stream.isUnidirectional(), is(true));
assertThat("URI header ends with css", pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version))
- .value().endsWith
+ .getValue().endsWith
("" +
".css"),
is(true));
@@ -278,7 +280,7 @@
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
LOG.info("onPush: stream: {}, pushInfo: {}", stream, pushInfo);
- String uriHeader = pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).value();
+ String uriHeader = pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).getValue();
switch ((int)allExpectedPushesReceivedLatch.getCount())
{
case 4:
@@ -356,7 +358,7 @@
outputStream.write(bytes);
baseRequest.setHandled(true);
}
- });
+ }, 30000);
Session pushCacheBuildSession = startClient(version, bigResponseServerAddress, null);
Fields mainResourceHeaders = createHeadersWithoutReferrer(mainResource);
@@ -376,7 +378,7 @@
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
LOG.info("Received push for stream: {} {}", stream.getId(), pushInfo);
- String uriHeader = pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).value();
+ String uriHeader = pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).getValue();
switch ((int)allExpectedPushesReceivedLatch.getCount())
{
case 4:
@@ -442,7 +444,7 @@
baseRequest.setHandled(true);
}
});
- return startHTTPServer(version, gzipHandler);
+ return startHTTPServer(version, gzipHandler, 30000);
}
private Session sendMainRequestAndCSSRequest(SessionFrameListener sessionFrameListener, boolean awaitPush) throws Exception
@@ -461,6 +463,7 @@
private void sendRequest(Session session, Fields requestHeaders, final CountDownLatch pushSynHeadersValid,
final CountDownLatch pushDataLatch, final boolean resetPush) throws InterruptedException
{
+ LOG.info("sendRequest. headers={},resetPush={}", requestHeaders, resetPush);
final CountDownLatch dataReceivedLatch = new CountDownLatch(1);
session.syn(new SynInfo(requestHeaders, true), new StreamFrameListener.Adapter()
{
@@ -472,7 +475,7 @@
assertThat("Stream is unidirectional", stream.isUnidirectional(), is(true));
assertThat("URI header ends with css", pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version))
- .value().endsWith
+ .getValue().endsWith
("" +
".css"),
is(true));
@@ -493,7 +496,7 @@
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
- assertThat(replyInfo.getHeaders().get(HTTPSPDYHeader.STATUS.name(version)).value(), is("200 OK"));
+ assertThat(replyInfo.getHeaders().get(HTTPSPDYHeader.STATUS.name(version)).getValue(), is("200 OK"));
}
@Override
@@ -525,7 +528,7 @@
assertThat("Stream is unidirectional", stream.isUnidirectional(), is(true));
assertThat("URI header ends with css", pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version))
- .value().endsWith
+ .getValue().endsWith
("" +
".css"),
is(true));
@@ -595,7 +598,7 @@
output.print("body { background: #FFF; }");
baseRequest.setHandled(true);
}
- });
+ }, 30000);
Session session1 = startClient(version, address, null);
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
@@ -686,7 +689,7 @@
}
baseRequest.setHandled(true);
}
- });
+ }, 30000);
Session session1 = startClient(version, address, null);
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
@@ -745,7 +748,7 @@
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
Assert.assertTrue(stream.isUnidirectional());
- Assert.assertTrue(pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).value().endsWith("" +
+ Assert.assertTrue(pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).getValue().endsWith("" +
".css"));
return new StreamFrameListener.Adapter()
{
@@ -797,7 +800,7 @@
output.print("\u0000");
baseRequest.setHandled(true);
}
- });
+ }, 30000);
Session session1 = startClient(version, address, null);
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
@@ -917,7 +920,7 @@
output.print("<html><head/><body>HELLO</body></html>");
baseRequest.setHandled(true);
}
- });
+ }, 30000);
Session session1 = startClient(version, address, null);
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
@@ -1002,7 +1005,7 @@
output.print("body { background: #FFF; }");
baseRequest.setHandled(true);
}
- });
+ }, 30000);
Session session1 = startClient(version, address, null);
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
@@ -1088,7 +1091,7 @@
private boolean validateHeader(Fields headers, String name, String expectedValue)
{
Fields.Field header = headers.get(name);
- if (header != null && expectedValue.equals(header.value()))
+ if (header != null && expectedValue.equals(header.getValue()))
return true;
System.out.println(name + " not valid! Expected: " + expectedValue + " headers received:" + headers);
return false;
@@ -1098,9 +1101,9 @@
{
Fields.Field uriHeader = headers.get(HTTPSPDYHeader.URI.name(version));
if (uriHeader != null)
- if (version == SPDY.V2 && uriHeader.value().startsWith("http://"))
+ if (version == SPDY.V2 && uriHeader.getValue().startsWith("http://"))
return true;
- else if (version == SPDY.V3 && uriHeader.value().startsWith("/")
+ else if (version == SPDY.V3 && uriHeader.getValue().startsWith("/")
&& headers.get(HTTPSPDYHeader.HOST.name(version)) != null && headers.get(HTTPSPDYHeader.SCHEME.name(version)) != null)
return true;
System.out.println(HTTPSPDYHeader.URI.name(version) + " not valid!");
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java
index 7d038bc..75fd170 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java
@@ -18,12 +18,17 @@
package org.eclipse.jetty.spdy.server.http;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
import java.util.Arrays;
import java.util.Set;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Fields;
import org.junit.Before;
import org.junit.Test;
@@ -31,10 +36,6 @@
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.when;
-
@RunWith(MockitoJUnitRunner.class)
public class ReferrerPushStrategyUnitTest
{
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SPDYTestUtils.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SPDYTestUtils.java
index d861a73..1acf93d 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SPDYTestUtils.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SPDYTestUtils.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.spdy.server.http;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.ssl.SslContextFactory;
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SSLExternalServerTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SSLExternalServerTest.java
index d307c93..3362c4f 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SSLExternalServerTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SSLExternalServerTest.java
@@ -28,11 +28,13 @@
import java.util.regex.Pattern;
import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
@@ -40,12 +42,12 @@
import org.junit.Ignore;
import org.junit.Test;
-@Ignore("Reliance on external server fails test")
public class SSLExternalServerTest extends AbstractHTTPSPDYTest
{
public SSLExternalServerTest(short version)
{
- super(version);
+ // Google Servers do not support SPDY/2 anymore
+ super(SPDY.V3);
}
@Override
@@ -58,7 +60,8 @@
return new SPDYClient.Factory(threadPool, null, sslContextFactory, 30000);
}
- @Test(timeout=5000)
+ @Test
+ @Ignore("Relies on an external server")
public void testExternalServer() throws Exception
{
String host = "encrypted.google.com";
@@ -94,7 +97,7 @@
Fields.Field versionHeader = headers.get(HTTPSPDYHeader.STATUS.name(version));
if (versionHeader != null)
{
- Matcher matcher = Pattern.compile("(\\d{3}).*").matcher(versionHeader.value());
+ Matcher matcher = Pattern.compile("(\\d{3}).*").matcher(versionHeader.getValue());
if (matcher.matches() && Integer.parseInt(matcher.group(1)) < 400)
latch.countDown();
}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ServerHTTPSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ServerHTTPSPDYTest.java
index 190234b..be541b0 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ServerHTTPSPDYTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ServerHTTPSPDYTest.java
@@ -18,18 +18,29 @@
package org.eclipse.jetty.spdy.server.http;
-import java.io.ByteArrayInputStream;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
+
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
@@ -50,23 +61,18 @@
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.StringDataInfo;
import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StdErrLog;
import org.junit.Assert;
-import org.junit.Ignore;
import org.junit.Test;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
+ private static final Logger LOG = Log.getLogger(ServerHTTPSPDYTest.class);
+
public ServerHTTPSPDYTest(short version)
{
super(version);
@@ -92,7 +98,7 @@
assertThat(httpRequest.getHeader("host"), is("localhost:" + connector.getLocalPort()));
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getLocalPort(), version, "GET", path);
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -103,7 +109,7 @@
{
assertTrue(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertThat(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"), is(true));
+ assertThat(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"), is(true));
assertThat(replyHeaders.get(HttpHeader.SERVER.asString()), is(notNullValue()));
assertThat(replyHeaders.get(HttpHeader.X_POWERED_BY.asString()), is(notNullValue()));
replyLatch.countDown();
@@ -133,7 +139,7 @@
assertEquals(query, httpRequest.getQueryString());
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", uri);
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -144,7 +150,7 @@
{
assertTrue(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
});
@@ -176,7 +182,7 @@
assertThat("requestUri is /foo", httpRequest.getRequestURI(), is(path));
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", uri);
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -187,11 +193,11 @@
{
assertThat("isClose is true", replyInfo.isClose(), is(true));
Fields replyHeaders = replyInfo.getHeaders();
- assertThat("response code is 200 OK", replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value()
+ assertThat("response code is 200 OK", replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue()
.contains("200"), is(true));
- assertThat(replyInfo.getHeaders().get("Set-Cookie").values()[0], is(cookie1 + "=\"" + cookie1Value +
+ assertThat(replyInfo.getHeaders().get("Set-Cookie").getValues().get(0), is(cookie1 + "=\"" + cookie1Value +
"\";Version=1"));
- assertThat(replyInfo.getHeaders().get("Set-Cookie").values()[1], is(cookie2 + "=\"" + cookie2Value +
+ assertThat(replyInfo.getHeaders().get("Set-Cookie").getValues().get(1), is(cookie2 + "=\"" + cookie2Value +
"\";Version=1"));
replyLatch.countDown();
}
@@ -218,7 +224,7 @@
httpResponse.getWriter().write("body that shouldn't be sent on a HEAD request");
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "HEAD", path);
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -229,7 +235,7 @@
{
assertTrue(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
@@ -258,7 +264,7 @@
request.setHandled(true);
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", path);
headers.put("content-type", "application/x-www-form-urlencoded");
@@ -271,7 +277,7 @@
{
assertTrue(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
});
@@ -307,7 +313,7 @@
assertNotNull(httpRequest.getServerName());
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", path);
headers.put("content-type", "application/x-www-form-urlencoded");
@@ -320,7 +326,7 @@
{
assertTrue(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
});
@@ -349,7 +355,7 @@
assertEquals("2", httpRequest.getParameter("b"));
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", path);
headers.put("content-type", "application/x-www-form-urlencoded");
@@ -362,7 +368,7 @@
{
assertTrue(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
});
@@ -394,7 +400,7 @@
assertEquals("2", httpRequest.getParameter("b"));
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", path);
headers.put("content-type", "application/x-www-form-urlencoded");
@@ -406,7 +412,7 @@
{
assertTrue(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.toString(), replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.toString(), replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
});
@@ -436,7 +442,7 @@
output.write(data.getBytes(StandardCharsets.UTF_8));
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -448,7 +454,7 @@
{
Assert.assertFalse(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
@@ -482,7 +488,7 @@
output.write(data);
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -494,7 +500,7 @@
{
Assert.assertFalse(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
@@ -533,7 +539,7 @@
output.write(data2.getBytes(StandardCharsets.UTF_8));
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -549,7 +555,7 @@
assertEquals(1, replyFrames.incrementAndGet());
Assert.assertFalse(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
@@ -588,7 +594,7 @@
output.write(data);
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -602,7 +608,7 @@
{
Assert.assertFalse(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
@@ -644,7 +650,7 @@
}
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -658,7 +664,7 @@
{
Assert.assertFalse(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
@@ -697,7 +703,7 @@
output.write(data);
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -711,7 +717,7 @@
{
Assert.assertFalse(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
@@ -750,7 +756,7 @@
output.close();
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -764,7 +770,7 @@
{
Assert.assertFalse(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
@@ -808,7 +814,7 @@
output.write(data2.getBytes(StandardCharsets.UTF_8));
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -822,7 +828,7 @@
{
Assert.assertFalse(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
@@ -861,7 +867,7 @@
httpResponse.sendRedirect(location);
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -875,8 +881,8 @@
assertEquals(1, replies.incrementAndGet());
assertTrue(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("302"));
- assertTrue(replyHeaders.get("location").value().endsWith(suffix));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("302"));
+ assertTrue(replyHeaders.get("location").getValue().endsWith(suffix));
replyLatch.countDown();
}
});
@@ -898,7 +904,7 @@
httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -913,7 +919,7 @@
assertEquals(1, replies.incrementAndGet());
Assert.assertFalse(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("404"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("404"));
replyLatch.countDown();
}
@@ -943,7 +949,7 @@
{
throw new NullPointerException("thrown_explicitly_by_the_test");
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -957,7 +963,7 @@
{
assertEquals(1, replies.incrementAndGet());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("500"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("500"));
replyLatch.countDown();
if (replyInfo.isClose())
latch.countDown();
@@ -997,7 +1003,7 @@
output.write(pangram2.getBytes(StandardCharsets.UTF_8));
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -1013,8 +1019,8 @@
assertEquals(1, replyFrames.incrementAndGet());
Assert.assertFalse(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
- assertTrue(replyHeaders.get("extra").value().contains("X"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+ assertTrue(replyHeaders.get("extra").getValue().contains("X"));
replyLatch.countDown();
}
@@ -1040,31 +1046,11 @@
assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
}
- @Ignore("The correspondent functionality in HttpOutput is not yet implemented")
- @Test
- public void testGETWithMediumContentAsInputStreamByPassed() throws Exception
- {
- byte[] data = new byte[2048];
- testGETWithContentByPassed(new ByteArrayInputStream(data), data.length);
- }
-
- @Ignore("The correspondent functionality in HttpOutput is not yet implemented")
- @Test
- public void testGETWithBigContentAsInputStreamByPassed() throws Exception
- {
- byte[] data = new byte[128 * 1024];
- testGETWithContentByPassed(new ByteArrayInputStream(data), data.length);
- }
-
@Test
public void testGETWithMediumContentAsBufferByPassed() throws Exception
{
- byte[] data = new byte[2048];
- testGETWithContentByPassed(ByteBuffer.wrap(data), data.length);
- }
+ final byte[] data = new byte[2048];
- private void testGETWithContentByPassed(final Object content, final int length) throws Exception
- {
final CountDownLatch handlerLatch = new CountDownLatch(1);
Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
{
@@ -1073,13 +1059,10 @@
throws IOException, ServletException
{
request.setHandled(true);
- // We use this trick that's present in Jetty code: if we add a request attribute
- // called "org.eclipse.jetty.server.sendContent", then it will trigger the
- // content bypass that we want to test
- request.setAttribute("org.eclipse.jetty.server.sendContent", content);
+ request.getResponse().getHttpOutput().sendContent(ByteBuffer.wrap(data));
handlerLatch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -1095,7 +1078,7 @@
assertEquals(1, replyFrames.incrementAndGet());
Assert.assertFalse(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
@@ -1105,7 +1088,7 @@
contentLength.addAndGet(dataInfo.asBytes(true).length);
if (dataInfo.isClose())
{
- assertEquals(length, contentLength.get());
+ Assert.assertEquals(data.length, contentLength.get());
dataLatch.countDown();
}
}
@@ -1135,7 +1118,7 @@
output.write(data);
output.write(data);
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -1148,7 +1131,7 @@
{
Assert.assertFalse(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
@@ -1199,7 +1182,7 @@
}
}.start();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -1209,7 +1192,7 @@
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
});
@@ -1242,7 +1225,7 @@
}
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -1252,7 +1235,7 @@
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
});
@@ -1287,7 +1270,7 @@
}
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -1297,7 +1280,7 @@
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
});
@@ -1355,7 +1338,7 @@
}
}.start();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo");
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -1365,7 +1348,7 @@
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
replyLatch.countDown();
}
});
@@ -1423,7 +1406,7 @@
output.write(data);
}
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo");
final CountDownLatch responseLatch = new CountDownLatch(2);
@@ -1433,7 +1416,7 @@
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
responseLatch.countDown();
}
@@ -1464,7 +1447,7 @@
request.setHandled(true);
latch.countDown();
}
- }), null);
+ }, 30000), null);
Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo");
final CountDownLatch responseLatch = new CountDownLatch(1);
@@ -1474,7 +1457,7 @@
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
responseLatch.countDown();
}
});
@@ -1485,4 +1468,145 @@
assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
}
+ @Test
+ public void testIdleTimeout() throws Exception
+ {
+ final int idleTimeout = 500;
+ final CountDownLatch timeoutReceivedLatch = new CountDownLatch(1);
+
+ Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+ throws IOException, ServletException
+ {
+ try
+ {
+ Thread.sleep(2 * idleTimeout);
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ request.setHandled(true);
+ }
+ }, 30000), null);
+
+ Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/");
+ Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, true, (byte)0),
+ new StreamFrameListener.Adapter()
+ {
+ @Override
+ public void onFailure(Stream stream, Throwable x)
+ {
+ assertThat("we got a TimeoutException", x, instanceOf(TimeoutException.class));
+ timeoutReceivedLatch.countDown();
+ }
+ });
+ stream.setIdleTimeout(idleTimeout);
+
+ assertThat("idle timeout hit", timeoutReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+ }
+
+ @Test
+ public void testIdleTimeoutSetOnConnectionOnly() throws Exception
+ {
+ final int idleTimeout = 500;
+ final CountDownLatch timeoutReceivedLatch = new CountDownLatch(1);
+ Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+ throws IOException, ServletException
+ {
+ try
+ {
+ Thread.sleep(2 * idleTimeout);
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ request.setHandled(true);
+ }
+ }, idleTimeout), null);
+
+ Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/");
+ Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, true, (byte)0),
+ new StreamFrameListener.Adapter()
+ {
+ @Override
+ public void onFailure(Stream stream, Throwable x)
+ {
+ assertThat("we got a TimeoutException", x, instanceOf(TimeoutException.class));
+ timeoutReceivedLatch.countDown();
+ }
+ });
+
+ assertThat("idle timeout hit", timeoutReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+ }
+
+ @Test
+ public void testSingleStreamIdleTimeout() throws Exception
+ {
+ final int idleTimeout = 500;
+ final CountDownLatch timeoutReceivedLatch = new CountDownLatch(1);
+ final CountDownLatch replyReceivedLatch = new CountDownLatch(3);
+ Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+ throws IOException, ServletException
+ {
+ if ("true".equals(request.getHeader("slow")))
+ {
+ try
+ {
+ Thread.sleep(2 * idleTimeout);
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ request.setHandled(true);
+ }
+ }, idleTimeout), null);
+
+ Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/");
+ Fields slowHeaders = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/");
+ slowHeaders.add("slow", "true");
+ sendSingleRequestThatIsNotExpectedToTimeout(replyReceivedLatch, session, headers);
+ session.syn(new SynInfo(5, TimeUnit.SECONDS, slowHeaders, true, (byte)0),
+ new StreamFrameListener.Adapter()
+ {
+ @Override
+ public void onFailure(Stream stream, Throwable x)
+ {
+ assertThat("we got a TimeoutException", x, instanceOf(TimeoutException.class));
+ timeoutReceivedLatch.countDown();
+ }
+ });
+ Thread.sleep(idleTimeout / 2);
+ sendSingleRequestThatIsNotExpectedToTimeout(replyReceivedLatch, session, headers);
+ Thread.sleep(idleTimeout / 2);
+ sendSingleRequestThatIsNotExpectedToTimeout(replyReceivedLatch, session, headers);
+ assertThat("idle timeout hit", timeoutReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+ assertThat("received replies on 3 non idle requests", replyReceivedLatch.await(5, TimeUnit.SECONDS),
+ is(true));
+ }
+
+ private void sendSingleRequestThatIsNotExpectedToTimeout(final CountDownLatch replyReceivedLatch, Session session, Fields headers) throws ExecutionException, InterruptedException, TimeoutException
+ {
+ session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, true, (byte)0),
+ new StreamFrameListener.Adapter()
+ {
+ @Override
+ public void onReply(Stream stream, ReplyInfo replyInfo)
+ {
+ replyReceivedLatch.countDown();
+ }
+ });
+ }
+
}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SimpleHTTPBenchmarkTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SimpleHTTPBenchmarkTest.java
index df4c69d..6d8e1a6 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SimpleHTTPBenchmarkTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SimpleHTTPBenchmarkTest.java
@@ -18,10 +18,18 @@
package org.eclipse.jetty.spdy.server.http;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -35,6 +43,7 @@
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -43,18 +52,11 @@
import org.junit.Ignore;
import org.junit.Test;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
@Ignore("So far only used for testing performance tweaks. So no need to run it in a build")
public class SimpleHTTPBenchmarkTest extends AbstractHTTPSPDYTest
{
private static final Logger LOG = Log.getLogger(SimpleHTTPBenchmarkTest.class);
- private final int dataSize = 4096*100;
+ private final int dataSize = 4096 * 100;
private Session session;
private int requestCount = 100;
@@ -81,7 +83,7 @@
assertThat(httpRequest.getHeader("host"), is("localhost:" + connector.getLocalPort()));
httpResponse.getOutputStream().write(data);
}
- }), null);
+ }, 0), null);
}
@Test
@@ -97,8 +99,8 @@
long timeElapsed = System.nanoTime() - start;
LOG.info("Requests with {}b response took: {}ms", dataSize, timeElapsed / 1000 / 1000);
}
- long timeElapsedOverall = (System.nanoTime() - overallStart)/1000/1000;
- LOG.info("Time elapsed overall: {}ms avg: {}ms", timeElapsedOverall, timeElapsedOverall/iterations);
+ long timeElapsedOverall = (System.nanoTime() - overallStart) / 1000 / 1000;
+ LOG.info("Time elapsed overall: {}ms avg: {}ms", timeElapsedOverall, timeElapsedOverall / iterations);
}
private void sendGetRequest() throws Exception
@@ -114,7 +116,7 @@
{
assertTrue(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertThat(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"), CoreMatchers.is(true));
+ assertThat(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"), CoreMatchers.is(true));
assertThat(replyHeaders.get(HttpHeader.SERVER.asString()), CoreMatchers.is(notNullValue()));
assertThat(replyHeaders.get(HttpHeader.X_POWERED_BY.asString()), CoreMatchers.is(notNullValue()));
replyLatch.countDown();
@@ -137,7 +139,7 @@
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Fields replyHeaders = replyInfo.getHeaders();
- assertThat(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"), CoreMatchers.is(true));
+ assertThat(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"), CoreMatchers.is(true));
assertThat(replyHeaders.get(HttpHeader.SERVER.asString()), CoreMatchers.is(notNullValue()));
assertThat(replyHeaders.get(HttpHeader.X_POWERED_BY.asString()), CoreMatchers.is(notNullValue()));
replyLatch.countDown();
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPToSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPToSPDYTest.java
index 66fffc1..84497ae 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPToSPDYTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPToSPDYTest.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.spdy.server.proxy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -47,9 +50,9 @@
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.spdy.server.SPDYServerConnectionFactory;
import org.eclipse.jetty.spdy.server.SPDYServerConnector;
-import org.eclipse.jetty.spdy.server.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Promise;
@@ -65,9 +68,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-
@RunWith(Parameterized.class)
public class ProxyHTTPToSPDYTest
{
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPLoadTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPLoadTest.java
index 6d6ba31..d117466 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPLoadTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPLoadTest.java
@@ -18,6 +18,11 @@
package org.eclipse.jetty.spdy.server.proxy;
+import static junit.framework.Assert.fail;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
@@ -33,6 +38,7 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -56,9 +62,13 @@
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestWatcher;
@@ -66,14 +76,11 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import static junit.framework.Assert.fail;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.junit.Assert.assertThat;
-
+@Ignore
@RunWith(value = Parameterized.class)
public class ProxySPDYToHTTPLoadTest
{
+ private static final Logger LOG = Log.getLogger(ProxySPDYToHTTPLoadTest.class);
@Rule
public final TestWatcher testName = new TestWatcher()
{
@@ -89,6 +96,8 @@
};
private final short version;
+ private final String server1String = "server1";
+ private final String server2String = "server2";
@Parameterized.Parameters
public static Collection<Short[]> parameters()
@@ -97,7 +106,8 @@
}
private SPDYClient.Factory factory;
- private Server server;
+ private Server server1;
+ private Server server2;
private Server proxy;
private ServerConnector proxyConnector;
private SslContextFactory sslContextFactory = SPDYTestUtils.newSslContextFactory();
@@ -107,20 +117,62 @@
this.version = version;
}
- protected InetSocketAddress startServer(Handler handler) throws Exception
+ @Before
+ public void init() throws Exception
{
- server = new Server();
+ // change the ports if you want to trace the network traffic
+ server1 = startServer(new TestServerHandler(server1String, null), 0);
+ server2 = startServer(new TestServerHandler(server2String, null), 0);
+ factory = new SPDYClient.Factory(sslContextFactory);
+ factory.start();
+ }
+
+ @After
+ public void destroy() throws Exception
+ {
+ stopServer(server1);
+ stopServer(server2);
+ if (proxy != null)
+ {
+ proxy.stop();
+ proxy.join();
+ }
+ factory.stop();
+ }
+
+ private void stopServer(Server server) throws Exception
+ {
+ if (server != null)
+ {
+ server.stop();
+ server.join();
+ }
+ }
+
+ protected Server startServer(Handler handler, int port) throws Exception
+ {
+ QueuedThreadPool threadPool = new QueuedThreadPool(256);
+ threadPool.setName("upstreamServerQTP");
+ Server server = new Server(threadPool);
ServerConnector connector = new ServerConnector(server);
+ connector.setPort(port);
server.setHandler(handler);
server.addConnector(connector);
server.start();
- return new InetSocketAddress("localhost", connector.getLocalPort());
+ return server;
+ }
+
+ private InetSocketAddress getServerAddress(Server server)
+ {
+ return new InetSocketAddress("localhost", ((ServerConnector)server.getConnectors()[0]).getLocalPort());
}
protected InetSocketAddress startProxy(InetSocketAddress server1, InetSocketAddress server2,
long proxyConnectorTimeout, long proxyEngineTimeout) throws Exception
{
- proxy = new Server();
+ QueuedThreadPool threadPool = new QueuedThreadPool(256);
+ threadPool.setName("proxyQTP");
+ proxy = new Server(threadPool);
ProxyEngineSelector proxyEngineSelector = new ProxyEngineSelector();
HttpClient httpClient = new HttpClient();
httpClient.start();
@@ -144,38 +196,11 @@
return new InetSocketAddress("localhost", proxyConnector.getLocalPort());
}
- @Before
- public void init() throws Exception
- {
- factory = new SPDYClient.Factory(sslContextFactory);
- factory.start();
- }
-
- @After
- public void destroy() throws Exception
- {
- if (server != null)
- {
- server.stop();
- server.join();
- }
- if (proxy != null)
- {
- proxy.stop();
- proxy.join();
- }
- factory.stop();
- }
-
@Test
public void testSimpleLoadTest() throws Exception
{
- String server1String = "server1";
- String server2String = "server2";
-
- InetSocketAddress server1 = startServer(new TestServerHandler(server1String, null));
- InetSocketAddress server2 = startServer(new TestServerHandler(server2String, null));
- final InetSocketAddress proxyAddress = startProxy(server1, server2, 30000, 30000);
+ final InetSocketAddress proxyAddress = startProxy(getServerAddress(server1), getServerAddress(server2), 30000,
+ 30000);
final int requestsPerClient = 50;
@@ -198,7 +223,7 @@
}
private Runnable createClientRunnable(final InetSocketAddress proxyAddress, final int requestsPerClient,
- final String serverIdentificationString, final String serverHost)
+ final String serverIdentificationString, final String serverHost)
{
Runnable client = new Runnable()
{
@@ -207,16 +232,16 @@
{
try
{
- Session client = factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
+ Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
for (int i = 0; i < requestsPerClient; i++)
{
sendSingleClientRequest(proxyAddress, client, serverIdentificationString, serverHost);
}
}
- catch (InterruptedException | ExecutionException | TimeoutException | IOException e)
+ catch (InterruptedException | ExecutionException | TimeoutException e)
{
- fail();
e.printStackTrace();
+ fail();
}
}
};
@@ -239,6 +264,7 @@
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
+ LOG.debug("Got reply: {}", replyInfo);
Fields headers = replyInfo.getHeaders();
assertThat("response comes from the given server", headers.get(serverIdentificationString),
is(notNullValue()));
@@ -251,7 +277,8 @@
result.write(dataInfo.asBytes(true), 0, dataInfo.length());
if (dataInfo.isClose())
{
- assertThat("received data matches send data", data, is(result.toString()));
+ LOG.debug("Got last dataFrame: {}", dataInfo);
+ assertThat("received data matches send data", result.toString(), is(data));
dataLatch.countDown();
}
}
@@ -259,8 +286,9 @@
stream.data(new StringDataInfo(data, true), new Callback.Adapter());
- assertThat("reply has been received", replyLatch.await(5, TimeUnit.SECONDS), is(true));
- assertThat("data has been received", dataLatch.await(5, TimeUnit.SECONDS), is(true));
+ assertThat("reply has been received", replyLatch.await(15, TimeUnit.SECONDS), is(true));
+ assertThat("data has been received", dataLatch.await(15, TimeUnit.SECONDS), is(true));
+ LOG.debug("Successfully received response");
}
private class TestServerHandler extends DefaultHandler
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPTest.java
index 58e9aa2..67dbfdb 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPTest.java
@@ -18,6 +18,11 @@
package org.eclipse.jetty.spdy.server.proxy;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -27,6 +32,7 @@
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -53,7 +59,7 @@
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.eclipse.jetty.spdy.client.SPDYClient;
-import org.eclipse.jetty.spdy.server.http.HTTPSPDYHeader;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.spdy.server.http.SPDYTestUtils;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
@@ -70,11 +76,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.nullValue;
-import static org.junit.Assert.assertThat;
-
@RunWith(value = Parameterized.class)
public class ProxySPDYToHTTPTest
{
@@ -170,7 +171,7 @@
InetSocketAddress proxyAddress = startProxy(startServer(new TestServerHandler(header, null)), 30000, 30000);
- Session client = factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
+ Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -203,7 +204,7 @@
InetSocketAddress proxyAddress = startProxy(startServer(new TestServerHandler(header, data)), 30000, 30000);
- Session client = factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
+ Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
@@ -276,7 +277,7 @@
{
resetLatch.countDown();
}
- }).get(5, TimeUnit.SECONDS);
+ });
Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "GET", "/");
headers.put("connection", "close");
@@ -310,7 +311,7 @@
final CountDownLatch replyLatch = new CountDownLatch(1);
- Session client = factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
+ Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "GET", "/");
client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
@@ -318,7 +319,7 @@
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
- assertThat("Status code is 302", replyInfo.getHeaders().get(HTTPSPDYHeader.STATUS.name(version)).value(),
+ assertThat("Status code is 302", replyInfo.getHeaders().get(HTTPSPDYHeader.STATUS.name(version)).getValue(),
is("302"));
assertThat("Location header has been received", replyInfo.getHeaders().get("Location"), is(notNullValue()));
replyLatch.countDown();
@@ -336,7 +337,7 @@
InetSocketAddress proxyAddress = startProxy(startServer(new TestServerHandler(header, null)), 30000, 30000);
- Session client = factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
+ Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
@@ -384,7 +385,7 @@
InetSocketAddress proxyAddress = startProxy(startServer(new TestServerHandler(header, null)), 30000, 30000);
- Session client = factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
+ Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
@@ -440,7 +441,7 @@
{
goAwayLatch.countDown();
}
- }).get(5, TimeUnit.SECONDS);
+ });
Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "POST", "/");
((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
@@ -471,7 +472,7 @@
}
}), 30000, timeout);
- Session client = factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
+ Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
final CountDownLatch replyLatch = new CountDownLatch(1);
@@ -485,7 +486,7 @@
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Fields headers = replyInfo.getHeaders();
- assertThat("status is 504", headers.get(HTTPSPDYHeader.STATUS.name(version)).value(), is("504"));
+ assertThat("status is 504", headers.get(HTTPSPDYHeader.STATUS.name(version)).getValue(), is("504"));
replyLatch.countDown();
}
@@ -511,7 +512,7 @@
{
pingLatch.countDown();
}
- }).get(5, TimeUnit.SECONDS);
+ });
client.ping(new PingInfo(5, TimeUnit.SECONDS));
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYLoadTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYLoadTest.java
index dfb7f38..73f3865 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYLoadTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYLoadTest.java
@@ -18,8 +18,11 @@
package org.eclipse.jetty.spdy.server.proxy;
+import static junit.framework.Assert.fail;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
@@ -62,10 +65,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import static junit.framework.Assert.fail;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-
@RunWith(value = Parameterized.class)
public class ProxySPDYToSPDYLoadTest
{
@@ -150,16 +149,10 @@
@After
public void destroy() throws Exception
{
- if (server != null)
- {
- server.stop();
- server.join();
- }
- if (proxy != null)
- {
- proxy.stop();
- proxy.join();
- }
+ server.stop();
+ server.join();
+ proxy.stop();
+ proxy.join();
factory.stop();
}
@@ -203,13 +196,13 @@
{
try
{
- Session client = factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
+ Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
for (int i = 0; i < requestsPerClient; i++)
{
sendSingleClientRequest(proxyAddress, client, serverIdentificationString, serverHost);
}
}
- catch (InterruptedException | ExecutionException | TimeoutException | IOException e)
+ catch (InterruptedException | ExecutionException | TimeoutException e)
{
fail();
e.printStackTrace();
@@ -237,8 +230,8 @@
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Fields headers = replyInfo.getHeaders();
- assertThat("uuid matches expected uuid", headers.get(UUID_HEADER_NAME).value(), is(uuid));
- assertThat("response comes from the given server", headers.get(SERVER_ID_HEADER).value(),
+ assertThat("uuid matches expected uuid", headers.get(UUID_HEADER_NAME).getValue(), is(uuid));
+ assertThat("response comes from the given server", headers.get(SERVER_ID_HEADER).getValue(),
is(serverIdentificationString));
replyLatch.countDown();
}
@@ -271,7 +264,7 @@
}
@Override
- public StreamFrameListener onSyn (Stream stream, SynInfo synInfo)
+ public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
{
Fields requestHeaders = synInfo.getHeaders();
Assert.assertNotNull(requestHeaders.get("via"));
@@ -279,7 +272,7 @@
Assert.assertNotNull(uuidHeader);
Fields responseHeaders = new Fields();
- responseHeaders.put(UUID_HEADER_NAME, uuidHeader.value());
+ responseHeaders.put(UUID_HEADER_NAME, uuidHeader.getValue());
responseHeaders.put(SERVER_ID_HEADER, serverId);
stream.reply(new ReplyInfo(responseHeaders, false), new Callback.Adapter());
return new StreamFrameListener.Adapter()
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYTest.java
index f651dd1..9e250a7 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYTest.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.spdy.server.proxy;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
@@ -45,9 +48,9 @@
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.spdy.server.SPDYServerConnectionFactory;
import org.eclipse.jetty.spdy.server.SPDYServerConnector;
-import org.eclipse.jetty.spdy.server.http.HTTPSPDYHeader;
import org.eclipse.jetty.spdy.server.http.SPDYTestUtils;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
@@ -63,9 +66,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertThat;
-
@RunWith(value = Parameterized.class)
public class ProxySPDYToSPDYTest
{
@@ -170,7 +170,7 @@
}));
proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));
- Session client = factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
+ Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
final CountDownLatch replyLatch = new CountDownLatch(1);
Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "GET", "/");
@@ -216,7 +216,7 @@
{
resetLatch.countDown();
}
- }).get(5, TimeUnit.SECONDS);
+ });
Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "GET", "/");
headers.put(header, "bar");
@@ -248,7 +248,7 @@
}));
proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));
- Session client = factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
+ Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
@@ -319,7 +319,7 @@
final CountDownLatch pushSynLatch = new CountDownLatch(1);
final CountDownLatch pushDataLatch = new CountDownLatch(1);
- Session client = factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
+ Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
Fields headers = new Fields();
headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + proxyAddress.getPort());
@@ -418,7 +418,7 @@
final CountDownLatch pushSynLatch = new CountDownLatch(3);
final CountDownLatch pushDataLatch = new CountDownLatch(3);
- Session client = factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
+ Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
Fields headers = new Fields();
headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + proxyAddress.getPort());
@@ -517,7 +517,7 @@
{
pingLatch.countDown();
}
- }).get(5, TimeUnit.SECONDS);
+ });
client.ping(new PingInfo(5, TimeUnit.SECONDS));
@@ -553,7 +553,7 @@
{
resetLatch.countDown();
}
- }).get(5, TimeUnit.SECONDS);
+ });
Fields headers = new Fields();
headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + proxyAddress.getPort());
diff --git a/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties b/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties
index 30da0a8..25faad4 100644
--- a/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties
+++ b/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties
@@ -5,4 +5,5 @@
#org.eclipse.jetty.spdy.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
#org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy.LEVEL=DEBUG
+#org.eclipse.jetty.spdy.server.proxy.LEVEL=DEBUG
#org.mortbay.LEVEL=DEBUG
diff --git a/jetty-spdy/spdy-server/pom.xml b/jetty-spdy/spdy-server/pom.xml
index 782105b..a73e7c1 100644
--- a/jetty-spdy/spdy-server/pom.xml
+++ b/jetty-spdy/spdy-server/pom.xml
@@ -3,12 +3,12 @@
<parent>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spdy-server</artifactId>
- <name>Jetty :: SPDY :: Jetty Server Binding</name>
+ <name>Jetty :: SPDY :: Server Binding</name>
<properties>
<bundle-symbolic-name>${project.groupId}.server</bundle-symbolic-name>
@@ -58,7 +58,7 @@
</goals>
<configuration>
<instructions>
- <Export-Package>org.eclipse.jetty.spdy.server;version="9.0"</Export-Package>
+ <Export-Package>org.eclipse.jetty.spdy.server;version="9.1"</Export-Package>
<Import-Package>org.eclipse.jetty.npn,org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
<_nouses>true</_nouses>
</instructions>
diff --git a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NPNServerConnection.java b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NPNServerConnection.java
new file mode 100644
index 0000000..1230580
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NPNServerConnection.java
@@ -0,0 +1,163 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.npn.NextProtoNego;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class NPNServerConnection extends AbstractConnection implements NextProtoNego.ServerProvider
+{
+ private final Logger LOG = Log.getLogger(getClass());
+ private final Connector connector;
+ private final SSLEngine engine;
+ private final List<String> protocols;
+ private final String defaultProtocol;
+ private String nextProtocol; // No need to be volatile: it is modified and read by the same thread
+
+ public NPNServerConnection(EndPoint endPoint, SSLEngine engine, Connector connector, List<String> protocols, String defaultProtocol)
+ {
+ super(endPoint, connector.getExecutor());
+ this.connector = connector;
+ this.protocols = protocols;
+ this.defaultProtocol = defaultProtocol;
+ this.engine = engine;
+ NextProtoNego.put(engine, this);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ int filled = fill();
+
+ if (filled == 0)
+ {
+ if (nextProtocol == null)
+ {
+ if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
+ {
+ // Here the SSL handshake is finished, but while the client sent
+ // the NPN extension, the server application did not select the
+ // protocol; we need to close as the protocol cannot be negotiated.
+ LOG.debug("{} missing next protocol. SSLEngine: {}", this, engine);
+ close();
+ }
+ else
+ {
+ // Here the SSL handshake is not finished yet but we filled 0 bytes,
+ // so we need to read more.
+ fillInterested();
+ }
+ }
+ else
+ {
+ ConnectionFactory connectionFactory = connector.getConnectionFactory(nextProtocol);
+ if (connectionFactory == null)
+ {
+ LOG.debug("{} application selected protocol '{}', but no correspondent {} has been configured",
+ this, nextProtocol, ConnectionFactory.class.getName());
+ close();
+ }
+ else
+ {
+ EndPoint endPoint = getEndPoint();
+ Connection oldConnection = endPoint.getConnection();
+ Connection newConnection = connectionFactory.newConnection(connector, endPoint);
+ LOG.debug("{} switching from {} to {}", this, oldConnection, newConnection);
+ oldConnection.onClose();
+ endPoint.setConnection(newConnection);
+ getEndPoint().getConnection().onOpen();
+ }
+ }
+ }
+ else if (filled < 0)
+ {
+ // Something went bad, we need to close.
+ LOG.debug("{} closing on client close", this);
+ close();
+ }
+ else
+ {
+ // Must never happen, since we fill using an empty buffer
+ throw new IllegalStateException();
+ }
+ }
+
+ private int fill()
+ {
+ try
+ {
+ return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
+ }
+ catch (IOException x)
+ {
+ LOG.debug(x);
+ close();
+ return -1;
+ }
+ }
+
+ @Override
+ public void unsupported()
+ {
+ protocolSelected(defaultProtocol);
+ }
+
+ @Override
+ public List<String> protocols()
+ {
+ return protocols;
+ }
+
+ @Override
+ public void protocolSelected(String protocol)
+ {
+ LOG.debug("{} protocol selected {}", this, protocol);
+ nextProtocol = protocol != null ? protocol : defaultProtocol;
+ NextProtoNego.remove(engine);
+ }
+
+ @Override
+ public void close()
+ {
+ NextProtoNego.remove(engine);
+ EndPoint endPoint = getEndPoint();
+ endPoint.shutdownOutput();
+ endPoint.close();
+ }
+}
diff --git a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NPNServerConnectionFactory.java b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NPNServerConnectionFactory.java
index b370e39..b982b61 100644
--- a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NPNServerConnectionFactory.java
+++ b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NPNServerConnectionFactory.java
@@ -110,7 +110,7 @@
ep=null;
}
- return configure(new NextProtoNegoServerConnection(endPoint, engine, connector,protocols,_defaultProtocol),connector,endPoint);
+ return configure(new NPNServerConnection(endPoint, engine, connector,protocols,_defaultProtocol),connector,endPoint);
}
@Override
diff --git a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NextProtoNegoServerConnection.java b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NextProtoNegoServerConnection.java
deleted file mode 100644
index 44af444..0000000
--- a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NextProtoNegoServerConnection.java
+++ /dev/null
@@ -1,162 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.spdy.server;
-
-import java.io.IOException;
-import java.util.List;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.npn.NextProtoNego;
-import org.eclipse.jetty.server.ConnectionFactory;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-public class NextProtoNegoServerConnection extends AbstractConnection implements NextProtoNego.ServerProvider
-{
- private final Logger LOG = Log.getLogger(getClass());
- private final Connector connector;
- private final SSLEngine engine;
- private final List<String> protocols;
- private final String defaultProtocol;
- private String nextProtocol; // No need to be volatile: it is modified and read by the same thread
-
- public NextProtoNegoServerConnection(EndPoint endPoint, SSLEngine engine, Connector connector, List<String>protocols, String defaultProtocol)
- {
- super(endPoint, connector.getExecutor());
- this.connector = connector;
- this.protocols = protocols;
- this.defaultProtocol = defaultProtocol;
- this.engine = engine;
- NextProtoNego.put(engine, this);
- }
-
- @Override
- public void onOpen()
- {
- super.onOpen();
- fillInterested();
- }
-
- @Override
- public void onFillable()
- {
- int filled = fill();
-
- if (filled == 0)
- {
- if (nextProtocol == null)
- {
- if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
- {
- // Here the SSL handshake is finished, but while the client sent
- // the NPN extension, the server application did not select the
- // protocol; we need to close as the protocol cannot be negotiated.
- LOG.debug("{} missing next protocol. SSLEngine: {}", this, engine);
- close();
- }
- else
- {
- // Here the SSL handshake is not finished yet but we filled 0 bytes,
- // so we need to read more.
- fillInterested();
- }
- }
- else
- {
- ConnectionFactory connectionFactory = connector.getConnectionFactory(nextProtocol);
- if (connectionFactory == null)
- {
- LOG.debug("{} application selected protocol '{}', but no correspondent {} has been configured",
- this, nextProtocol, ConnectionFactory.class.getName());
- close();
- }
- else
- {
- EndPoint endPoint = getEndPoint();
- Connection oldConnection = endPoint.getConnection();
- Connection newConnection = connectionFactory.newConnection(connector, endPoint);
- LOG.debug("{} switching from {} to {}", this, oldConnection, newConnection);
- oldConnection.onClose();
- endPoint.setConnection(newConnection);
- getEndPoint().getConnection().onOpen();
- }
- }
- }
- else if (filled < 0)
- {
- // Something went bad, we need to close.
- LOG.debug("{} closing on client close", this);
- close();
- }
- else
- {
- // Must never happen, since we fill using an empty buffer
- throw new IllegalStateException();
- }
- }
-
- private int fill()
- {
- try
- {
- return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
- }
- catch (IOException x)
- {
- LOG.debug(x);
- close();
- return -1;
- }
- }
-
- @Override
- public void unsupported()
- {
- protocolSelected(defaultProtocol);
- }
-
- @Override
- public List<String> protocols()
- {
- return protocols;
- }
-
- @Override
- public void protocolSelected(String protocol)
- {
- LOG.debug("{} protocol selected {}", this, protocol);
- nextProtocol = protocol != null ? protocol : defaultProtocol;
- NextProtoNego.remove(engine);
- }
-
- @Override
- public void close()
- {
- NextProtoNego.remove(engine);
- EndPoint endPoint = getEndPoint();
- endPoint.shutdownOutput();
- endPoint.close();
- }
-}
diff --git a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java
index 5e6815b..1ac498f 100644
--- a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java
+++ b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java
@@ -42,14 +42,10 @@
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.log.Log;
-import org.eclipse.jetty.util.log.Logger;
@ManagedObject("SPDY Server Connection Factory")
public class SPDYServerConnectionFactory extends AbstractConnectionFactory
{
- private static final Logger LOG = Log.getLogger(SPDYServerConnectionFactory.class);
-
// This method is placed here so as to provide a check for NPN before attempting to load any
// NPN classes.
public static void checkNPNAvailable()
@@ -66,11 +62,11 @@
}
}
+ private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
private final short version;
private final ServerSessionFrameListener listener;
private int initialWindowSize;
- private boolean executeOnFillable = true;
- private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
+ private boolean dispatchIO;
public SPDYServerConnectionFactory(int version)
{
@@ -83,6 +79,7 @@
this.version = (short)version;
this.listener = listener;
setInitialWindowSize(65536);
+ setDispatchIO(true);
}
@ManagedAttribute("SPDY version")
@@ -105,14 +102,14 @@
ServerSessionFrameListener listener = provideServerSessionFrameListener(connector, endPoint);
SPDYConnection connection = new ServerSPDYConnection(connector, endPoint, parser, listener,
- executeOnFillable, getInputBufferSize());
+ isDispatchIO(), getInputBufferSize());
FlowControlStrategy flowControlStrategy = newFlowControlStrategy(version);
StandardSession session = new StandardSession(getVersion(), connector.getByteBufferPool(),
- connector.getExecutor(), connector.getScheduler(), connection, endPoint, connection, 2, listener,
+ connector.getScheduler(), connection, endPoint, connection, 2, listener,
generator, flowControlStrategy);
- session.setWindowSize(initialWindowSize);
+ session.setWindowSize(getInitialWindowSize());
parser.addListener(session);
connection.setSession(session);
@@ -142,15 +139,15 @@
this.initialWindowSize = initialWindowSize;
}
- @ManagedAttribute("Execute onFillable")
- public boolean isExecuteOnFillable()
+ @ManagedAttribute("Dispatch I/O to a pooled thread")
+ public boolean isDispatchIO()
{
- return executeOnFillable;
+ return dispatchIO;
}
- public void setExecuteOnFillable(boolean executeOnFillable)
+ public void setDispatchIO(boolean dispatchIO)
{
- this.executeOnFillable = executeOnFillable;
+ this.dispatchIO = dispatchIO;
}
protected boolean sessionOpened(Session session)
@@ -191,10 +188,10 @@
private final AtomicBoolean connected = new AtomicBoolean();
private ServerSPDYConnection(Connector connector, EndPoint endPoint, Parser parser,
- ServerSessionFrameListener listener, boolean executeOnFillable, int bufferSize)
+ ServerSessionFrameListener listener, boolean dispatchIO, int bufferSize)
{
super(endPoint, connector.getByteBufferPool(), parser, connector.getExecutor(),
- executeOnFillable, bufferSize);
+ dispatchIO, bufferSize);
this.listener = listener;
}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/AbstractTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/AbstractTest.java
index cdbe2ba..829ceb9 100644
--- a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/AbstractTest.java
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/AbstractTest.java
@@ -21,7 +21,6 @@
import java.net.InetSocketAddress;
import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Server;
@@ -114,7 +113,7 @@
clientFactory.start();
}
- return clientFactory.newSPDYClient(version).connect(socketAddress, listener).get(5, TimeUnit.SECONDS);
+ return clientFactory.newSPDYClient(version).connect(socketAddress, listener);
}
protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/IdleTimeoutTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/IdleTimeoutTest.java
index 708e6dc..0291fd3 100644
--- a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/IdleTimeoutTest.java
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/IdleTimeoutTest.java
@@ -173,7 +173,7 @@
clientFactory.start();
SPDYClient client = clientFactory.newSPDYClient(SPDY.V2);
client.setIdleTimeout(idleTimeout);
- Session session = client.connect(address, null).get(5, TimeUnit.SECONDS);
+ Session session = client.connect(address, null);
session.syn(new SynInfo(new Fields(), true), null);
@@ -199,7 +199,7 @@
clientFactory.start();
SPDYClient client = clientFactory.newSPDYClient(SPDY.V2);
client.setIdleTimeout(idleTimeout);
- Session session = client.connect(address, null).get(5, TimeUnit.SECONDS);
+ Session session = client.connect(address, null);
session.syn(new SynInfo(new Fields(), true), null);
@@ -232,7 +232,7 @@
clientFactory.start();
SPDYClient client = clientFactory.newSPDYClient(SPDY.V2);
client.setIdleTimeout(idleTimeout);
- Session session = client.connect(address, null).get(5, TimeUnit.SECONDS);
+ Session session = client.connect(address, null);
final CountDownLatch replyLatch = new CountDownLatch(1);
session.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java
index a3fe141..ea79d5d 100644
--- a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.spdy.server;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@@ -40,9 +43,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-
@RunWith(JUnit4.class)
public class MaxConcurrentStreamTest extends AbstractTest
{
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/PushStreamTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/PushStreamTest.java
index 243bd84..f1cf46b 100644
--- a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/PushStreamTest.java
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/PushStreamTest.java
@@ -19,6 +19,12 @@
package org.eclipse.jetty.spdy.server;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
@@ -69,12 +75,6 @@
import org.junit.Assert;
import org.junit.Test;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.sameInstance;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
public class PushStreamTest extends AbstractTest
{
private static final Logger LOG = Log.getLogger(PushStreamTest.class);
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SSLSynReplyTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SSLSynReplyTest.java
index 8af6083..e658442 100644
--- a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SSLSynReplyTest.java
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SSLSynReplyTest.java
@@ -24,6 +24,7 @@
import java.nio.channels.SocketChannel;
import java.util.List;
import java.util.concurrent.Executor;
+
import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.npn.NextProtoNego;
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SettingsTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SettingsTest.java
index 3f7963b..692109a 100644
--- a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SettingsTest.java
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SettingsTest.java
@@ -159,7 +159,7 @@
Session sessionV2 = startClient(address, null);
sessionV2.settings(settingsInfo);
- Session sessionV3 = clientFactory.newSPDYClient(SPDY.V3).connect(address, null).get(5, TimeUnit.SECONDS);
+ Session sessionV3 = clientFactory.newSPDYClient(SPDY.V3).connect(address, null);
sessionV3.settings(settingsInfo);
Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynDataReplyDataLoadTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynDataReplyDataLoadTest.java
index fb522c2..5759f58 100644
--- a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynDataReplyDataLoadTest.java
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynDataReplyDataLoadTest.java
@@ -49,7 +49,7 @@
public class SynDataReplyDataLoadTest extends AbstractTest
{
- @Test
+ @Test(timeout=60000)
public void testSynDataReplyDataLoad() throws Exception
{
ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynReplyTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynReplyTest.java
index efe68b4..3393a42 100644
--- a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynReplyTest.java
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynReplyTest.java
@@ -20,7 +20,6 @@
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/UnsupportedVersionTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/UnsupportedVersionTest.java
index e3b2df0..5849544 100644
--- a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/UnsupportedVersionTest.java
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/UnsupportedVersionTest.java
@@ -27,6 +27,7 @@
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.spdy.StandardCompressionFactory;
import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.StreamStatus;
@@ -58,7 +59,7 @@
}
@Override
- public void onException(Throwable x)
+ public void onFailure(Session session, Throwable x)
{
// Suppress exception logging for this test
}
diff --git a/jetty-spring/pom.xml b/jetty-spring/pom.xml
index f6fab64..03bc138 100644
--- a/jetty-spring/pom.xml
+++ b/jetty-spring/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-spring</artifactId>
diff --git a/jetty-spring/src/test/java/org/eclipse/jetty/spring/SpringXmlConfigurationTest.java b/jetty-spring/src/test/java/org/eclipse/jetty/spring/SpringXmlConfigurationTest.java
index 13a333d..42f95a9 100644
--- a/jetty-spring/src/test/java/org/eclipse/jetty/spring/SpringXmlConfigurationTest.java
+++ b/jetty-spring/src/test/java/org/eclipse/jetty/spring/SpringXmlConfigurationTest.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.spring;
+import static junit.framework.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
@@ -30,9 +33,6 @@
import org.junit.Before;
import org.junit.Test;
-import static junit.framework.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
-
public class SpringXmlConfigurationTest
{
protected String _configure="org/eclipse/jetty/spring/configure.xml";
diff --git a/jetty-start/pom.xml b/jetty-start/pom.xml
index 73c099c..cf6be2f 100644
--- a/jetty-start/pom.xml
+++ b/jetty-start/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-start</artifactId>
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java
new file mode 100644
index 0000000..3cd5199
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java
@@ -0,0 +1,385 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * File access for <code>${jetty.home}</code>, <code>${jetty.base}</code>, directories.
+ * <p>
+ * By default, both <code>${jetty.home}</code> and <code>${jetty.base}</code> are the same directory, but they can point at different directories.
+ * <p>
+ * The <code>${jetty.home}</code> directory is where the main Jetty binaries and default configuration is housed.
+ * <p>
+ * The <code>${jetty.base}</code> directory is where the execution specific configuration and webapps are obtained from.
+ */
+public class BaseHome
+{
+ private File homeDir;
+ private File baseDir;
+
+ public BaseHome()
+ {
+ try
+ {
+ this.baseDir = new File(System.getProperty("jetty.base",System.getProperty("user.dir",".")));
+ URL jarfile = this.getClass().getClassLoader().getResource("org/eclipse/jetty/start/BaseHome.class");
+ if (jarfile != null)
+ {
+ Matcher m = Pattern.compile("jar:(file:.*)!/org/eclipse/jetty/start/BaseHome.class").matcher(jarfile.toString());
+ if (m.matches())
+ {
+ homeDir = new File(new URI(m.group(1))).getParentFile();
+ }
+ }
+ homeDir = new File(System.getProperty("jetty.home",(homeDir == null?baseDir:homeDir).getAbsolutePath()));
+
+ baseDir = baseDir.getAbsoluteFile().getCanonicalFile();
+ homeDir = homeDir.getAbsoluteFile().getCanonicalFile();
+ }
+ catch (IOException | URISyntaxException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public BaseHome(File homeDir, File baseDir)
+ {
+ try
+ {
+ this.homeDir = homeDir.getCanonicalFile();
+ this.baseDir = baseDir == null?this.homeDir:baseDir.getCanonicalFile();
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String getBase()
+ {
+ if (baseDir == null)
+ {
+ return null;
+ }
+ return baseDir.getAbsolutePath();
+ }
+
+ public File getBaseDir()
+ {
+ return baseDir;
+ }
+
+ /**
+ * Create a file reference to some content in <code>"${jetty.base}"</code>
+ *
+ * @param path
+ * the path to reference
+ * @return the file reference
+ */
+ public File getBaseFile(String path)
+ {
+ return new File(baseDir,FS.separators(path));
+ }
+
+ /**
+ * Get a specific file reference.
+ * <p>
+ * File references go through 3 possibly scenarios.
+ * <ol>
+ * <li>If exists relative to <code>${jetty.base}</code>, return that reference</li>
+ * <li>If exists relative to <code>${jetty.home}</code>, return that reference</li>
+ * <li>Otherwise return absolute path reference</li>
+ * </ol>
+ *
+ * @param path
+ * the path to get.
+ * @return the file reference.
+ */
+ public File getFile(String path)
+ {
+ String rpath = FS.separators(path);
+
+ // Relative to Base Directory First
+ if (isBaseDifferent())
+ {
+ File file = new File(baseDir,rpath);
+ if (file.exists())
+ {
+ return file;
+ }
+ }
+
+ // Then relative to Home Directory
+ File file = new File(homeDir,rpath);
+ if (file.exists())
+ {
+ return file;
+ }
+
+ // Finally, as an absolute path
+ return new File(rpath);
+ }
+
+ public String getHome()
+ {
+ return homeDir.getAbsolutePath();
+ }
+
+ public File getHomeDir()
+ {
+ return homeDir;
+ }
+
+ public void initialize(StartArgs args)
+ {
+ Pattern jetty_home = Pattern.compile("(-D)?jetty.home=(.*)");
+ Pattern jetty_base = Pattern.compile("(-D)?jetty.base=(.*)");
+
+ File homePath = null;
+ File basePath = null;
+
+ for (String arg : args.getCommandLine())
+ {
+ Matcher home_match = jetty_home.matcher(arg);
+ if (home_match.matches())
+ {
+ homePath = new File(home_match.group(2));
+ }
+ Matcher base_match = jetty_base.matcher(arg);
+ if (base_match.matches())
+ {
+ basePath = new File(base_match.group(2));
+ }
+ }
+
+ if (homePath != null)
+ {
+ // logic if home is specified
+ this.homeDir = homePath;
+ this.baseDir = basePath == null?homePath:basePath;
+ }
+ else if (basePath != null)
+ {
+ // logic if home is undeclared
+ this.baseDir = basePath;
+ }
+ }
+
+ public boolean isBaseDifferent()
+ {
+ return homeDir.compareTo(baseDir) != 0;
+ }
+
+ /**
+ * Get all of the files that are in a specific relative directory.
+ * <p>
+ * If the same found path exists in both <code>${jetty.base}</code> and <code>${jetty.home}</code>, then the one in <code>${jetty.base}</code> is returned
+ * (it overrides the one in ${jetty.home})
+ *
+ * @param relPathToDirectory
+ * the relative path to the directory
+ * @return the list of files found.
+ */
+ public List<File> listFiles(String relPathToDirectory)
+ {
+ return listFiles(relPathToDirectory,FS.AllFilter.INSTANCE);
+ }
+
+ /**
+ * Get all of the files that are in a specific relative directory, with applied {@link FileFilter}
+ * <p>
+ * If the same found path exists in both <code>${jetty.base}</code> and <code>${jetty.home}</code>, then the one in <code>${jetty.base}</code> is returned
+ * (it overrides the one in ${jetty.home})
+ *
+ * @param relPathToDirectory
+ * the relative path to the directory
+ * @param filter
+ * the filter to use
+ * @return the list of files found.
+ */
+ public List<File> listFiles(String relPathToDirectory, FileFilter filter)
+ {
+ Objects.requireNonNull(filter,"FileFilter cannot be null");
+
+ File homePath = new File(homeDir,FS.separators(relPathToDirectory));
+ List<File> homeFiles = new ArrayList<>();
+ if (FS.canReadDirectory(homePath))
+ {
+ homeFiles.addAll(Arrays.asList(homePath.listFiles(filter)));
+ }
+
+ if (isBaseDifferent())
+ {
+ // merge
+ File basePath = new File(baseDir,FS.separators(relPathToDirectory));
+ List<File> ret = new ArrayList<>();
+ if (FS.canReadDirectory(basePath))
+ {
+ File baseFiles[] = basePath.listFiles(filter);
+
+ if (baseFiles != null)
+ {
+ for (File base : baseFiles)
+ {
+ String relpath = toRelativePath(baseDir,base);
+ File home = new File(homeDir,FS.separators(relpath));
+ if (home.exists())
+ {
+ homeFiles.remove(home);
+ }
+ ret.add(base);
+ }
+ }
+ }
+
+ // add any remaining home files.
+ ret.addAll(homeFiles);
+
+ Collections.sort(ret,new NaturalSort.Files());
+ return ret;
+ }
+ else
+ {
+ // simple return
+ Collections.sort(homeFiles,new NaturalSort.Files());
+ return homeFiles;
+ }
+ }
+
+ /**
+ * Collect the list of files in both <code>${jetty.base}</code> and <code>${jetty.home}</code>, with , even if the same file shows up in both places.
+ */
+ public List<File> rawListFiles(String relPathToDirectory, FileFilter filter)
+ {
+ Objects.requireNonNull(filter,"FileFilter cannot be null");
+
+ List<File> ret = new ArrayList<>();
+
+ // Home Dir
+ File homePath = new File(homeDir,FS.separators(relPathToDirectory));
+ ret.addAll(Arrays.asList(homePath.listFiles(filter)));
+
+ if (isBaseDifferent())
+ {
+ // Base Dir
+ File basePath = new File(baseDir,FS.separators(relPathToDirectory));
+ ret.addAll(Arrays.asList(basePath.listFiles(filter)));
+ }
+
+ // Sort
+ Collections.sort(ret,new NaturalSort.Files());
+ return ret;
+ }
+
+ public void setBaseDir(File dir)
+ {
+ try
+ {
+ this.baseDir = dir.getCanonicalFile();
+ System.setProperty("jetty.base",dir.getCanonicalPath());
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace(System.err);
+ }
+ }
+
+ public void setHomeDir(File dir)
+ {
+ try
+ {
+ this.homeDir = dir.getCanonicalFile();
+ System.setProperty("jetty.home",dir.getCanonicalPath());
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace(System.err);
+ }
+ }
+
+ private String toRelativePath(File dir, File path)
+ {
+ return dir.toURI().relativize(path.toURI()).toASCIIString();
+ }
+
+ /**
+ * Convenience method for <code>toShortForm(file.getCanonicalPath())</code>
+ */
+ public String toShortForm(File path)
+ {
+ try
+ {
+ return toShortForm(path.getCanonicalPath());
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ return toShortForm(path.getAbsolutePath());
+ }
+
+ /**
+ * Replace/Shorten arbitrary path with property strings <code>"${jetty.home}"</code> or <code>"${jetty.base}"</code> where appropriate.
+ *
+ * @param path
+ * the path to shorten
+ * @return the potentially shortened path
+ */
+ public String toShortForm(String path)
+ {
+ if (path == null)
+ {
+ return path;
+ }
+
+ String value;
+
+ if (isBaseDifferent())
+ {
+ value = baseDir.getAbsolutePath();
+ if (path.startsWith(value))
+ {
+ return "${jetty.base}" + path.substring(value.length());
+ }
+ }
+
+ value = homeDir.getAbsolutePath();
+
+ if (path.startsWith(value))
+ {
+ return "${jetty.home}" + path.substring(value.length());
+ }
+
+ return path;
+ }
+
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java
index 775936c..3333b52 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java
@@ -24,16 +24,31 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
import java.util.StringTokenizer;
-import java.util.Vector;
/**
* Class to handle CLASSPATH construction
*/
-public class Classpath
+public class Classpath implements Iterable<File>
{
+ private static class Loader extends URLClassLoader
+ {
+ Loader(URL[] urls, ClassLoader parent)
+ {
+ super(urls,parent);
+ }
- private final Vector<File> _elements = new Vector<File>();
+ @Override
+ public String toString()
+ {
+ return "startJarLoader@" + Long.toHexString(hashCode());
+ }
+ }
+
+ private final List<File> elements = new ArrayList<File>();
public Classpath()
{
@@ -44,69 +59,12 @@
addClasspath(initial);
}
- public File[] getElements()
- {
- return _elements.toArray(new File[_elements.size()]);
- }
-
- public int count()
- {
- return _elements.size();
- }
-
- public boolean addComponent(String component)
- {
- if ((component != null) && (component.length() > 0))
- {
- try
- {
- File f = new File(component);
- if (f.exists())
- {
- File key = f.getCanonicalFile();
- if (!_elements.contains(key))
- {
- _elements.add(key);
- return true;
- }
- }
- }
- catch (IOException e)
- {
- }
- }
- return false;
- }
-
- public boolean addComponent(File component)
- {
- if (component != null)
- {
- try
- {
- if (component.exists())
- {
- File key = component.getCanonicalFile();
- if (!_elements.contains(key))
- {
- _elements.add(key);
- return true;
- }
- }
- }
- catch (IOException e)
- {
- }
- }
- return false;
- }
-
public boolean addClasspath(String s)
{
boolean added = false;
if (s != null)
{
- StringTokenizer t = new StringTokenizer(s, File.pathSeparator);
+ StringTokenizer t = new StringTokenizer(s,File.pathSeparator);
while (t.hasMoreTokens())
{
added |= addComponent(t.nextToken());
@@ -115,46 +73,73 @@
return added;
}
+ public boolean addComponent(File path)
+ {
+ if ((path == null) || (!path.exists()))
+ {
+ // not a valid component
+ return false;
+ }
+
+ try
+ {
+ File key = path.getCanonicalFile();
+ if (!elements.contains(key))
+ {
+ elements.add(key);
+ return true;
+ }
+ }
+ catch (IOException e)
+ {
+ StartLog.debug(e);
+ }
+
+ return false;
+ }
+
+ public boolean addComponent(String component)
+ {
+ if ((component == null) || (component.length() <= 0))
+ {
+ // nothing to add
+ return false;
+ }
+
+ return addComponent(new File(component));
+ }
+
+ public int count()
+ {
+ return elements.size();
+ }
+
public void dump(PrintStream out)
{
int i = 0;
- for (File element : _elements)
+ for (File element : elements)
{
- out.printf("%2d: %s\n", i++, element.getAbsolutePath());
+ out.printf("%2d: %s%n",i++,element.getAbsolutePath());
}
}
- @Override
- public String toString()
- {
- StringBuffer cp = new StringBuffer(1024);
- int cnt = _elements.size();
- if (cnt >= 1)
- {
- cp.append(((_elements.elementAt(0))).getPath());
- }
- for (int i = 1; i < cnt; i++)
- {
- cp.append(File.pathSeparatorChar);
- cp.append(((_elements.elementAt(i))).getPath());
- }
- return cp.toString();
- }
-
public ClassLoader getClassLoader()
{
- int cnt = _elements.size();
+ int cnt = elements.size();
URL[] urls = new URL[cnt];
for (int i = 0; i < cnt; i++)
{
try
{
- urls[i] = _elements.elementAt(i).toURI().toURL();
+ urls[i] = elements.get(i).toURI().toURL();
+ StartLog.debug("URLClassLoader.url[%d] = %s",i,urls[i]);
}
catch (MalformedURLException e)
{
+ StartLog.warn(e);
}
}
+ StartLog.debug("Loaded %d URLs into URLClassLoader",urls.length);
ClassLoader parent = Thread.currentThread().getContextClassLoader();
if (parent == null)
@@ -165,46 +150,58 @@
{
parent = ClassLoader.getSystemClassLoader();
}
- return new Loader(urls, parent);
+ return new Loader(urls,parent);
}
- private static class Loader extends URLClassLoader
+ public List<File> getElements()
{
- Loader(URL[] urls, ClassLoader parent)
- {
- super(urls, parent);
- }
-
- @Override
- public String toString()
- {
- return "startJarLoader@" + Long.toHexString(hashCode());
- }
- }
-
-
-
- /**
- * Overlay another classpath, copying its elements into place on this
- * Classpath, while eliminating duplicate entries on the classpath.
- *
- * @param cpOther the other classpath to overlay
- */
- public void overlay(Classpath cpOther)
- {
- for (File otherElement : cpOther._elements)
- {
- if (this._elements.contains(otherElement))
- {
- // Skip duplicate entries
- continue;
- }
- this._elements.add(otherElement);
- }
+ return elements;
}
public boolean isEmpty()
{
- return (_elements == null) || (_elements.isEmpty());
+ return (elements == null) || (elements.isEmpty());
+ }
+
+ @Override
+ public Iterator<File> iterator()
+ {
+ return elements.iterator();
+ }
+
+ /**
+ * Overlay another classpath, copying its elements into place on this Classpath, while eliminating duplicate entries on the classpath.
+ *
+ * @param other
+ * the other classpath to overlay
+ */
+ public void overlay(Classpath other)
+ {
+ for (File otherElement : other.elements)
+ {
+ if (this.elements.contains(otherElement))
+ {
+ // Skip duplicate entries
+ continue;
+ }
+ this.elements.add(otherElement);
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuffer cp = new StringBuffer(1024);
+ boolean needDelim = false;
+ for (File element : elements)
+ {
+ if (needDelim)
+ {
+ cp.append(File.pathSeparatorChar);
+ }
+ cp.append(element.getAbsolutePath());
+ needDelim = true;
+ }
+ return cp.toString();
}
}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineBuilder.java b/jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineBuilder.java
index d50a4b7..d0a4197 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineBuilder.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineBuilder.java
@@ -18,16 +18,92 @@
package org.eclipse.jetty.start;
+import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class CommandLineBuilder
{
+ public static File findExecutable(File root, String path)
+ {
+ String npath = path.replace('/',File.separatorChar);
+ File exe = new File(root,npath);
+ if (!exe.exists())
+ {
+ return null;
+ }
+ return exe;
+ }
+
+ public static String findJavaBin()
+ {
+ File javaHome = new File(System.getProperty("java.home"));
+ if (!javaHome.exists())
+ {
+ return null;
+ }
+
+ File javabin = findExecutable(javaHome,"bin/java");
+ if (javabin != null)
+ {
+ return javabin.getAbsolutePath();
+ }
+
+ javabin = findExecutable(javaHome,"bin/java.exe");
+ if (javabin != null)
+ {
+ return javabin.getAbsolutePath();
+ }
+
+ return "java";
+ }
+
+ /**
+ * Perform an optional quoting of the argument, being intelligent with spaces and quotes as needed. If a subString is set in quotes it won't the subString
+ * won't be escaped.
+ *
+ * @param arg
+ * @return
+ */
+ public static String quote(String arg)
+ {
+ boolean needsQuoting = (arg.indexOf(' ') >= 0) || (arg.indexOf('"') >= 0);
+ if (!needsQuoting)
+ {
+ return arg;
+ }
+ StringBuilder buf = new StringBuilder();
+ // buf.append('"');
+ boolean escaped = false;
+ boolean quoted = false;
+ for (char c : arg.toCharArray())
+ {
+ if (!quoted && !escaped && ((c == '"') || (c == ' ')))
+ {
+ buf.append("\\");
+ }
+ // don't quote text in single quotes
+ if (!escaped && (c == '\''))
+ {
+ quoted = !quoted;
+ }
+ escaped = (c == '\\');
+ buf.append(c);
+ }
+ // buf.append('"');
+ return buf.toString();
+ }
+
private List<String> args;
+ public CommandLineBuilder()
+ {
+ args = new ArrayList<String>();
+ }
+
public CommandLineBuilder(String bin)
{
- args = new ArrayList<String>();
+ this();
args.add(bin);
}
@@ -42,7 +118,9 @@
public void addArg(String arg)
{
if (arg != null)
+ {
args.add(quote(arg));
+ }
}
/**
@@ -65,7 +143,7 @@
*/
public void addEqualsArg(String name, String value)
{
- if (value != null && value.length() > 0)
+ if ((value != null) && (value.length() > 0))
{
args.add(quote(name + "=" + value));
}
@@ -86,7 +164,9 @@
public void addRawArg(String arg)
{
if (arg != null)
+ {
args.add(arg);
+ }
}
public List<String> getArgs()
@@ -94,42 +174,6 @@
return args;
}
- /**
- * Perform an optional quoting of the argument, being intelligent with spaces and quotes as needed. If a
- * subString is set in quotes it won't the subString won't be escaped.
- *
- * @param arg
- * @return
- */
- public static String quote(String arg)
- {
- boolean needsQuoting = arg.indexOf(' ') >= 0 || arg.indexOf('"') >= 0;
- if (!needsQuoting)
- {
- return arg;
- }
- StringBuilder buf = new StringBuilder();
- // buf.append('"');
- boolean escaped = false;
- boolean quoted = false;
- for (char c : arg.toCharArray())
- {
- if (!quoted && !escaped && ((c == '"') || (c == ' ')))
- {
- buf.append("\\");
- }
- // don't quote text in single quotes
- if (!escaped && c == '\'')
- {
- quoted = !quoted;
- }
- escaped = (c == '\\');
- buf.append(c);
- }
- // buf.append('"');
- return buf.toString();
- }
-
@Override
public String toString()
{
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Config.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Config.java
deleted file mode 100644
index 9ed3b1b..0000000
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Config.java
+++ /dev/null
@@ -1,1002 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.start;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.StringReader;
-import java.net.URL;
-import java.text.CollationKey;
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.TreeSet;
-
-/**
- * <p>
- * It allows an application to be started with the command <code>"java -jar start.jar"</code>.
- * </p>
- *
- * <p>
- * The behaviour of Main is controlled by the <code>"org/eclipse/start/start.config"</code> file obtained as a resource
- * or file. This can be overridden with the START system property. The format of each line in this file is:
- * </p>
- *
- * <p>
- * Each line contains entry in the format:
- * </p>
- *
- * <pre>
- * SUBJECT [ [!] CONDITION [AND|OR] ]*
- * </pre>
- *
- * <p>
- * where SUBJECT:
- * </p>
- * <ul>
- * <li>ends with <code>".class"</code> is the Main class to run.</li>
- * <li>ends with <code>".xml"</code> is a configuration file for the command line</li>
- * <li>ends with <code>"/"</code> is a directory from which to add all jar and zip files.</li>
- * <li>ends with <code>"/*"</code> is a directory from which to add all unconsidered jar and zip files.</li>
- * <li>ends with <code>"/**"</code> is a directory from which to recursively add all unconsidered jar and zip files.</li>
- * <li>Containing <code>=</code> are used to assign system properties.</li>
- * <li>Containing <code>~=</code> are used to assign start properties.</li>
- * <li>Containing <code>/=</code> are used to assign a canonical path.</li>
- * <li>all other subjects are treated as files to be added to the classpath.</li>
- * </ul>
- *
- * <p>
- * property expansion:
- * </p>
- * <ul>
- * <li><code>${name}</code> is expanded to a start property</li>
- * <li><code>$(name)</code> is expanded to either a start property or a system property.</li>
- * <li>The start property <code>${version}</code> is defined as the version of the start.jar</li>
- * </ul>
- *
- * <p>
- * Files starting with <code>"/"</code> are considered absolute, all others are relative to the home directory.
- * </p>
- *
- * <p>
- * CONDITION is one of:
- * </p>
- * <ul>
- * <li><code>always</code></li>
- * <li><code>never</code></li>
- * <li><code>available classname</code> - true if class on classpath</li>
- * <li><code>property name</code> - true if set as start property</li>
- * <li><code>system name</code> - true if set as system property</li>
- * <li><code>exists file</code> - true if file/dir exists</li>
- * <li><code>java OPERATOR version</code> - java version compared to literal</li>
- * <li><code>nargs OPERATOR number</code> - number of command line args compared to literal</li>
- * <li>OPERATOR := one of <code>"<"</code>,<code>">"</code>,<code>"<="</code>,<code>">="</code>,
- * <code>"=="</code>,<code>"!="</code></li>
- * </ul>
- *
- * <p>
- * CONDITIONS can be combined with <code>AND</code> <code>OR</code> or <code>!</code>, with <code>AND</code> being the
- * assume operator for a list of CONDITIONS.
- * </p>
- *
- * <p>
- * Classpath operations are evaluated on the fly, so once a class or jar is added to the classpath, subsequent available
- * conditions will see that class.
- * </p>
- *
- * <p>
- * The configuration file may be divided into sections with option names like: [ssl,default]
- * </p>
- *
- * <p>
- * Note: a special discovered section identifier <code>[=path_to_directory/*]</code> is allowed to auto-create section
- * IDs, based on directory names found in the path specified in the "path_to_directory/" part of the identifier.
- * </p>
- *
- * <p>
- * Clauses after a section header will only be included if they match one of the tags in the options property. By
- * default options are set to "default,*" or the OPTIONS property may be used to pass in a list of tags, eg. :
- * </p>
- *
- * <pre>
- * java -jar start.jar OPTIONS=jetty,jsp,ssl
- * </pre>
- *
- * <p>
- * The tag '*' is always appended to the options, so any section with the * tag is always applied.
- * </p>
- *
- * <p>
- * The property map maintained by this class is static and shared between all instances in the same classloader
- * </p>
- */
-public class Config
-{
- public static final String DEFAULT_SECTION = "";
- static
- {
- String ver = System.getProperty("jetty.version", null);
-
- if(ver == null) {
- Package pkg = Config.class.getPackage();
- if (pkg != null &&
- "Eclipse.org - Jetty".equals(pkg.getImplementationVendor()) &&
- (pkg.getImplementationVersion() != null))
- {
- ver = pkg.getImplementationVersion();
- }
- }
-
- if (ver == null)
- {
- ver = "Unknown";
- }
- _version = ver;
- }
-
- /**
- * Natural language sorting for key names.
- */
- private final Comparator<String> keySorter = new Comparator<String>()
- {
- private final Collator collator = Collator.getInstance();
-
- public int compare(String o1, String o2)
- {
- CollationKey key1 = collator.getCollationKey(o1);
- CollationKey key2 = collator.getCollationKey(o2);
- return key1.compareTo(key2);
- }
- };
-
- private static final String _version;
- private static boolean DEBUG = false;
- private static final Map<String, String> __properties = new HashMap<String, String>();
- private final Map<String, Classpath> _classpaths = new HashMap<String, Classpath>();
- private final List<String> _xml = new ArrayList<String>();
- private String _classname = null;
-
- private int argCount = 0;
-
- private final Set<String> _activeOptions = new TreeSet<String>(new Comparator<String>()
- {
- // Make sure "*" is always at the end of the list
- public int compare(String o1, String o2)
- {
- if ("*".equals(o1))
- {
- return 1;
- }
- if ("*".equals(o2))
- {
- return -1;
- }
- return o1.compareTo(o2);
- }
- });
-
- private boolean addClasspathComponent(List<String> sections, String component)
- {
- for (String section : sections)
- {
- Classpath cp = _classpaths.get(section);
- if (cp == null)
- cp = new Classpath();
-
- boolean added = cp.addComponent(component);
- _classpaths.put(section,cp);
-
- if (!added)
- {
- // First failure means all failed.
- return false;
- }
- }
-
- return true;
- }
-
- private boolean addClasspathPath(List<String> sections, String path)
- {
- for (String section : sections)
- {
- Classpath cp = _classpaths.get(section);
- if (cp == null)
- {
- cp = new Classpath();
- }
- if (!cp.addClasspath(path))
- {
- // First failure means all failed.
- return false;
- }
- _classpaths.put(section,cp);
- }
-
- return true;
- }
-
- private void addJars(List<String> sections, File dir, boolean recurse) throws IOException
- {
- List<File> entries = new ArrayList<File>();
- File[] files = dir.listFiles();
- if (files == null)
- {
- // No files found, skip it.
- return;
- }
- entries.addAll(Arrays.asList(files));
- Collections.sort(entries,FilenameComparator.INSTANCE);
-
- for (File entry : entries)
- {
- if (entry.isDirectory())
- {
- if (recurse)
- addJars(sections,entry,recurse);
- }
- else
- {
- String name = entry.getName().toLowerCase(Locale.ENGLISH);
- if (name.endsWith(".jar") || name.endsWith(".zip"))
- {
- String jar = entry.getCanonicalPath();
- boolean added = addClasspathComponent(sections,jar);
- debug((added?" CLASSPATH+=":" !") + jar);
- }
- }
- }
- }
-
- private void close(InputStream stream)
- {
- if (stream == null)
- return;
-
- try
- {
- stream.close();
- }
- catch (IOException ignore)
- {
- /* ignore */
- }
- }
-
- private void close(Reader reader)
- {
- if (reader == null)
- return;
-
- try
- {
- reader.close();
- }
- catch (IOException ignore)
- {
- /* ignore */
- }
- }
-
- public static boolean isDebug()
- {
- return DEBUG;
- }
-
- public static void debug(String msg)
- {
- if (DEBUG)
- {
- System.err.println(msg);
- }
- }
-
- public static void debug(Throwable t)
- {
- if (DEBUG)
- {
- t.printStackTrace(System.err);
- }
- }
-
- private String expand(String s)
- {
- int i1 = 0;
- int i2 = 0;
- while (s != null)
- {
- i1 = s.indexOf("$(",i2);
- if (i1 < 0)
- break;
- i2 = s.indexOf(")",i1 + 2);
- if (i2 < 0)
- break;
- String name = s.substring(i1 + 2,i2);
- String property = getProperty(name);
- s = s.substring(0,i1) + property + s.substring(i2 + 1);
- }
-
- i1 = 0;
- i2 = 0;
- while (s != null)
- {
- i1 = s.indexOf("${",i2);
- if (i1 < 0)
- break;
- i2 = s.indexOf("}",i1 + 2);
- if (i2 < 0)
- break;
- String name = s.substring(i1 + 2,i2);
- String property = getProperty(name);
- s = s.substring(0,i1) + property + s.substring(i2 + 1);
- }
-
- return s;
- }
-
- /**
- * Get the default classpath.
- *
- * @return the default classpath
- */
- public Classpath getClasspath()
- {
- return _classpaths.get(DEFAULT_SECTION);
- }
-
- /**
- * Get the active classpath, as dictated by OPTIONS= entries.
- *
- * @return the Active classpath
- * @see #getCombinedClasspath(Collection)
- */
- public Classpath getActiveClasspath()
- {
- return getCombinedClasspath(_activeOptions);
- }
-
- /**
- * Get the combined classpath representing the default classpath plus all named sections.
- *
- * NOTE: the default classpath will be prepended, and the '*' classpath will be appended.
- *
- * @param optionIds
- * the list of section ids to fetch
- * @return the {@link Classpath} representing combination all of the selected sectionIds, combined with the default
- * section id, and '*' special id.
- */
- public Classpath getCombinedClasspath(Collection<String> optionIds)
- {
- Classpath cp = new Classpath();
-
- cp.overlay(_classpaths.get(DEFAULT_SECTION));
- for (String optionId : optionIds)
- {
- Classpath otherCp = _classpaths.get(optionId);
- if (otherCp == null)
- {
- throw new IllegalArgumentException("No such OPTIONS: " + optionId);
- }
- cp.overlay(otherCp);
- }
- cp.overlay(_classpaths.get("*"));
- return cp;
- }
-
- public String getMainClassname()
- {
- return _classname;
- }
-
- public static void clearProperties()
- {
- __properties.clear();
- }
-
- public static Properties getProperties()
- {
- Properties properties = new Properties();
- // Add System Properties First
- Enumeration<?> ensysprop = System.getProperties().propertyNames();
- while(ensysprop.hasMoreElements()) {
- String name = (String)ensysprop.nextElement();
- properties.put(name, System.getProperty(name));
- }
- // Add Config Properties Next (overwriting any System Properties that exist)
- for (String key : __properties.keySet()) {
- properties.put(key,__properties.get(key));
- }
- return properties;
- }
-
- public static String getProperty(String name)
- {
- if ("version".equalsIgnoreCase(name)) {
- return _version;
- }
- // Search Config Properties First
- if (__properties.containsKey(name)) {
- return __properties.get(name);
- }
- // Return what exists in System.Properties otherwise.
- return System.getProperty(name);
- }
-
- public static String getProperty(String name, String defaultValue)
- {
- // Search Config Properties First
- if (__properties.containsKey(name))
- return __properties.get(name);
- // Return what exists in System.Properties otherwise.
- return System.getProperty(name, defaultValue);
- }
-
- /**
- * Get the classpath for the named section
- *
- * @param sectionId
- * @return the classpath for the specified section id
- */
- public Classpath getSectionClasspath(String sectionId)
- {
- return _classpaths.get(sectionId);
- }
-
- /**
- * Get the list of section Ids.
- *
- * @return the set of unique section ids
- */
- public Set<String> getSectionIds()
- {
- Set<String> ids = new TreeSet<String>(keySorter);
- ids.addAll(_classpaths.keySet());
- return ids;
- }
-
- public List<String> getXmlConfigs()
- {
- return _xml;
- }
-
- private boolean isAvailable(List<String> options, String classname)
- {
- // Try default/parent class loader first.
- try
- {
- Class.forName(classname);
- return true;
- }
- catch (NoClassDefFoundError e)
- {
- debug(e);
- }
- catch (ClassNotFoundException e)
- {
- debug("ClassNotFoundException (parent class loader): " + classname);
- }
-
- // Try option classloaders instead
- ClassLoader loader;
- Classpath classpath;
- for (String optionId : options)
- {
- classpath = _classpaths.get(optionId);
- if (classpath == null)
- {
- // skip, no classpath
- continue;
- }
-
- loader = classpath.getClassLoader();
-
- try
- {
- loader.loadClass(classname);
- return true;
- }
- catch (NoClassDefFoundError e)
- {
- debug(e);
- }
- catch (ClassNotFoundException e)
- {
- debug("ClassNotFoundException (section class loader: " + optionId + "): " + classname);
- }
- }
- return false;
- }
-
- /**
- * Parse the configuration
- *
- * @param buf
- * @throws IOException
- */
- public void parse(CharSequence buf) throws IOException
- {
- parse(new StringReader(buf.toString()));
- }
-
- /**
- * Parse the configuration
- *
- * @param stream the stream to read from
- * @throws IOException
- */
- public void parse(InputStream stream) throws IOException
- {
- InputStreamReader reader = null;
- try
- {
- reader = new InputStreamReader(stream);
- parse(reader);
- }
- finally
- {
- close(reader);
- }
- }
-
- /**
- */
- public void parse(Reader reader) throws IOException
- {
- BufferedReader buf = null;
-
- try
- {
- buf = new BufferedReader(reader);
-
- List<String> options = new ArrayList<String>();
- options.add(DEFAULT_SECTION);
- _classpaths.put(DEFAULT_SECTION,new Classpath());
- Version java_version = new Version(System.getProperty("java.version"));
- Version ver = new Version();
-
- String line = null;
- while ((line = buf.readLine()) != null)
- {
- String trim = line.trim();
- if (trim.length() == 0) // empty line
- continue;
-
- if (trim.startsWith("#")) // comment
- continue;
-
- // handle options
- if (trim.startsWith("[") && trim.endsWith("]"))
- {
- String identifier = trim.substring(1,trim.length() - 1);
-
- // Normal case: section identifier (possibly separated by commas)
- options = Arrays.asList(identifier.split(","));
- List<String> option_ids=new ArrayList<String>();
-
- // Ensure section classpaths exist
- for (String optionId : options)
- {
- if (optionId.charAt(0) == '=')
- continue;
-
- if (!_classpaths.containsKey(optionId))
- _classpaths.put(optionId,new Classpath());
-
- if (!option_ids.contains(optionId))
- option_ids.add(optionId);
- }
-
-
- // Process Dynamic
- for (String optionId : options)
- {
- if (optionId.charAt(0) != '=')
- continue;
-
- option_ids = processDynamicSectionIdentifier(optionId.substring(1),option_ids);
- }
-
- options = option_ids;
-
- continue;
- }
-
- try
- {
- StringTokenizer st = new StringTokenizer(line);
- String subject = st.nextToken();
- boolean expression = true;
- boolean not = false;
- String condition = null;
- // Evaluate all conditions
- while (st.hasMoreTokens())
- {
- condition = st.nextToken();
- if (condition.equalsIgnoreCase("!"))
- {
- not = true;
- continue;
- }
- if (condition.equalsIgnoreCase("OR"))
- {
- if (expression)
- break;
- expression = true;
- continue;
- }
- if (condition.equalsIgnoreCase("AND"))
- {
- if (!expression)
- break;
- continue;
- }
- boolean eval = true;
- if (condition.equals("true") || condition.equals("always"))
- {
- eval = true;
- }
- else if (condition.equals("false") || condition.equals("never"))
- {
- eval = false;
- }
- else if (condition.equals("available"))
- {
- String class_to_check = st.nextToken();
- eval = isAvailable(options,class_to_check);
- }
- else if (condition.equals("exists"))
- {
- try
- {
- eval = false;
- File file = new File(expand(st.nextToken()));
- eval = file.exists();
- }
- catch (Exception e)
- {
- debug(e);
- }
- }
- else if (condition.equals("property"))
- {
- String property = getProperty(st.nextToken());
- eval = property != null && property.length() > 0;
- }
- else if (condition.equals("system"))
- {
- String property = System.getProperty(st.nextToken());
- eval = property != null && property.length() > 0;
- }
- else if (condition.equals("java"))
- {
- String operator = st.nextToken();
- String version = st.nextToken();
- ver.parse(version);
- eval = (operator.equals("<") && java_version.compare(ver) < 0) || (operator.equals(">") && java_version.compare(ver) > 0)
- || (operator.equals("<=") && java_version.compare(ver) <= 0) || (operator.equals("=<") && java_version.compare(ver) <= 0)
- || (operator.equals("=>") && java_version.compare(ver) >= 0) || (operator.equals(">=") && java_version.compare(ver) >= 0)
- || (operator.equals("==") && java_version.compare(ver) == 0) || (operator.equals("!=") && java_version.compare(ver) != 0);
- }
- else if (condition.equals("nargs"))
- {
- String operator = st.nextToken();
- int number = Integer.parseInt(st.nextToken());
- eval = (operator.equals("<") && argCount < number) || (operator.equals(">") && argCount > number)
- || (operator.equals("<=") && argCount <= number) || (operator.equals("=<") && argCount <= number)
- || (operator.equals("=>") && argCount >= number) || (operator.equals(">=") && argCount >= number)
- || (operator.equals("==") && argCount == number) || (operator.equals("!=") && argCount != number);
- }
- else
- {
- System.err.println("ERROR: Unknown condition: " + condition);
- eval = false;
- }
- expression &= not?!eval:eval;
- not = false;
- }
-
- String file = expand(subject);
- debug((expression?"T ":"F ") + line);
- if (!expression)
- continue;
-
- // Setting of a start property
- if (subject.indexOf("~=") > 0)
- {
- int i = file.indexOf("~=");
- String property = file.substring(0,i);
- String value = fixPath(file.substring(i + 2));
- debug(" " + property + "~=" + value);
- setProperty(property,value);
- continue;
- }
-
- // Setting of start property with canonical path
- if (subject.indexOf("/=") > 0)
- {
- int i = file.indexOf("/=");
- String property = file.substring(0,i);
- String value = fixPath(file.substring(i + 2));
- String canonical = new File(value).getCanonicalPath();
- debug(" " + property + "/=" + value + "==" + canonical);
- setProperty(property,canonical);
- continue;
- }
-
- // Setting of system property
- if (subject.indexOf("=") > 0)
- {
- int i = file.indexOf("=");
- String property = file.substring(0,i);
- String value = fixPath(file.substring(i + 1));
- debug(" " + property + "=" + value);
- System.setProperty(property,value);
- continue;
- }
-
- // Add all unconsidered JAR and ZIP files to classpath
- if (subject.endsWith("/*"))
- {
- // directory of JAR files - only add jars and zips within the directory
- File dir = new File(fixPath(file.substring(0,file.length() - 1)));
- addJars(options,dir,false);
- continue;
- }
-
- // Recursively add all unconsidered JAR and ZIP files to classpath
- if (subject.endsWith("/**"))
- {
- //directory hierarchy of jar files - recursively add all jars and zips in the hierarchy
- File dir = new File(fixPath(file.substring(0,file.length() - 2)));
- addJars(options,dir,true);
- continue;
- }
-
- // Add raw classpath directory to classpath
- if (subject.endsWith("/"))
- {
- // class directory
- File cd = new File(fixPath(file));
- String d = cd.getCanonicalPath();
- boolean added = addClasspathComponent(options,d);
- debug((added?" CLASSPATH+=":" !") + d);
- continue;
- }
-
- // Add XML configuration
- if (subject.toLowerCase(Locale.ENGLISH).endsWith(".xml"))
- {
- // Config file
- File f = new File(fixPath(file));
- if (f.exists())
- _xml.add(f.getCanonicalPath());
- debug(" ARGS+=" + f);
- continue;
- }
-
- // Set the main class to execute (overrides any previously set)
- if (subject.toLowerCase(Locale.ENGLISH).endsWith(".class"))
- {
- // Class
- String cn = expand(subject.substring(0,subject.length() - 6));
- if (cn != null && cn.length() > 0)
- {
- debug(" CLASS=" + cn);
- _classname = cn;
- }
- continue;
- }
-
- // Add raw classpath entry
- if (subject.toLowerCase(Locale.ENGLISH).endsWith(".path"))
- {
- // classpath (jetty.class.path?) to add to runtime classpath
- String cn = expand(subject.substring(0,subject.length() - 5));
- if (cn != null && cn.length() > 0)
- {
- debug(" PATH=" + cn);
- addClasspathPath(options,cn);
- }
- continue;
- }
-
- // single JAR file
- File f = new File(fixPath(file));
- if (f.exists())
- {
- String d = f.getCanonicalPath();
- boolean added = addClasspathComponent(options,d);
- if (!added)
- {
- added = addClasspathPath(options,expand(subject));
- }
- debug((added?" CLASSPATH+=":" !") + d);
- }
- }
- catch (Exception e)
- {
- System.err.println("on line: '" + line + "'");
- e.printStackTrace();
- }
- }
- }
- finally
- {
- close(buf);
- }
- }
-
- private List<String> processDynamicSectionIdentifier(String dynamicPathId,List<String> sections) throws IOException
- {
- String rawPath;
- boolean deep;
-
- if (dynamicPathId.endsWith("/*"))
- {
- deep=false;
- rawPath = fixPath(dynamicPathId.substring(0,dynamicPathId.length() - 1));
- }
- else if (dynamicPathId.endsWith("/**"))
- {
- deep=true;
- rawPath = fixPath(dynamicPathId.substring(0,dynamicPathId.length() - 2));
- }
- else
- {
- String msg = "Illegal dynamic path [" + dynamicPathId + "]";
- throw new IOException(msg);
- }
-
- File parentDir = new File(expand(rawPath));
- if (!parentDir.exists())
- return sections;
- debug("dynamic: " + parentDir);
-
- File dirs[] = parentDir.listFiles(new FileFilter()
- {
- public boolean accept(File path)
- {
- return path.isDirectory();
- }
- });
-
- List<String> dyn_sections = new ArrayList<String>();
- List<String> super_sections = new ArrayList<String>();
- if (sections!=null)
- super_sections.addAll(sections);
-
- for (File dir : dirs)
- {
- String id = dir.getName();
- if (!_classpaths.keySet().contains(id))
- _classpaths.put(id, new Classpath());
-
- dyn_sections.clear();
- if (sections!=null)
- dyn_sections.addAll(sections);
- dyn_sections.add(id);
- super_sections.add(id);
- debug("dynamic: " + dyn_sections);
- addJars(dyn_sections,dir,deep);
- }
-
- return super_sections;
- }
-
- private String fixPath(String path)
- {
- return path.replace('/',File.separatorChar);
- }
-
- public void parse(URL url) throws IOException
- {
- InputStream stream = null;
- InputStreamReader reader = null;
- try
- {
- stream = url.openStream();
- reader = new InputStreamReader(stream);
- parse(reader);
- }
- finally
- {
- close(reader);
- close(stream);
- }
- }
-
- public void setArgCount(int argCount)
- {
- this.argCount = argCount;
- }
-
- public void setProperty(String name, String value)
- {
- if (name.equals("DEBUG"))
- {
- DEBUG = Boolean.parseBoolean(value);
- if (DEBUG)
- {
- System.setProperty("org.eclipse.jetty.util.log.stderr.DEBUG","true");
- System.setProperty("org.eclipse.jetty.start.DEBUG","true");
- }
- }
- if (name.equals("OPTIONS"))
- {
- _activeOptions.clear();
- String ids[] = value.split(",");
- for (String id : ids)
- {
- addActiveOption(id);
- }
- }
- __properties.put(name,value);
- }
-
- public void addActiveOption(String option)
- {
- _activeOptions.add(option);
- __properties.put("OPTIONS",join(_activeOptions,","));
- }
-
- public Set<String> getActiveOptions()
- {
- return _activeOptions;
- }
-
- public void removeActiveOption(String option)
- {
- _activeOptions.remove(option);
- __properties.put("OPTIONS",join(_activeOptions,","));
- }
-
- private String join(Collection<?> coll, String delim)
- {
- StringBuffer buf = new StringBuffer();
- Iterator<?> i = coll.iterator();
- boolean hasNext = i.hasNext();
- while (hasNext)
- {
- buf.append(String.valueOf(i.next()));
- hasNext = i.hasNext();
- if (hasNext)
- buf.append(delim);
- }
-
- return buf.toString();
- }
-
-}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java
new file mode 100644
index 0000000..977985c
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java
@@ -0,0 +1,169 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+public class FS
+{
+ public static class AllFilter implements FileFilter
+ {
+ public static final AllFilter INSTANCE = new AllFilter();
+
+ @Override
+ public boolean accept(File pathname)
+ {
+ return true;
+ }
+ }
+
+ public static class FilenameRegexFilter implements FileFilter
+ {
+ private final Pattern pattern;
+
+ public FilenameRegexFilter(String regex)
+ {
+ pattern = Pattern.compile(regex,Pattern.CASE_INSENSITIVE);
+ }
+
+ @Override
+ public boolean accept(File path)
+ {
+ return path.isFile() && pattern.matcher(path.getName()).matches();
+ }
+ }
+
+ public static class FileNamesFilter implements FileFilter
+ {
+ private final String filenames[];
+
+ public FileNamesFilter(String... names)
+ {
+ this.filenames = names;
+ }
+
+ @Override
+ public boolean accept(File path)
+ {
+ if (!path.isFile())
+ {
+ return false;
+ }
+ for (String name : filenames)
+ {
+ if (name.equalsIgnoreCase(path.getName()))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ public static class IniFilter extends FilenameRegexFilter
+ {
+ public IniFilter()
+ {
+ super("^.*\\.ini$");
+ }
+ }
+
+ public static class XmlFilter extends FilenameRegexFilter
+ {
+ public XmlFilter()
+ {
+ super("^.*\\.xml$");
+ }
+ }
+
+ public static boolean canReadDirectory(File path)
+ {
+ return (path.exists() && path.isDirectory() && path.canRead());
+ }
+
+ public static boolean canReadFile(File path)
+ {
+ return (path.exists() && path.isFile() && path.canRead());
+ }
+
+ public static void close(Closeable c)
+ {
+ if (c == null)
+ {
+ return;
+ }
+
+ try
+ {
+ c.close();
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ }
+
+ public static void ensureDirectoryExists(File dir) throws IOException
+ {
+ if (dir.exists())
+ {
+ return;
+ }
+ if (!dir.mkdirs())
+ {
+ throw new IOException("Unable to create directory: " + dir.getAbsolutePath());
+ }
+ }
+
+ public static boolean isFile(File file)
+ {
+ if (file == null)
+ {
+ return false;
+ }
+ return file.exists() && file.isFile();
+ }
+
+ public static boolean isXml(String filename)
+ {
+ return filename.toLowerCase(Locale.ENGLISH).endsWith(".xml");
+ }
+
+ public static String separators(String path)
+ {
+ StringBuilder ret = new StringBuilder();
+ for (char c : path.toCharArray())
+ {
+ if ((c == '/') || (c == '\\'))
+ {
+ ret.append(File.separatorChar);
+ }
+ else
+ {
+ ret.append(c);
+ }
+ }
+ return ret.toString();
+ }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FileArg.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FileArg.java
new file mode 100644
index 0000000..c3b6b41
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/FileArg.java
@@ -0,0 +1,111 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+public class FileArg
+{
+ public String uri;
+ public String location;
+
+ public FileArg(String uriLocation)
+ {
+ String parts[] = uriLocation.split(":",3);
+ if (parts.length == 3)
+ {
+ if (!"http".equalsIgnoreCase(parts[0]))
+ {
+ throw new IllegalArgumentException("Download only supports http protocol");
+ }
+ if (!parts[1].startsWith("//"))
+ {
+ throw new IllegalArgumentException("Download URI invalid: " + uriLocation);
+ }
+ this.uri = String.format("%s:%s",parts[0],parts[1]);
+ this.location = parts[2];
+ }
+ else
+ {
+ this.uri = null;
+ this.location = uriLocation;
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ FileArg other = (FileArg)obj;
+ if (uri == null)
+ {
+ if (other.uri != null)
+ {
+ return false;
+ }
+ }
+ else if (!uri.equals(other.uri))
+ {
+ return false;
+ }
+ if (location == null)
+ {
+ if (other.location != null)
+ {
+ return false;
+ }
+ }
+ else if (!location.equals(other.location))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((uri == null)?0:uri.hashCode());
+ result = (prime * result) + ((location == null)?0:location.hashCode());
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("DownloadArg [uri=");
+ builder.append(uri);
+ builder.append(", location=");
+ builder.append(location);
+ builder.append("]");
+ return builder.toString();
+ }
+}
\ No newline at end of file
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FilenameComparator.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FilenameComparator.java
deleted file mode 100644
index 266716f..0000000
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/FilenameComparator.java
+++ /dev/null
@@ -1,70 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.start;
-
-import java.io.File;
-import java.text.CollationKey;
-import java.text.Collator;
-import java.util.Comparator;
-
-/**
- * Smart comparator for filenames, with natural language sorting, and files sorted before sub directories.
- */
-public class FilenameComparator implements Comparator<File>
-{
- public static final FilenameComparator INSTANCE = new FilenameComparator();
- private Collator collator = Collator.getInstance();
-
- public int compare(File o1, File o2)
- {
- if (o1.isFile())
- {
- if (o2.isFile())
- {
- CollationKey key1 = toKey(o1);
- CollationKey key2 = toKey(o2);
- return key1.compareTo(key2);
- }
- else
- {
- // Push o2 directories below o1 files
- return -1;
- }
- }
- else
- {
- if (o2.isDirectory())
- {
- CollationKey key1 = toKey(o1);
- CollationKey key2 = toKey(o2);
- return key1.compareTo(key2);
- }
- else
- {
- // Push o2 files above o1 directories
- return 1;
- }
- }
- }
-
- private CollationKey toKey(File f)
- {
- return collator.getCollationKey(f.getAbsolutePath());
- }
-}
\ No newline at end of file
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/JarVersion.java b/jetty-start/src/main/java/org/eclipse/jetty/start/JarVersion.java
index 858a2c0..51fc8dc 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/JarVersion.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/JarVersion.java
@@ -50,7 +50,7 @@
return entry;
}
}
-
+
return null;
}
@@ -58,11 +58,15 @@
{
Attributes attribs = manifest.getMainAttributes();
if (attribs == null)
+ {
return null;
+ }
String version = attribs.getValue("Bundle-Version");
if (version == null)
+ {
return null;
+ }
return stripV(version);
}
@@ -71,11 +75,15 @@
{
Attributes attribs = manifest.getMainAttributes();
if (attribs == null)
+ {
return null;
+ }
String version = attribs.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
if (version == null)
+ {
return null;
+ }
return stripV(version);
}
@@ -84,40 +92,48 @@
{
JarEntry pomProp = findEntry(jar,"META-INF/maven/.*/pom\\.properties$");
if (pomProp == null)
+ {
return null;
-
+ }
+
InputStream stream = null;
-
+
try
{
stream = jar.getInputStream(pomProp);
Properties props = new Properties();
props.load(stream);
-
+
String version = props.getProperty("version");
if (version == null)
+ {
return null;
+ }
return stripV(version);
}
finally
{
- Main.close(stream);
+ FS.close(stream);
}
}
private static String getSubManifestImplVersion(Manifest manifest)
{
Map<String, Attributes> entries = manifest.getEntries();
-
+
for (Attributes attribs : entries.values())
{
if (attribs == null)
+ {
continue; // skip entry
+ }
String version = attribs.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
if (version == null)
+ {
continue; // empty, no value, skip it
+ }
return stripV(version);
}
@@ -127,30 +143,36 @@
public static String getVersion(File file)
{
- try
+ try (JarFile jar = new JarFile(file))
{
- JarFile jar = new JarFile(file);
-
String version = null;
-
+
Manifest manifest = jar.getManifest();
version = getMainManifestImplVersion(manifest);
if (version != null)
+ {
return version;
-
+ }
+
version = getSubManifestImplVersion(manifest);
if (version != null)
+ {
return version;
-
+ }
+
version = getBundleVersion(manifest);
if (version != null)
+ {
return version;
-
+ }
+
version = getMavenVersion(jar);
if (version != null)
+ {
return version;
-
+ }
+
return "(not specified)";
}
catch (IOException e)
@@ -162,7 +184,9 @@
private static String stripV(String version)
{
if (version.charAt(0) == 'v')
+ {
return version.substring(1);
+ }
return version;
}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
index 4a5ed83..0fcf25a 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
@@ -18,21 +18,20 @@
package org.eclipse.jetty.start;
+import static org.eclipse.jetty.start.UsageException.ERR_INVOKE_MAIN;
+import static org.eclipse.jetty.start.UsageException.ERR_NOT_STOPPED;
+import static org.eclipse.jetty.start.UsageException.ERR_UNKNOWN;
+
import java.io.BufferedReader;
-import java.io.Closeable;
import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.FilenameFilter;
+import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
-import java.io.PrintStream;
+import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ConnectException;
@@ -40,626 +39,109 @@
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URL;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
import java.util.List;
import java.util.Locale;
-import java.util.Properties;
-import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
-import javax.naming.OperationNotSupportedException;
-
-/*-------------------------------------------*/
/**
+ * Main start class.
* <p>
- * Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It
- * allows an application to be started with the command "java -jar start.jar".
- * </p>
- *
+ * This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It allows the Jetty Application server to be started with the
+ * command "java -jar start.jar".
* <p>
- * The behaviour of Main is controlled by the parsing of the {@link Config} "org/eclipse/start/start.config" file
- * obtained as a resource or file.
- * </p>
+ * Argument processing steps:
+ * <ol>
+ * <li>Directory Locations:
+ * <ul>
+ * <li>jetty.home=[directory] (the jetty.home location)</li>
+ * <li>jetty.base=[directory] (the jetty.base location)</li>
+ * </ul>
+ * </li>
+ * <li>Start Logging behavior:
+ * <ul>
+ * <li>--debug (debugging enabled)</li>
+ * <li>--start-log-file=logs/start.log (output start logs to logs/start.log location)</li>
+ * </ul>
+ * </li>
+ * <li>Module Resolution</li>
+ * <li>Properties Resolution</li>
+ * <li>Present Optional Informational Options</li>
+ * <li>Normal Startup</li>
+ * </li>
+ * </ol>
*/
public class Main
{
- private static final String START_LOG_FILENAME = "start.log";
- private static final SimpleDateFormat START_LOG_ROLLOVER_DATEFORMAT = new SimpleDateFormat("yyyy_MM_dd-HHmmSSSSS.'" + START_LOG_FILENAME + "'");
-
private static final int EXIT_USAGE = 1;
- private static final int ERR_LOGGING = -1;
- private static final int ERR_INVOKE_MAIN = -2;
- private static final int ERR_NOT_STOPPED = -4;
- private static final int ERR_UNKNOWN = -5;
- private boolean _showUsage = false;
- private boolean _dumpVersions = false;
- private boolean _listConfig = false;
- private boolean _listOptions = false;
- private boolean _dryRun = false;
- private boolean _exec = false;
- private final Config _config = new Config();
- private final Set<String> _sysProps = new HashSet<String>();
- private final List<String> _jvmArgs = new ArrayList<String>();
- private String _startConfig = null;
- private String _jettyHome;
+ public static String join(Collection<?> objs, String delim)
+ {
+ StringBuilder str = new StringBuilder();
+ boolean needDelim = false;
+ for (Object obj : objs)
+ {
+ if (needDelim)
+ {
+ str.append(delim);
+ }
+ str.append(obj);
+ needDelim = true;
+ }
+ return str.toString();
+ }
public static void main(String[] args)
{
try
{
Main main = new Main();
- List<String> arguments = main.expandCommandLine(args);
- List<String> xmls = main.processCommandLine(arguments);
- if (xmls != null)
- main.start(xmls);
+ StartArgs startArgs = main.processCommandLine(args);
+ main.start(startArgs);
+ }
+ catch (UsageException e)
+ {
+ System.err.println(e.getMessage());
+ usageExit(e.getCause(),e.getExitCode());
}
catch (Throwable e)
{
- usageExit(e,ERR_UNKNOWN);
+ usageExit(e,UsageException.ERR_UNKNOWN);
}
}
+ static void usageExit(int exit)
+ {
+ usageExit(null,exit);
+ }
+
+ static void usageExit(Throwable t, int exit)
+ {
+ if (t != null)
+ {
+ t.printStackTrace(System.err);
+ }
+ System.err.println();
+ System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
+ System.err.println(" java -jar start.jar --help # for more information");
+ System.exit(exit);
+ }
+
+ private final BaseHome baseHome;
+
Main() throws IOException
{
- _jettyHome = System.getProperty("jetty.home",".");
- _jettyHome = new File(_jettyHome).getCanonicalPath();
- }
-
- public List<String> expandCommandLine(String[] args) throws Exception
- {
- List<String> arguments = new ArrayList<String>();
-
- // add the command line args and look for start.ini args
- boolean ini = false;
- for (String arg : args)
- {
- if (arg.startsWith("--ini=") || arg.equals("--ini"))
- {
- ini = true;
- if (arg.length() > 6)
- {
- arguments.addAll(loadStartIni(new File(arg.substring(6))));
- }
- }
- else if (arg.startsWith("--config="))
- {
- _startConfig = arg.substring(9);
- }
- else
- {
- arguments.add(arg);
- }
- }
-
- // if no non-option inis, add the start.ini and start.d
- if (!ini)
- {
- arguments.addAll(0,parseStartIniFiles());
- }
-
- return arguments;
- }
-
- List<String> parseStartIniFiles()
- {
- List<String> ini_args = new ArrayList<String>();
- File start_ini = new File(_jettyHome,"start.ini");
- if (start_ini.exists())
- ini_args.addAll(loadStartIni(start_ini));
-
- return ini_args;
- }
-
- public List<String> processCommandLine(List<String> arguments) throws Exception
- {
- // The XML Configuration Files to initialize with
- List<String> xmls = new ArrayList<String>();
-
- // Process the arguments
- int startup = 0;
- for (String arg : arguments)
- {
- if ("--help".equals(arg) || "-?".equals(arg))
- {
- _showUsage = true;
- continue;
- }
-
- if ("--stop".equals(arg))
- {
- int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
- String key = Config.getProperty("STOP.KEY",null);
- int timeout = Integer.parseInt(Config.getProperty("STOP.WAIT","0"));
- stop(port,key,timeout);
- return null;
- }
-
- if (arg.startsWith("--download="))
- {
- download(arg);
- continue;
- }
-
- if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
- {
- _dumpVersions = true;
- continue;
- }
-
- if ("--list-modes".equals(arg) || "--list-options".equals(arg))
- {
- _listOptions = true;
- continue;
- }
-
- if ("--list-config".equals(arg))
- {
- _listConfig = true;
- continue;
- }
-
- if ("--exec-print".equals(arg) || "--dry-run".equals(arg))
- {
- _dryRun = true;
- continue;
- }
-
- if ("--exec".equals(arg))
- {
- _exec = true;
- continue;
- }
-
- // Special internal indicator that jetty was started by the jetty.sh Daemon
- if ("--daemon".equals(arg))
- {
- File startDir = new File(System.getProperty("jetty.logs","logs"));
- if (!startDir.exists() || !startDir.canWrite())
- startDir = new File(".");
-
- File startLog = new File(startDir,START_LOG_ROLLOVER_DATEFORMAT.format(new Date()));
-
- if (!startLog.exists() && !startLog.createNewFile())
- {
- // Output about error is lost in majority of cases.
- System.err.println("Unable to create: " + startLog.getAbsolutePath());
- // Toss a unique exit code indicating this failure.
- usageExit(ERR_LOGGING);
- }
-
- if (!startLog.canWrite())
- {
- // Output about error is lost in majority of cases.
- System.err.println("Unable to write to: " + startLog.getAbsolutePath());
- // Toss a unique exit code indicating this failure.
- usageExit(ERR_LOGGING);
- }
- PrintStream logger = new PrintStream(new FileOutputStream(startLog,false));
- System.setOut(logger);
- System.setErr(logger);
- System.out.println("Establishing " + START_LOG_FILENAME + " on " + new Date());
- continue;
- }
-
- if (arg.startsWith("-D"))
- {
- String[] assign = arg.substring(2).split("=",2);
- _sysProps.add(assign[0]);
- switch (assign.length)
- {
- case 2:
- System.setProperty(assign[0],assign[1]);
- break;
- case 1:
- System.setProperty(assign[0],"");
- break;
- default:
- break;
- }
- continue;
- }
-
- if (arg.startsWith("-"))
- {
- _jvmArgs.add(arg);
- continue;
- }
-
- // Is this a Property?
- if (arg.indexOf('=') >= 0)
- {
- String[] assign = arg.split("=",2);
-
- switch (assign.length)
- {
- case 2:
- if ("OPTIONS".equals(assign[0]))
- {
- String opts[] = assign[1].split(",");
- for (String opt : opts)
- _config.addActiveOption(opt.trim());
- }
- else
- {
- this._config.setProperty(assign[0],assign[1]);
- }
- break;
- case 1:
- this._config.setProperty(assign[0],null);
- break;
- default:
- break;
- }
-
- continue;
- }
-
- // Anything else is considered an XML file.
- if (xmls.contains(arg))
- {
- System.out.println("WARN: Argument '" + arg + "' specified multiple times. Check start.ini?");
- System.out.println("Use \"java -jar start.jar --help\" for more information.");
- }
- xmls.add(arg);
- }
-
- return xmls;
- }
-
- private void download(String arg)
- {
- try
- {
- String[] split = arg.split(":",3);
- if (split.length!=3 || "http".equalsIgnoreCase(split[0]) || !split[1].startsWith("//"))
- throw new IllegalArgumentException("Not --download=<http uri>:<location>");
-
- String location=split[2];
- if (File.separatorChar!='/')
- location.replaceAll("/",File.separator);
- File file = new File(location);
-
- if (Config.isDebug())
- System.err.println("Download to "+file.getAbsolutePath()+(file.exists()?" Exists!":""));
- if (file.exists())
- return;
-
- URL url = new URL(split[0].substring(11)+":"+split[1]);
-
- System.err.println("DOWNLOAD: "+url+" to "+location);
-
- if (!file.getParentFile().exists())
- file.getParentFile().mkdirs();
-
- byte[] buf=new byte[8192];
- try (InputStream in = url.openStream(); OutputStream out = new FileOutputStream(file);)
- {
- while(true)
- {
- int len = in.read(buf);
-
- if (len>0)
- out.write(buf,0,len);
- if (len<0)
- break;
- }
- }
- }
- catch(Exception e)
- {
- System.err.println("ERROR: processing "+arg+"\n"+e);
- e.printStackTrace();
- usageExit(EXIT_USAGE);
- }
- }
-
- private void usage()
- {
- String usageResource = "org/eclipse/jetty/start/usage.txt";
- InputStream usageStream = getClass().getClassLoader().getResourceAsStream(usageResource);
-
- if (usageStream == null)
- {
- System.err.println("ERROR: detailed usage resource unavailable");
- usageExit(EXIT_USAGE);
- }
-
- BufferedReader buf = null;
- try
- {
- buf = new BufferedReader(new InputStreamReader(usageStream));
- String line;
-
- while ((line = buf.readLine()) != null)
- {
- if (line.endsWith("@") && line.indexOf('@') != line.lastIndexOf('@'))
- {
- String indent = line.substring(0,line.indexOf("@"));
- String info = line.substring(line.indexOf('@'),line.lastIndexOf('@'));
-
- if (info.equals("@OPTIONS"))
- {
- List<String> sortedOptions = new ArrayList<String>();
- sortedOptions.addAll(_config.getSectionIds());
- Collections.sort(sortedOptions);
-
- for (String option : sortedOptions)
- {
- if ("*".equals(option) || option.trim().length() == 0)
- continue;
- System.out.print(indent);
- System.out.println(option);
- }
- }
- else if (info.equals("@CONFIGS"))
- {
- File etc = new File(System.getProperty("jetty.home","."),"etc");
- if (!etc.exists() || !etc.isDirectory())
- {
- System.out.print(indent);
- System.out.println("Unable to find/list " + etc);
- continue;
- }
-
- File configs[] = etc.listFiles(new FileFilter()
- {
- public boolean accept(File path)
- {
- if (!path.isFile())
- {
- return false;
- }
-
- String name = path.getName().toLowerCase(Locale.ENGLISH);
- return (name.startsWith("jetty") && name.endsWith(".xml"));
- }
- });
-
- List<File> configFiles = new ArrayList<File>();
- configFiles.addAll(Arrays.asList(configs));
- Collections.sort(configFiles);
-
- for (File configFile : configFiles)
- {
- System.out.print(indent);
- System.out.print("etc/");
- System.out.println(configFile.getName());
- }
- }
- else if (info.equals("@STARTINI"))
- {
- List<String> ini = parseStartIniFiles();
- if (ini != null && ini.size() > 0)
- {
- for (String a : ini)
- {
- System.out.print(indent);
- System.out.println(a);
- }
- }
- else
- {
- System.out.print(indent);
- System.out.println("none");
- }
- }
- }
- else
- {
- System.out.println(line);
- }
- }
- }
- catch (IOException e)
- {
- usageExit(e,EXIT_USAGE);
- }
- finally
- {
- close(buf);
- }
- System.exit(EXIT_USAGE);
- }
-
- public void invokeMain(ClassLoader classloader, String classname, List<String> args) throws IllegalAccessException, InvocationTargetException,
- NoSuchMethodException, ClassNotFoundException
- {
- Class<?> invoked_class = null;
-
- try
- {
- invoked_class = classloader.loadClass(classname);
- }
- catch (ClassNotFoundException e)
- {
- e.printStackTrace();
- }
-
- if (Config.isDebug() || invoked_class == null)
- {
- if (invoked_class == null)
- {
- System.err.println("ClassNotFound: " + classname);
- }
- else
- {
- System.err.println(classname + " " + invoked_class.getPackage().getImplementationVersion());
- }
-
- if (invoked_class == null)
- {
- usageExit(ERR_INVOKE_MAIN);
- return;
- }
- }
-
- String argArray[] = args.toArray(new String[0]);
-
- Class<?>[] method_param_types = new Class[]
- { argArray.getClass() };
-
- Method main = invoked_class.getDeclaredMethod("main",method_param_types);
- Object[] method_params = new Object[]
- { argArray };
- main.invoke(null,method_params);
- }
-
- /* ------------------------------------------------------------ */
- public static void close(Closeable c)
- {
- if (c == null)
- {
- return;
- }
- try
- {
- c.close();
- }
- catch (IOException e)
- {
- e.printStackTrace(System.err);
- }
- }
-
- /* ------------------------------------------------------------ */
- public void start(List<String> xmls) throws IOException, InterruptedException
- {
- // Load potential Config (start.config)
- List<String> configuredXmls = loadConfig(xmls);
-
- // No XML defined in start.config or command line. Can't execute.
- if (configuredXmls.isEmpty())
- {
- throw new FileNotFoundException("No XML configuration files specified in start.config or command line.");
- }
-
- // Normalize the XML config options passed on the command line.
- configuredXmls = resolveXmlConfigs(configuredXmls);
-
- // Get Desired Classpath based on user provided Active Options.
- Classpath classpath = _config.getActiveClasspath();
-
- System.setProperty("java.class.path",classpath.toString());
- ClassLoader cl = classpath.getClassLoader();
- if (Config.isDebug())
- {
- System.err.println("java.class.path=" + System.getProperty("java.class.path"));
- System.err.println("jetty.home=" + System.getProperty("jetty.home"));
- System.err.println("java.home=" + System.getProperty("java.home"));
- System.err.println("java.io.tmpdir=" + System.getProperty("java.io.tmpdir"));
- System.err.println("java.class.path=" + classpath);
- System.err.println("classloader=" + cl);
- System.err.println("classloader.parent=" + cl.getParent());
- System.err.println("properties=" + Config.getProperties());
- }
-
- // Show the usage information and return
- if (_showUsage)
- {
- usage();
- return;
- }
-
- // Show the version information and return
- if (_dumpVersions)
- {
- showClasspathWithVersions(classpath);
- return;
- }
-
- // Show all options with version information
- if (_listOptions)
- {
- showAllOptionsWithVersions();
- return;
- }
-
- if (_listConfig)
- {
- listConfig();
- return;
- }
-
- // Show Command Line to execute Jetty
- if (_dryRun)
- {
- CommandLineBuilder cmd = buildCommandLine(classpath,configuredXmls);
- System.out.println(cmd.toString());
- return;
- }
-
- // execute Jetty in another JVM
- if (_exec)
- {
- CommandLineBuilder cmd = buildCommandLine(classpath,configuredXmls);
-
- ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs());
- final Process process = pbuilder.start();
- Runtime.getRuntime().addShutdownHook(new Thread()
- {
- @Override
- public void run()
- {
- Config.debug("Destroying " + process);
- process.destroy();
- }
- });
-
- copyInThread(process.getErrorStream(),System.err);
- copyInThread(process.getInputStream(),System.out);
- copyInThread(System.in,process.getOutputStream());
- process.waitFor();
- System.exit(0); // exit JVM when child process ends.
- return;
- }
-
- if (_jvmArgs.size() > 0 || _sysProps.size() > 0)
- {
- System.err.println("WARNING: System properties and/or JVM args set. Consider using --dry-run or --exec");
- }
-
- // Set current context class loader to what is selected.
- Thread.currentThread().setContextClassLoader(cl);
-
- // Invoke the Main Class
- try
- {
- // Get main class as defined in start.config
- String classname = _config.getMainClassname();
-
- // Check for override of start class (via "jetty.server" property)
- String mainClass = System.getProperty("jetty.server");
- if (mainClass != null)
- {
- classname = mainClass;
- }
-
- // Check for override of start class (via "main.class" property)
- mainClass = System.getProperty("main.class");
- if (mainClass != null)
- {
- classname = mainClass;
- }
-
- Config.debug("main.class=" + classname);
-
- invokeMain(cl,classname,configuredXmls);
- }
- catch (Exception e)
- {
- usageExit(e,ERR_INVOKE_MAIN);
- }
+ baseHome = new BaseHome();
}
private void copyInThread(final InputStream in, final OutputStream out)
{
new Thread(new Runnable()
{
+ @Override
public void run()
{
try
@@ -681,249 +163,86 @@
}).start();
}
- private String resolveXmlConfig(String xmlFilename) throws FileNotFoundException
+ private void initFile(FileArg arg)
{
- if (!xmlFilename.toLowerCase(Locale.ENGLISH).endsWith(".xml"))
+ try
{
- // Nothing to resolve.
- return xmlFilename;
- }
+ File file = baseHome.getBaseFile(arg.location);
- File xml = new File(xmlFilename);
- if (xml.exists() && xml.isFile())
- {
- return xml.getAbsolutePath();
- }
-
- xml = new File(_jettyHome,fixPath(xmlFilename));
- if (xml.exists() && xml.isFile())
- {
- return xml.getAbsolutePath();
- }
-
- xml = new File(_jettyHome,fixPath("etc/" + xmlFilename));
- if (xml.exists() && xml.isFile())
- {
- return xml.getAbsolutePath();
- }
-
- throw new FileNotFoundException("Unable to find XML Config: " + xmlFilename);
- }
-
- CommandLineBuilder buildCommandLine(Classpath classpath, List<String> xmls) throws IOException
- {
- CommandLineBuilder cmd = new CommandLineBuilder(findJavaBin());
-
- for (String x : _jvmArgs)
- {
- cmd.addArg(x);
- }
- cmd.addRawArg("-Djetty.home=" + _jettyHome);
-
- // Special Stop/Shutdown properties
- ensureSystemPropertySet("STOP.PORT");
- ensureSystemPropertySet("STOP.KEY");
-
- // System Properties
- for (String p : _sysProps)
- {
- String v = System.getProperty(p);
- cmd.addEqualsArg("-D" + p,v);
- }
-
- cmd.addArg("-cp");
- cmd.addRawArg(classpath.toString());
- cmd.addRawArg(_config.getMainClassname());
-
- // Check if we need to pass properties as a file
- Properties properties = Config.getProperties();
- if (properties.size() > 0)
- {
- File prop_file = File.createTempFile("start",".properties");
- if (!_dryRun)
- prop_file.deleteOnExit();
- try (OutputStream out = new FileOutputStream(prop_file))
+ StartLog.debug("Module file %s %s",file.getAbsolutePath(),(file.exists()?"[Exists!]":""));
+ if (file.exists())
{
- properties.store(out,"start.jar properties");
+ return;
}
- cmd.addArg(prop_file.getAbsolutePath());
- }
- for (String xml : xmls)
- {
- cmd.addRawArg(xml);
- }
- return cmd;
- }
-
- /**
- * Ensure that the System Properties are set (if defined as a System property, or start.config property, or
- * start.ini property)
- *
- * @param key
- * the key to be sure of
- */
- private void ensureSystemPropertySet(String key)
- {
- if (_sysProps.contains(key))
- {
- return; // done
- }
-
- Properties props = Config.getProperties();
- if (props.containsKey(key))
- {
- String val = props.getProperty(key,null);
- if (val == null)
+ if (arg.uri!=null)
{
- return; // no value to set
+ URL url = new URL(arg.uri);
+
+ System.err.println("DOWNLOAD: " + url + " to " + arg.location);
+
+ FS.ensureDirectoryExists(file.getParentFile());
+
+ byte[] buf = new byte[8192];
+ try (InputStream in = url.openStream(); OutputStream out = new FileOutputStream(file);)
+ {
+ while (true)
+ {
+ int len = in.read(buf);
+
+ if (len > 0)
+ {
+ out.write(buf,0,len);
+ }
+ if (len < 0)
+ {
+ break;
+ }
+ }
+ }
}
- // setup system property
- _sysProps.add(key);
- System.setProperty(key,val);
- }
- }
-
- private String findJavaBin()
- {
- File javaHome = new File(System.getProperty("java.home"));
- if (!javaHome.exists())
- {
- return null;
- }
-
- File javabin = findExecutable(javaHome,"bin/java");
- if (javabin != null)
- {
- return javabin.getAbsolutePath();
- }
-
- javabin = findExecutable(javaHome,"bin/java.exe");
- if (javabin != null)
- {
- return javabin.getAbsolutePath();
- }
-
- return "java";
- }
-
- private File findExecutable(File root, String path)
- {
- String npath = path.replace('/',File.separatorChar);
- File exe = new File(root,npath);
- if (!exe.exists())
- {
- return null;
- }
- return exe;
- }
-
- private void showAllOptionsWithVersions()
- {
- Set<String> sectionIds = _config.getSectionIds();
-
- StringBuffer msg = new StringBuffer();
- msg.append("There ");
- if (sectionIds.size() > 1)
- {
- msg.append("are ");
- }
- else
- {
- msg.append("is ");
- }
- msg.append(String.valueOf(sectionIds.size()));
- msg.append(" OPTION");
- if (sectionIds.size() > 1)
- {
- msg.append("s");
- }
- msg.append(" available to use.");
- System.out.println(msg);
- System.out.println("Each option is listed along with associated available classpath entries, in the order that they would appear from that mode.");
- System.out.println("Note: If using multiple options (eg: 'Server,servlet,webapp,jms,jmx') "
- + "then overlapping entries will not be repeated in the eventual classpath.");
- System.out.println();
- System.out.printf("${jetty.home} = %s%n",_jettyHome);
- System.out.println();
-
- for (String sectionId : sectionIds)
- {
- if (Config.DEFAULT_SECTION.equals(sectionId))
+ else if (arg.location.endsWith("/"))
{
- System.out.println("GLOBAL option (Prepended Entries)");
- }
- else if ("*".equals(sectionId))
- {
- System.out.println("GLOBAL option (Appended Entries) (*)");
+ System.err.println("MKDIR: " + baseHome.toShortForm(file));
+ file.mkdirs();
}
else
- {
- System.out.printf("Option [%s]",sectionId);
- if (Character.isUpperCase(sectionId.charAt(0)))
- {
- System.out.print(" (Aggregate)");
- }
- System.out.println();
- }
- System.out.println("-------------------------------------------------------------");
-
- Classpath sectionCP = _config.getSectionClasspath(sectionId);
-
- if (sectionCP.isEmpty())
- {
- System.out.println("Empty option, no classpath entries active.");
- System.out.println();
- continue;
- }
-
- int i = 0;
- for (File element : sectionCP.getElements())
- {
- String elementPath = element.getAbsolutePath();
- if (elementPath.startsWith(_jettyHome))
- {
- elementPath = "${jetty.home}" + elementPath.substring(_jettyHome.length());
- }
- System.out.printf("%2d: %20s | %s\n",i++,getVersion(element),elementPath);
- }
-
- System.out.println();
+ StartLog.warn("MISSING: required file "+ baseHome.toShortForm(file));
+
+ }
+ catch (Exception e)
+ {
+ StartLog.warn("ERROR: processing %s%n%s",arg,e);
+ StartLog.warn(e);
+ usageExit(EXIT_USAGE);
}
}
- private void showClasspathWithVersions(Classpath classpath)
+ private void dumpClasspathWithVersions(Classpath classpath)
{
- // Iterate through active classpath, and fetch Implementation Version from each entry (if present)
- // to dump to end user.
-
- System.out.println("Active Options: " + _config.getActiveOptions());
-
+ System.out.println();
+ System.out.println("Jetty Server Classpath:");
+ System.out.println("-----------------------");
if (classpath.count() == 0)
{
- System.out.println("No version information available show.");
+ System.out.println("No classpath entries and/or version information available show.");
return;
}
System.out.println("Version Information on " + classpath.count() + " entr" + ((classpath.count() > 1)?"ies":"y") + " in the classpath.");
System.out.println("Note: order presented here is how they would appear on the classpath.");
- System.out.println(" changes to the OPTIONS=[option,option,...] command line option will be reflected here.");
+ System.out.println(" changes to the --module=name command line options will be reflected here.");
int i = 0;
for (File element : classpath.getElements())
{
- String elementPath = element.getAbsolutePath();
- if (elementPath.startsWith(_jettyHome))
- {
- elementPath = "${jetty.home}" + elementPath.substring(_jettyHome.length());
- }
- System.out.printf("%2d: %20s | %s\n",i++,getVersion(element),elementPath);
+ System.out.printf("%2d: %24s | %s\n",i++,getVersion(element),baseHome.toShortForm(element));
}
}
- private String fixPath(String path)
+ public BaseHome getBaseHome()
{
- return path.replace('/',File.separatorChar);
+ return baseHome;
}
private String getVersion(File element)
@@ -945,119 +264,451 @@
return "";
}
- private List<String> resolveXmlConfigs(List<String> xmls) throws FileNotFoundException
+ public void invokeMain(StartArgs args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException, IOException
{
- List<String> ret = new ArrayList<String>();
- for (String xml : xmls)
- {
- ret.add(resolveXmlConfig(xml));
- }
+ Class<?> invoked_class = null;
+ ClassLoader classloader = args.getClasspath().getClassLoader();
+ String mainclass = args.getMainClassname();
- return ret;
- }
-
- private void listConfig()
- {
- InputStream cfgstream = null;
try
{
- cfgstream = getConfigStream();
- byte[] buf = new byte[4096];
+ invoked_class = classloader.loadClass(mainclass);
+ }
+ catch (ClassNotFoundException e)
+ {
+ System.out.println("WARNING: Nothing to start, exiting ...");
+ StartLog.debug(e);
+ usageExit(ERR_INVOKE_MAIN);
+ return;
+ }
- int len = 0;
+ StartLog.debug("%s - %s",invoked_class,invoked_class.getPackage().getImplementationVersion());
- while (len >= 0)
+ CommandLineBuilder cmd = args.getMainArgs(baseHome,false);
+ String argArray[] = cmd.getArgs().toArray(new String[0]);
+ StartLog.debug("Command Line Args: %s",cmd.toString());
+
+ Class<?>[] method_param_types = new Class[]
+ { argArray.getClass() };
+
+ Method main = invoked_class.getDeclaredMethod("main",method_param_types);
+ Object[] method_params = new Object[]
+ { argArray };
+ main.invoke(null,method_params);
+ }
+
+ public void listConfig(StartArgs args)
+ {
+ // Dump Jetty Home / Base
+ args.dumpEnvironment();
+
+ // Dump JVM Args
+ args.dumpJvmArgs();
+
+ // Dump System Properties
+ args.dumpSystemProperties();
+
+ // Dump Properties
+ args.dumpProperties();
+
+ // Dump Classpath
+ dumpClasspathWithVersions(args.getClasspath());
+
+ // Dump Resolved XMLs
+ args.dumpActiveXmls(baseHome);
+ }
+
+ private void listModules(StartArgs args)
+ {
+ System.out.println();
+ System.out.println("Jetty All Available Modules:");
+ System.out.println("----------------------------");
+ args.getAllModules().dump();
+
+ // Dump Enabled Modules
+ System.out.println();
+ System.out.println("Jetty Active Module Tree:");
+ System.out.println("-------------------------");
+ Modules modules = args.getAllModules();
+ modules.dumpEnabledTree();
+ }
+
+ private void moduleIni(StartArgs args, String name, boolean topLevel, boolean appendStartIni) throws IOException
+ {
+ // Find the start.d relative to the base directory only.
+ File start_d = baseHome.getBaseFile("start.d");
+
+ // Is this a module?
+ Modules modules = args.getAllModules();
+ Module module = modules.get(name);
+ if (module == null)
+ {
+ StartLog.warn("ERROR: No known module for %s",name);
+ return;
+ }
+
+ // Find any named ini file and check it follows the convention
+ File start_ini = baseHome.getBaseFile("start.ini");
+ String short_start_ini = baseHome.toShortForm(start_ini);
+ File ini = new File(start_d,name + ".ini");
+ String short_ini = baseHome.toShortForm(ini);
+ StartIni module_ini = null;
+ if (ini.exists())
+ {
+ module_ini = new StartIni(ini);
+ if (module_ini.getLineMatches(Pattern.compile("--module=(.*, *)*" + name)).size() == 0)
{
- len = cfgstream.read(buf);
- if (len > 0)
- System.out.write(buf,0,len);
+ StartLog.warn("ERROR: %s is not enabled in %s!",name,short_ini);
+ return;
}
}
- catch (Exception e)
+
+ boolean transitive = module.isEnabled() && (module.getSources().size() == 0);
+ boolean has_ini_lines = module.getInitialise().size() > 0;
+
+ // If it is not enabled or is transitive with ini template lines or toplevel and doesn't exist
+ if (!module.isEnabled() || (transitive && has_ini_lines) || (topLevel && !ini.exists() && !appendStartIni))
{
- usageExit(e,ERR_UNKNOWN);
+ String source = null;
+ PrintWriter out = null;
+ try
+ {
+ if (appendStartIni)
+ {
+ if ((!start_ini.exists() && !start_ini.createNewFile()) || !start_ini.canWrite())
+ {
+ StartLog.warn("ERROR: Bad %s! ",start_ini);
+ return;
+ }
+ source = short_start_ini;
+ StartLog.warn("%-15s initialised in %s (appended)",name,source);
+ out = new PrintWriter(new FileWriter(start_ini,true));
+ }
+ else
+ {
+ // Create the directory if needed
+ if (!start_d.exists())
+ {
+ start_d.mkdirs();
+ }
+ if (!start_d.isDirectory() || !start_d.canWrite())
+ {
+ StartLog.warn("ERROR: Bad start.d %s! ",start_d);
+ return;
+ }
+ // Create a new ini file for it
+ if (!ini.createNewFile())
+ {
+ StartLog.warn("ERROR: %s cannot be initialised in %s! ",name,short_ini);
+ return;
+ }
+ source = short_ini;
+ StartLog.warn("%-15s initialised in %s (created)",name,source);
+ out = new PrintWriter(ini);
+ }
+
+ if (appendStartIni)
+ {
+ out.println();
+ }
+ out.println("#");
+ out.println("# Initialize module " + name);
+ out.println("#");
+ Pattern p = Pattern.compile("--module=([^,]+)(,([^,]+))*");
+
+ out.println("--module=" + name);
+ args.parse("--module=" + name,source);
+ modules.enable(name,Collections.singletonList(source));
+ for (String line : module.getInitialise())
+ {
+ out.println(line);
+ args.parse(line,source);
+ Matcher m = p.matcher(line);
+ if (m.matches())
+ {
+ for (int i = 1; i <= m.groupCount(); i++)
+ {
+ String n = m.group(i);
+ if (n == null)
+ {
+ continue;
+ }
+ n = n.trim();
+ if ((n.length() == 0) || n.startsWith(","))
+ {
+ continue;
+ }
+
+ modules.enable(n,Collections.singletonList(source));
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (out != null)
+ {
+ out.close();
+ }
+ }
}
- finally
+ else if (ini.exists())
{
- close(cfgstream);
+ StartLog.info("%-15s initialised in %s",name,short_ini);
+ }
+
+ // Also list other places this module is enabled
+ for (String source : module.getSources())
+ {
+ if (!short_ini.equals(source))
+ {
+ StartLog.warn("%-15s enabled in %s",name,baseHome.toShortForm(source));
+ }
+ }
+
+ // Do downloads now
+ for (String file : module.getFiles())
+ {
+ initFile(new FileArg(file));
+ }
+
+ // Process dependencies from top level only
+ if (topLevel)
+ {
+ List<Module> parents = new ArrayList<>();
+ for (String parent : modules.resolveParentModulesOf(name))
+ {
+ if (!name.equals(parent))
+ {
+ Module m = modules.get(parent);
+ m.setEnabled(true);
+ parents.add(m);
+ }
+ }
+ Collections.sort(parents,Collections.reverseOrder(new Module.DepthComparator()));
+ for (Module m : parents)
+ {
+ moduleIni(args,m.getName(),false,appendStartIni);
+ }
}
}
/**
- * Load Configuration.
- *
- * No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to
- * execute the {@link Class} specified by {@link Config#getMainClassname()} is executed.
- *
- * @param xmls
- * the command line specified xml configuration options.
- * @return the list of xml configurations arriving via command line and start.config choices.
+ * Convenience for <code>processCommandLine(cmdLine.toArray(new String[cmdLine.size()]))</code>
*/
- private List<String> loadConfig(List<String> xmls)
+ public StartArgs processCommandLine(List<String> cmdLine) throws Exception
{
- InputStream cfgstream = null;
+ return this.processCommandLine(cmdLine.toArray(new String[cmdLine.size()]));
+ }
+
+ public StartArgs processCommandLine(String[] cmdLine) throws Exception
+ {
+ StartArgs args = new StartArgs(cmdLine);
+
+ // Processing Order is important!
+ // ------------------------------------------------------------
+ // 1) Directory Locations
+
+ // Set Home and Base at the start, as all other paths encountered
+ // will be based off of them.
+ baseHome.initialize(args);
+
+ // ------------------------------------------------------------
+ // 2) Start Logging
+ StartLog.getInstance().initialize(baseHome,args);
+
+ StartLog.debug("jetty.home=%s",baseHome.getHome());
+ StartLog.debug("jetty.base=%s",baseHome.getBase());
+ args.addSystemProperty("jetty.home",baseHome.getHome());
+ args.addSystemProperty("jetty.base",baseHome.getBase());
+
+ // ------------------------------------------------------------
+ // 3) Load Inis
+ File start_ini = baseHome.getBaseFile("start.ini");
+ if (FS.canReadFile(start_ini))
+ {
+ StartLog.debug("Reading ${jetty.base}/start.ini - %s",start_ini);
+ args.parse(baseHome,new StartIni(start_ini));
+ }
+
+ File start_d = baseHome.getBaseFile("start.d");
+ if (FS.canReadDirectory(start_d))
+ {
+ List<File> files = new ArrayList<>();
+ for (File file : start_d.listFiles(new FS.IniFilter()))
+ {
+ files.add(file);
+ }
+
+ Collections.sort(files,new NaturalSort.Files());
+ for (File file : files)
+ {
+ StartLog.debug("Reading ${jetty.base}/start.d/%s - %s",file.getName(),file);
+ args.parse(baseHome,new StartIni(file));
+ }
+ }
+
+ // 4) Parse everything provided.
+ // This would be the directory information +
+ // the various start inis
+ // and then the raw command line arguments
+ StartLog.debug("Parsing collected arguments");
+ args.parseCommandLine();
+
+ // 5) Module Registration
+ Modules modules = new Modules();
+ StartLog.debug("Registering all modules");
+ modules.registerAll(baseHome);
+
+ // 6) Active Module Resolution
+ for (String enabledModule : args.getEnabledModules())
+ {
+ List<String> sources = args.getSources(enabledModule);
+ modules.enable(enabledModule,sources);
+ }
+
+ StartLog.debug("Building Module Graph");
+ modules.buildGraph();
+
+ args.setAllModules(modules);
+ List<Module> activeModules = modules.resolveEnabled();
+
+ // 7) Lib & XML Expansion / Resolution
+ args.expandModules(baseHome,activeModules);
+
+ // 8) Resolve Extra XMLs
+ args.resolveExtraXmls(baseHome);
+
+ return args;
+ }
+
+ public void start(StartArgs args) throws IOException, InterruptedException
+ {
+ StartLog.debug("StartArgs: %s",args);
+
+ // Get Desired Classpath based on user provided Active Options.
+ Classpath classpath = args.getClasspath();
+
+ System.setProperty("java.class.path",classpath.toString());
+
+ // Show the usage information and return
+ if (args.isHelp())
+ {
+ usage(true);
+ }
+
+ // Show the version information and return
+ if (args.isListClasspath())
+ {
+ dumpClasspathWithVersions(classpath);
+ }
+
+ // Show configuration
+ if (args.isListConfig())
+ {
+ listConfig(args);
+ }
+
+ // Show modules
+ if (args.isListModules())
+ {
+ listModules(args);
+ }
+
+ // Generate Module Graph File
+ if (args.getModuleGraphFilename() != null)
+ {
+ File outputFile = baseHome.getBaseFile(args.getModuleGraphFilename());
+ System.out.printf("Generating GraphViz Graph of Jetty Modules at %s%n",baseHome.toShortForm(outputFile));
+ ModuleGraphWriter writer = new ModuleGraphWriter();
+ writer.config(args.getProperties());
+ writer.write(args.getAllModules(),outputFile);
+ }
+
+ // Show Command Line to execute Jetty
+ if (args.isDryRun())
+ {
+ CommandLineBuilder cmd = args.getMainArgs(baseHome,true);
+ System.out.println(cmd.toString());
+ }
+
+ if (args.isStopCommand())
+ {
+ int stopPort = Integer.parseInt(args.getProperties().getProperty("STOP.PORT"));
+ String stopKey = args.getProperties().getProperty("STOP.KEY");
+
+ if (args.getProperties().getProperty("STOP.WAIT") != null)
+ {
+ int stopWait = Integer.parseInt(args.getProperties().getProperty("STOP.PORT"));
+
+ stop(stopPort,stopKey,stopWait);
+ }
+ else
+ {
+ stop(stopPort,stopKey);
+ }
+ }
+
+ // Initialize
+ for (String module : args.getModuleStartIni())
+ {
+ moduleIni(args,module,true,true);
+ }
+
+ // Initialize
+ for (String module : args.getModuleStartdIni())
+ {
+ moduleIni(args,module,true,false);
+ }
+
+ // Informational command line, don't run jetty
+ if (!args.isRun())
+ {
+ return;
+ }
+
+ // execute Jetty in another JVM
+ if (args.isExec())
+ {
+ CommandLineBuilder cmd = args.getMainArgs(baseHome,true);
+ ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs());
+ final Process process = pbuilder.start();
+ Runtime.getRuntime().addShutdownHook(new Thread()
+ {
+ @Override
+ public void run()
+ {
+ StartLog.debug("Destroying " + process);
+ process.destroy();
+ }
+ });
+
+ copyInThread(process.getErrorStream(),System.err);
+ copyInThread(process.getInputStream(),System.out);
+ copyInThread(System.in,process.getOutputStream());
+ process.waitFor();
+ System.exit(0); // exit JVM when child process ends.
+ return;
+ }
+
+ if (args.hasJvmArgs() || args.hasSystemProperties())
+ {
+ System.err.println("WARNING: System properties and/or JVM args set. Consider using --dry-run or --exec");
+ }
+
+ // Set current context class loader to what is selected.
+ ClassLoader cl = classpath.getClassLoader();
+ Thread.currentThread().setContextClassLoader(cl);
+
+ // Invoke the Main Class
try
{
- // Pass in xmls.size into Config so that conditions based on "nargs" work.
- _config.setArgCount(xmls.size());
-
- cfgstream = getConfigStream();
-
- // parse the config
- _config.parse(cfgstream);
-
- _jettyHome = Config.getProperty("jetty.home",_jettyHome);
- if (_jettyHome != null)
- {
- _jettyHome = new File(_jettyHome).getCanonicalPath();
- System.setProperty("jetty.home",_jettyHome);
- }
-
- // Collect the configured xml configurations.
- List<String> ret = new ArrayList<String>();
- ret.addAll(xmls); // add command line provided xmls first.
- for (String xmlconfig : _config.getXmlConfigs())
- {
- // add xmlconfigs arriving via start.config
- if (!ret.contains(xmlconfig))
- {
- ret.add(xmlconfig);
- }
- }
-
- return ret;
+ invokeMain(args);
}
catch (Exception e)
{
- usageExit(e,ERR_UNKNOWN);
- return null; // never executed (just here to satisfy javac compiler)
+ usageExit(e,ERR_INVOKE_MAIN);
}
- finally
- {
- close(cfgstream);
- }
- }
-
- private InputStream getConfigStream() throws FileNotFoundException
- {
- String config = _startConfig;
- if (config == null || config.length() == 0)
- {
- config = System.getProperty("START","org/eclipse/jetty/start/start.config");
- }
-
- Config.debug("config=" + config);
-
- // Look up config as resource first.
- InputStream cfgstream = getClass().getClassLoader().getResourceAsStream(config);
-
- // resource not found, try filesystem next
- if (cfgstream == null)
- {
- cfgstream = new FileInputStream(config);
- }
-
- return cfgstream;
}
/**
@@ -1086,32 +737,34 @@
System.err.println("Using empty key");
}
- Socket s = new Socket(InetAddress.getByName("127.0.0.1"),_port);
- if (timeout > 0)
- s.setSoTimeout(timeout * 1000);
- try
+ try (Socket s = new Socket(InetAddress.getByName("127.0.0.1"),_port))
{
- OutputStream out = s.getOutputStream();
- out.write((_key + "\r\nstop\r\n").getBytes());
- out.flush();
-
if (timeout > 0)
{
- System.err.printf("Waiting %,d seconds for jetty to stop%n",timeout);
- LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
- String response;
- while ((response = lin.readLine()) != null)
+ s.setSoTimeout(timeout * 1000);
+ }
+
+ try (OutputStream out = s.getOutputStream())
+ {
+ out.write((_key + "\r\nstop\r\n").getBytes());
+ out.flush();
+
+ if (timeout > 0)
{
- Config.debug("Received \"" + response + "\"");
- if ("Stopped".equals(response))
- System.err.println("Server reports itself as Stopped");
+ System.err.printf("Waiting %,d seconds for jetty to stop%n",timeout);
+ LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
+ String response;
+ while ((response = lin.readLine()) != null)
+ {
+ StartLog.debug("Received \"%s\"",response);
+ if ("Stopped".equals(response))
+ {
+ StartLog.warn("Server reports itself as Stopped");
+ }
+ }
}
}
}
- finally
- {
- s.close();
- }
}
catch (SocketTimeoutException e)
{
@@ -1128,103 +781,40 @@
}
}
- static void usageExit(Throwable t, int exit)
+ public void usage(boolean exit)
{
- t.printStackTrace(System.err);
- System.err.println();
- System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
- System.err.println(" java -jar start.jar --help # for more information");
- System.exit(exit);
- }
-
- static void usageExit(int exit)
- {
- System.err.println();
- System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
- System.err.println(" java -jar start.jar --help # for more information");
- System.exit(exit);
- }
-
- /**
- * Convert a start.ini format file into an argument list.
- */
- List<String> loadStartIni(File ini)
- {
- if (!ini.exists())
+ String usageResource = "org/eclipse/jetty/start/usage.txt";
+ boolean usagePresented = false;
+ try (InputStream usageStream = getClass().getClassLoader().getResourceAsStream(usageResource))
{
- System.err.println("Warning - can't find ini file: " + ini);
- // No start.ini found, skip load.
- return Collections.emptyList();
- }
-
- List<String> args = new ArrayList<String>();
-
- FileReader reader = null;
- BufferedReader buf = null;
- try
- {
- reader = new FileReader(ini);
- buf = new BufferedReader(reader);
-
- String arg;
- while ((arg = buf.readLine()) != null)
+ if (usageStream != null)
{
- arg = arg.trim();
- if (arg.length() == 0 || arg.startsWith("#"))
+ try (InputStreamReader reader = new InputStreamReader(usageStream); BufferedReader buf = new BufferedReader(reader))
{
- continue;
- }
-
- if (arg.endsWith("/"))
- {
- try
+ usagePresented = true;
+ String line;
+ while ((line = buf.readLine()) != null)
{
- File start_d = new File(arg);
- if (!start_d.exists() || !start_d.isDirectory())
- start_d = new File(_jettyHome,arg);
-
- if (start_d.isDirectory())
- {
- File[] inis = start_d.listFiles(new FilenameFilter()
- {
- @Override
- public boolean accept(File dir, String name)
- {
- return name.toLowerCase(Locale.ENGLISH).endsWith(".ini");
- }
- });
- Arrays.sort(inis);
-
- for (File i : inis)
- args.addAll(loadStartIni(i));
-
- continue;
- }
- }
- catch(Exception e)
- {
- e.printStackTrace();
+ System.out.println(line);
}
}
-
- args.add(arg);
+ }
+ else
+ {
+ System.out.println("No usage.txt ??");
}
}
catch (IOException e)
{
- usageExit(e,ERR_UNKNOWN);
+ StartLog.warn(e);
}
- finally
+ if (!usagePresented)
{
- Main.close(buf);
- Main.close(reader);
+ System.err.println("ERROR: detailed usage resource unavailable");
}
-
- return args;
- }
-
- void addJvmArgs(List<String> jvmArgs)
- {
- _jvmArgs.addAll(jvmArgs);
+ if (exit)
+ {
+ System.exit(EXIT_USAGE);
+ }
}
}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java
new file mode 100644
index 0000000..fdb1416
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java
@@ -0,0 +1,349 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.text.CollationKey;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a Module metadata, as defined in Jetty.
+ */
+public class Module
+{
+ public static class NameComparator implements Comparator<Module>
+ {
+ private Collator collator = Collator.getInstance();
+
+ @Override
+ public int compare(Module o1, Module o2)
+ {
+ // by name (not really needed, but makes for predictable test cases)
+ CollationKey k1 = collator.getCollationKey(o1.name);
+ CollationKey k2 = collator.getCollationKey(o2.name);
+ return k1.compareTo(k2);
+ }
+ }
+
+ public static class DepthComparator implements Comparator<Module>
+ {
+ private Collator collator = Collator.getInstance();
+
+ @Override
+ public int compare(Module o1, Module o2)
+ {
+ // order by depth first.
+ int diff = o1.depth - o2.depth;
+ if (diff != 0)
+ {
+ return diff;
+ }
+ // then by name (not really needed, but makes for predictable test cases)
+ CollationKey k1 = collator.getCollationKey(o1.name);
+ CollationKey k2 = collator.getCollationKey(o2.name);
+ return k1.compareTo(k2);
+ }
+ }
+
+ /** The file of the module */
+ private File file;
+ /** The name of this Module */
+ private String name;
+ /** The depth of the module in the tree */
+ private int depth = 0;
+ /** List of Modules, by name, that this Module depends on */
+ private Set<String> parentNames;
+ /** List of Modules, by name, that this Module optionally depend on */
+ private Set<String> optionalParentNames;
+ /** The Edges to parent modules */
+ private Set<Module> parentEdges;
+ /** The Edges to child modules */
+ private Set<Module> childEdges;
+ /** List of xml configurations for this Module */
+ private List<String> xmls;
+ /** List of ini template lines */
+ private List<String> initialise;
+ /** List of library options for this Module */
+ private List<String> libs;
+ /** List of files for this Module */
+ private List<String> files;
+
+ /** Is this Module enabled via start.jar command line, start.ini, or start.d/*.ini ? */
+ private boolean enabled = false;
+ /** List of sources that enabled this module */
+ private final Set<String> sources = new HashSet<>();
+
+ public Module(File file) throws FileNotFoundException, IOException
+ {
+ this.file = file;
+
+ String name = file.getName();
+ // Strip .ini
+ name = Pattern.compile(".mod$",Pattern.CASE_INSENSITIVE).matcher(name).replaceFirst("");
+
+ init();
+ process();
+ }
+
+ public void addChildEdge(Module child)
+ {
+ if (childEdges.contains(child))
+ {
+ // already present, skip
+ return;
+ }
+ this.childEdges.add(child);
+ }
+
+ public void addParentEdge(Module parent)
+ {
+ if (parentEdges.contains(parent))
+ {
+ // already present, skip
+ return;
+ }
+ this.parentEdges.add(parent);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ Module other = (Module)obj;
+ if (name == null)
+ {
+ if (other.name != null)
+ {
+ return false;
+ }
+ }
+ else if (!name.equals(other.name))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public Set<Module> getChildEdges()
+ {
+ return childEdges;
+ }
+
+ public int getDepth()
+ {
+ return depth;
+ }
+
+ public List<String> getLibs()
+ {
+ return libs;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public Set<String> getOptionalParentNames()
+ {
+ return optionalParentNames;
+ }
+
+ public Set<Module> getParentEdges()
+ {
+ return parentEdges;
+ }
+
+ public Set<String> getParentNames()
+ {
+ return parentNames;
+ }
+
+ public List<String> getXmls()
+ {
+ return xmls;
+ }
+
+ public List<String> getInitialise()
+ {
+ return initialise;
+ }
+
+ public List<String> getFiles()
+ {
+ return files;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((name == null)?0:name.hashCode());
+ return result;
+ }
+
+ public void init()
+ {
+ String name = file.getName();
+
+ // Strip .ini
+ this.name = Pattern.compile(".mod$",Pattern.CASE_INSENSITIVE).matcher(name).replaceFirst("");
+
+ parentNames = new HashSet<>();
+ optionalParentNames = new HashSet<>();
+ parentEdges = new HashSet<>();
+ childEdges = new HashSet<>();
+ xmls = new ArrayList<>();
+ initialise = new ArrayList<>();
+ libs = new ArrayList<>();
+ files = new ArrayList<>();
+ }
+
+ public boolean isEnabled()
+ {
+ return enabled;
+ }
+
+ public void process() throws FileNotFoundException, IOException
+ {
+ Pattern section = Pattern.compile("\\s*\\[([^]]*)\\]\\s*");
+
+ if (!FS.canReadFile(file))
+ {
+ StartLog.debug("Skipping read of missing file: %s",file.getAbsolutePath());
+ return;
+ }
+
+ try (FileReader reader = new FileReader(file))
+ {
+ try (BufferedReader buf = new BufferedReader(reader))
+ {
+ String line;
+ String sectionType = "";
+ while ((line = buf.readLine()) != null)
+ {
+ line = line.trim();
+ Matcher sectionMatcher = section.matcher(line);
+
+ if (sectionMatcher.matches())
+ {
+ sectionType = sectionMatcher.group(1).trim().toUpperCase();
+ }
+ else
+ {
+ // blank lines and comments are valid for initialize section
+ if (line.length() == 0 || line.startsWith("#"))
+ {
+ if ("INI-TEMPLATE".equals(sectionType))
+ {
+ initialise.add(line);
+ }
+ }
+ else
+ {
+ switch (sectionType)
+ {
+ case "DEPEND":
+ parentNames.add(line);
+ break;
+ case "LIB":
+ libs.add(line);
+ break;
+ case "XML":
+ xmls.add(line);
+ break;
+ case "OPTIONAL":
+ optionalParentNames.add(line);
+ break;
+ case "FILES":
+ files.add(line);
+ break;
+ case "INI-TEMPLATE":
+ initialise.add(line);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void setDepth(int depth)
+ {
+ this.depth = depth;
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ this.enabled = enabled;
+ }
+
+ public void addSources(List<String> sources)
+ {
+ this.sources.addAll(sources);
+ }
+
+ public void clearSources()
+ {
+ this.sources.clear();
+ }
+
+ public Set<String> getSources()
+ {
+ return Collections.unmodifiableSet(sources);
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder();
+ str.append("Module[").append(name);
+ if (enabled)
+ {
+ str.append(",enabled");
+ }
+ str.append(']');
+ return str.toString();
+ }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java b/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java
new file mode 100644
index 0000000..7eacae3
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java
@@ -0,0 +1,257 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * Generate a graphviz dot graph of the modules found
+ */
+public class ModuleGraphWriter
+{
+ private String colorModuleBg;
+ private String colorEnabledBg;
+ private String colorTransitiveBg;
+ private String colorCellBg;
+ private String colorHeaderBg;
+ private String colorModuleFont;
+
+ public ModuleGraphWriter()
+ {
+ colorModuleBg = "#B8FFB8";
+ colorEnabledBg = "#66FFCC";
+ colorTransitiveBg = "#66CC66";
+ colorCellBg = "#FFFFFF80";
+ colorHeaderBg = "#00000020";
+ colorModuleFont = "#888888";
+ }
+
+ public void config(Properties props)
+ {
+ String prefix = "jetty.graph.";
+ colorModuleBg = getProperty(props,prefix + "color.module.bg",colorModuleBg);
+ colorEnabledBg = getProperty(props,prefix + "color.enabled.bg",colorEnabledBg);
+ colorTransitiveBg = getProperty(props,prefix + "color.transitive.bg",colorTransitiveBg);
+ colorCellBg = getProperty(props,prefix + "color.cell.bg",colorCellBg);
+ colorHeaderBg = getProperty(props,prefix + "color.header.bg",colorHeaderBg);
+ colorModuleFont = getProperty(props,prefix + "color.font",colorModuleFont);
+ }
+
+ private String getProperty(Properties props, String key, String defVal)
+ {
+ String val = props.getProperty(key,defVal);
+ if (val == null)
+ {
+ return defVal;
+ }
+ val = val.trim();
+ if (val.length() <= 0)
+ {
+ return defVal;
+ }
+ return val;
+ }
+
+ public void write(Modules modules, File outputFile) throws IOException
+ {
+ try (FileWriter writer = new FileWriter(outputFile,false); PrintWriter out = new PrintWriter(writer);)
+ {
+ writeHeaderMessage(out,outputFile);
+
+ out.println();
+ out.println("digraph modules {");
+
+ // Node Style
+ out.println(" node [color=gray, style=filled, shape=rectangle];");
+ out.println(" node [fontname=\"Verdana\", size=\"20,20\"];");
+ // Graph Style
+ out.println(" graph [");
+ out.println(" concentrate=false,");
+ out.println(" fontname=\"Verdana\",");
+ out.println(" fontsize = 20,");
+ out.println(" rankdir = LR,");
+ out.println(" ranksep = 1.5,");
+ out.println(" nodesep = .5,");
+ out.println(" style = bold,");
+ out.println(" labeljust = l,");
+ out.println(" label = \"Jetty Modules\",");
+ out.println(" ssize = \"20,40\"");
+ out.println(" ];");
+
+ List<Module> enabled = modules.resolveEnabled();
+
+ // Module Nodes
+ writeModules(out,modules,enabled);
+
+ // Module Relationships
+ writeRelationships(out,modules,enabled);
+
+ out.println("}");
+ out.println();
+ }
+ }
+
+ private void writeHeaderMessage(PrintWriter out, File outputFile)
+ {
+ out.println("/*");
+ out.println(" * GraphViz Graph of Jetty Modules");
+ out.println(" * ");
+ out.println(" * Jetty: http://eclipse.org/jetty/");
+ out.println(" * GraphViz: http://graphviz.org/");
+ out.println(" * ");
+ out.println(" * To Generate Graph image using graphviz:");
+ String filename = outputFile.getName();
+ String basename = filename.substring(0,filename.indexOf('.'));
+ out.printf(" * $ dot -Tpng -Goverlap=false -o %s.png %s%n",basename,filename);
+ out.println(" */");
+ }
+
+ private void writeModuleDetailHeader(PrintWriter out, String header)
+ {
+ writeModuleDetailHeader(out,header,1);
+ }
+
+ private void writeModuleDetailHeader(PrintWriter out, String header, int count)
+ {
+ out.printf(" <TR>");
+ out.printf("<TD BGCOLOR=\"%s\" ALIGN=\"LEFT\"><I>",colorHeaderBg);
+ out.printf("%s%s</I></TD>",header,count > 1?"s":"");
+ out.println("</TR>");
+ }
+
+ private void writeModuleDetailLine(PrintWriter out, String line)
+ {
+ out.printf(" <TR>");
+ StringBuilder escape = new StringBuilder();
+ for(char c: line.toCharArray()) {
+ switch(c) {
+ case '<': escape.append("<"); break;
+ case '>': escape.append(">"); break;
+ default:
+ escape.append(c);
+ break;
+ }
+ }
+
+ out.printf("<TD BGCOLOR=\"%s\" ALIGN=\"LEFT\">%s</TD></TR>%n",colorCellBg,escape.toString());
+ }
+
+ private void writeModuleNode(PrintWriter out, Module module, boolean resolved)
+ {
+ String color = colorModuleBg;
+ if (module.isEnabled())
+ {
+ // specifically enabled by config
+ color = colorEnabledBg;
+ }
+ else if (resolved)
+ {
+ // enabled by transitive reasons
+ color = colorTransitiveBg;
+ }
+
+ out.printf(" \"%s\" [ color=\"%s\" label=<",module.getName(),color);
+ out.printf("<TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"2\">%n");
+ out.printf(" <TR><TD ALIGN=\"LEFT\"><B>%s</B></TD></TR>%n",module.getName());
+
+ if (module.isEnabled())
+ {
+ writeModuleDetailHeader(out,"ENABLED");
+ for (String source : module.getSources())
+ {
+ writeModuleDetailLine(out,"via: " + source);
+ }
+ }
+ else if (resolved)
+ {
+ writeModuleDetailHeader(out,"TRANSITIVE");
+ }
+
+ if (!module.getXmls().isEmpty())
+ {
+ List<String> xmls = module.getXmls();
+ writeModuleDetailHeader(out,"XML",xmls.size());
+ for (String xml : xmls)
+ {
+ writeModuleDetailLine(out,xml);
+ }
+ }
+
+ if (!module.getLibs().isEmpty())
+ {
+ List<String> libs = module.getLibs();
+ writeModuleDetailHeader(out,"LIB",libs.size());
+ for (String lib : libs)
+ {
+ writeModuleDetailLine(out,lib);
+ }
+ }
+
+ if (!module.getInitialise().isEmpty())
+ {
+ List<String> inis = module.getInitialise();
+ writeModuleDetailHeader(out,"INI Template",inis.size());
+ }
+
+ out.printf("</TABLE>>];%n");
+ }
+
+ private void writeModules(PrintWriter out, Modules allmodules, List<Module> enabled)
+ {
+ out.println();
+ out.println(" /* Modules */");
+ out.println();
+
+ out.println(" node [ labeljust = l ];");
+
+ for (int depth = 0; depth <= allmodules.getMaxDepth(); depth++)
+ {
+ out.println();
+ Collection<Module> depthModules = allmodules.getModulesAtDepth(depth);
+ if (depthModules.size() > 0)
+ {
+ out.printf(" /* Level %d */%n",depth);
+ out.println(" { rank = same;");
+ for (Module module : depthModules)
+ {
+ boolean resolved = enabled.contains(module);
+ writeModuleNode(out,module,resolved);
+ }
+ out.println(" }");
+ }
+ }
+ }
+
+ private void writeRelationships(PrintWriter out, Modules modules, List<Module> enabled)
+ {
+ for (Module module : modules)
+ {
+ for (Module parent : module.getParentEdges())
+ {
+ out.printf(" \"%s\" -> \"%s\";%n",module.getName(),parent.getName());
+ }
+ }
+ }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java
new file mode 100644
index 0000000..8b21764
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java
@@ -0,0 +1,361 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Access for all modules declared, as well as what is enabled.
+ */
+public class Modules implements Iterable<Module>
+{
+ private Map<String, Module> modules = new HashMap<>();
+ private int maxDepth = -1;
+
+ private Set<String> asNameSet(Set<Module> moduleSet)
+ {
+ Set<String> ret = new HashSet<>();
+ for (Module module : moduleSet)
+ {
+ ret.add(module.getName());
+ }
+ return ret;
+ }
+
+ private void assertNoCycle(Module module, Stack<String> refs)
+ {
+ for (Module parent : module.getParentEdges())
+ {
+ if (refs.contains(parent.getName()))
+ {
+ // Cycle detected.
+ StringBuilder err = new StringBuilder();
+ err.append("A cyclic reference in the modules has been detected: ");
+ for (int i = 0; i < refs.size(); i++)
+ {
+ if (i > 0)
+ {
+ err.append(" -> ");
+ }
+ err.append(refs.get(i));
+ }
+ err.append(" -> ").append(parent.getName());
+ throw new IllegalStateException(err.toString());
+ }
+
+ refs.push(parent.getName());
+ assertNoCycle(parent,refs);
+ refs.pop();
+ }
+ }
+
+ private void bfsCalculateDepth(final Module module, final int depthNow)
+ {
+ int depth = depthNow + 1;
+
+ // Set depth on every child first
+ for (Module child : module.getChildEdges())
+ {
+ child.setDepth(Math.max(depth,child.getDepth()));
+ this.maxDepth = Math.max(this.maxDepth,child.getDepth());
+ }
+
+ // Dive down
+ for (Module child : module.getChildEdges())
+ {
+ bfsCalculateDepth(child,depth);
+ }
+ }
+
+ /**
+ * Using the provided dependencies, build the module graph
+ */
+ public void buildGraph()
+ {
+ // Connect edges
+ for (Module module : modules.values())
+ {
+ for (String parentName : module.getParentNames())
+ {
+ Module parent = get(parentName);
+
+ if (parent == null)
+ {
+ System.err.printf("WARNING: module not found [%s]%n",parentName);
+ }
+ else
+ {
+ module.addParentEdge(parent);
+ parent.addChildEdge(module);
+ }
+ }
+
+ for (String optionalParentName : module.getOptionalParentNames())
+ {
+ Module optional = get(optionalParentName);
+ if (optional == null)
+ {
+ System.err.printf("WARNING: module not found [%s]%n",optionalParentName);
+ }
+ else if (optional.isEnabled())
+ {
+ module.addParentEdge(optional);
+ optional.addChildEdge(module);
+ }
+ }
+ }
+
+ // Verify there is no cyclic references
+ Stack<String> refs = new Stack<>();
+ for (Module module : modules.values())
+ {
+ refs.push(module.getName());
+ assertNoCycle(module,refs);
+ refs.pop();
+ }
+
+ // Calculate depth of all modules for sorting later
+ for (Module module : modules.values())
+ {
+ if (module.getParentEdges().isEmpty())
+ {
+ bfsCalculateDepth(module,0);
+ }
+ }
+ }
+
+ public Integer count()
+ {
+ return modules.size();
+ }
+
+ public void dump()
+ {
+ List<Module> ordered = new ArrayList<>();
+ ordered.addAll(modules.values());
+ Collections.sort(ordered,new Module.NameComparator());
+
+ for (Module module : ordered)
+ {
+ System.out.printf("%nModule: %s%n",module.getName());
+ for (String lib : module.getLibs())
+ {
+ System.out.printf(" LIB: %s%n",lib);
+ }
+ for (String xml : module.getXmls())
+ {
+ System.out.printf(" XML: %s%n",xml);
+ }
+ System.out.printf(" depends: [%s]%n",Main.join(module.getParentNames(),", "));
+ if (StartLog.isDebugEnabled())
+ {
+ System.out.printf(" depth: %d%n",module.getDepth());
+ }
+ for (String source : module.getSources())
+ {
+ System.out.printf(" enabled: %s%n",source);
+ }
+ }
+ }
+
+ public void dumpEnabledTree()
+ {
+ List<Module> ordered = new ArrayList<>();
+ ordered.addAll(modules.values());
+ Collections.sort(ordered,new Module.DepthComparator());
+
+ List<Module> active = resolveEnabled();
+
+ for (Module module : ordered)
+ {
+ if (active.contains(module))
+ {
+ // Show module name
+ String indent = toIndent(module.getDepth());
+ System.out.printf("%s + Module: %s [%s]%n",indent,module.getName(),module.isEnabled()?"enabled":"transitive");
+ }
+ }
+ }
+
+ public void enable(String name, List<String> sources)
+ {
+ Module module = modules.get(name);
+ if (module == null)
+ {
+ System.err.printf("WARNING: Cannot enable requested module [%s]: not a valid module name.%n",name);
+ return;
+ }
+ StartLog.debug("Enabling module: %s (via %s)",name,Main.join(sources,", "));
+ module.setEnabled(true);
+ if (sources != null)
+ {
+ module.addSources(sources);
+ }
+ }
+
+ private void findChildren(Module module, Set<Module> ret)
+ {
+ ret.add(module);
+ for (Module child : module.getChildEdges())
+ {
+ ret.add(child);
+ }
+ }
+
+ private void findParents(Module module, Set<Module> ret)
+ {
+ ret.add(module);
+ for (Module parent : module.getParentEdges())
+ {
+ ret.add(parent);
+ findParents(parent,ret);
+ }
+ }
+
+ public Module get(String name)
+ {
+ return modules.get(name);
+ }
+
+ public int getMaxDepth()
+ {
+ return maxDepth;
+ }
+
+ public Set<Module> getModulesAtDepth(int depth)
+ {
+ Set<Module> ret = new HashSet<>();
+ for (Module module : modules.values())
+ {
+ if (module.getDepth() == depth)
+ {
+ ret.add(module);
+ }
+ }
+ return ret;
+ }
+
+ @Override
+ public Iterator<Module> iterator()
+ {
+ return modules.values().iterator();
+ }
+
+ public List<String> normalizeLibs(List<Module> active)
+ {
+ List<String> libs = new ArrayList<>();
+ for (Module module : active)
+ {
+ for (String lib : module.getLibs())
+ {
+ if (!libs.contains(lib))
+ {
+ libs.add(lib);
+ }
+ }
+ }
+ return libs;
+ }
+
+ public List<String> normalizeXmls(List<Module> active)
+ {
+ List<String> xmls = new ArrayList<>();
+ for (Module module : active)
+ {
+ for (String xml : module.getXmls())
+ {
+ if (!xmls.contains(xml))
+ {
+ xmls.add(xml);
+ }
+ }
+ }
+ return xmls;
+ }
+
+ public void register(Module module)
+ {
+ modules.put(module.getName(),module);
+ }
+
+ public void registerAll(BaseHome basehome) throws IOException
+ {
+ for (File file : basehome.listFiles("modules",new FS.FilenameRegexFilter("^.*\\.mod$")))
+ {
+ register(new Module(file));
+ }
+ }
+
+ public Set<String> resolveChildModulesOf(String moduleName)
+ {
+ Set<Module> ret = new HashSet<>();
+ Module module = get(moduleName);
+ findChildren(module,ret);
+ return asNameSet(ret);
+ }
+
+ /**
+ * Resolve the execution order of the enabled modules, and all dependant modules, based on depth first transitive reduction.
+ *
+ * @return the list of active modules (plus dependant modules), in execution order.
+ */
+ public List<Module> resolveEnabled()
+ {
+ Set<Module> active = new HashSet<Module>();
+
+ for (Module module : modules.values())
+ {
+ if (module.isEnabled())
+ {
+ findParents(module,active);
+ }
+ }
+
+ List<Module> ordered = new ArrayList<>();
+ ordered.addAll(active);
+ Collections.sort(ordered,new Module.DepthComparator());
+ return ordered;
+ }
+
+ public Set<String> resolveParentModulesOf(String moduleName)
+ {
+ Set<Module> ret = new HashSet<>();
+ Module module = get(moduleName);
+ findParents(module,ret);
+ return asNameSet(ret);
+ }
+
+ private String toIndent(int depth)
+ {
+ char indent[] = new char[depth * 2];
+ Arrays.fill(indent,' ');
+ return new String(indent);
+ }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/NaturalSort.java b/jetty-start/src/main/java/org/eclipse/jetty/start/NaturalSort.java
new file mode 100644
index 0000000..f6a7e7a
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/NaturalSort.java
@@ -0,0 +1,56 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.text.CollationKey;
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Natural Language Sorting
+ */
+public class NaturalSort
+{
+ public static class Files implements Comparator<File>
+ {
+ private final Collator collator = Collator.getInstance();
+
+ @Override
+ public int compare(File o1, File o2)
+ {
+ CollationKey key1 = collator.getCollationKey(o1.getAbsolutePath());
+ CollationKey key2 = collator.getCollationKey(o2.getAbsolutePath());
+ return key1.compareTo(key2);
+ }
+ }
+
+ public static class Strings implements Comparator<String>
+ {
+ private final Collator collator = Collator.getInstance();
+
+ @Override
+ public int compare(String o1, String o2)
+ {
+ CollationKey key1 = collator.getCollationKey(o1);
+ CollationKey key2 = collator.getCollationKey(o2);
+ return key1.compareTo(key2);
+ }
+ }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/README.TXT b/jetty-start/src/main/java/org/eclipse/jetty/start/README.TXT
new file mode 100644
index 0000000..fc569bc
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/README.TXT
@@ -0,0 +1,48 @@
+Jetty start
+-----------
+
+The run directory is either the top-level of a distribution
+or jetty-distribution/target/distribution directory when built from
+source.
+
+Jetty start.jar provides a cross platform replacement for startup scripts.
+It makes use of executable JAR that builds the classpath and then executes
+jetty.
+
+To run with the demo:
+
+ java -jar start.jar --enable=demo
+ java -jar start.jar
+
+To run with the default modules:
+
+ java -jar start.jar
+
+The default options may be specified in the start.ini file, or if
+that is not present, they are defined in the start.config file that
+is within the start.jar.
+
+To run with specific configuration file(s)
+
+ java -jar start.jar etc/jetty.xml
+
+To see the available options
+
+ java -jar start.jar --help
+
+To run with JSP support (if available)
+
+ java -jar start.jar --module=jsp
+
+To run with JMX support
+
+ java -jar start.jar --module=jmx
+
+To run with JSP & JMX support
+
+ java -jar start.jar --module=jsp,jmx
+
+Note that JSP requires the jasper jars to be within $JETTY/lib/jsp These
+are currently not distributed with the eclipse release and must be
+obtained from a jetty-hightide release from codehaus.
+
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/README.txt b/jetty-start/src/main/java/org/eclipse/jetty/start/README.txt
deleted file mode 100644
index 3669750..0000000
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/README.txt
+++ /dev/null
@@ -1,47 +0,0 @@
-Jetty start
------------
-
-The run directory is either the top-level of a distribution
-or jetty-distribution/target/distribution directory when built from
-source.
-
-Jetty start.jar provides a cross platform replacement for startup scripts.
-It makes use of executable JAR that builds the classpath and then executes
-jetty.
-
-To run with all the demo options:
-
- java -jar start.jar OPTIONS=All
-
-To run with the default options:
-
- java -jar start.jar
-
-The default options may be specified in the start.ini file, or if
-that is not present, they are defined in the start.config file that
-is within the start.jar.
-
-To run with specific configuration file(s)
-
- java -jar start.jar etc/jetty.xml
-
-To see the available options
-
- java -jar start.jar --help
-
-To run with JSP support (if available)
-
- java -jar start.jar OPTIONS=Server,jsp
-
-To run with JMX support
-
- java -jar start.jar OPTIONS=Server,jmx etc/jetty-jmx.xml etc/jetty.xml
-
-To run with JSP & JMX support
-
- java -jar start.jar OPTIONS=Server,jsp,jmx etc/jetty-jmx.xml etc/jetty.xml
-
-Note that JSP requires the jasper jars to be within $JETTY/lib/jsp These
-are currently not distributed with the eclipse release and must be
-obtained from a jetty-hightide release from codehaus.
-
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java
new file mode 100644
index 0000000..8157e33
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java
@@ -0,0 +1,867 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.eclipse.jetty.start.UsageException.ERR_BAD_ARG;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * The Arguments required to start Jetty.
+ */
+public class StartArgs
+{
+ public static final String CMD_LINE_SOURCE = "<cmd-line>";
+ public static final String VERSION;
+
+ static
+ {
+ String ver = System.getProperty("jetty.version",null);
+
+ if (ver == null)
+ {
+ Package pkg = StartArgs.class.getPackage();
+ if ((pkg != null) && "Eclipse.org - Jetty".equals(pkg.getImplementationVendor()) && (pkg.getImplementationVersion() != null))
+ {
+ ver = pkg.getImplementationVersion();
+ }
+ }
+
+ if (ver == null)
+ {
+ ver = "TEST";
+ }
+
+ VERSION = ver;
+ System.setProperty("jetty.version",VERSION);
+ }
+
+ private static final String SERVER_MAIN = "org.eclipse.jetty.xml.XmlConfiguration";
+
+ private List<String> commandLine = new ArrayList<>();
+ private Set<String> modules = new HashSet<>();
+ private Map<String, List<String>> sources = new HashMap<>();
+ private List<FileArg> files = new ArrayList<>();
+ private Classpath classpath;
+ private List<String> xmlRefs = new ArrayList<>();
+ private List<File> xmls = new ArrayList<>();
+ private Properties properties = new Properties();
+ private Set<String> systemPropertyKeys = new HashSet<>();
+ private List<String> jvmArgs = new ArrayList<>();
+ private List<String> moduleStartdIni = new ArrayList<>();
+ private List<String> moduleStartIni = new ArrayList<>();
+ private Map<String, String> propertySource = new HashMap<>();
+ private String moduleGraphFilename;
+
+ private Modules allModules;
+ // Should the server be run?
+ private boolean run = true;
+ private boolean help = false;
+ private boolean stopCommand = false;
+ private boolean listModules = false;
+ private boolean listClasspath = false;
+ private boolean listConfig = false;
+ private boolean version = false;
+ private boolean dryRun = false;
+
+ private boolean exec = false;
+
+ public StartArgs(String[] commandLineArgs)
+ {
+ commandLine.addAll(Arrays.asList(commandLineArgs));
+ classpath = new Classpath();
+ }
+
+ private void addFile(String uriLocation)
+ {
+ FileArg arg = new FileArg(uriLocation);
+ if (!files.contains(arg))
+ {
+ files.add(arg);
+ }
+ }
+
+ public void addSystemProperty(String key, String value)
+ {
+ this.systemPropertyKeys.add(key);
+ System.setProperty(key,value);
+ }
+
+ private void addUniqueXmlFile(String xmlRef, File xmlfile) throws IOException
+ {
+ if (!FS.canReadFile(xmlfile))
+ {
+ throw new IOException("Cannot read file: " + xmlRef);
+ }
+ xmlfile = xmlfile.getCanonicalFile();
+ if (!xmls.contains(xmlfile))
+ {
+ xmls.add(xmlfile);
+ }
+ }
+
+ public void dumpActiveXmls(BaseHome baseHome)
+ {
+ System.out.println();
+ System.out.println("Jetty Active XMLs:");
+ System.out.println("------------------");
+ if (xmls.isEmpty())
+ {
+ System.out.println(" (no xml files specified)");
+ return;
+ }
+
+ for (File xml : xmls)
+ {
+ System.out.printf(" %s%n",baseHome.toShortForm(xml.getAbsolutePath()));
+ }
+ }
+
+ public void dumpEnvironment()
+ {
+ // Java Details
+ System.out.println();
+ System.out.println("Java Environment:");
+ System.out.println("-----------------");
+ dumpSystemProperty("java.home");
+ dumpSystemProperty("java.vm.vendor");
+ dumpSystemProperty("java.vm.version");
+ dumpSystemProperty("java.vm.name");
+ dumpSystemProperty("java.vm.info");
+ dumpSystemProperty("java.runtime.name");
+ dumpSystemProperty("java.runtime.version");
+ dumpSystemProperty("java.io.tmpdir");
+
+ // Jetty Environment
+ System.out.println();
+ System.out.println("Jetty Environment:");
+ System.out.println("-----------------");
+
+ dumpSystemProperty("jetty.home");
+ dumpSystemProperty("jetty.base");
+ dumpSystemProperty("jetty.version");
+ }
+
+ public void dumpJvmArgs()
+ {
+ System.out.println();
+ System.out.println("JVM Arguments:");
+ System.out.println("--------------");
+ if (jvmArgs.isEmpty())
+ {
+ System.out.println(" (no jvm args specified)");
+ return;
+ }
+
+ for (String jvmArgKey : jvmArgs)
+ {
+ String value = System.getProperty(jvmArgKey);
+ if (value != null)
+ {
+ System.out.printf(" %s = %s%n",jvmArgKey,value);
+ }
+ else
+ {
+ System.out.printf(" %s%n",jvmArgKey);
+ }
+ }
+ }
+
+ public void dumpProperties()
+ {
+ System.out.println();
+ System.out.println("Properties:");
+ System.out.println("-----------");
+
+ if (properties.isEmpty())
+ {
+ System.out.println(" (no properties specified)");
+ return;
+ }
+
+ List<String> sortedKeys = new ArrayList<>();
+ @SuppressWarnings("unchecked")
+ Enumeration<String> keyEnum = (Enumeration<String>)properties.propertyNames();
+ while (keyEnum.hasMoreElements())
+ {
+ sortedKeys.add(keyEnum.nextElement());
+ }
+
+ Collections.sort(sortedKeys);
+
+ for (String key : sortedKeys)
+ {
+ String value = properties.getProperty(key);
+ System.out.printf(" %s = %s%n",key,value);
+ }
+ }
+
+ public void dumpSystemProperties()
+ {
+ System.out.println();
+ System.out.println("System Properties:");
+ System.out.println("------------------");
+
+ if (systemPropertyKeys.isEmpty())
+ {
+ System.out.println(" (no system properties specified)");
+ return;
+ }
+
+ List<String> sortedKeys = new ArrayList<>();
+ sortedKeys.addAll(systemPropertyKeys);
+ Collections.sort(sortedKeys);
+
+ for (String key : sortedKeys)
+ {
+ String value = System.getProperty(key);
+ System.out.printf(" %s = %s%n",key,value);
+ }
+ }
+
+ private void dumpSystemProperty(String key)
+ {
+ System.out.printf(" %s=%s%n",key,System.getProperty(key));
+ }
+
+ /**
+ * Ensure that the System Properties are set (if defined as a System property, or start.config property, or start.ini property)
+ *
+ * @param key
+ * the key to be sure of
+ */
+ private void ensureSystemPropertySet(String key)
+ {
+ if (systemPropertyKeys.contains(key))
+ {
+ return; // done
+ }
+
+ if (properties.containsKey(key))
+ {
+ String val = properties.getProperty(key,null);
+ if (val == null)
+ {
+ return; // no value to set
+ }
+ // setup system property
+ systemPropertyKeys.add(key);
+ System.setProperty(key,val);
+ }
+ }
+
+ /**
+ * Build up the Classpath and XML file references based on enabled Module list.
+ *
+ * @param baseHome
+ * @param activeModules
+ * @throws IOException
+ */
+ public void expandModules(BaseHome baseHome, List<Module> activeModules) throws IOException
+ {
+ for (Module module : activeModules)
+ {
+ // Find and Expand Libraries
+ for (String rawlibref : module.getLibs())
+ {
+ String libref = rawlibref.replace("${jetty.version}",VERSION);
+ libref = FS.separators(libref);
+
+ if (libref.contains("*"))
+ {
+ // Glob Reference
+ int idx = libref.lastIndexOf(File.separatorChar);
+
+ String relativePath = "/";
+ String filenameRef = libref;
+ if (idx >= 0)
+ {
+ relativePath = libref.substring(0,idx);
+ filenameRef = libref.substring(idx + 1);
+ }
+
+ StringBuilder regex = new StringBuilder();
+ regex.append('^');
+ for (char c : filenameRef.toCharArray())
+ {
+ switch (c)
+ {
+ case '*':
+ regex.append(".*");
+ break;
+ case '.':
+ regex.append("\\.");
+ break;
+ default:
+ regex.append(c);
+ }
+ }
+ regex.append('$');
+
+ FileFilter filter = new FS.FilenameRegexFilter(regex.toString());
+
+ for (File libfile : baseHome.listFiles(relativePath,filter))
+ {
+ classpath.addComponent(libfile);
+ }
+ }
+ else
+ {
+ // Straight Reference
+ File libfile = baseHome.getFile(libref);
+ classpath.addComponent(libfile);
+ }
+ }
+
+ // Find and Expand XML files
+ for (String xmlRef : module.getXmls())
+ {
+ // Straight Reference
+ File xmlfile = baseHome.getFile(xmlRef);
+ addUniqueXmlFile(xmlRef,xmlfile);
+ }
+
+ // Register Download operations
+ for (String file : module.getFiles())
+ {
+ StartLog.debug("Adding module specified file: %s",file);
+ addFile(file);
+ }
+ }
+ }
+
+ public Modules getAllModules()
+ {
+ return allModules;
+ }
+
+ public Classpath getClasspath()
+ {
+ return classpath;
+ }
+
+ public List<String> getCommandLine()
+ {
+ return this.commandLine;
+ }
+
+ public List<FileArg> getFiles()
+ {
+ return files;
+ }
+
+ public Set<String> getEnabledModules()
+ {
+ return this.modules;
+ }
+
+ public List<String> getJvmArgs()
+ {
+ return jvmArgs;
+ }
+
+ public CommandLineBuilder getMainArgs(BaseHome baseHome, boolean addJavaInit) throws IOException
+ {
+ CommandLineBuilder cmd = new CommandLineBuilder();
+
+ if (addJavaInit)
+ {
+ cmd.addArg(CommandLineBuilder.findJavaBin());
+
+ for (String x : jvmArgs)
+ {
+ cmd.addArg(x);
+ }
+
+ cmd.addRawArg("-Djetty.home=" + baseHome.getHome());
+ cmd.addRawArg("-Djetty.base=" + baseHome.getBase());
+
+ // System Properties
+ for (String propKey : systemPropertyKeys)
+ {
+ String value = System.getProperty(propKey);
+ cmd.addEqualsArg("-D" + propKey,value);
+ }
+
+ cmd.addArg("-cp");
+ cmd.addRawArg(classpath.toString());
+ cmd.addRawArg(getMainClassname());
+ }
+
+ // Special Stop/Shutdown properties
+ ensureSystemPropertySet("STOP.PORT");
+ ensureSystemPropertySet("STOP.KEY");
+ ensureSystemPropertySet("STOP.WAIT");
+
+ // Check if we need to pass properties as a file
+ if (properties.size() > 0)
+ {
+ File prop_file = File.createTempFile("start",".properties");
+ if (!dryRun)
+ {
+ prop_file.deleteOnExit();
+ }
+ try (FileOutputStream out = new FileOutputStream(prop_file))
+ {
+ properties.store(out,"start.jar properties");
+ }
+ cmd.addArg(prop_file.getAbsolutePath());
+ }
+
+ for (File xml : xmls)
+ {
+ cmd.addRawArg(xml.getAbsolutePath());
+ }
+
+ return cmd;
+ }
+
+ public String getMainClassname()
+ {
+ String mainclass = System.getProperty("jetty.server",SERVER_MAIN);
+ return System.getProperty("main.class",mainclass);
+ }
+
+ public String getModuleGraphFilename()
+ {
+ return moduleGraphFilename;
+ }
+
+ public List<String> getModuleStartdIni()
+ {
+ return moduleStartdIni;
+ }
+
+ public List<String> getModuleStartIni()
+ {
+ return moduleStartIni;
+ }
+
+ public Properties getProperties()
+ {
+ return properties;
+ }
+
+ public List<String> getSources(String module)
+ {
+ return sources.get(module);
+ }
+
+ private String getValue(String arg)
+ {
+ int idx = arg.indexOf('=');
+ if (idx == (-1))
+ {
+ throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg);
+ }
+ String value = arg.substring(idx + 1).trim();
+ if (value.length() <= 0)
+ {
+ throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg);
+ }
+ return value;
+ }
+
+ private List<String> getValues(String arg)
+ {
+ String v = getValue(arg);
+ ArrayList<String> l = new ArrayList<>();
+ for (String s : v.split(","))
+ {
+ if (s != null)
+ {
+ s = s.trim();
+ if (s.length() > 0)
+ {
+ l.add(s);
+ }
+ }
+ }
+ return l;
+ }
+
+ public List<File> getXmlFiles()
+ {
+ return xmls;
+ }
+
+ public boolean hasJvmArgs()
+ {
+ return jvmArgs.size() > 0;
+ }
+
+ public boolean hasSystemProperties()
+ {
+ for (String key : systemPropertyKeys)
+ {
+ // ignored keys
+ if ("jetty.home".equals(key) || "jetty.base".equals(key))
+ {
+ // skip
+ continue;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isDryRun()
+ {
+ return dryRun;
+ }
+
+ public boolean isExec()
+ {
+ return exec;
+ }
+
+ public boolean isHelp()
+ {
+ return help;
+ }
+
+ public boolean isListClasspath()
+ {
+ return listClasspath;
+ }
+
+ public boolean isListConfig()
+ {
+ return listConfig;
+ }
+
+ public boolean isListModules()
+ {
+ return listModules;
+ }
+
+ public boolean isRun()
+ {
+ return run;
+ }
+
+ public boolean isStopCommand()
+ {
+ return stopCommand;
+ }
+
+ public boolean isVersion()
+ {
+ return version;
+ }
+
+ public void parse(BaseHome baseHome, TextFile file)
+ {
+ String source;
+ try
+ {
+ source = baseHome.toShortForm(file.getFile());
+ }
+ catch (Exception e)
+ {
+ throw new UsageException(ERR_BAD_ARG,"Bad file: %s",file);
+ }
+ for (String line : file)
+ {
+ parse(line,source);
+ }
+ }
+
+ public void parse(final String rawarg, String source)
+ {
+ if (rawarg == null)
+ {
+ return;
+ }
+
+ final String arg = rawarg.trim();
+
+ if (arg.length() <= 0)
+ {
+ return;
+ }
+
+ if (arg.startsWith("#"))
+ {
+ return;
+ }
+
+ if ("--help".equals(arg) || "-?".equals(arg))
+ {
+ if (!CMD_LINE_SOURCE.equals(source))
+ {
+ throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,source);
+ }
+
+ help = true;
+ run = false;
+ return;
+ }
+
+ if ("--debug".equals(arg))
+ {
+ // valid, but handled in StartLog instead
+ return;
+ }
+
+ if ("--stop".equals(arg))
+ {
+ if (!CMD_LINE_SOURCE.equals(source))
+ {
+ throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,source);
+ }
+ stopCommand = true;
+ run = false;
+ return;
+ }
+
+ if (arg.startsWith("--download="))
+ {
+ addFile(getValue(arg));
+ return;
+ }
+
+ if ("--list-classpath".equals(arg) || "--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
+ {
+ listClasspath = true;
+ run = false;
+ return;
+ }
+
+ if ("--list-config".equals(arg))
+ {
+ listConfig = true;
+ run = false;
+ return;
+ }
+
+ if ("--dry-run".equals(arg) || "--exec-print".equals(arg))
+ {
+ if (!CMD_LINE_SOURCE.equals(source))
+ {
+ throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,source);
+ }
+ dryRun = true;
+ run = false;
+ return;
+ }
+
+ if ("--exec".equals(arg))
+ {
+ exec = true;
+ return;
+ }
+
+ // Arbitrary Libraries
+
+ if (arg.startsWith("--lib="))
+ {
+ String cp = getValue(arg);
+ classpath.addClasspath(cp);
+ return;
+ }
+
+ // Module Management
+ if ("--list-modules".equals(arg))
+ {
+ listModules = true;
+ run = false;
+ return;
+ }
+
+ if (arg.startsWith("--add-to-startd"))
+ {
+ if (!CMD_LINE_SOURCE.equals(source))
+ {
+ throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,source);
+ }
+ moduleStartdIni.addAll(getValues(arg));
+ run = false;
+ return;
+ }
+
+ if (arg.startsWith("--add-to-start"))
+ {
+ if (!CMD_LINE_SOURCE.equals(source))
+ {
+ throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,source);
+ }
+ moduleStartIni.addAll(getValues(arg));
+ run = false;
+ return;
+ }
+
+ if (arg.startsWith("--module="))
+ {
+ for (String moduleName : getValues(arg))
+ {
+ modules.add(moduleName);
+ List<String> list = sources.get(moduleName);
+ if (list == null)
+ {
+ list = new ArrayList<String>();
+ sources.put(moduleName,list);
+ }
+ list.add(source);
+ }
+ return;
+ }
+
+ if (arg.startsWith("--write-module-graph="))
+ {
+ this.moduleGraphFilename = getValue(arg);
+ run = false;
+ return;
+ }
+
+ // Start property (syntax similar to System property)
+ if (arg.startsWith("-D"))
+ {
+ String[] assign = arg.substring(2).split("=",2);
+ systemPropertyKeys.add(assign[0]);
+ switch (assign.length)
+ {
+ case 2:
+ System.setProperty(assign[0],assign[1]);
+ break;
+ case 1:
+ System.setProperty(assign[0],"");
+ break;
+ default:
+ break;
+ }
+ return;
+ }
+
+ // Anything else with a "-" is considered a JVM argument
+ if (arg.startsWith("-"))
+ {
+ // Only add non-duplicates
+ if (!jvmArgs.contains(arg))
+ {
+ jvmArgs.add(arg);
+ }
+ return;
+ }
+
+ // Is this a raw property declaration?
+ int idx = arg.indexOf('=');
+ if (idx >= 0)
+ {
+ String key = arg.substring(0,idx);
+ String value = arg.substring(idx + 1);
+
+ if (source != CMD_LINE_SOURCE)
+ {
+ if (propertySource.containsKey(key))
+ {
+ throw new UsageException(ERR_BAD_ARG,"Property %s in %s already set in %s",key,source,propertySource.get(key));
+ }
+ propertySource.put(key,source);
+ }
+
+ if ("OPTION".equals(key) || "OPTIONS".equals(key))
+ {
+ StringBuilder warn = new StringBuilder();
+ warn.append("The behavior of the argument ");
+ warn.append(arg).append(" (seen in ").append(source);
+ warn.append(") has changed, and is now considered a normal property. ");
+ warn.append(key).append(" no longer controls what libraries are on your classpath,");
+ warn.append(" use --module instead. See --help for details.");
+ StartLog.warn(warn.toString());
+ }
+
+ properties.setProperty(key,value);
+ return;
+ }
+
+ // Is this an xml file?
+ if (FS.isXml(arg))
+ {
+ // only add non-duplicates
+ if (!xmlRefs.contains(arg))
+ {
+ xmlRefs.add(arg);
+ }
+ return;
+ }
+
+ // Anything else is unrecognized
+ throw new UsageException(ERR_BAD_ARG,"Unrecognized argument: \"%s\" in %s",arg,source);
+ }
+
+ public void parseCommandLine()
+ {
+ for (String line : commandLine)
+ {
+ parse(line,StartArgs.CMD_LINE_SOURCE);
+ }
+ }
+
+ public void resolveExtraXmls(BaseHome baseHome) throws IOException
+ {
+ // Find and Expand XML files
+ for (String xmlRef : xmlRefs)
+ {
+ // Straight Reference
+ File xmlfile = baseHome.getFile(xmlRef);
+ if (!xmlfile.exists())
+ {
+ xmlfile = baseHome.getFile("etc/" + xmlRef);
+ }
+ addUniqueXmlFile(xmlRef,xmlfile);
+ }
+ }
+
+ public void setAllModules(Modules allModules)
+ {
+ this.allModules = allModules;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("StartArgs [commandLine=");
+ builder.append(commandLine);
+ builder.append(", enabledModules=");
+ builder.append(modules);
+ builder.append(", xmlRefs=");
+ builder.append(xmlRefs);
+ builder.append(", properties=");
+ builder.append(properties);
+ builder.append(", jvmArgs=");
+ builder.append(jvmArgs);
+ builder.append("]");
+ return builder.toString();
+ }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java
new file mode 100644
index 0000000..ed268ea
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java
@@ -0,0 +1,52 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Simple Start .INI handler
+ */
+public class StartIni extends TextFile
+{
+ public StartIni(File file) throws FileNotFoundException, IOException
+ {
+ super(file);
+ }
+
+ @Override
+ public void addUniqueLine(String line)
+ {
+ if (line.startsWith("--module="))
+ {
+ int idx = line.indexOf('=');
+ String value = line.substring(idx + 1);
+ for (String part : value.split(","))
+ {
+ super.addUniqueLine("--module=" + part);
+ }
+ }
+ else
+ {
+ super.addUniqueLine(line);
+ }
+ }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartLog.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartLog.java
new file mode 100644
index 0000000..e949deb
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartLog.java
@@ -0,0 +1,149 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Centralized Place for logging.
+ * <p>
+ * Because startup cannot rely on Jetty's Logging, an alternative logging is established.
+ * <p>
+ * Optional behavior is to create a ${jetty.base}/logs/start.log with whatever output the startup process produces.
+ */
+public class StartLog
+{
+ private final static StartLog INSTANCE = new StartLog();
+
+ public static void debug(String format, Object... args)
+ {
+ if (INSTANCE.debug)
+ {
+ System.out.printf(format + "%n",args);
+ }
+ }
+
+ public static void debug(Throwable t)
+ {
+ if (INSTANCE.debug)
+ {
+ t.printStackTrace(System.out);
+ }
+ }
+
+ public static StartLog getInstance()
+ {
+ return INSTANCE;
+ }
+
+ public static void info(String format, Object... args)
+ {
+ System.err.printf("WARNING: " + format + "%n",args);
+ }
+
+ public static void warn(String format, Object... args)
+ {
+ System.err.printf("WARNING: " + format + "%n",args);
+ }
+
+ public static void warn(Throwable t)
+ {
+ t.printStackTrace(System.err);
+ }
+
+ public static boolean isDebugEnabled()
+ {
+ return INSTANCE.debug;
+ }
+
+ private boolean debug = false;
+
+ public void initialize(BaseHome baseHome, StartArgs args) throws IOException
+ {
+ // Debug with boolean
+ Pattern debugBoolPat = Pattern.compile("(-D)?debug=(.*)");
+ // Log file name
+ Pattern logFilePat = Pattern.compile("(-D)?start-log-file=(.*)");
+
+ // TODO: support backward compatible --daemon argument ??
+
+ Matcher matcher;
+ for (String arg : args.getCommandLine())
+ {
+ if ("--debug".equals(arg))
+ {
+ debug = true;
+ continue;
+ }
+
+ matcher = debugBoolPat.matcher(arg);
+ if (matcher.matches())
+ {
+ debug = Boolean.parseBoolean(matcher.group(2));
+ continue;
+ }
+
+ matcher = logFilePat.matcher(arg);
+ if (matcher.matches())
+ {
+ String filename = matcher.group(2);
+ File logfile = baseHome.getBaseFile(filename);
+ initLogFile(logfile);
+ }
+ }
+ }
+
+ public void initLogFile(File logfile) throws IOException
+ {
+ if (logfile != null)
+ {
+ File logDir = logfile.getParentFile();
+ if (!logDir.exists() || !logDir.canWrite())
+ {
+ String err = String.format("Cannot write %s to directory %s [directory doesn't exist or is read-only]",logfile.getName(),
+ logDir.getAbsolutePath());
+ throw new UsageException(UsageException.ERR_LOGGING,new IOException(err));
+ }
+
+ File startLog = logfile;
+
+ if (!startLog.exists() && !startLog.createNewFile())
+ {
+ // Output about error is lost in majority of cases.
+ throw new UsageException(UsageException.ERR_LOGGING,new IOException("Unable to create: " + startLog.getAbsolutePath()));
+ }
+
+ if (!startLog.canWrite())
+ {
+ // Output about error is lost in majority of cases.
+ throw new UsageException(UsageException.ERR_LOGGING,new IOException("Unable to write to: " + startLog.getAbsolutePath()));
+ }
+ PrintStream logger = new PrintStream(new FileOutputStream(startLog,false));
+ System.setOut(logger);
+ System.setErr(logger);
+ System.out.println("Establishing " + logfile + " on " + new Date());
+ }
+ }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/TextFile.java b/jetty-start/src/main/java/org/eclipse/jetty/start/TextFile.java
new file mode 100644
index 0000000..4da2951
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/TextFile.java
@@ -0,0 +1,129 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.regex.Pattern;
+
+/**
+ * Simple common abstraction for Text files, that consist of a series of lines.
+ * <p>
+ * Ignoring lines that are empty, deemed to be comments, or are duplicates of prior lines.
+ */
+public class TextFile implements Iterable<String>
+{
+ private final File file;
+ private final List<String> lines = new ArrayList<>();
+
+ public TextFile(File file) throws FileNotFoundException, IOException
+ {
+ this.file = file;
+ init();
+
+ if (!FS.canReadFile(file))
+ {
+ StartLog.debug("Skipping read of missing file: %s",file.getAbsolutePath());
+ return;
+ }
+
+ try (FileReader reader = new FileReader(file))
+ {
+ try (BufferedReader buf = new BufferedReader(reader))
+ {
+ String line;
+ while ((line = buf.readLine()) != null)
+ {
+ if (line.length() == 0)
+ {
+ continue;
+ }
+
+ if (line.charAt(0) == '#')
+ {
+ continue;
+ }
+
+ // TODO - bad form calling derived method from base class constructor
+ process(line.trim());
+ }
+ }
+ }
+ }
+
+ public void addUniqueLine(String line)
+ {
+ if (lines.contains(line))
+ {
+ // skip
+ return;
+ }
+ lines.add(line);
+ }
+
+ public File getFile()
+ {
+ return file;
+ }
+
+ public List<String> getLineMatches(Pattern pattern)
+ {
+ List<String> ret = new ArrayList<>();
+ for (String line : lines)
+ {
+ if (pattern.matcher(line).matches())
+ {
+ ret.add(line);
+ }
+ }
+ return ret;
+ }
+
+ public List<String> getLines()
+ {
+ return lines;
+ }
+
+ public void init()
+ {
+ }
+
+ @Override
+ public Iterator<String> iterator()
+ {
+ return lines.iterator();
+ }
+
+ public ListIterator<String> listIterator()
+ {
+ return lines.listIterator();
+ }
+
+ public void process(String line)
+ {
+ addUniqueLine(line);
+ }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/UsageException.java b/jetty-start/src/main/java/org/eclipse/jetty/start/UsageException.java
new file mode 100644
index 0000000..08ba684
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/UsageException.java
@@ -0,0 +1,50 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+/**
+ * A Usage Error has occured. Print the usage and exit with the appropriate exit code.
+ */
+@SuppressWarnings("serial")
+public class UsageException extends RuntimeException
+{
+ public static final int ERR_LOGGING = -1;
+ public static final int ERR_INVOKE_MAIN = -2;
+ public static final int ERR_NOT_STOPPED = -4;
+ public static final int ERR_UNKNOWN = -5;
+ public static final int ERR_BAD_ARG = -6;
+ private int exitCode;
+
+ public UsageException(int exitCode, String format, Object... objs)
+ {
+ super(String.format(format,objs));
+ this.exitCode = exitCode;
+ }
+
+ public UsageException(int exitCode, Throwable cause)
+ {
+ super(cause);
+ this.exitCode = exitCode;
+ }
+
+ public int getExitCode()
+ {
+ return exitCode;
+ }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Version.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Version.java
index 49d823c..c49de4a 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Version.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Version.java
@@ -19,30 +19,79 @@
package org.eclipse.jetty.start;
/**
- * Utility class for parsing and comparing version strings.
- * JDK 1.1 compatible.
+ * Utility class for parsing and comparing version strings. JDK 1.1 compatible.
*
*/
-
-public class Version {
-
+
+public class Version
+{
+
int _version = 0;
int _revision = 0;
int _subrevision = 0;
String _suffix = "";
-
- public Version() {
+
+ public Version()
+ {
}
-
- public Version(String version_string) {
+
+ public Version(String version_string)
+ {
parse(version_string);
}
-
+
+ // java.lang.Comparable is Java 1.2! Cannot use it
/**
- * parses version string in the form version[.revision[.subrevision[extension]]]
- * into this instance.
+ * Compares with other version. Does not take extension into account, as there is no reliable way to order them.
+ *
+ * @return -1 if this is older version that other, 0 if its same version, 1 if it's newer version than other
*/
- public void parse(String version_string) {
+ public int compare(Version other)
+ {
+ if (other == null)
+ {
+ throw new NullPointerException("other version is null");
+ }
+ if (this._version < other._version)
+ {
+ return -1;
+ }
+ if (this._version > other._version)
+ {
+ return 1;
+ }
+ if (this._revision < other._revision)
+ {
+ return -1;
+ }
+ if (this._revision > other._revision)
+ {
+ return 1;
+ }
+ if (this._subrevision < other._subrevision)
+ {
+ return -1;
+ }
+ if (this._subrevision > other._subrevision)
+ {
+ return 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Check whether this verion is in range of versions specified
+ */
+ public boolean isInRange(Version low, Version high)
+ {
+ return ((compare(low) >= 0) && (compare(high) <= 0));
+ }
+
+ /**
+ * parses version string in the form version[.revision[.subrevision[extension]]] into this instance.
+ */
+ public void parse(String version_string)
+ {
_version = 0;
_revision = 0;
_subrevision = 0;
@@ -50,33 +99,41 @@
int pos = 0;
int startpos = 0;
int endpos = version_string.length();
- while ( (pos < endpos) && Character.isDigit(version_string.charAt(pos))) {
+ while ((pos < endpos) && Character.isDigit(version_string.charAt(pos)))
+ {
pos++;
}
_version = Integer.parseInt(version_string.substring(startpos,pos));
- if ((pos < endpos) && version_string.charAt(pos)=='.') {
+ if ((pos < endpos) && (version_string.charAt(pos) == '.'))
+ {
startpos = ++pos;
- while ( (pos < endpos) && Character.isDigit(version_string.charAt(pos))) {
+ while ((pos < endpos) && Character.isDigit(version_string.charAt(pos)))
+ {
pos++;
}
_revision = Integer.parseInt(version_string.substring(startpos,pos));
}
- if ((pos < endpos) && version_string.charAt(pos)=='.') {
+ if ((pos < endpos) && (version_string.charAt(pos) == '.'))
+ {
startpos = ++pos;
- while ( (pos < endpos) && Character.isDigit(version_string.charAt(pos))) {
+ while ((pos < endpos) && Character.isDigit(version_string.charAt(pos)))
+ {
pos++;
}
_subrevision = Integer.parseInt(version_string.substring(startpos,pos));
}
- if (pos < endpos) {
+ if (pos < endpos)
+ {
_suffix = version_string.substring(pos);
}
}
-
+
/**
* @return string representation of this version
*/
- public String toString() {
+ @Override
+ public String toString()
+ {
StringBuffer sb = new StringBuffer(10);
sb.append(_version);
sb.append('.');
@@ -86,30 +143,4 @@
sb.append(_suffix);
return sb.toString();
}
-
- // java.lang.Comparable is Java 1.2! Cannot use it
- /**
- * Compares with other version. Does not take extension into account,
- * as there is no reliable way to order them.
- * @return -1 if this is older version that other,
- * 0 if its same version,
- * 1 if it's newer version than other
- */
- public int compare(Version other) {
- if (other == null) throw new NullPointerException("other version is null");
- if (this._version < other._version) return -1;
- if (this._version > other._version) return 1;
- if (this._revision < other._revision) return -1;
- if (this._revision > other._revision) return 1;
- if (this._subrevision < other._subrevision) return -1;
- if (this._subrevision > other._subrevision) return 1;
- return 0;
- }
-
- /**
- * Check whether this verion is in range of versions specified
- */
- public boolean isInRange(Version low, Version high) {
- return (compare(low)>=0 && compare(high)<=0);
- }
}
diff --git a/jetty-start/src/main/resources/org/eclipse/jetty/start/start.config b/jetty-start/src/main/resources/org/eclipse/jetty/start/start.config
deleted file mode 100644
index 4dde6bf..0000000
--- a/jetty-start/src/main/resources/org/eclipse/jetty/start/start.config
+++ /dev/null
@@ -1,166 +0,0 @@
-# This file controls what file are to be put on classpath or command line.
-#
-# Format is as follows:
-#
-# Each line contains entry in the format:
-#
-# SUBJECT [ [!] CONDITION [AND|OR] ]*
-#
-# where SUBJECT:
-# ends with ".class" is the Main class to run.
-# ends with ".xml" is a configuration file for the command line
-# ends with "/" is a directory from which to add all jar and zip files.
-# ends with "/*" is a directory from which to add all unconsidered jar and zip files.
-# ends with "/**" is a directory from which to recursively add all unconsidered jar and zip files.
-# Containing = are used to assign system properties.
-# Containing ~= are used to assign start properties.
-# Containing /= are used to assign a canonical path.
-# all other subjects are treated as files to be added to the classpath.
-#
-# ${name} is expanded to a start property
-# $(name) is expanded to either a start property or a system property.
-# The start property ${version} is defined as the version of the start.jar
-#
-# Files starting with "/" are considered absolute, all others are relative to
-# the home directory.
-#
-# CONDITION is one of:
-# always
-# never
-# available classname # true if class on classpath
-# property name # true if set as start property
-# system name # true if set as system property
-# exists file # true if file/dir exists
-# java OPERATOR version # java version compared to literal
-# nargs OPERATOR number # number of command line args compared to literal
-# OPERATOR := one of "<",">","<=",">=","==","!="
-#
-# CONTITIONS can be combined with AND OR or !, with AND being the assume
-# operator for a list of CONDITIONS.
-#
-# Classpath operations are evaluated on the fly, so once a class or jar is
-# added to the classpath, subsequent available conditions will see that class.
-#
-# The configuration file may be divided into sections with option names like:
-# [ssl,default]
-#
-# Clauses after a section header will only be included if they match one of the tags in the
-# options property. By default options are set to "default,*" or the OPTIONS property may
-# be used to pass in a list of tags, eg. :
-#
-# java -jar start.jar OPTIONS=jetty,jsp,ssl
-#
-# The tag '*' is always appended to the options, so any section with the * tag is always
-# applied.
-#
-
-# add a property defined classpath
-${path}.path property path
-
-# add a property defined library directory
-${lib}/** exists ${lib}
-
-# Try different settings of jetty.home until the start.jar is found.
-jetty.home=. ! exists $(jetty.home)/start.jar
-jetty.home=.. ! exists $(jetty.home)/start.jar
-jetty.home=jetty-distribution/src/main/resources ! exists $(jetty.home)/start.jar
-jetty.home=../jetty-distribution/src/main/resources ! exists $(jetty.home)/start.jar
-jetty.home=. ! exists $(jetty.home)/start.jar
-jetty.home/=$(jetty.home) exists $(jetty.home)/start.jar
-
-# The main class to run
-org.eclipse.jetty.xml.XmlConfiguration.class
-${start.class}.class property start.class
-
-# The default configuration files
-$(jetty.home)/etc/jetty.xml nargs == 0
-./jetty-server/src/main/config/etc/jetty.xml nargs == 0 AND ! exists $(jetty.home)/etc/jetty.xml
-
-# Default OPTIONS if not specified on the command line
-OPTIONS~=default,* ! property OPTIONS
-
-# Add a resources directory if it is there
-[All,resources,default]
-$(jetty.home)/resources/
-
-# Add jetty modules
-[*]
-$(jetty.home)/lib/jetty-util-$(version).jar ! available org.eclipse.jetty.util.StringUtil
-$(jetty.home)/lib/jetty-io-$(version).jar ! available org.eclipse.jetty.io.Buffer
-
-[Server,All,xml,default]
-$(jetty.home)/lib/jetty-xml-$(version).jar ! available org.eclipse.jetty.xml.XmlParser
-
-[Server,All,server,default]
-$(jetty.home)/lib/servlet-api-3.0.jar ! available javax.servlet.ServletContext
-$(jetty.home)/lib/jetty-http-$(version).jar ! available org.eclipse.jetty.http.HttpParser
-$(jetty.home)/lib/jetty-continuation-$(version).jar ! available org.eclipse.jetty.continuation.Continuation
-$(jetty.home)/lib/jetty-server-$(version).jar ! available org.eclipse.jetty.server.Server
-
-[Server,All,security,default]
-$(jetty.home)/lib/jetty-security-$(version).jar ! available org.eclipse.jetty.security.LoginService
-
-[Server,All,servlet,default]
-$(jetty.home)/lib/servlet-api-3.0.jar ! available javax.servlet.ServletContext
-$(jetty.home)/lib/jetty-servlet-$(version).jar ! available org.eclipse.jetty.servlet.ServletHandler
-
-[servlets]
-$(jetty.home)/lib/jetty-servlets-$(version).jar ! available org.eclipse.jetty.servlets.MultiPartFilter
-
-[Server,All,webapp,default]
-$(jetty.home)/lib/jetty-webapp-$(version).jar ! available org.eclipse.jetty.webapp.WebAppContext
-
-[Server,All,deploy,default]
-$(jetty.home)/lib/jetty-deploy-$(version).jar ! available org.eclipse.jetty.deploy.ContextDeployer
-
-[All,rewrite]
-$(jetty.home)/lib/jetty-rewrite-$(version).jar ! available org.eclipse.jetty.rewrite.handler.RewriteHandler
-
-[All,jmx]
-$(jetty.home)/lib/jetty-jmx-$(version).jar ! available org.eclipse.jetty.jmx.MBeanContainer
-
-[All,ajp]
-$(jetty.home)/lib/jetty-ajp-$(version).jar ! available org.eclipse.jetty.ajp.Ajp13Connection
-
-[All,plus,jndi]
-$(jetty.home)/lib/jetty-jndi-${version}.jar ! available org.eclipse.jetty.jndi.ContextFactory
-$(jetty.home)/lib/jetty-plus-${version}.jar ! available org.eclipse.jetty.plus.jndi.NamingEntry
-$(jetty.home)/lib/jndi/** exists $(jetty.home)/lib/jndi
-
-[All,jaas]
-$(jetty.home)/lib/jetty-jaas-${version}.jar ! available org.eclipse.jetty.jaas.JAASLoginService
-
-[All,annotations]
-$(jetty.home)/lib/jetty-annotations-$(version).jar ! available org.eclipse.jetty.annotations.AnnotationParser
-$(jetty.home)/lib/annotations/** exists $(jetty.home)/lib/annotations
-
-[All,setuid]
-$(jetty.home)/lib/jetty-setuid-$(version).jar ! available org.eclipse.jetty.setuid.SetUID
-$(jetty.home)/lib/setuid/**
-
-[All,policy]
-$(jetty.home)/lib/jetty-policy-$(version).jar ! available org.eclipse.jetty.policy.JettyPolicy
-
-[All,client]
-$(jetty.home)/lib/jetty-http-$(version).jar ! available org.eclipse.jetty.http.HttpParser
-$(jetty.home)/lib/jetty-client-$(version).jar ! available org.eclipse.jetty.client.HttpClient
-
-[All,proxy]
-$(jetty.home)/lib/jetty-proxy-$(version).jar ! available org.eclipse.jetty.proxy.ConnectHandler
-$(jetty.home)/lib/jetty-http-$(version).jar ! available org.eclipse.jetty.http.HttpParser
-$(jetty.home)/lib/jetty-client-$(version).jar ! available org.eclipse.jetty.client.HttpClient
-
-[All,websocket]
-$(jetty.home)/lib/websocket/**
-
-[All,overlay,overlays]
-$(jetty.home)/lib/jetty-overlay-deployer-$(version).jar ! available org.eclipse.jetty.overlay.OverlayedAppProvider
-
-
-# Add ext if it exists
-[Server,All,default,ext]
-$(jetty.home)/lib/ext/**
-
-# Add all other sub-directories in /lib/ as options in a dynamic way
-[All,=$(jetty.home)/lib/**]
-
diff --git a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
index e11ddd1..d19a150 100644
--- a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
+++ b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
@@ -2,69 +2,149 @@
The start.jar builds a classpath and executes a main java class with
a classloader built from that classpath. By default the start.jar
- mechanism is configured to start the jetty server, but it can be
+ mechanism is configured to start the jetty server, but it can be
configured to start any java main class.
Command Line Options:
+---------------------
+
--help This help / usage information.
-
+
--version Print the version information for Jetty and
dependent jars, then exit.
-
- --list-options List the details of each classpath OPTION
-
- --list-config List the start.config file.
-
- --exec-print Same as --dry-run
+
+ --list-classpath Print the classpath information that will be used to start
+ Jetty
+
+ --list-config List the resolved configuration that will be used to
+ start Jetty.
+ Output includes:
+ o Java Environment
+ o Jetty Environment
+ o JVM Arguments
+ o Properties
+ o Server Classpath
+ o Server XML Configuration
--dry-run Print the command line that the start.jar generates,
then exit. This may be used to generate command lines
when the start.ini includes -X or -D arguments.
-
- --exec Run the generated command line (see --dry-run) in
+
+ --exec Run the generated command line (see --dry-run) in
a sub process. This can be used when start.ini
contains -X or -D arguments, but creates an extra
JVM instance.
-
+
+
+Debug and Start Logging:
+------------------------
+
+ --debug Enable debug output of the startup procedure.
+ Note: this does not setup debug for Jetty itself.
+ If you want debug for Jetty, configure your logging.
+ http://www.eclipse.org/jetty/documentation/
+
+ --start-log-file=<filename>
+ A filename, relative to ${jetty.base}, where all startup
+ output will be sent. This is useful for capturing startup
+ issues where the jetty specific logger has not yet kicked
+ in due to startup configuration errors.
+
+
+Module Management:
+------------------
+
+ --list-modules List all modules defined by the system.
+ Looking for module files in ${jetty.base}/modules/*.mod and
+ then ${jetty.home}/modules/*.mod
+ Will also list enabled state based on information
+ present on ..
+ o The command line
+ o The ${jetty.base}/start.ini
+ o The ${jetty.base}/start.d/*.ini files
+
+ --module=<modulename>(,<modulename>)*
+ Temporarily enable a module from the command line.
+ Note: this can also be used in the ${jetty.base}/start.ini
+ or ${jetty.base}/start.d/*.ini files.
+
+ --add-to-start=<modulename>(,<modulename>)*
+ Enable a module by appending lines to the
+ ${jetty.base}/start.ini file.
+ Lines that are added come from the ini template that
+ the module itself maintains.
+ Transitive module dependencies are followed and all
+ modules that the specified module depends on are also
+ enabled in the ${jetty.base}/start.ini using the same
+ techniques.
+ Note: not all modules have ini templates.
+
+ --add-to-startd=<modulename>(,<modulename>)*
+ Enable a module via creation of an ini file in the
+ ${jetty.base}/start.d/ directory.
+ Uses ini template that the module itself maintains.
+ Transitive module dependencies are followed and all
+ modules that the specified module depends on are also
+ enabled via their own ini files in the same directory.
+ Note: not all modules have ini templates.
+
+ --write-module-graph=<filename>
+ Create a graphviz *.dot file of the module graph as it
+ exists for the active ${jetty.base}.
+ See http://graphviz.org/ for details on how to post-process
+ this file into the output best suited for your needs.
+
+
+Startup / Shutdown Command Line:
+--------------------------------
+
--stop Send a stop signal to the running Jetty instance.
The server must have been started with a STOP.PORT=<port>
- property set and the stop command must have the same property.
-
- --daemon Start in daemon mode with stderr and stdout
- redirected to ${jetty.logs}/start.log
-
- --config=<file> Specify an alternate start.config file.
- The default is the start.config file inside
- the start.jar. The default can also be specified
- with the START system property.
-
- --ini=<file> Load command line arguments from a file. If
- no --ini options are specified, then the
- start.ini file will be read if it exists in
- jetty.home. If specified jetty.home/start.ini
- and additional .ini files in jetty.home/start.d/
- will NOT be read. A --ini option with no file indicates that
- start.ini should not be read.
-
- --download=<http-uri>:location
- If the file does not exist at the given location, then
- download it from the given http URI
+ property set and the stop command must have the same property.
+
+Properties:
+
+ STOP.PORT=[number]
+ The port to use to stop the running Jetty server.
+ Required along with STOP.KEY if you want to use the --stop option above.
+
+ STOP.KEY=[alphanumeric]
+ The passphrase defined to stop the server.
+ Requried along with STOP.PORT if you want to use the --stop option above.
+
+ STOP.WAIT=[number]
+ The time (in seconds) to wait for confirmation that the running
+ Jetty server has stopped. If not specified, the stopper will wait
+ indefinitely. Use in conjunction with the --stop option.
+
+Advanced Commands:
+------------------
+
+ --download=<http-uri>:<location>
+ Advanced usage, If the file does not exist at the given
+ location, download it from the given http URI.
+ Note: location is always relative to ${jetty.base}
+
+ --lib=<classpath>
+ Add arbitrary classpath entries to the the server classpath.
System Properties:
+------------------
+
These are set with a command line like "java -Dname=value ..." and are
accessible via the java.lang.System#getProperty(String) API.
Some key system properties are:
-
- org.eclipse.jetty.util.log.class=[class]
+
+ org.eclipse.jetty.util.log.class=[class]
A Low Level Jetty Logger Implementation to use
(default: org.eclipse.jetty.util.log.Slf4jLog)
-
+
[name|hierarchy].LEVEL=[loglevel]
Change loglevel for the stderr and javautil Loggers. Slf4j
and other loggers must be separately configured for debug.
For example: Dorg.eclipse.jetty.LEVEL=DEBUG
(default: INFO)
-
+
org.eclipse.jetty.util.log.IGNORED=[boolean]
Ignored exceptions are logged, independent of DEBUG settings
(default: false)
@@ -72,69 +152,31 @@
org.eclipse.jetty.util.log.SOURCE=[boolean]
The source location of logs is logged in the stderr Logger.
(default: false)
-
+
com.sun.management.jmxremote
Enable remote JMX management in Sun JVMS.
-
-
+
+
Properties:
+-----------
+
These are set with a command line like "java -jar start.jar name=value"
- and only affect the start mechanism. Some of these are defined in the
+ and only affect the start mechanism. Some of these are defined in the
default start.config and will not be available if another configuration
file is used. NOTE: Not all properties are listed here:
- path=[directory]
- An additional class path element to add to the started class path. Typically
- this is used to add directories of classes and/or resources
-
- lib=[directory]
- An additional library directory to add to the started class path. This must
- be a (deep) directory of jars
-
- STOP.PORT=[number]
- The port to use to stop the running Jetty server.
- Required along with STOP.KEY if you want to use the --stop option above.
-
- STOP.KEY=[alphanumeric]
- The passphrase defined to stop the server.
- Requried along with STOP.PORT if you want to use the --stop option above.
+ jetty.home=[directory]
+ Set the home directory of the jetty distribution.
- STOP.WAIT=[number]
- The time (in seconds) to wait for confirmation that the running Jetty server
- has stopped. If not specified, the stopper will wait indefinitely. Use in
- conjunction with the --stop option.
-
- DEBUG=true
- Enable debug on the start mechanism and sets the
- org.eclipse.jetty.util.log.stderr.DEBUG system property to true.
- (default: false)
-
- OPTIONS=[option,option,...]
- Enable classpath OPTIONS. Each options represents one or more jars
- to be added to the classpath. The options are defined in
- the start.config file and can be listed with --help or --list-options.
- By convention, options starting with a capital letter (eg Server)
- are aggregations of other available options. Available OPTIONS:
-
- @OPTIONS@
-
-
-Available Configurations:
- By convention, configuration files are kept in $JETTY_HOME/etc.
- The known configuration files are:
-
- @CONFIGS@
+ jetty.base=[directory]
+ Set the jetty configuration directory. This is where the etc, webapps and start
+ files will be looked for. If not found in jetty.base, they are looked for in
+ jetty.home.
Defaults:
- A start.ini file may be used to specify default arguments to start.jar,
- which are used if no command line arguments are provided and override
- the defaults in the start.config file. If a line of start.ini contains
- a directory (eg start.d/) then that directory is scanned for *.ini files
- will be processed in name sorted order.
-
- If --ini options are provided on the command line, then start.ini will NOT be read.
-
- The current start.ini arguments are:
+---------
- @STARTINI@
+ A ${jetty.base}/start.ini file and/or ${jetty.base|/start.d/*.ini files may be
+ used to specify default arguments to start.jar. In case of a conflict between
+ the command line, and ini files, the command line will win.
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java
new file mode 100644
index 0000000..ab407f8
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java
@@ -0,0 +1,145 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.startsWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class BaseHomeTest
+{
+ private void assertFileList(BaseHome hb, String message, List<String> expected, List<File> files)
+ {
+ List<String> actual = new ArrayList<>();
+ for (File file : files)
+ {
+ actual.add(hb.toShortForm(file));
+ }
+ Assert.assertThat(message + ": " + Main.join(actual,", "),actual,containsInAnyOrder(expected.toArray()));
+ }
+
+ @Test
+ public void testGetFile_OnlyHome() throws IOException
+ {
+ File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home");
+ File baseDir = null;
+
+ BaseHome hb = new BaseHome(homeDir,baseDir);
+ File startIni = hb.getFile("/start.ini");
+
+ String ref = hb.toShortForm(startIni);
+ Assert.assertThat("Reference",ref,startsWith("${jetty.home}"));
+
+ String contents = IO.readToString(startIni);
+ Assert.assertThat("Contents",contents,containsString("Home Ini"));
+ }
+
+ @Test
+ public void testListFiles_OnlyHome() throws IOException
+ {
+ File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home");
+ File baseDir = null;
+
+ BaseHome hb = new BaseHome(homeDir,baseDir);
+ List<File> files = hb.listFiles("/start.d");
+
+ List<String> expected = new ArrayList<>();
+ expected.add("${jetty.home}/start.d/jmx.ini");
+ expected.add("${jetty.home}/start.d/jndi.ini");
+ expected.add("${jetty.home}/start.d/jsp.ini");
+ expected.add("${jetty.home}/start.d/logging.ini");
+ expected.add("${jetty.home}/start.d/ssl.ini");
+
+ assertFileList(hb,"Files found",expected,files);
+ }
+
+ @Test
+ public void testListFiles_Filtered_OnlyHome() throws IOException
+ {
+ File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home");
+ File baseDir = null;
+
+ BaseHome hb = new BaseHome(homeDir,baseDir);
+ List<File> files = hb.listFiles("/start.d",new FS.IniFilter());
+
+ List<String> expected = new ArrayList<>();
+ expected.add("${jetty.home}/start.d/jmx.ini");
+ expected.add("${jetty.home}/start.d/jndi.ini");
+ expected.add("${jetty.home}/start.d/jsp.ini");
+ expected.add("${jetty.home}/start.d/logging.ini");
+ expected.add("${jetty.home}/start.d/ssl.ini");
+
+ assertFileList(hb,"Files found",expected,files);
+ }
+
+ @Test
+ public void testListFiles_Both() throws IOException
+ {
+ File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home");
+ File baseDir = MavenTestingUtils.getTestResourceDir("hb.1/base");
+
+ BaseHome hb = new BaseHome(homeDir,baseDir);
+ List<File> files = hb.listFiles("/start.d");
+
+ List<String> expected = new ArrayList<>();
+ expected.add("${jetty.base}/start.d/jmx.ini");
+ expected.add("${jetty.home}/start.d/jndi.ini");
+ expected.add("${jetty.home}/start.d/jsp.ini");
+ expected.add("${jetty.base}/start.d/logging.ini");
+ expected.add("${jetty.home}/start.d/ssl.ini");
+ expected.add("${jetty.base}/start.d/myapp.ini");
+
+ assertFileList(hb,"Files found",expected,files);
+ }
+
+ @Test
+ public void testDefault() throws IOException
+ {
+ BaseHome bh = new BaseHome();
+ Assert.assertThat("Home",bh.getHome(),notNullValue());
+ Assert.assertThat("Base",bh.getBase(),notNullValue());
+ }
+
+ @Test
+ public void testGetFile_Both() throws IOException
+ {
+ File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home");
+ File baseDir = MavenTestingUtils.getTestResourceDir("hb.1/base");
+
+ BaseHome hb = new BaseHome(homeDir,baseDir);
+ File startIni = hb.getFile("/start.ini");
+
+ String ref = hb.toShortForm(startIni);
+ Assert.assertThat("Reference",ref,startsWith("${jetty.base}"));
+
+ String contents = IO.readToString(startIni);
+ Assert.assertThat("Contents",contents,containsString("Base Ini"));
+ }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/CommandLineBuilderTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/CommandLineBuilderTest.java
index 562d408..896741e 100644
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/CommandLineBuilderTest.java
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/CommandLineBuilderTest.java
@@ -18,12 +18,12 @@
package org.eclipse.jetty.start;
-import org.junit.Before;
-import org.junit.Test;
-
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
+import org.junit.Before;
+import org.junit.Test;
+
public class CommandLineBuilderTest
{
private CommandLineBuilder cmd = new CommandLineBuilder("java");
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigTest.java
deleted file mode 100644
index 59bbeaa..0000000
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigTest.java
+++ /dev/null
@@ -1,649 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.start;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-public class ConfigTest
-{
- private void assertEquals(String msg, Classpath expected, Classpath actual)
- {
- Assert.assertNotNull(msg + " : expected classpath should not be null",expected);
- Assert.assertNotNull(msg + " : actual classpath should not be null",actual);
- Assert.assertTrue(msg + " : expected should have an entry",expected.count() >= 1);
- Assert.assertTrue(msg + " : actual should have an entry",actual.count() >= 1);
- if (expected.count() != actual.count())
- {
- expected.dump(System.err);
- actual.dump(System.err);
- Assert.assertEquals(msg + " : count",expected.count(),actual.count());
- }
-
- List<File> actualEntries = Arrays.asList(actual.getElements());
- List<File> expectedEntries = Arrays.asList(expected.getElements());
-
- int len = expectedEntries.size();
-
- for (int i = 0; i < len; i++)
- {
- File expectedFile = expectedEntries.get(i);
- File actualFile = actualEntries.get(i);
- if (!expectedFile.equals(actualFile))
- {
- expected.dump(System.err);
- actual.dump(System.err);
- Assert.assertEquals(msg + ": entry [" + i + "]",expectedEntries.get(i),actualEntries.get(i));
- }
- }
- }
-
- private void assertEquals(String msg, Collection<String> expected, Collection<String> actual)
- {
- Assert.assertTrue(msg + " : expected should have an entry",expected.size() >= 1);
- Assert.assertEquals(msg + " : size",expected.size(),actual.size());
- for (String expectedVal : expected)
- {
- Assert.assertTrue(msg + " : should contain <" + expectedVal + ">",actual.contains(expectedVal));
- }
- }
-
- private String getJettyEtcFile(String name)
- {
- File etc = new File(getTestableJettyHome(),"etc");
- return new File(etc,name).getAbsolutePath();
- }
-
- private File getJettyHomeDir()
- {
- return new File(getTestResourcesDir(),"jetty.home");
- }
-
- private String getTestableJettyHome()
- {
- return getJettyHomeDir().getAbsolutePath();
- }
-
- private File getTestResourcesDir()
- {
- File src = new File(System.getProperty("user.dir"),"src");
- File test = new File(src,"test");
- return new File(test,"resources");
- }
-
- @Before
- public void reset()
- {
- Config.clearProperties();
- }
-
- /*
- * Test for SUBJECT "/=" for assign canonical path
- */
- @Test
- public void testSubjectAssignCanonicalPath() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("test.resources.dir/=src/test/resources\n");
-
- Config cfg = new Config();
- cfg.parse(buf);
-
- Assert.assertEquals(getTestResourcesDir().getCanonicalPath(),Config.getProperty("test.resources.dir"));
- }
-
- /*
- * Test for SUBJECT "~=" for assigning Start Properties
- */
- @Test
- public void testSubjectAssignStartProperty() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("test.jetty.start.text~=foo\n");
- buf.append("test.jetty.start.quote~=Eatagramovabits\n");
-
- Config options = new Config();
- options.parse(buf);
-
- Assert.assertEquals("foo",Config.getProperty("test.jetty.start.text"));
- Assert.assertEquals("Eatagramovabits",Config.getProperty("test.jetty.start.quote"));
- }
-
- /*
- * Test for SUBJECT "=" for assigning System Properties
- */
- @Test
- public void testSubjectAssignSystemProperty() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("test.jetty.start.text=foo\n");
- buf.append("test.jetty.start.quote=Eatagramovabits\n");
-
- Config options = new Config();
- options.parse(buf);
-
- Assert.assertEquals("foo",System.getProperty("test.jetty.start.text"));
- Assert.assertEquals("Eatagramovabits",System.getProperty("test.jetty.start.quote"));
- }
-
- /*
- * Test for SUBJECT ending with "/**", all jar and zip components in dir (deep, recursive)
- */
- @Test
- public void testSubjectComponentDirDeep() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("$(jetty.home)/lib/**\n");
-
- String jettyHome = getTestableJettyHome();
-
- Config options = new Config();
- options.setProperty("jetty.home",jettyHome);
- options.parse(buf);
-
- Classpath actual = options.getClasspath();
- Classpath expected = new Classpath();
-
- File lib = new File(getJettyHomeDir(),"lib");
-
- expected.addComponent(new File(lib,"core.jar"));
- expected.addComponent(new File(lib,"example.jar"));
- expected.addComponent(new File(lib,"http.jar"));
- expected.addComponent(new File(lib,"io.jar"));
- expected.addComponent(new File(lib,"JSR.ZIP"));
- expected.addComponent(new File(lib,"LOGGING.JAR"));
- expected.addComponent(new File(lib,"server.jar"));
- expected.addComponent(new File(lib,"spec.zip"));
- expected.addComponent(new File(lib,"util.jar"));
- expected.addComponent(new File(lib,"xml.jar"));
-
- File ext = new File(lib,"ext");
- expected.addComponent(new File(ext,"custom-impl.jar"));
- File foo = new File(lib,"foo");
- File bar = new File(foo,"bar");
- expected.addComponent(new File(bar,"foobar.jar"));
-
- assertEquals("Components (Deep)",expected,actual);
- }
-
- /*
- * Test for SUBJECT ending with "/*", all jar and zip components in dir (shallow, no recursion)
- */
- @Test
- public void testSubjectComponentDirShallow() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("# Example of any shallow components in /lib/\n");
- buf.append("$(jetty.home)/lib/*\n");
-
- String jettyHome = getTestableJettyHome();
-
- Config options = new Config();
- options.setProperty("jetty.home",jettyHome);
- options.parse(buf);
-
- Classpath actual = options.getClasspath();
- Classpath expected = new Classpath();
-
- File lib = new File(getJettyHomeDir(),"lib");
-
- expected.addComponent(new File(lib,"core.jar"));
- expected.addComponent(new File(lib,"example.jar"));
- expected.addComponent(new File(lib,"http.jar"));
- expected.addComponent(new File(lib,"io.jar"));
- expected.addComponent(new File(lib,"JSR.ZIP"));
- expected.addComponent(new File(lib,"LOGGING.JAR"));
- expected.addComponent(new File(lib,"server.jar"));
- expected.addComponent(new File(lib,"spec.zip"));
- expected.addComponent(new File(lib,"util.jar"));
- expected.addComponent(new File(lib,"xml.jar"));
-
- assertEquals("Components (Shallow)",expected,actual);
- }
-
- /*
- * Test for SUBJECT ending with ".class", a Main Class
- */
- @Test
- public void testSubjectMainClass() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("org.eclipse.jetty.xml.XmlConfiguration.class");
-
- Config options = new Config();
- options.parse(buf);
-
- Assert.assertEquals("org.eclipse.jetty.xml.XmlConfiguration",options.getMainClassname());
- }
-
- /*
- * Test for SUBJECT ending with ".class", a Main Class
- */
- @Test
- public void testSubjectMainClassConditionalPropertySet() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("org.eclipse.jetty.xml.XmlConfiguration.class\n");
- buf.append("${start.class}.class property start.class");
-
- Config options = new Config();
- options.setProperty("start.class","net.company.server.Start");
- options.parse(buf);
-
- Assert.assertEquals("net.company.server.Start",options.getMainClassname());
- }
-
- /*
- * Test for SUBJECT ending with ".class", a Main Class
- */
- @Test
- public void testSubjectMainClassConditionalPropertyUnset() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("org.eclipse.jetty.xml.XmlConfiguration.class\n");
- buf.append("${start.class}.class property start.class");
-
- Config options = new Config();
- // The "start.class" property is unset.
- options.parse(buf);
-
- Assert.assertEquals("org.eclipse.jetty.xml.XmlConfiguration",options.getMainClassname());
- }
-
- /*
- * Test for SUBJECT ending with "/", a simple Classpath Entry
- */
- @Test
- public void testSubjectSimpleComponent() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("$(jetty.home)/resources/\n");
-
- String jettyHome = getTestableJettyHome();
-
- Config options = new Config();
- options.setProperty("jetty.home",jettyHome);
- options.parse(buf);
-
- Classpath actual = options.getClasspath();
- Classpath expected = new Classpath();
-
- expected.addComponent(new File(getJettyHomeDir(),"resources"));
-
- assertEquals("Simple Component",expected,actual);
- }
-
- /*
- * Test for SUBJECT ending with "/", a simple Classpath Entry
- */
- @Test
- public void testSubjectSimpleComponentMultiple() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("$(jetty.home)/resources/\n");
- buf.append("$(jetty.home)/etc/\n");
-
- String jettyHome = getTestableJettyHome();
-
- Config options = new Config();
- options.setProperty("jetty.home",jettyHome);
- options.parse(buf);
-
- Classpath actual = options.getClasspath();
- Classpath expected = new Classpath();
-
- expected.addComponent(new File(getJettyHomeDir(),"resources"));
- expected.addComponent(new File(getJettyHomeDir(),"etc"));
-
- assertEquals("Simple Component",expected,actual);
- }
-
- /*
- * Test for SUBJECT ending with "/", a simple Classpath Entry
- */
- @Test
- public void testSubjectSimpleComponentNotExists() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("$(jetty.home)/resources/\n");
- buf.append("$(jetty.home)/foo/\n");
-
- String jettyHome = getTestableJettyHome();
-
- Config options = new Config();
- options.setProperty("jetty.home",jettyHome);
- options.parse(buf);
-
- Classpath actual = options.getClasspath();
- Classpath expected = new Classpath();
-
- expected.addComponent(new File(getJettyHomeDir(),"resources"));
-
- assertEquals("Simple Component",expected,actual);
- }
-
- /*
- * Test for SUBJECT ending with ".xml", an XML Configuration File
- */
- @Test
- public void testSubjectXmlConfigAlt() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- // Doesn't exist
- buf.append("$(jetty.home)/etc/jetty.xml nargs == 0\n");
- // test-alt does exist.
- buf.append("./src/test/resources/test-alt.xml nargs == 0 AND ! exists $(jetty.home)/etc/jetty.xml");
-
- String jettyHome = getTestableJettyHome();
-
- Config options = new Config();
- options.setProperty("jetty.home",jettyHome);
- options.parse(buf);
-
- List<String> actual = options.getXmlConfigs();
- String expected = new File("src/test/resources/test-alt.xml").getAbsolutePath();
- Assert.assertEquals("XmlConfig.size",1,actual.size());
- Assert.assertEquals(expected,actual.get(0));
- }
-
- /*
- * Test for SUBJECT ending with ".xml", an XML Configuration File
- */
- @Test
- public void testSubjectXmlConfigDefault() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("$(jetty.home)/etc/test-jetty.xml nargs == 0\n");
- buf.append("./jetty-server/src/main/config/etc/test-jetty.xml nargs == 0 AND ! exists $(jetty.home)/etc/test-jetty.xml");
-
- String jettyHome = getTestableJettyHome();
-
- Config options = new Config();
- options.setProperty("jetty.home",jettyHome);
- options.parse(buf);
-
- List<String> actual = options.getXmlConfigs();
- String expected = getJettyEtcFile("test-jetty.xml");
- Assert.assertEquals("XmlConfig.size",1,actual.size());
- Assert.assertEquals(expected,actual.get(0));
- }
-
- /*
- * Test for SUBJECT ending with ".xml", an XML Configuration File.
- */
- @Test
- public void testSubjectXmlConfigMultiple() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("$(jetty.home)/etc/test-jetty.xml nargs == 0\n");
- buf.append("$(jetty.home)/etc/test-jetty-ssl.xml nargs == 0\n");
- buf.append("$(jetty.home)/etc/test-jetty-security.xml nargs == 0\n");
-
- String jettyHome = getTestableJettyHome();
-
- Config options = new Config();
- options.setProperty("jetty.home",jettyHome);
- options.parse(buf);
-
- List<String> actual = options.getXmlConfigs();
- List<String> expected = new ArrayList<String>();
- expected.add(getJettyEtcFile("test-jetty.xml"));
- expected.add(getJettyEtcFile("test-jetty-ssl.xml"));
- expected.add(getJettyEtcFile("test-jetty-security.xml"));
-
- assertEquals("Multiple XML Configs",expected,actual);
- }
-
- /*
- * Test Section Handling
- */
- @Test
- public void testSectionClasspathSingle() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("[All]\n");
- buf.append("$(jetty.home)/lib/core-test.jar\n");
- buf.append("$(jetty.home)/lib/util.jar\n");
-
- String jettyHome = getTestableJettyHome();
-
- Config options = new Config();
- options.setProperty("jetty.home",jettyHome);
- options.parse(buf);
-
- Classpath defaultClasspath = options.getClasspath();
- Assert.assertNotNull("Default Classpath should not be null",defaultClasspath);
- Classpath foocp = options.getSectionClasspath("Foo");
- Assert.assertNull("Foo Classpath should not exist",foocp);
-
- Classpath allcp = options.getSectionClasspath("All");
- Assert.assertNotNull("Classpath section 'All' should exist",allcp);
-
- File lib = new File(getJettyHomeDir(),"lib");
-
- Classpath expected = new Classpath();
- expected.addComponent(new File(lib,"core-test.jar"));
- expected.addComponent(new File(lib,"util.jar"));
-
- assertEquals("Single Classpath Section",expected,allcp);
- }
-
- /*
- * Test Section Handling
- */
- @Test
- public void testSectionClasspathAvailable() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("[All]\n");
- buf.append("$(jetty.home)/lib/core.jar ! available org.eclipse.jetty.dummy.Handler\n");
- buf.append("$(jetty.home)/lib/util.jar ! available org.eclipse.jetty.dummy.StringUtils\n");
-
- String jettyHome = getTestableJettyHome();
-
- Config options = new Config();
- options.setProperty("jetty.home",jettyHome);
- options.parse(buf);
-
- Classpath defaultClasspath = options.getClasspath();
- Assert.assertNotNull("Default Classpath should not be null",defaultClasspath);
- Classpath foocp = options.getSectionClasspath("Foo");
- Assert.assertNull("Foo Classpath should not exist",foocp);
-
- Classpath allcp = options.getSectionClasspath("All");
- Assert.assertNotNull("Classpath section 'All' should exist",allcp);
-
- File lib = new File(getJettyHomeDir(),"lib");
-
- Classpath expected = new Classpath();
- expected.addComponent(new File(lib,"core.jar"));
- expected.addComponent(new File(lib,"util.jar"));
-
- assertEquals("Single Classpath Section",expected,allcp);
- }
-
- /*
- * Test Section Handling, with multiple defined sections.
- */
- @Test
- public void testSectionClasspathMultiples() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("# default\n");
- buf.append("$(jetty.home)/lib/spec.zip\n");
- buf.append("\n");
- buf.append("[*]\n");
- buf.append("$(jetty.home)/lib/io.jar\n");
- buf.append("$(jetty.home)/lib/util.jar\n");
- buf.append("\n");
- buf.append("[All,server,default]\n");
- buf.append("$(jetty.home)/lib/core.jar\n");
- buf.append("$(jetty.home)/lib/server.jar\n");
- buf.append("$(jetty.home)/lib/http.jar\n");
- buf.append("\n");
- buf.append("[All,xml,default]\n");
- buf.append("$(jetty.home)/lib/xml.jar\n");
- buf.append("\n");
- buf.append("[All,logging]\n");
- buf.append("$(jetty.home)/lib/LOGGING.JAR\n");
-
- String jettyHome = getTestableJettyHome();
-
- Config cfg = new Config();
- cfg.setProperty("jetty.home",jettyHome);
- cfg.parse(buf);
-
- Classpath defaultClasspath = cfg.getClasspath();
- Assert.assertNotNull("Default Classpath should not be null",defaultClasspath);
-
- Classpath foocp = cfg.getSectionClasspath("Foo");
- Assert.assertNull("Foo Classpath should not exist",foocp);
-
- // Test if entire section list can be fetched
- Set<String> sections = cfg.getSectionIds();
-
- Set<String> expected = new HashSet<String>();
- expected.add(Config.DEFAULT_SECTION);
- expected.add("*");
- expected.add("All");
- expected.add("server");
- expected.add("default");
- expected.add("xml");
- expected.add("logging");
-
- assertEquals("Multiple Section IDs",expected,sections);
-
- // Test fetch of specific section by name works
- Classpath cpAll = cfg.getSectionClasspath("All");
- Assert.assertNotNull("Classpath section 'All' should exist",cpAll);
-
- File lib = new File(getJettyHomeDir(),"lib");
-
- Classpath expectedAll = new Classpath();
- expectedAll.addComponent(new File(lib,"core.jar"));
- expectedAll.addComponent(new File(lib,"server.jar"));
- expectedAll.addComponent(new File(lib,"http.jar"));
- expectedAll.addComponent(new File(lib,"xml.jar"));
- expectedAll.addComponent(new File(lib,"LOGGING.JAR"));
-
- assertEquals("Classpath 'All' Section",expectedAll,cpAll);
-
- // Test combined classpath fetch of multiple sections works
- List<String> activated = new ArrayList<String>();
- activated.add("server");
- activated.add("logging");
-
- Classpath cpCombined = cfg.getCombinedClasspath(activated);
-
- Classpath expectedCombined = new Classpath();
- // from default
- expectedCombined.addComponent(new File(lib,"spec.zip"));
- // from 'server'
- expectedCombined.addComponent(new File(lib,"core.jar"));
- expectedCombined.addComponent(new File(lib,"server.jar"));
- expectedCombined.addComponent(new File(lib,"http.jar"));
- // from 'logging'
- expectedCombined.addComponent(new File(lib,"LOGGING.JAR"));
- // from '*'
- expectedCombined.addComponent(new File(lib,"io.jar"));
- expectedCombined.addComponent(new File(lib,"util.jar"));
-
- assertEquals("Classpath combined 'server,logging'",expectedCombined,cpCombined);
- }
-
- @Test
- public void testDynamicSection() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("[All,default,=$(jetty.home)/lib/*]\n");
-
- String jettyHome = getTestableJettyHome();
-
- Config options = new Config();
- options.setProperty("jetty.home",jettyHome);
- options.parse(buf);
-
- Classpath defaultClasspath = options.getClasspath();
- Assert.assertNotNull("Default Classpath should not be null",defaultClasspath);
- Classpath foocp = options.getSectionClasspath("foo");
- Assert.assertNotNull("Foo Classpath should not exist",foocp);
-
- Classpath allcp = options.getSectionClasspath("All");
- Assert.assertNotNull("Classpath section 'All' should exist",allcp);
-
- Classpath extcp = options.getSectionClasspath("ext");
- Assert.assertNotNull("Classpath section 'ext' should exist", extcp);
-
- Assert.assertEquals("Deep Classpath Section",0,foocp.count());
-
- File lib = new File(getJettyHomeDir(),"lib");
- File ext = new File(lib, "ext");
- Classpath expected = new Classpath();
- expected.addComponent(new File(ext,"custom-impl.jar"));
- assertEquals("Single Classpath Section",expected,extcp);
- }
-
- @Test
- public void testDeepDynamicSection() throws IOException
- {
- StringBuffer buf = new StringBuffer();
- buf.append("[All,default,=$(jetty.home)/lib/**]\n");
-
-
- String jettyHome = getTestableJettyHome();
-
- Config options = new Config();
- options.setProperty("jetty.home",jettyHome);
- options.parse(buf);
-
- Classpath defaultClasspath = options.getClasspath();
- Assert.assertNotNull("Default Classpath should not be null",defaultClasspath);
- Classpath foocp = options.getSectionClasspath("foo");
- Assert.assertNotNull("Foo Classpath should not exist",foocp);
-
- Classpath allcp = options.getSectionClasspath("All");
- Assert.assertNotNull("Classpath section 'All' should exist",allcp);
-
- Classpath extcp = options.getSectionClasspath("ext");
- Assert.assertNotNull("Classpath section 'ext' should exist", extcp);
-
- File lib = new File(getJettyHomeDir(),"lib");
-
- Classpath expected = new Classpath();
- File foo = new File(lib, "foo");
- File bar = new File(foo, "bar");
- expected.addComponent(new File(bar,"foobar.jar"));
- assertEquals("Deep Classpath Section",expected,foocp);
-
- File ext = new File(lib, "ext");
- expected = new Classpath();
- expected.addComponent(new File(ext,"custom-impl.jar"));
- assertEquals("Single Classpath Section",expected,extcp);
- }
-}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java
new file mode 100644
index 0000000..cc821a6
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java
@@ -0,0 +1,257 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Assert;
+
+public class ConfigurationAssert
+{
+ /**
+ * Given a provided StartArgs, assert that the configuration it has determined is valid based on values in a assert text file.
+ *
+ * @param baseHome
+ * the BaseHome used. Access it via {@link Main#getBaseHome()}
+ * @param args
+ * the StartArgs that has been processed via {@link Main#processCommandLine(String[])}
+ * @param filename
+ * the filename of the assertion values
+ * @throws IOException
+ */
+ public static void assertConfiguration(BaseHome baseHome, StartArgs args, String filename) throws FileNotFoundException, IOException
+ {
+ File testResourcesDir = MavenTestingUtils.getTestResourcesDir();
+ File file = MavenTestingUtils.getTestResourceFile(filename);
+ TextFile textFile = new TextFile(file);
+
+ // Validate XMLs (order is important)
+ List<String> expectedXmls = new ArrayList<>();
+ for (String line : textFile)
+ {
+ if (line.startsWith("XML|"))
+ {
+ expectedXmls.add(getValue(line));
+ }
+ }
+ List<String> actualXmls = new ArrayList<>();
+ for (File xml : args.getXmlFiles())
+ {
+ actualXmls.add(shorten(baseHome,xml,testResourcesDir));
+ }
+ assertOrdered("XML Resolution Order",expectedXmls,actualXmls);
+
+ // Validate LIBs (order is not important)
+ List<String> expectedLibs = new ArrayList<>();
+ for (String line : textFile)
+ {
+ if (line.startsWith("LIB|"))
+ {
+ expectedLibs.add(getValue(line));
+ }
+ }
+ List<String> actualLibs = new ArrayList<>();
+ for (File path : args.getClasspath())
+ {
+ actualLibs.add(shorten(baseHome,path,testResourcesDir));
+ }
+ assertContainsUnordered("Libs",expectedLibs,actualLibs);
+
+ // Validate PROPERTIES (order is not important)
+ Set<String> expectedProperties = new HashSet<>();
+ for (String line : textFile)
+ {
+ if (line.startsWith("PROP|"))
+ {
+ expectedProperties.add(getValue(line));
+ }
+ }
+ List<String> actualProperties = new ArrayList<>();
+ @SuppressWarnings("unchecked")
+ Enumeration<String> nameEnum = (Enumeration<String>)args.getProperties().propertyNames();
+ while (nameEnum.hasMoreElements())
+ {
+ String name = nameEnum.nextElement();
+ if ("jetty.home".equals(name) || "jetty.base".equals(name))
+ {
+ // strip these out from assertion, to make assertions easier.
+ continue;
+ }
+ String value = args.getProperties().getProperty(name);
+ actualProperties.add(name + "=" + value);
+ }
+ assertContainsUnordered("Properties",expectedProperties,actualProperties);
+
+ // Validate Downloads
+ List<String> expectedDownloads = new ArrayList<>();
+ for (String line : textFile)
+ {
+ if (line.startsWith("DOWNLOAD|"))
+ {
+ expectedDownloads.add(getValue(line));
+ }
+ }
+ List<String> actualDownloads = new ArrayList<>();
+ for (FileArg darg : args.getFiles())
+ {
+ actualDownloads.add(String.format("%s:%s",darg.uri,darg.location));
+ }
+ assertContainsUnordered("Downloads",expectedDownloads,actualDownloads);
+
+ }
+
+ private static String shorten(BaseHome baseHome, File path, File testResourcesDir)
+ {
+ String value = baseHome.toShortForm(path);
+ if (value.startsWith(testResourcesDir.getAbsolutePath()))
+ {
+ int len = testResourcesDir.getAbsolutePath().length();
+ value = "${maven-test-resources}" + value.substring(len);
+ }
+ return value;
+ }
+
+ private static void assertContainsUnordered(String msg, Collection<String> expectedSet, Collection<String> actualSet)
+ {
+ // same size?
+ boolean mismatch = expectedSet.size() != actualSet.size();
+
+ // test content
+ Set<String> missing = new HashSet<>();
+ for (String expected : expectedSet)
+ {
+ if (!actualSet.contains(expected))
+ {
+ missing.add(expected);
+ }
+ }
+
+ if (mismatch || missing.size() > 0)
+ {
+ // build up detailed error message
+ StringWriter message = new StringWriter();
+ PrintWriter err = new PrintWriter(message);
+
+ err.printf("%s: Assert Contains (Unordered)",msg);
+ if (mismatch)
+ {
+ err.print(" [size mismatch]");
+ }
+ if (missing.size() >= 0)
+ {
+ err.printf(" [%d entries missing]",missing.size());
+ }
+ err.println();
+ err.printf("Actual Entries (size: %d)%n",actualSet.size());
+ for (String actual : actualSet)
+ {
+ char indicator = expectedSet.contains(actual)?' ':'>';
+ err.printf("%s| %s%n",indicator,actual);
+ }
+ err.printf("Expected Entries (size: %d)%n",expectedSet.size());
+ for (String expected : expectedSet)
+ {
+ char indicator = actualSet.contains(expected)?' ':'>';
+ err.printf("%s| %s%n",indicator,expected);
+ }
+ err.flush();
+ Assert.fail(message.toString());
+ }
+ }
+
+ private static void assertOrdered(String msg, List<String> expectedList, List<String> actualList)
+ {
+ // same size?
+ boolean mismatch = expectedList.size() != actualList.size();
+
+ // test content
+ List<Integer> badEntries = new ArrayList<>();
+ int min = Math.min(expectedList.size(),actualList.size());
+ int max = Math.max(expectedList.size(),actualList.size());
+ for (int i = 0; i < min; i++)
+ {
+ if (!expectedList.get(i).equals(actualList.get(i)))
+ {
+ badEntries.add(i);
+ }
+ }
+ for (int i = min; i < max; i++)
+ {
+ badEntries.add(i);
+ }
+
+ if (mismatch || badEntries.size() > 0)
+ {
+ // build up detailed error message
+ StringWriter message = new StringWriter();
+ PrintWriter err = new PrintWriter(message);
+
+ err.printf("%s: Assert Contains (Unordered)",msg);
+ if (mismatch)
+ {
+ err.print(" [size mismatch]");
+ }
+ if (badEntries.size() >= 0)
+ {
+ err.printf(" [%d entries not matched]",badEntries.size());
+ }
+ err.println();
+ err.printf("Actual Entries (size: %d)%n",actualList.size());
+ for (int i = 0; i < actualList.size(); i++)
+ {
+ String actual = actualList.get(i);
+ char indicator = badEntries.contains(i)?'>':' ';
+ err.printf("%s[%d] %s%n",indicator,i,actual);
+ }
+
+ err.printf("Expected Entries (size: %d)%n",expectedList.size());
+ for (int i = 0; i < expectedList.size(); i++)
+ {
+ String expected = expectedList.get(i);
+ char indicator = badEntries.contains(i)?'>':' ';
+ err.printf("%s[%d] %s%n",indicator,i,expected);
+ }
+ err.flush();
+ Assert.fail(message.toString());
+ }
+ }
+
+ private static String getValue(String arg)
+ {
+ int idx = arg.indexOf('|');
+ Assert.assertThat("Expecting '|' sign in [" + arg + "]",idx,greaterThanOrEqualTo(0));
+ String value = arg.substring(idx + 1).trim();
+ Assert.assertThat("Expecting Value after '|' in [" + arg + "]",value.length(),greaterThan(0));
+ return value;
+ }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java
index 4d399c0..713d5df 100644
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java
@@ -18,128 +18,122 @@
package org.eclipse.jetty.start;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasItems;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-
import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
-import java.util.Vector;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.junit.Before;
+import org.junit.Assert;
+import org.junit.Ignore;
import org.junit.Test;
-/* ------------------------------------------------------------ */
-/**
- */
public class MainTest
{
- /* ------------------------------------------------------------ */
- /**
- * @throws java.lang.Exception
- */
- @Before
- public void setUp() throws Exception
+ private void addUseCasesHome(List<String> cmdLineArgs)
{
- File testJettyHome = MavenTestingUtils.getTestResourceDir("jetty.home");
- System.setProperty("jetty.home",testJettyHome.getAbsolutePath());
+ File testJettyHome = MavenTestingUtils.getTestResourceDir("usecases/home");
+ cmdLineArgs.add("jetty.home=" + testJettyHome);
}
@Test
- public void testLoadStartIni() throws IOException
+ public void testBasicProcessing() throws Exception
{
+ List<String> cmdLineArgs = new ArrayList<>();
+ addUseCasesHome(cmdLineArgs);
+ cmdLineArgs.add("jetty.port=9090");
+
Main main = new Main();
- List<String> args = main.parseStartIniFiles();
-
- assertEquals("Expected 5 uncommented lines in start.ini",9,args.size());
- assertEquals("First uncommented line in start.ini doesn't match expected result","OPTIONS=Server,jsp,resources,websocket,ext",args.get(0));
- assertEquals("Last uncommented line in start.ini doesn't match expected result","etc/jetty-contexts.xml",args.get(8));
- }
-
- @Test
- public void testExpandCommandLine() throws Exception
- {
- Main main = new Main();
- List<String> args = main.expandCommandLine(new String[] {});
-
- assertEquals("start.ini OPTIONS","OPTIONS=Server,jsp,resources,websocket,ext",args.get(0));
- assertEquals("start.d/jmx OPTIONS","OPTIONS=jmx",args.get(2));
- assertEquals("start.d/jmx XML","etc/jetty-jmx.xml",args.get(3));
- assertEquals("start.d/websocket OPTIONS","OPTIONS=websocket",args.get(4));
- }
-
- @Test
- public void testProcessCommandLine() throws Exception
- {
- Main main = new Main();
- List<String> args = main.expandCommandLine(new String[] {});
- List<String> xmls = main.processCommandLine(args);
-
+ StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
+ BaseHome baseHome = main.getBaseHome();
System.err.println(args);
- System.err.println(xmls);
- assertEquals("etc/jetty.xml",xmls.get(0));
- assertEquals("etc/jetty-jmx.xml",xmls.get(1));
- assertEquals("start.d","etc/jetty-testrealm.xml",xmls.get(2));
- assertEquals("start.d","etc/jetty-contexts.xml",xmls.get(5));
+
+ ConfigurationAssert.assertConfiguration(baseHome,args,"assert-home.txt");
}
@Test
- public void testBuildCommandLine() throws IOException, NoSuchFieldException, IllegalAccessException
+ public void testStopProcessing() throws Exception
{
- List<String> jvmArgs = new ArrayList<String>();
- jvmArgs.add("--exec");
- jvmArgs.add("-Xms1024m");
- jvmArgs.add("-Xmx1024m");
-
- List<String> xmls = new ArrayList<String>();
- xmls.add("jetty.xml");
- xmls.add("jetty-jmx.xml");
- xmls.add("jetty-logging.xml");
+ List<String> cmdLineArgs = new ArrayList<>();
+ cmdLineArgs.add("--stop");
+ cmdLineArgs.add("STOP.PORT=10000");
+ cmdLineArgs.add("STOP.KEY=foo");
+ cmdLineArgs.add("STOP.WAIT=300");
Main main = new Main();
- main.addJvmArgs(jvmArgs);
+ StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
+ System.err.println(args);
- Classpath classpath = nastyWayToCreateAClasspathObject("/jetty/home with spaces/");
- CommandLineBuilder cmd = main.buildCommandLine(classpath,xmls);
- assertThat("CommandLineBuilder shouldn't be null",cmd,notNullValue());
-
- List<String> commandArgs = cmd.getArgs();
- assertThat("commandArgs should contain 11 elements",commandArgs.size(),equalTo(11));
- assertThat("args does not contain -cp",commandArgs,hasItems("-cp"));
- assertThat("Classpath should be correctly quoted and match expected value",commandArgs,
- hasItems("/jetty/home with spaces/somejar.jar:/jetty/home with spaces/someotherjar.jar"));
- assertThat("args does not contain --exec",commandArgs,hasItems("--exec"));
- assertThat("CommandLine should contain jvmArgs",commandArgs,hasItems("-Xms1024m"));
- assertThat("CommandLine should contain jvmArgs", commandArgs, hasItems("-Xmx1024m"));
- assertThat("CommandLine should contain xmls",commandArgs,hasItems("jetty.xml"));
- assertThat("CommandLine should contain xmls",commandArgs,hasItems("jetty-jmx.xml"));
- assertThat("CommandLine should contain xmls", commandArgs, hasItems("jetty-logging.xml"));
-
- String commandLine = cmd.toString();
- assertThat("cmd.toString() should be properly escaped",commandLine,containsString("-cp /jetty/home\\ with\\ " +
- "spaces/somejar.jar:/jetty/home\\ with\\ spaces/someotherjar.jar"));
- assertThat("cmd.toString() doesn't contain xml config files",commandLine,containsString(" jetty.xml jetty-jmx.xml jetty-logging.xml"));
+ //Assert.assertEquals("--stop should not build module tree", 0, args.getEnabledModules().size());
+ Assert.assertEquals("--stop missing port","10000",args.getProperties().get("STOP.PORT"));
+ Assert.assertEquals("--stop missing key","foo",args.getProperties().get("STOP.KEY"));
+ Assert.assertEquals("--stop missing wait","300",args.getProperties().get("STOP.WAIT"));
}
-
- private Classpath nastyWayToCreateAClasspathObject(String jettyHome) throws NoSuchFieldException, IllegalAccessException
+
+ @Test
+ public void testListConfig() throws Exception
{
- Classpath classpath = new Classpath();
- Field classpathElements = Classpath.class.getDeclaredField("_elements");
- classpathElements.setAccessible(true);
- File file = new File(jettyHome + "somejar.jar");
- File file2 = new File(jettyHome + "someotherjar.jar");
- Vector<File> elements = new Vector<File>();
- elements.add(file);
- elements.add(file2);
- classpathElements.set(classpath,elements);
- return classpath;
+ List<String> cmdLineArgs = new ArrayList<>();
+ addUseCasesHome(cmdLineArgs);
+ cmdLineArgs.add("jetty.port=9090");
+ cmdLineArgs.add("--list-config");
+
+ Main main = new Main();
+ StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
+ main.listConfig(args);
+ }
+
+ @Test
+ @Ignore("Just a bit noisy for general testing")
+ public void testHelp() throws Exception
+ {
+ Main main = new Main();
+ main.usage(false);
}
+ @Test
+ public void testWithCommandLine() throws Exception
+ {
+ List<String> cmdLineArgs = new ArrayList<>();
+
+ addUseCasesHome(cmdLineArgs);
+
+ // JVM args
+ cmdLineArgs.add("--exec");
+ cmdLineArgs.add("-Xms1024m");
+ cmdLineArgs.add("-Xmx1024m");
+
+ // Arbitrary Libs
+ File extraJar = MavenTestingUtils.getTestResourceFile("extra-libs/example.jar");
+ File extraDir = MavenTestingUtils.getTestResourceDir("extra-resources");
+ cmdLineArgs.add(String.format("--lib=%s%s%s",extraJar.getAbsolutePath(),File.pathSeparatorChar,extraDir.getAbsolutePath()));
+
+ // Arbitrary XMLs
+ cmdLineArgs.add("jetty.xml");
+ cmdLineArgs.add("jetty-jmx.xml");
+ cmdLineArgs.add("jetty-logging.xml");
+
+ Main main = new Main();
+
+ StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
+ BaseHome baseHome = main.getBaseHome();
+ System.err.println(args);
+
+ ConfigurationAssert.assertConfiguration(baseHome,args,"assert-home-with-jvm.txt");
+ }
+
+ @Test
+ public void testJettyHomeWithSpaces() throws Exception
+ {
+ List<String> cmdLineArgs = new ArrayList<>();
+
+ File homePath = MavenTestingUtils.getTestResourceDir("jetty home with spaces");
+ cmdLineArgs.add("jetty.home=" + homePath);
+
+ Main main = new Main();
+ StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
+ BaseHome baseHome = main.getBaseHome();
+ System.err.println(args);
+
+ ConfigurationAssert.assertConfiguration(baseHome,args,"assert-home-with-spaces.txt");
+ }
}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java
new file mode 100644
index 0000000..df39d9c
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java
@@ -0,0 +1,60 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.is;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ModuleGraphWriterTest
+{
+ @SuppressWarnings("unused")
+ private final static List<String> TEST_SOURCE = Collections.singletonList("<test>");
+
+ @Rule
+ public TestingDir testdir = new TestingDir();
+
+ @Test
+ public void testGenerate_NothingEnabled() throws IOException
+ {
+ File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
+ File baseDir = testdir.getEmptyDir();
+ BaseHome basehome = new BaseHome(homeDir,baseDir);
+
+ Modules modules = new Modules();
+ modules.registerAll(basehome);
+ modules.buildGraph();
+
+ File outputFile = new File(baseDir,"graph.dot");
+
+ ModuleGraphWriter writer = new ModuleGraphWriter();
+ writer.write(modules,outputFile);
+
+ Assert.assertThat("Output File Exists",outputFile.exists(),is(true));
+ }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java
new file mode 100644
index 0000000..2074935
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java
@@ -0,0 +1,53 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.is;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ModuleTest
+{
+ private Module loadTestHomeModule(String moduleFileName) throws IOException
+ {
+ File file = MavenTestingUtils.getTestResourceFile("usecases/home/modules/" + moduleFileName);
+ return new Module(file);
+ }
+
+ @Test
+ public void testLoadWebSocket() throws IOException
+ {
+ Module Module = loadTestHomeModule("websocket.mod");
+
+ Assert.assertThat("Module Name",Module.getName(),is("websocket"));
+ Assert.assertThat("Module Parents Size",Module.getParentNames().size(),is(2));
+ Assert.assertThat("Module Parents",Module.getParentNames(),containsInAnyOrder("annotations","server"));
+ Assert.assertThat("Module Xmls Size",Module.getXmls().size(),is(1));
+ Assert.assertThat("Module Xmls",Module.getXmls(),contains("etc/jetty-websockets.xml"));
+ Assert.assertThat("Module Options Size",Module.getLibs().size(),is(1));
+ Assert.assertThat("Module Options",Module.getLibs(),contains("lib/websocket/*.jar"));
+ }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java
new file mode 100644
index 0000000..c031a0d
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java
@@ -0,0 +1,176 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.is;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ModulesTest
+{
+ private final static List<String> TEST_SOURCE=Collections.singletonList("<test>");
+
+ @Test
+ public void testLoadAllModules() throws IOException
+ {
+ File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
+ BaseHome basehome = new BaseHome(homeDir,homeDir);
+
+ Modules modules = new Modules();
+ modules.registerAll(basehome);
+ Assert.assertThat("Module count",modules.count(),is(28));
+ }
+
+ @Test
+ public void testResolve_ServerHttp() throws IOException
+ {
+ File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
+ BaseHome basehome = new BaseHome(homeDir,homeDir);
+
+ // Register modules
+ Modules modules = new Modules();
+ modules.registerAll(basehome);
+ modules.buildGraph();
+
+ // Enable 2 modules
+ modules.enable("server",TEST_SOURCE);
+ modules.enable("http",TEST_SOURCE);
+
+ // Collect active module list
+ List<Module> active = modules.resolveEnabled();
+
+ // Assert names are correct, and in the right order
+ List<String> expectedNames = new ArrayList<>();
+ expectedNames.add("base");
+ expectedNames.add("xml");
+ expectedNames.add("server");
+ expectedNames.add("http");
+
+ List<String> actualNames = new ArrayList<>();
+ for (Module actual : active)
+ {
+ actualNames.add(actual.getName());
+ }
+
+ Assert.assertThat("Resolved Names: " + actualNames,actualNames,contains(expectedNames.toArray()));
+
+ // Assert Library List
+ List<String> expectedLibs = new ArrayList<>();
+ expectedLibs.add("lib/jetty-util-${jetty.version}.jar");
+ expectedLibs.add("lib/jetty-io-${jetty.version}.jar");
+ expectedLibs.add("lib/jetty-xml-${jetty.version}.jar");
+ expectedLibs.add("lib/servlet-api-3.1.jar");
+ expectedLibs.add("lib/jetty-schemas-3.1.jar");
+ expectedLibs.add("lib/jetty-http-${jetty.version}.jar");
+ expectedLibs.add("lib/jetty-continuation-${jetty.version}.jar");
+ expectedLibs.add("lib/jetty-server-${jetty.version}.jar");
+
+ List<String> actualLibs = modules.normalizeLibs(active);
+ Assert.assertThat("Resolved Libs: " + actualLibs,actualLibs,contains(expectedLibs.toArray()));
+
+ // Assert XML List
+ List<String> expectedXmls = new ArrayList<>();
+ expectedXmls.add("etc/jetty.xml");
+ expectedXmls.add("etc/jetty-http.xml");
+
+ List<String> actualXmls = modules.normalizeXmls(active);
+ Assert.assertThat("Resolved XMLs: " + actualXmls,actualXmls,contains(expectedXmls.toArray()));
+ }
+
+ @Test
+ public void testResolve_WebSocket() throws IOException
+ {
+ File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
+ BaseHome basehome = new BaseHome(homeDir,homeDir);
+
+ // Register modules
+ Modules modules = new Modules();
+ modules.registerAll(basehome);
+ modules.buildGraph();
+ // modules.dump();
+
+ // Enable 2 modules
+ modules.enable("websocket",TEST_SOURCE);
+ modules.enable("http",TEST_SOURCE);
+
+ // Collect active module list
+ List<Module> active = modules.resolveEnabled();
+
+ // Assert names are correct, and in the right order
+ List<String> expectedNames = new ArrayList<>();
+ expectedNames.add("base");
+ expectedNames.add("xml");
+ expectedNames.add("server");
+ expectedNames.add("http");
+ expectedNames.add("jndi");
+ expectedNames.add("security");
+ expectedNames.add("plus");
+ expectedNames.add("annotations");
+ expectedNames.add("websocket");
+
+ List<String> actualNames = new ArrayList<>();
+ for (Module actual : active)
+ {
+ actualNames.add(actual.getName());
+ }
+
+ Assert.assertThat("Resolved Names: " + actualNames,actualNames,contains(expectedNames.toArray()));
+
+ // Assert Library List
+ List<String> expectedLibs = new ArrayList<>();
+ expectedLibs.add("lib/jetty-util-${jetty.version}.jar");
+ expectedLibs.add("lib/jetty-io-${jetty.version}.jar");
+ expectedLibs.add("lib/jetty-xml-${jetty.version}.jar");
+ expectedLibs.add("lib/servlet-api-3.1.jar");
+ expectedLibs.add("lib/jetty-schemas-3.1.jar");
+ expectedLibs.add("lib/jetty-http-${jetty.version}.jar");
+ expectedLibs.add("lib/jetty-continuation-${jetty.version}.jar");
+ expectedLibs.add("lib/jetty-server-${jetty.version}.jar");
+ expectedLibs.add("lib/jetty-jndi-${jetty.version}.jar");
+ expectedLibs.add("lib/jndi/*.jar");
+ expectedLibs.add("lib/jetty-security-${jetty.version}.jar");
+ expectedLibs.add("lib/jetty-plus-${jetty.version}.jar");
+ expectedLibs.add("lib/jetty-annotations-${jetty.version}.jar");
+ expectedLibs.add("lib/annotations/*.jar");
+ expectedLibs.add("lib/websocket/*.jar");
+
+ List<String> actualLibs = modules.normalizeLibs(active);
+ Assert.assertThat("Resolved Libs: " + actualLibs,actualLibs,contains(expectedLibs.toArray()));
+
+ // Assert XML List
+ List<String> expectedXmls = new ArrayList<>();
+ expectedXmls.add("etc/jetty.xml");
+ expectedXmls.add("etc/jetty-http.xml");
+ expectedXmls.add("etc/jetty-plus.xml");
+ expectedXmls.add("etc/jetty-annotations.xml");
+ expectedXmls.add("etc/jetty-websockets.xml");
+
+ List<String> actualXmls = modules.normalizeXmls(active);
+ Assert.assertThat("Resolved XMLs: " + actualXmls,actualXmls,contains(expectedXmls.toArray()));
+ }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyDump.java b/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyDump.java
index 4928fc0..dc81001 100644
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyDump.java
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyDump.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.start;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
@@ -25,6 +28,7 @@
{
public static void main(String[] args)
{
+ // As System Properties
Properties props = System.getProperties();
Enumeration<?> names = props.propertyNames();
while (names.hasMoreElements())
@@ -36,6 +40,35 @@
System.out.printf("%s=%s%n",name,props.getProperty(name));
}
}
+
+ // As File Argument
+ for (String arg : args)
+ {
+ if (arg.endsWith(".properties"))
+ {
+ Properties aprops = new Properties();
+ File propFile = new File(arg);
+ System.out.printf("[load file %s]%n",propFile.getName());
+ try (FileReader reader = new FileReader(propFile))
+ {
+ aprops.load(reader);
+ Enumeration<?> anames = aprops.propertyNames();
+ while (anames.hasMoreElements())
+ {
+ String name = (String)anames.nextElement();
+ if (name.startsWith("test."))
+ {
+ System.out.printf("%s=%s%n",name,aprops.getProperty(name));
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
System.exit(0);
}
}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyPassingTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyPassingTest.java
index 9275957..3c889c4 100644
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyPassingTest.java
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyPassingTest.java
@@ -18,7 +18,8 @@
package org.eclipse.jetty.start;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
import java.io.BufferedReader;
import java.io.File;
@@ -32,10 +33,8 @@
import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.junit.Assert;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
@@ -94,18 +93,17 @@
@Test
public void testAsJvmArg() throws IOException, InterruptedException
{
- File testCfg = MavenTestingUtils.getTestResourceFile("property-dump-start.config");
File bogusXml = MavenTestingUtils.getTestResourceFile("bogus.xml");
// Setup command line
List<String> commands = new ArrayList<>();
commands.add(getJavaBin());
+ commands.add("-Dmain.class=" + PropertyDump.class.getName());
commands.add("-cp");
commands.add(getClassPath());
// addDebug(commands);
commands.add("-Dtest.foo=bar"); // TESTING THIS
commands.add(getStartJarBin());
- commands.add("--config=" + testCfg.getAbsolutePath());
commands.add(bogusXml.getAbsolutePath());
// Run command, collect output
@@ -116,21 +114,19 @@
}
@Test
- @Ignore("not working yet")
public void testAsCommandLineArg() throws IOException, InterruptedException
{
- File testCfg = MavenTestingUtils.getTestResourceFile("property-dump-start.config");
File bogusXml = MavenTestingUtils.getTestResourceFile("bogus.xml");
// Setup command line
List<String> commands = new ArrayList<>();
commands.add(getJavaBin());
+ commands.add("-Dmain.class=" + PropertyDump.class.getName());
commands.add("-cp");
commands.add(getClassPath());
// addDebug(commands);
commands.add(getStartJarBin());
commands.add("test.foo=bar"); // TESTING THIS
- commands.add("--config=" + testCfg.getAbsolutePath());
commands.add(bogusXml.getAbsolutePath());
// Run command, collect output
@@ -143,18 +139,17 @@
@Test
public void testAsDashDCommandLineArg() throws IOException, InterruptedException
{
- File testCfg = MavenTestingUtils.getTestResourceFile("property-dump-start.config");
File bogusXml = MavenTestingUtils.getTestResourceFile("bogus.xml");
// Setup command line
List<String> commands = new ArrayList<>();
commands.add(getJavaBin());
+ commands.add("-Dmain.class=" + PropertyDump.class.getName());
commands.add("-cp");
commands.add(getClassPath());
// addDebug(commands);
commands.add(getStartJarBin());
commands.add("-Dtest.foo=bar"); // TESTING THIS
- commands.add("--config=" + testCfg.getAbsolutePath());
commands.add(bogusXml.getAbsolutePath());
// Run command, collect output
@@ -190,13 +185,16 @@
System.out.println("Command line: " + cline);
ProcessBuilder builder = new ProcessBuilder(commands);
+ // Set PWD
+ builder.directory(MavenTestingUtils.getTestResourceDir("empty.home"));
Process pid = builder.start();
ConsoleCapture stdOutPump = new ConsoleCapture("STDOUT",pid.getInputStream()).start();
ConsoleCapture stdErrPump = new ConsoleCapture("STDERR",pid.getErrorStream()).start();
int exitCode = pid.waitFor();
- if(exitCode != 0) {
+ if (exitCode != 0)
+ {
System.out.printf("STDERR: [" + stdErrPump.getConsoleOutput() + "]%n");
System.out.printf("STDOUT: [" + stdOutPump.getConsoleOutput() + "]%n");
Assert.assertThat("Exit code",exitCode,is(0));
@@ -211,35 +209,6 @@
private String getJavaBin()
{
- File javaHome = new File(System.getProperty("java.home"));
- if (!javaHome.exists())
- {
- return null;
- }
-
- File javabin = findExecutable(javaHome,"bin/java");
- if (javabin != null)
- {
- return javabin.getAbsolutePath();
- }
-
- javabin = findExecutable(javaHome,"bin/java.exe");
- if (javabin != null)
- {
- return javabin.getAbsolutePath();
- }
-
- return "java";
- }
-
- private File findExecutable(File root, String path)
- {
- String npath = OS.separators(path);
- File exe = new File(root,npath);
- if (!exe.exists())
- {
- return null;
- }
- return exe;
+ return CommandLineBuilder.findJavaBin();
}
}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java b/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java
new file mode 100644
index 0000000..ebc6736
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java
@@ -0,0 +1,70 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Test;
+
+/**
+ * Various Home + Base use cases
+ */
+public class TestUseCases
+{
+ private void assertUseCase(String homeName, String baseName, String assertName) throws Exception
+ {
+ File homeDir = MavenTestingUtils.getTestResourceDir("usecases/" + homeName);
+ File baseDir = MavenTestingUtils.getTestResourceDir("usecases/" + baseName);
+
+ Main main = new Main();
+ List<String> cmdLine = new ArrayList<>();
+ cmdLine.add("jetty.home=" + homeDir.getAbsolutePath());
+ cmdLine.add("jetty.base=" + baseDir.getAbsolutePath());
+ StartArgs args = main.processCommandLine(cmdLine);
+ BaseHome baseHome = main.getBaseHome();
+ ConfigurationAssert.assertConfiguration(baseHome,args,"usecases/" + assertName);
+ }
+
+ @Test
+ public void testBarebones() throws Exception
+ {
+ assertUseCase("home","base.barebones","assert-barebones.txt");
+ }
+
+ @Test
+ public void testJMX() throws Exception
+ {
+ assertUseCase("home","base.jmx","assert-jmx.txt");
+ }
+
+ @Test
+ public void testWithSpdy() throws Exception
+ {
+ assertUseCase("home","base.enable.spdy","assert-enable-spdy.txt");
+ }
+
+ @Test
+ public void testWithDatabase() throws Exception
+ {
+ assertUseCase("home","base.with.db","assert-with-db.txt");
+ }
+}
diff --git a/jetty-start/src/test/resources/assert-home-with-jvm.txt b/jetty-start/src/test/resources/assert-home-with-jvm.txt
new file mode 100644
index 0000000..b73061b
--- /dev/null
+++ b/jetty-start/src/test/resources/assert-home-with-jvm.txt
@@ -0,0 +1,44 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty-jmx.xml
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+XML|${jetty.home}/etc/jetty-plus.xml
+XML|${jetty.home}/etc/jetty-annotations.xml
+XML|${jetty.home}/etc/jetty-websockets.xml
+XML|${jetty.home}/etc/jetty-logging.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/annotations/javax.annotation-api-1.2.jar
+LIB|${jetty.home}/lib/annotations/org.objectweb.asm-TEST.jar
+LIB|${jetty.home}/lib/jetty-annotations-TEST.jar
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-jmx-TEST.jar
+LIB|${jetty.home}/lib/jetty-jndi-TEST.jar
+LIB|${jetty.home}/lib/jetty-plus-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-security-TEST.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/jndi/javax.activation-1.1.jar
+LIB|${jetty.home}/lib/jndi/javax.transaction-api-1.2.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.home}/lib/websocket/javax.websocket-api-1.0.jar
+LIB|${jetty.home}/lib/websocket/javax-websocket-client-impl-TEST.jar
+LIB|${jetty.home}/lib/websocket/javax-websocket-server-impl-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-api-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-client-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-common-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-server-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-servlet-TEST.jar
+LIB|${maven-test-resources}/extra-resources
+LIB|${maven-test-resources}/extra-libs/example.jar
+
+# The Properties we expect (order is irrelevant)
+# PROP|jetty.port=9090
+
+# JVM Args
+JVM|-Xms1024m
+JVM|-Xmx1024m
diff --git a/jetty-start/src/test/resources/assert-home-with-spaces.txt b/jetty-start/src/test/resources/assert-home-with-spaces.txt
new file mode 100644
index 0000000..01d429a
--- /dev/null
+++ b/jetty-start/src/test/resources/assert-home-with-spaces.txt
@@ -0,0 +1,11 @@
+# The XMLs we expect (order is important)
+# No XMLs in this home
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/example of a library with spaces.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|test.message=Hello
+
+# JVM Args
+# no jvm args
diff --git a/jetty-start/src/test/resources/assert-home.txt b/jetty-start/src/test/resources/assert-home.txt
new file mode 100644
index 0000000..327b770
--- /dev/null
+++ b/jetty-start/src/test/resources/assert-home.txt
@@ -0,0 +1,37 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty-jmx.xml
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+XML|${jetty.home}/etc/jetty-plus.xml
+XML|${jetty.home}/etc/jetty-annotations.xml
+XML|${jetty.home}/etc/jetty-websockets.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/annotations/javax.annotation-api-1.2.jar
+LIB|${jetty.home}/lib/annotations/org.objectweb.asm-TEST.jar
+LIB|${jetty.home}/lib/jetty-annotations-TEST.jar
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-jmx-TEST.jar
+LIB|${jetty.home}/lib/jetty-jndi-TEST.jar
+LIB|${jetty.home}/lib/jetty-plus-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-security-TEST.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/jndi/javax.activation-1.1.jar
+LIB|${jetty.home}/lib/jndi/javax.transaction-api-1.2.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.home}/lib/websocket/javax.websocket-api-1.0.jar
+LIB|${jetty.home}/lib/websocket/javax-websocket-client-impl-TEST.jar
+LIB|${jetty.home}/lib/websocket/javax-websocket-server-impl-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-api-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-client-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-common-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-server-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-servlet-TEST.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
diff --git a/jetty-start/src/test/resources/empty.home/start.ini b/jetty-start/src/test/resources/empty.home/start.ini
new file mode 100644
index 0000000..e751e99
--- /dev/null
+++ b/jetty-start/src/test/resources/empty.home/start.ini
@@ -0,0 +1,3 @@
+#===========================================================
+# Empty start.ini
+#===========================================================
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/extra-libs/example.jar
similarity index 100%
rename from jetty-start/src/test/resources/jetty.home/lib/example.jar
rename to jetty-start/src/test/resources/extra-libs/example.jar
diff --git a/jetty-start/src/test/resources/jetty.home/resources/example.properties b/jetty-start/src/test/resources/extra-resources/example.properties
similarity index 100%
rename from jetty-start/src/test/resources/jetty.home/resources/example.properties
rename to jetty-start/src/test/resources/extra-resources/example.properties
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/hb.1/base/start.d/jmx.ini
similarity index 100%
rename from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
rename to jetty-start/src/test/resources/hb.1/base/start.d/jmx.ini
diff --git a/jetty-start/src/test/resources/hb.1/base/start.d/logging.ini b/jetty-start/src/test/resources/hb.1/base/start.d/logging.ini
new file mode 100644
index 0000000..d648d1b
--- /dev/null
+++ b/jetty-start/src/test/resources/hb.1/base/start.d/logging.ini
@@ -0,0 +1 @@
+# Base Logging
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/hb.1/base/start.d/myapp.ini
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/hb.1/base/start.d/myapp.ini
diff --git a/jetty-start/src/test/resources/hb.1/base/start.ini b/jetty-start/src/test/resources/hb.1/base/start.ini
new file mode 100644
index 0000000..c0ebe8c
--- /dev/null
+++ b/jetty-start/src/test/resources/hb.1/base/start.ini
@@ -0,0 +1,7 @@
+#===========================================================
+# Base Ini
+#===========================================================
+
+OPTIONS=jmx
+etc/jetty-jmx.xml
+
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/hb.1/home/start.d/jmx.ini
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/hb.1/home/start.d/jmx.ini
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/hb.1/home/start.d/jndi.ini
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/hb.1/home/start.d/jndi.ini
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/hb.1/home/start.d/jsp.ini
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/hb.1/home/start.d/jsp.ini
diff --git a/jetty-start/src/test/resources/hb.1/home/start.d/logging.ini b/jetty-start/src/test/resources/hb.1/home/start.d/logging.ini
new file mode 100644
index 0000000..7161329
--- /dev/null
+++ b/jetty-start/src/test/resources/hb.1/home/start.d/logging.ini
@@ -0,0 +1 @@
+# Home Logging
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/hb.1/home/start.d/ssl.ini
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/hb.1/home/start.d/ssl.ini
diff --git a/jetty-start/src/test/resources/hb.1/home/start.ini b/jetty-start/src/test/resources/hb.1/home/start.ini
new file mode 100644
index 0000000..69a418e
--- /dev/null
+++ b/jetty-start/src/test/resources/hb.1/home/start.ini
@@ -0,0 +1,11 @@
+#===========================================================
+# Home Ini
+#===========================================================
+
+OPTIONS=Server,jsp,resources,websocket,ext
+etc/jetty.xml
+start.d/
+etc/jetty-deploy.xml
+etc/jetty-webapps.xml
+etc/jetty-contexts.xml
+
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/jetty home with spaces/lib/example of a library with spaces.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/jetty home with spaces/lib/example of a library with spaces.jar
diff --git a/jetty-start/src/test/resources/jetty home with spaces/modules/base.mod b/jetty-start/src/test/resources/jetty home with spaces/modules/base.mod
new file mode 100644
index 0000000..f14d52a
--- /dev/null
+++ b/jetty-start/src/test/resources/jetty home with spaces/modules/base.mod
@@ -0,0 +1,2 @@
+[lib]
+lib/example*with spaces.jar
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/jetty home with spaces/start.ini b/jetty-start/src/test/resources/jetty home with spaces/start.ini
new file mode 100644
index 0000000..ad2e4c2
--- /dev/null
+++ b/jetty-start/src/test/resources/jetty home with spaces/start.ini
@@ -0,0 +1,6 @@
+#===========================================================
+# Empty start.ini
+#===========================================================
+
+--module=base
+test.message=Hello
diff --git a/jetty-start/src/test/resources/jetty.home/etc/test-jetty-security.xml b/jetty-start/src/test/resources/jetty.home/etc/test-jetty-security.xml
deleted file mode 100644
index 38c0208..0000000
--- a/jetty-start/src/test/resources/jetty.home/etc/test-jetty-security.xml
+++ /dev/null
@@ -1 +0,0 @@
-<!-- nothing in here, just used to test the start.config logic in ConfigTest.java -->
diff --git a/jetty-start/src/test/resources/jetty.home/etc/test-jetty-ssl.xml b/jetty-start/src/test/resources/jetty.home/etc/test-jetty-ssl.xml
deleted file mode 100644
index 38c0208..0000000
--- a/jetty-start/src/test/resources/jetty.home/etc/test-jetty-ssl.xml
+++ /dev/null
@@ -1 +0,0 @@
-<!-- nothing in here, just used to test the start.config logic in ConfigTest.java -->
diff --git a/jetty-start/src/test/resources/jetty.home/etc/test-jetty.xml b/jetty-start/src/test/resources/jetty.home/etc/test-jetty.xml
deleted file mode 100644
index 38c0208..0000000
--- a/jetty-start/src/test/resources/jetty.home/etc/test-jetty.xml
+++ /dev/null
@@ -1 +0,0 @@
-<!-- nothing in here, just used to test the start.config logic in ConfigTest.java -->
diff --git a/jetty-start/src/test/resources/jetty.home/lib/LOGGING.JAR b/jetty-start/src/test/resources/jetty.home/lib/LOGGING.JAR
deleted file mode 100644
index e69de29..0000000
--- a/jetty-start/src/test/resources/jetty.home/lib/LOGGING.JAR
+++ /dev/null
diff --git a/jetty-start/src/test/resources/jetty.home/lib/core.jar b/jetty-start/src/test/resources/jetty.home/lib/core.jar
deleted file mode 100644
index e6110ca..0000000
--- a/jetty-start/src/test/resources/jetty.home/lib/core.jar
+++ /dev/null
Binary files differ
diff --git a/jetty-start/src/test/resources/jetty.home/lib/foo/bar/foobar.jar b/jetty-start/src/test/resources/jetty.home/lib/foo/bar/foobar.jar
deleted file mode 100644
index a032b2d..0000000
--- a/jetty-start/src/test/resources/jetty.home/lib/foo/bar/foobar.jar
+++ /dev/null
Binary files differ
diff --git a/jetty-start/src/test/resources/jetty.home/lib/http.jar b/jetty-start/src/test/resources/jetty.home/lib/http.jar
deleted file mode 100644
index a032b2d..0000000
--- a/jetty-start/src/test/resources/jetty.home/lib/http.jar
+++ /dev/null
Binary files differ
diff --git a/jetty-start/src/test/resources/jetty.home/lib/io.jar b/jetty-start/src/test/resources/jetty.home/lib/io.jar
deleted file mode 100644
index 164dee4..0000000
--- a/jetty-start/src/test/resources/jetty.home/lib/io.jar
+++ /dev/null
Binary files differ
diff --git a/jetty-start/src/test/resources/jetty.home/lib/readme.txt b/jetty-start/src/test/resources/jetty.home/lib/readme.txt
deleted file mode 100644
index f27a277..0000000
--- a/jetty-start/src/test/resources/jetty.home/lib/readme.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-Many of these files are zero length on purpose.
-Of those jars that have content, a simple "Hello World" style class has been
-compiled (included) to allow the ConfigTest of available classes.
-This directory is used by the various tests, and having legitimate jar/zip files
-is often not important.
diff --git a/jetty-start/src/test/resources/jetty.home/lib/server.jar b/jetty-start/src/test/resources/jetty.home/lib/server.jar
deleted file mode 100644
index 67fb429..0000000
--- a/jetty-start/src/test/resources/jetty.home/lib/server.jar
+++ /dev/null
Binary files differ
diff --git a/jetty-start/src/test/resources/jetty.home/lib/spec.zip b/jetty-start/src/test/resources/jetty.home/lib/spec.zip
deleted file mode 100644
index e69de29..0000000
--- a/jetty-start/src/test/resources/jetty.home/lib/spec.zip
+++ /dev/null
diff --git a/jetty-start/src/test/resources/jetty.home/lib/util.jar b/jetty-start/src/test/resources/jetty.home/lib/util.jar
deleted file mode 100644
index 515acf6..0000000
--- a/jetty-start/src/test/resources/jetty.home/lib/util.jar
+++ /dev/null
Binary files differ
diff --git a/jetty-start/src/test/resources/jetty.home/lib/xml.jar b/jetty-start/src/test/resources/jetty.home/lib/xml.jar
deleted file mode 100644
index 9650bac..0000000
--- a/jetty-start/src/test/resources/jetty.home/lib/xml.jar
+++ /dev/null
Binary files differ
diff --git a/jetty-start/src/test/resources/jetty.home/start.d/10-jmx.ini b/jetty-start/src/test/resources/jetty.home/start.d/10-jmx.ini
deleted file mode 100644
index 356fccd..0000000
--- a/jetty-start/src/test/resources/jetty.home/start.d/10-jmx.ini
+++ /dev/null
@@ -1,22 +0,0 @@
-#===========================================================
-# Additional Jetty start.jar arguments
-# Each line of this file is prepended to the command line
-# arguments # of a call to:
-# java -jar start.jar [arg...]
-#===========================================================
-
-
-
-#===========================================================
-#-----------------------------------------------------------
-OPTIONS=jmx
-#-----------------------------------------------------------
-
-
-#===========================================================
-# Configuration files.
-# For a full list of available configuration files do
-# java -jar start.jar --help
-#-----------------------------------------------------------
-etc/jetty-jmx.xml
-#===========================================================
diff --git a/jetty-start/src/test/resources/jetty.home/start.d/20-websocket.ini b/jetty-start/src/test/resources/jetty.home/start.d/20-websocket.ini
deleted file mode 100644
index 679a221..0000000
--- a/jetty-start/src/test/resources/jetty.home/start.d/20-websocket.ini
+++ /dev/null
@@ -1,13 +0,0 @@
-#===========================================================
-# Additional Jetty start.jar arguments
-# Each line of this file is prepended to the command line
-# arguments # of a call to:
-# java -jar start.jar [arg...]
-#===========================================================
-
-
-
-#===========================================================
-#-----------------------------------------------------------
-OPTIONS=websocket
-#-----------------------------------------------------------
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/jetty.home/start.d/90-testrealm.ini b/jetty-start/src/test/resources/jetty.home/start.d/90-testrealm.ini
deleted file mode 100644
index 59313d3..0000000
--- a/jetty-start/src/test/resources/jetty.home/start.d/90-testrealm.ini
+++ /dev/null
@@ -1 +0,0 @@
-etc/jetty-testrealm.xml
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/jetty.home/start.ini b/jetty-start/src/test/resources/jetty.home/start.ini
deleted file mode 100644
index eda5499..0000000
--- a/jetty-start/src/test/resources/jetty.home/start.ini
+++ /dev/null
@@ -1,76 +0,0 @@
-#===========================================================
-# Jetty start.jar arguments
-# Each line of this file is prepended to the command line
-# arguments # of a call to:
-# java -jar start.jar [arg...]
-#===========================================================
-
-
-
-#===========================================================
-# If the arguements in this file include JVM arguments
-# (eg -Xmx512m) or JVM System properties (eg com.sun.???),
-# then these will not take affect unless the --exec
-# parameter is included or if the output from --dry-run
-# is executed like:
-# eval $(java -jar start.jar --dry-run)
-#
-# Below are some recommended options for Sun's JRE
-#-----------------------------------------------------------
-# --exec
-# -Dorg.apache.jasper.compiler.disablejsr199=true
-# -Dcom.sun.management.jmxremote
-# -Dorg.eclipse.jetty.util.log.IGNORED=true
-# -Dorg.eclipse.jetty.util.log.stderr.DEBUG=true
-# -Dorg.eclipse.jetty.util.log.stderr.SOURCE=true
-# -Xmx2000m
-# -Xmn512m
-# -verbose:gc
-# -XX:+PrintGCDateStamps
-# -XX:+PrintGCTimeStamps
-# -XX:+PrintGCDetails
-# -XX:+PrintTenuringDistribution
-# -XX:+PrintCommandLineFlags
-# -XX:+DisableExplicitGC
-# -XX:+UseConcMarkSweepGC
-# -XX:ParallelCMSThreads=2
-# -XX:+CMSClassUnloadingEnabled
-# -XX:+UseCMSCompactAtFullCollection
-# -XX:CMSInitiatingOccupancyFraction=80
-#-----------------------------------------------------------
-
-
-#===========================================================
-# Start classpath OPTIONS.
-# These control what classes are on the classpath
-# for a full listing do
-# java -jar start.jar --list-options
-#
-# Enable classpath OPTIONS. Each options represents one or more jars
-# to be added to the classpath. The options can be listed with --help
-# or --list-options.
-# By convention, options starting with a capital letter (eg Server)
-# are aggregations of other available options.
-# Directories in $JETTY_HOME/lib can be added as dynamic OPTIONS by
-# convention. E.g. put some logging jars in $JETTY_HOME/lib/logging
-# and make them available in the classpath by adding a "logging" OPTION
-# like so: OPTIONS=Server,jsp,logging
-#-----------------------------------------------------------
-OPTIONS=Server,jsp,resources,websocket,ext
-#-----------------------------------------------------------
-
-
-#===========================================================
-# Configuration files.
-# For a full list of available configuration files do
-# java -jar start.jar --help
-#-----------------------------------------------------------
-etc/jetty.xml
-start.d/
-# etc/jetty-ssl.xml
-# etc/jetty-requestlog.xml
-etc/jetty-deploy.xml
-#etc/jetty-overlay.xml
-etc/jetty-webapps.xml
-etc/jetty-contexts.xml
-#===========================================================
diff --git a/jetty-start/src/test/resources/property-dump-start.config b/jetty-start/src/test/resources/property-dump-start.config
deleted file mode 100644
index f38e5a4..0000000
--- a/jetty-start/src/test/resources/property-dump-start.config
+++ /dev/null
@@ -1,8 +0,0 @@
-
-org.eclipse.jetty.start.PropertyDump.class
-
-[*]
-$(basedir)/src/test/resources
-
-[default]
-$(basedir)/src/test/resources
diff --git a/jetty-start/src/test/resources/test-alt.xml b/jetty-start/src/test/resources/test-alt.xml
deleted file mode 100644
index 38c0208..0000000
--- a/jetty-start/src/test/resources/test-alt.xml
+++ /dev/null
@@ -1 +0,0 @@
-<!-- nothing in here, just used to test the start.config logic in ConfigTest.java -->
diff --git a/jetty-start/src/test/resources/usecases/assert-barebones.txt b/jetty-start/src/test/resources/usecases/assert-barebones.txt
new file mode 100644
index 0000000..164f3f9
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-barebones.txt
@@ -0,0 +1,16 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/assert-enable-spdy.txt b/jetty-start/src/test/resources/usecases/assert-enable-spdy.txt
new file mode 100644
index 0000000..5d9f2af
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-enable-spdy.txt
@@ -0,0 +1,36 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty-jmx.xml
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+XML|${jetty.home}/etc/jetty-ssl.xml
+XML|${jetty.home}/etc/jetty-spdy.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-jmx-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.home}/lib/spdy/spdy-client-TEST.jar
+LIB|${jetty.home}/lib/spdy/spdy-http-server-TEST.jar
+LIB|${jetty.home}/lib/spdy/spdy-http-common-TEST.jar
+LIB|${jetty.home}/lib/spdy/spdy-server-TEST.jar
+LIB|${jetty.home}/lib/spdy/spdy-core-TEST.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+PROP|jetty.keystore=etc/keystore
+PROP|jetty.keystore.password=friendly
+PROP|jetty.keymanager.password=icecream
+PROP|jetty.truststore=etc/keystore
+PROP|jetty.truststore.password=sundae
+
+# The Downloads
+DOWNLOAD|http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar:lib/npn/npn-boot-1.1.5.v20130313.jar
+
+# The Bootlib
+BOOTLIB|-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-start/src/test/resources/usecases/assert-jmx.txt b/jetty-start/src/test/resources/usecases/assert-jmx.txt
new file mode 100644
index 0000000..def27d7
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-jmx.txt
@@ -0,0 +1,18 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty-jmx.xml
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-jmx-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/assert-with-db.txt b/jetty-start/src/test/resources/usecases/assert-with-db.txt
new file mode 100644
index 0000000..1651974
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-with-db.txt
@@ -0,0 +1,31 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+XML|${jetty.home}/etc/jetty-plus.xml
+XML|${jetty.home}/etc/jetty-deploy.xml
+XML|${jetty.base}/etc/jetty-db.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.home}/lib/jetty-jndi-TEST.jar
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jndi/javax.transaction-api-1.2.jar
+LIB|${jetty.home}/lib/jetty-plus-TEST.jar
+LIB|${jetty.home}/lib/jetty-deploy-TEST.jar
+LIB|${jetty.home}/lib/jetty-security-TEST.jar
+LIB|${jetty.home}/lib/jndi/javax.activation-1.1.jar
+LIB|${jetty.home}/lib/jetty-webapp-TEST.jar
+LIB|${jetty.home}/lib/jetty-servlet-TEST.jar
+LIB|${jetty.base}/lib/db/mysql-driver.jar
+LIB|${jetty.base}/lib/db/bonecp.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+PROP|mysql.user=frank
+PROP|mysql.pass=secret
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/usecases/assert-with-module-persistence.txt b/jetty-start/src/test/resources/usecases/assert-with-module-persistence.txt
new file mode 100644
index 0000000..75fb89c
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-with-module-persistence.txt
@@ -0,0 +1,44 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-ssl.xml
+XML|${jetty.home}/etc/jetty-https.xml
+XML|${jetty.home}/etc/jetty-plus.xml
+XML|${jetty.home}/etc/jetty-annotations.xml
+XML|${jetty.home}/etc/jetty-websockets.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/annotations/javax.annotation-api-1.2.jar
+LIB|${jetty.home}/lib/annotations/org.objectweb.asm-TEST.jar
+LIB|${jetty.home}/lib/jetty-annotations-TEST.jar
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-jndi-TEST.jar
+LIB|${jetty.home}/lib/jetty-plus-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-security-TEST.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/jndi/javax.activation-1.1.jar
+LIB|${jetty.home}/lib/jndi/javax.transaction-api-1.2.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.home}/lib/websocket/javax.websocket-api-1.0.jar
+LIB|${jetty.home}/lib/websocket/javax-websocket-client-impl-TEST.jar
+LIB|${jetty.home}/lib/websocket/javax-websocket-server-impl-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-api-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-client-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-common-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-server-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-servlet-TEST.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=12345
+PROP|jetty.keystore=etc/keystore
+PROP|jetty.keystore.password=friendly
+PROP|jetty.keymanager.password=icecream
+PROP|jetty.truststore=etc/keystore
+PROP|jetty.truststore.password=sundae
+
+# JVM Args
+# JVM|-Xms1024m
diff --git a/jetty-start/src/test/resources/usecases/base.barebones/start.ini b/jetty-start/src/test/resources/usecases/base.barebones/start.ini
new file mode 100644
index 0000000..1c0e065
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.barebones/start.ini
@@ -0,0 +1,5 @@
+
+--module=server
+--module=http
+
+jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/base.enable.spdy/start.ini b/jetty-start/src/test/resources/usecases/base.enable.spdy/start.ini
new file mode 100644
index 0000000..13fa508
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.enable.spdy/start.ini
@@ -0,0 +1,12 @@
+
+--module=server,http,jmx,spdy
+
+jetty.port=9090
+
+# Some SSL keystore configuration
+jetty.keystore=etc/keystore
+jetty.keystore.password=friendly
+jetty.keymanager.password=icecream
+jetty.truststore=etc/keystore
+jetty.truststore.password=sundae
+
diff --git a/jetty-start/src/test/resources/usecases/base.jmx/start.ini b/jetty-start/src/test/resources/usecases/base.jmx/start.ini
new file mode 100644
index 0000000..d3950e6
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.jmx/start.ini
@@ -0,0 +1,4 @@
+
+--module=server,http,jmx
+
+jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/base.with.db/etc/jetty-db.xml b/jetty-start/src/test/resources/usecases/base.with.db/etc/jetty-db.xml
new file mode 100644
index 0000000..7dd6100
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.with.db/etc/jetty-db.xml
@@ -0,0 +1 @@
+<!-- build up org.eclipse.jetty.plus.jndi.Resource here -->
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/base.with.db/lib/db/bonecp.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/base.with.db/lib/db/bonecp.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/base.with.db/lib/db/mysql-driver.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/base.with.db/lib/db/mysql-driver.jar
diff --git a/jetty-start/src/test/resources/usecases/base.with.db/modules/db.mod b/jetty-start/src/test/resources/usecases/base.with.db/modules/db.mod
new file mode 100644
index 0000000..5acded8
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.with.db/modules/db.mod
@@ -0,0 +1,11 @@
+
+[depend]
+deploy
+jndi
+plus
+
+[lib]
+lib/db/*.jar
+
+[xml]
+etc/jetty-db.xml
diff --git a/jetty-start/src/test/resources/usecases/base.with.db/start.ini b/jetty-start/src/test/resources/usecases/base.with.db/start.ini
new file mode 100644
index 0000000..d5513df
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.with.db/start.ini
@@ -0,0 +1,7 @@
+
+--module=http,db
+
+mysql.user=frank
+mysql.pass=secret
+
+jetty.port=9090
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/README.spnego
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/README.spnego
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jdbcRealm.properties
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jdbcRealm.properties
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-annotations.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-annotations.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-contexts.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-contexts.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-debug.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-debug.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-demo.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-demo.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-deploy.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-deploy.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-http.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-http.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-https.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-https.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-ipaccess.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-ipaccess.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-jaas.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-jaas.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-jmx.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-jmx.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-logging.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-logging.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-lowresources.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-lowresources.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-monitor.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-monitor.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-plus.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-plus.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-proxy.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-proxy.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-requestlog.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-requestlog.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-rewrite.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-rewrite.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-setuid.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-setuid.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-spdy-proxy.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-spdy-proxy.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-spdy.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-spdy.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-ssl.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-ssl.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-started.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-started.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-stats.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-stats.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-testrealm.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-testrealm.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-webapps.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-webapps.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-websockets.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-websockets.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty-xinetd.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-xinetd.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty.conf
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty.conf
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/jetty.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/jetty.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/keystore
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/keystore
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/krb5.ini
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/krb5.ini
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/realm.properties
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/realm.properties
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/spnego.conf
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/spnego.conf
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/spnego.properties
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/spnego.properties
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/test-realm.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/test-realm.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/etc/webdefault.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/etc/webdefault.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/annotations/javax.annotation-api-1.2.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/annotations/javax.annotation-api-1.2.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/annotations/org.objectweb.asm-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/annotations/org.objectweb.asm-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/ext/custom-impl.jar b/jetty-start/src/test/resources/usecases/home/lib/ext/.nodelete
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/ext/custom-impl.jar
copy to jetty-start/src/test/resources/usecases/home/lib/ext/.nodelete
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-annotations-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-annotations-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-client-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-client-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-continuation-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-continuation-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-deploy-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-deploy-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-http-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-http-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-io-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-io-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-jaas-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-jaas-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-jmx-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-jmx-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-jndi-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-jndi-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-jsp-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-jsp-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-plus-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-plus-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-proxy-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-proxy-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-rewrite-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-rewrite-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.RC0.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.RC0.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-security-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-security-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-server-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-server-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-servlet-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-servlet-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-servlets-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-servlets-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-util-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-util-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-webapp-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-webapp-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-xml-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-xml-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jndi/javax.activation-1.1.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jndi/javax.activation-1.1.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jndi/javax.transaction-api-1.2.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jndi/javax.transaction-api-1.2.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.el-3.0.0.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jsp/javax.el-3.0.0.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp-2.3.2.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp-2.3.2.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp-api-2.3.1.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp-api-2.3.1.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp.jstl-1.2.0.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp.jstl-1.2.0.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jsp/org.apache.taglibs.standard.glassfish-1.2.0.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jsp/org.apache.taglibs.standard.glassfish-1.2.0.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/jsp/org.eclipse.jdt.core-3.8.2.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/jsp/org.eclipse.jdt.core-3.8.2.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/monitor/jetty-monitor-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/monitor/jetty-monitor-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/servlet-api-3.1.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/servlet-api-3.1.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/setuid/jetty-setuid-java-1.0.1.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/setuid/jetty-setuid-java-1.0.1.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-linux.so
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-linux.so
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-osx.so
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-osx.so
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-client-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-client-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-core-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-core-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-common-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-common-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-server-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-server-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-server-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-server-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/ext/custom-impl.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-client-impl-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/ext/custom-impl.jar
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-client-impl-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/ext/custom-impl.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-server-impl-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/ext/custom-impl.jar
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-server-impl-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/javax.websocket-api-1.0.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/javax.websocket-api-1.0.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-api-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-api-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-client-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-client-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-common-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-common-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-server-TEST.jar
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-server-TEST.jar
diff --git a/jetty-start/src/test/resources/jetty.home/lib/ext/custom-impl.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-servlet-TEST.jar
similarity index 100%
rename from jetty-start/src/test/resources/jetty.home/lib/ext/custom-impl.jar
rename to jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-servlet-TEST.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/annotations.mod b/jetty-start/src/test/resources/usecases/home/modules/annotations.mod
new file mode 100644
index 0000000..65e4654
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/annotations.mod
@@ -0,0 +1,17 @@
+#
+# Jetty Annotation Scanning Module
+#
+
+[depend]
+# Annotations needs plus, and jndi features
+plus
+
+[lib]
+# Annotations needs jetty annotation jars
+lib/jetty-annotations-${jetty.version}.jar
+# Need annotation processing jars too
+lib/annotations/*.jar
+
+[xml]
+# Enable annotation scanning webapp configurations
+etc/jetty-annotations.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/base.mod b/jetty-start/src/test/resources/usecases/home/modules/base.mod
new file mode 100644
index 0000000..ad8ea32
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/base.mod
@@ -0,0 +1,11 @@
+#
+# Base Module
+#
+
+[optional]
+# JMX is optional, if it appears in the module tree then depend on it
+jmx
+
+[lib]
+lib/jetty-util-${jetty.version}.jar
+lib/jetty-io-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/client.mod b/jetty-start/src/test/resources/usecases/home/modules/client.mod
new file mode 100644
index 0000000..6788eac
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/client.mod
@@ -0,0 +1,7 @@
+#
+# Client Feature
+#
+
+[lib]
+# Client jars
+lib/jetty-client-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/debug.mod b/jetty-start/src/test/resources/usecases/home/modules/debug.mod
new file mode 100644
index 0000000..f740ea2
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/debug.mod
@@ -0,0 +1,9 @@
+#
+# Debug module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-debug.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/deploy.mod b/jetty-start/src/test/resources/usecases/home/modules/deploy.mod
new file mode 100644
index 0000000..94c0e40
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/deploy.mod
@@ -0,0 +1,14 @@
+#
+# Deploy Feature
+#
+
+[depend]
+webapp
+
+[lib]
+# Deploy jars
+lib/jetty-deploy-${jetty.version}.jar
+
+[xml]
+# Deploy configuration
+etc/jetty-deploy.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/http.mod b/jetty-start/src/test/resources/usecases/home/modules/http.mod
new file mode 100644
index 0000000..8515414
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/http.mod
@@ -0,0 +1,9 @@
+#
+# Jetty HTTP Server
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-http.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/https.mod b/jetty-start/src/test/resources/usecases/home/modules/https.mod
new file mode 100644
index 0000000..281c5db
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/https.mod
@@ -0,0 +1,10 @@
+#
+# Jetty HTTP Server
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-ssl.xml
+etc/jetty-https.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/ipaccess.mod b/jetty-start/src/test/resources/usecases/home/modules/ipaccess.mod
new file mode 100644
index 0000000..956ea0f
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/ipaccess.mod
@@ -0,0 +1,9 @@
+#
+# IPAccess module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-ipaccess.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/jaas.mod b/jetty-start/src/test/resources/usecases/home/modules/jaas.mod
new file mode 100644
index 0000000..9fb04f7
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/jaas.mod
@@ -0,0 +1,14 @@
+#
+# JAAS Feature
+#
+
+[depend]
+server
+
+[lib]
+# JAAS jars
+lib/jetty-jaas-${jetty.version}.jar
+
+[xml]
+# JAAS configuration
+etc/jetty-jaas.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/jmx.mod b/jetty-start/src/test/resources/usecases/home/modules/jmx.mod
new file mode 100644
index 0000000..fd8740a
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/jmx.mod
@@ -0,0 +1,11 @@
+#
+# JMX Feature
+#
+
+[lib]
+# JMX jars (as defined in start.config)
+lib/jetty-jmx-${jetty.version}.jar
+
+[xml]
+# JMX configuration
+etc/jetty-jmx.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/jndi.mod b/jetty-start/src/test/resources/usecases/home/modules/jndi.mod
new file mode 100644
index 0000000..33c077c
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/jndi.mod
@@ -0,0 +1,11 @@
+#
+# JNDI Support
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-jndi-${jetty.version}.jar
+lib/jndi/*.jar
+
diff --git a/jetty-start/src/test/resources/usecases/home/modules/jsp.mod b/jetty-start/src/test/resources/usecases/home/modules/jsp.mod
new file mode 100644
index 0000000..f85530d
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/jsp.mod
@@ -0,0 +1,10 @@
+#
+# Jetty Servlet Module
+#
+
+[depend]
+servlet
+
+[lib]
+lib/jsp/*.jar
+
diff --git a/jetty-start/src/test/resources/usecases/home/modules/lowresources.mod b/jetty-start/src/test/resources/usecases/home/modules/lowresources.mod
new file mode 100644
index 0000000..4ca96de
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/lowresources.mod
@@ -0,0 +1,9 @@
+#
+# Low Resources module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-lowresources.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/monitor.mod b/jetty-start/src/test/resources/usecases/home/modules/monitor.mod
new file mode 100644
index 0000000..67f006d
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/monitor.mod
@@ -0,0 +1,13 @@
+#
+# Jetty Monitor module
+#
+
+[depend]
+server
+client
+
+[lib]
+lib/jetty-monitor-${jetty.version}.jar
+
+[xml]
+etc/jetty-monitor.xml
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/usecases/home/modules/npn.mod b/jetty-start/src/test/resources/usecases/home/modules/npn.mod
new file mode 100644
index 0000000..711bdea
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/npn.mod
@@ -0,0 +1,6 @@
+
+[files]
+http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar:lib/npn/npn-boot-1.1.5.v20130313.jar
+
+[ini-template]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/plus.mod b/jetty-start/src/test/resources/usecases/home/modules/plus.mod
new file mode 100644
index 0000000..b781f00
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/plus.mod
@@ -0,0 +1,15 @@
+#
+# Jetty Proxy module
+#
+
+[depend]
+server
+security
+jndi
+
+[lib]
+lib/jetty-plus-${jetty.version}.jar
+
+[xml]
+# Plus requires configuration
+etc/jetty-plus.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/proxy.mod b/jetty-start/src/test/resources/usecases/home/modules/proxy.mod
new file mode 100644
index 0000000..7873329
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/proxy.mod
@@ -0,0 +1,14 @@
+#
+# Jetty Proxy module
+#
+
+[depend]
+server
+client
+
+[lib]
+lib/jetty-proxy-${jetty.version}.jar
+
+[xml]
+# Proxy requires configuration
+etc/jetty-proxy.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/requestlog.mod b/jetty-start/src/test/resources/usecases/home/modules/requestlog.mod
new file mode 100644
index 0000000..2b048db
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/requestlog.mod
@@ -0,0 +1,9 @@
+#
+# Request Log module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-requestlog.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/rewrite.mod b/jetty-start/src/test/resources/usecases/home/modules/rewrite.mod
new file mode 100644
index 0000000..85fe5f0
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/rewrite.mod
@@ -0,0 +1,13 @@
+#
+# Jetty Rewrite module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-rewrite-${jetty.version}.jar
+
+[xml]
+# Annotations needs annotations configuration
+etc/jetty-rewrite.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/security.mod b/jetty-start/src/test/resources/usecases/home/modules/security.mod
new file mode 100644
index 0000000..ba31632
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/security.mod
@@ -0,0 +1,9 @@
+#
+# Jetty Security Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-security-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/server.mod b/jetty-start/src/test/resources/usecases/home/modules/server.mod
new file mode 100644
index 0000000..d0c2de3
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/server.mod
@@ -0,0 +1,18 @@
+#
+# Base server
+#
+
+[depend]
+base
+xml
+
+[lib]
+lib/servlet-api-3.1.jar
+lib/jetty-schemas-3.1.jar
+lib/jetty-http-${jetty.version}.jar
+lib/jetty-continuation-${jetty.version}.jar
+lib/jetty-server-${jetty.version}.jar
+
+[xml]
+# Annotations needs annotations configuration
+etc/jetty.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/servlet.mod b/jetty-start/src/test/resources/usecases/home/modules/servlet.mod
new file mode 100644
index 0000000..fdb65c5
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/servlet.mod
@@ -0,0 +1,9 @@
+#
+# Jetty Servlet Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-servlet-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/spdy.mod b/jetty-start/src/test/resources/usecases/home/modules/spdy.mod
new file mode 100644
index 0000000..92e31a2
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/spdy.mod
@@ -0,0 +1,11 @@
+
+[depend]
+server
+npn
+
+[lib]
+lib/spdy/*.jar
+
+[xml]
+etc/jetty-ssl.xml
+etc/jetty-spdy.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/stats.mod b/jetty-start/src/test/resources/usecases/home/modules/stats.mod
new file mode 100644
index 0000000..0922469
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/stats.mod
@@ -0,0 +1,9 @@
+#
+# Stats module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-stats.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/webapp.mod b/jetty-start/src/test/resources/usecases/home/modules/webapp.mod
new file mode 100644
index 0000000..f62c554
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/webapp.mod
@@ -0,0 +1,9 @@
+#
+# Base server
+#
+
+[depend]
+servlet
+
+[lib]
+lib/jetty-webapp-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/websocket.mod b/jetty-start/src/test/resources/usecases/home/modules/websocket.mod
new file mode 100644
index 0000000..f45babd
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/websocket.mod
@@ -0,0 +1,17 @@
+#
+# WebSocket Feature
+#
+
+# WebSocket needs Annotations feature
+[depend]
+server
+annotations
+
+# WebSocket needs websocket jars (as defined in start.config)
+[lib]
+lib/websocket/*.jar
+
+# WebSocket needs websocket configuration
+[xml]
+etc/jetty-websockets.xml
+
diff --git a/jetty-start/src/test/resources/usecases/home/modules/xinetd.mod b/jetty-start/src/test/resources/usecases/home/modules/xinetd.mod
new file mode 100644
index 0000000..fdc1b3c
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/xinetd.mod
@@ -0,0 +1,9 @@
+#
+# Stats module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-xinetd.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/xml.mod b/jetty-start/src/test/resources/usecases/home/modules/xml.mod
new file mode 100644
index 0000000..d53107a
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/xml.mod
@@ -0,0 +1,10 @@
+#
+# Jetty XML Configuration
+#
+
+[depend]
+base
+
+[lib]
+lib/jetty-xml-${jetty.version}.jar
+
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/usecases/home/resources/.nodelete
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
copy to jetty-start/src/test/resources/usecases/home/resources/.nodelete
diff --git a/jetty-start/src/test/resources/usecases/home/start.ini b/jetty-start/src/test/resources/usecases/home/start.ini
new file mode 100644
index 0000000..a65a9cb
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/start.ini
@@ -0,0 +1,2 @@
+
+--module=server,http,jmx,annotations,websocket
diff --git a/jetty-util-ajax/pom.xml b/jetty-util-ajax/pom.xml
index bfdbecf..3c322b3 100644
--- a/jetty-util-ajax/pom.xml
+++ b/jetty-util-ajax/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-util-ajax</artifactId>
@@ -25,7 +25,7 @@
</goals>
<configuration>
<instructions>
- <Import-Package>javax.servlet.*;version="2.6.0",org.slf4j;version="[1.5,2.0)";resolution:=optional,org.slf4j.impl;version="[1.5,2.0)";resolution:=optional,*</Import-Package>
+ <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.slf4j;version="[1.5,2.0)";resolution:=optional,org.slf4j.impl;version="[1.5,2.0)";resolution:=optional,*</Import-Package>
</instructions>
</configuration>
</execution>
@@ -78,8 +78,8 @@
<version>${project.version}</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java
index 894a17f..70278f3 100644
--- a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java
+++ b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java
@@ -1606,7 +1606,7 @@
/**
* Construct a literal JSON instance for use by
- * {@link JSON#toString(Object)}. If {@link Log#isDebugEnabled()} is
+ * {@link JSON#toString(Object)}. If {@link Logger#isDebugEnabled()} is
* true, the JSON will be parsed to check validity
*
* @param json
diff --git a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java
index e50206b..4e7ce78 100644
--- a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java
+++ b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java
@@ -57,8 +57,7 @@
public JSONDateConvertor(String format,TimeZone zone,boolean fromJSON)
{
- _dateCache=new DateCache(format);
- _dateCache.setTimeZone(zone);
+ _dateCache=new DateCache(format,null,zone);
_fromJSON=fromJSON;
_format=new SimpleDateFormat(format);
_format.setTimeZone(zone);
@@ -66,8 +65,7 @@
public JSONDateConvertor(String format, TimeZone zone, boolean fromJSON, Locale locale)
{
- _dateCache = new DateCache(format, locale);
- _dateCache.setTimeZone(zone);
+ _dateCache = new DateCache(format, locale, zone);
_fromJSON = fromJSON;
_format = new SimpleDateFormat(format, new DateFormatSymbols(locale));
_format.setTimeZone(zone);
diff --git a/jetty-util/pom.xml b/jetty-util/pom.xml
index abc9005..f688fde 100644
--- a/jetty-util/pom.xml
+++ b/jetty-util/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-util</artifactId>
@@ -25,7 +25,7 @@
</goals>
<configuration>
<instructions>
- <Import-Package>javax.servlet.*;version="2.6.0",org.slf4j;version="[1.5,2.0)";resolution:=optional,org.slf4j.impl;version="[1.5,2.0)";resolution:=optional,*</Import-Package>
+ <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.slf4j;version="[1.5,2.0)";resolution:=optional,org.slf4j.impl;version="[1.5,2.0)";resolution:=optional,*</Import-Package>
</instructions>
</configuration>
</execution>
@@ -71,8 +71,8 @@
</build>
<dependencies>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/jetty-util/src/main/config/modules/logging.mod b/jetty-util/src/main/config/modules/logging.mod
new file mode 100644
index 0000000..9d36e2e
--- /dev/null
+++ b/jetty-util/src/main/config/modules/logging.mod
@@ -0,0 +1,27 @@
+#
+# Jetty std err/out logging
+#
+
+[xml]
+etc/jetty-logging.xml
+
+[files]
+logs/
+
+[ini-template]
+## Logging Configuration
+# Configure jetty logging for default internal behavior STDERR output
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+
+# Configure jetty logging for slf4j
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog
+
+# Configure jetty logging for java.util.logging
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.JavaUtilLog
+
+# STDERR / STDOUT Logging
+# Number of days to retain logs
+# jetty.log.retain=90
+# Directory for logging output
+# Either a path relative to ${jetty.base} or an absolute path
+# jetty.logs=logs
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayQueue.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayQueue.java
index e4f04d6..4bebefd 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayQueue.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayQueue.java
@@ -48,6 +48,12 @@
{
this(DEFAULT_CAPACITY, -1);
}
+
+ /* ------------------------------------------------------------ */
+ public ArrayQueue(Object lock)
+ {
+ this(DEFAULT_CAPACITY, -1,lock);
+ }
/* ------------------------------------------------------------ */
public ArrayQueue(int capacity)
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java b/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java
index f61d1bd..3868f09 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java
@@ -378,8 +378,7 @@
* Base 64 decode as described in RFC 2045.
* <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
* @param encoded String to decode.
- * @param output stream for decoded bytes
- * @return byte array containing the decoded form of the input.
+ * @param bout stream for decoded bytes
* @throws IllegalArgumentException if the input is not a valid
* B64 encoding.
*/
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java
index 7d34cfc..6c62b3b 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java
@@ -34,29 +34,25 @@
/**
* A BlockingQueue backed by a circular array capable or growing.
* <p/>
- * This queue is uses a variant of the two lock queue algorithm to provide an
- * efficient queue or list backed by a growable circular array.
+ * This queue is uses a variant of the two lock queue algorithm to provide an efficient queue or list backed by a growable circular array.
* <p/>
- * Unlike {@link java.util.concurrent.ArrayBlockingQueue}, this class is
- * able to grow and provides a blocking put call.
+ * Unlike {@link java.util.concurrent.ArrayBlockingQueue}, this class is able to grow and provides a blocking put call.
* <p/>
- * The queue has both a capacity (the size of the array currently allocated)
- * and a max capacity (the maximum size that may be allocated), which defaults to
+ * The queue has both a capacity (the size of the array currently allocated) and a max capacity (the maximum size that may be allocated), which defaults to
* {@link Integer#MAX_VALUE}.
- *
- * @param <E> The element type
+ *
+ * @param <E>
+ * The element type
*/
public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQueue<E>
{
/**
- * The head offset in the {@link #_indexes} array, displaced
- * by 15 slots to avoid false sharing with the array length
- * (stored before the first element of the array itself).
+ * The head offset in the {@link #_indexes} array, displaced by 15 slots to avoid false sharing with the array length (stored before the first element of
+ * the array itself).
*/
private static final int HEAD_OFFSET = MemoryUtils.getIntegersPerCacheLine() - 1;
/**
- * The tail offset in the {@link #_indexes} array, displaced
- * by 16 slots from the head to avoid false sharing with it.
+ * The tail offset in the {@link #_indexes} array, displaced by 16 slots from the head to avoid false sharing with it.
*/
private static final int TAIL_OFFSET = HEAD_OFFSET + MemoryUtils.getIntegersPerCacheLine();
/**
@@ -82,7 +78,7 @@
/**
* Creates an unbounded {@link BlockingArrayQueue} with default initial capacity and grow factor.
- *
+ *
* @see #DEFAULT_CAPACITY
* @see #DEFAULT_GROWTH
*/
@@ -94,10 +90,10 @@
}
/**
- * Creates a bounded {@link BlockingArrayQueue} that does not grow.
- * The capacity of the queue is fixed and equal to the given parameter.
- *
- * @param maxCapacity the maximum capacity
+ * Creates a bounded {@link BlockingArrayQueue} that does not grow. The capacity of the queue is fixed and equal to the given parameter.
+ *
+ * @param maxCapacity
+ * the maximum capacity
*/
public BlockingArrayQueue(int maxCapacity)
{
@@ -108,9 +104,11 @@
/**
* Creates an unbounded {@link BlockingArrayQueue} that grows by the given parameter.
- *
- * @param capacity the initial capacity
- * @param growBy the growth factor
+ *
+ * @param capacity
+ * the initial capacity
+ * @param growBy
+ * the growth factor
*/
public BlockingArrayQueue(int capacity, int growBy)
{
@@ -121,10 +119,13 @@
/**
* Create a bounded {@link BlockingArrayQueue} that grows by the given parameter.
- *
- * @param capacity the initial capacity
- * @param growBy the growth factor
- * @param maxCapacity the maximum capacity
+ *
+ * @param capacity
+ * the initial capacity
+ * @param growBy
+ * the growth factor
+ * @param maxCapacity
+ * the maximum capacity
*/
public BlockingArrayQueue(int capacity, int growBy, int maxCapacity)
{
@@ -136,18 +137,18 @@
}
/*----------------------------------------------------------------------------*/
- /* Collection methods */
+ /* Collection methods */
/*----------------------------------------------------------------------------*/
@Override
public void clear()
{
- final Lock tailLock = _tailLock;
- tailLock.lock();
+
+ _tailLock.lock();
try
{
- final Lock headLock = _headLock;
- headLock.lock();
+
+ _headLock.lock();
try
{
_indexes[HEAD_OFFSET] = 0;
@@ -156,12 +157,12 @@
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
}
finally
{
- tailLock.unlock();
+ _tailLock.unlock();
}
}
@@ -178,7 +179,7 @@
}
/*----------------------------------------------------------------------------*/
- /* Queue methods */
+ /* Queue methods */
/*----------------------------------------------------------------------------*/
@SuppressWarnings("unchecked")
@@ -189,8 +190,8 @@
return null;
E e = null;
- final Lock headLock = _headLock;
- headLock.lock(); // Size cannot shrink
+
+ _headLock.lock(); // Size cannot shrink
try
{
if (_size.get() > 0)
@@ -205,7 +206,7 @@
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
return e;
}
@@ -218,8 +219,8 @@
return null;
E e = null;
- final Lock headLock = _headLock;
- headLock.lock(); // Size cannot shrink
+
+ _headLock.lock(); // Size cannot shrink
try
{
if (_size.get() > 0)
@@ -227,7 +228,7 @@
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
return e;
}
@@ -251,7 +252,7 @@
}
/*----------------------------------------------------------------------------*/
- /* BlockingQueue methods */
+ /* BlockingQueue methods */
/*----------------------------------------------------------------------------*/
@Override
@@ -259,10 +260,8 @@
{
Objects.requireNonNull(e);
- final Lock tailLock = _tailLock;
- final Lock headLock = _headLock;
boolean notEmpty = false;
- tailLock.lock(); // Size cannot grow... only shrink
+ _tailLock.lock(); // Size cannot grow... only shrink
try
{
int size = _size.get();
@@ -272,7 +271,7 @@
// Should we expand array?
if (size == _elements.length)
{
- headLock.lock();
+ _headLock.lock();
try
{
if (!grow())
@@ -280,7 +279,7 @@
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
}
@@ -292,19 +291,19 @@
}
finally
{
- tailLock.unlock();
+ _tailLock.unlock();
}
if (notEmpty)
{
- headLock.lock();
+ _headLock.lock();
try
{
_notEmpty.signal();
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
}
@@ -339,8 +338,8 @@
public E take() throws InterruptedException
{
E e = null;
- final Lock headLock = _headLock;
- headLock.lockInterruptibly(); // Size cannot shrink
+
+ _headLock.lockInterruptibly(); // Size cannot shrink
try
{
try
@@ -366,7 +365,7 @@
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
return e;
}
@@ -377,8 +376,8 @@
{
long nanos = unit.toNanos(time);
E e = null;
- final Lock headLock = _headLock;
- headLock.lockInterruptibly(); // Size cannot shrink
+
+ _headLock.lockInterruptibly(); // Size cannot shrink
try
{
try
@@ -406,7 +405,7 @@
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
return e;
}
@@ -414,12 +413,12 @@
@Override
public boolean remove(Object o)
{
- final Lock tailLock = _tailLock;
- tailLock.lock();
+
+ _tailLock.lock();
try
{
- final Lock headLock = _headLock;
- headLock.lock();
+
+ _headLock.lock();
try
{
if (isEmpty())
@@ -432,9 +431,9 @@
int i = head;
while (true)
{
- if (Objects.equals(_elements[i], o))
+ if (Objects.equals(_elements[i],o))
{
- remove(i >= head ? i - head : capacity - head + i);
+ remove(i >= head?i - head:capacity - head + i);
return true;
}
++i;
@@ -446,36 +445,36 @@
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
}
finally
{
- tailLock.unlock();
+ _tailLock.unlock();
}
}
@Override
public int remainingCapacity()
{
- final Lock tailLock = _tailLock;
- tailLock.lock();
+
+ _tailLock.lock();
try
{
- final Lock headLock = _headLock;
- headLock.lock();
+
+ _headLock.lock();
try
{
return getCapacity() - size();
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
}
finally
{
- tailLock.unlock();
+ _tailLock.unlock();
}
}
@@ -492,19 +491,19 @@
}
/*----------------------------------------------------------------------------*/
- /* List methods */
+ /* List methods */
/*----------------------------------------------------------------------------*/
@SuppressWarnings("unchecked")
@Override
public E get(int index)
{
- final Lock tailLock = _tailLock;
- tailLock.lock();
+
+ _tailLock.lock();
try
{
- final Lock headLock = _headLock;
- headLock.lock();
+
+ _headLock.lock();
try
{
if (index < 0 || index >= _size.get())
@@ -517,12 +516,12 @@
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
}
finally
{
- tailLock.unlock();
+ _tailLock.unlock();
}
}
@@ -532,12 +531,11 @@
if (e == null)
throw new NullPointerException();
- final Lock tailLock = _tailLock;
- tailLock.lock();
+ _tailLock.lock();
try
{
- final Lock headLock = _headLock;
- headLock.lock();
+
+ _headLock.lock();
try
{
final int size = _size.get();
@@ -568,30 +566,30 @@
if (i < tail)
{
- System.arraycopy(_elements, i, _elements, i + 1, tail - i);
+ System.arraycopy(_elements,i,_elements,i + 1,tail - i);
_elements[i] = e;
}
else
{
if (tail > 0)
{
- System.arraycopy(_elements, 0, _elements, 1, tail);
+ System.arraycopy(_elements,0,_elements,1,tail);
_elements[0] = _elements[capacity - 1];
}
- System.arraycopy(_elements, i, _elements, i + 1, capacity - i - 1);
+ System.arraycopy(_elements,i,_elements,i + 1,capacity - i - 1);
_elements[i] = e;
}
}
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
}
finally
{
- tailLock.unlock();
+ _tailLock.unlock();
}
}
@@ -601,12 +599,11 @@
{
Objects.requireNonNull(e);
- final Lock tailLock = _tailLock;
- tailLock.lock();
+ _tailLock.lock();
try
{
- final Lock headLock = _headLock;
- headLock.lock();
+
+ _headLock.lock();
try
{
if (index < 0 || index >= _size.get())
@@ -622,12 +619,12 @@
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
}
finally
{
- tailLock.unlock();
+ _tailLock.unlock();
}
}
@@ -635,12 +632,12 @@
@Override
public E remove(int index)
{
- final Lock tailLock = _tailLock;
- tailLock.lock();
+
+ _tailLock.lock();
try
{
- final Lock headLock = _headLock;
- headLock.lock();
+
+ _headLock.lock();
try
{
if (index < 0 || index >= _size.get())
@@ -655,16 +652,16 @@
int tail = _indexes[TAIL_OFFSET];
if (i < tail)
{
- System.arraycopy(_elements, i + 1, _elements, i, tail - i);
+ System.arraycopy(_elements,i + 1,_elements,i,tail - i);
--_indexes[TAIL_OFFSET];
}
else
{
- System.arraycopy(_elements, i + 1, _elements, i, capacity - i - 1);
+ System.arraycopy(_elements,i + 1,_elements,i,capacity - i - 1);
_elements[capacity - 1] = _elements[0];
if (tail > 0)
{
- System.arraycopy(_elements, 1, _elements, 0, tail);
+ System.arraycopy(_elements,1,_elements,0,tail);
--_indexes[TAIL_OFFSET];
}
else
@@ -680,24 +677,24 @@
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
}
finally
{
- tailLock.unlock();
+ _tailLock.unlock();
}
}
@Override
public ListIterator<E> listIterator(int index)
{
- final Lock tailLock = _tailLock;
- tailLock.lock();
+
+ _tailLock.lock();
try
{
- final Lock headLock = _headLock;
- headLock.lock();
+
+ _headLock.lock();
try
{
Object[] elements = new Object[size()];
@@ -707,30 +704,30 @@
int tail = _indexes[TAIL_OFFSET];
if (head < tail)
{
- System.arraycopy(_elements, head, elements, 0, tail - head);
+ System.arraycopy(_elements,head,elements,0,tail - head);
}
else
{
int chunk = _elements.length - head;
- System.arraycopy(_elements, head, elements, 0, chunk);
- System.arraycopy(_elements, 0, elements, chunk, tail);
+ System.arraycopy(_elements,head,elements,0,chunk);
+ System.arraycopy(_elements,0,elements,chunk,tail);
}
}
- return new Itr(elements, index);
+ return new Itr(elements,index);
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
}
finally
{
- tailLock.unlock();
+ _tailLock.unlock();
}
}
/*----------------------------------------------------------------------------*/
- /* Additional methods */
+ /* Additional methods */
/*----------------------------------------------------------------------------*/
/**
@@ -738,7 +735,15 @@
*/
public int getCapacity()
{
- return _elements.length;
+ _tailLock.lock();
+ try
+ {
+ return _elements.length;
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
}
/**
@@ -750,7 +755,7 @@
}
/*----------------------------------------------------------------------------*/
- /* Implementation methods */
+ /* Implementation methods */
/*----------------------------------------------------------------------------*/
private boolean grow()
@@ -758,12 +763,11 @@
if (_growCapacity <= 0)
return false;
- final Lock tailLock = _tailLock;
- tailLock.lock();
+ _tailLock.lock();
try
{
- final Lock headLock = _headLock;
- headLock.lock();
+
+ _headLock.lock();
try
{
final int head = _indexes[HEAD_OFFSET];
@@ -776,14 +780,14 @@
if (head < tail)
{
newTail = tail - head;
- System.arraycopy(_elements, head, elements, 0, newTail);
+ System.arraycopy(_elements,head,elements,0,newTail);
}
else if (head > tail || _size.get() > 0)
{
newTail = capacity + tail - head;
int cut = capacity - head;
- System.arraycopy(_elements, head, elements, 0, cut);
- System.arraycopy(_elements, 0, elements, cut, tail);
+ System.arraycopy(_elements,head,elements,0,cut);
+ System.arraycopy(_elements,0,elements,cut,tail);
}
else
{
@@ -797,12 +801,12 @@
}
finally
{
- headLock.unlock();
+ _headLock.unlock();
}
}
finally
{
- tailLock.unlock();
+ _tailLock.unlock();
}
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingCallback.java
index 2351d73..170bd24 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingCallback.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingCallback.java
@@ -81,13 +81,11 @@
}
}
- /** Block until the FutureCallback is done or cancelled and
- * after the return leave in the state as if a {@link #reset()} had been
- * done.
+ /** Block until the Callback has succeeded or failed and
+ * after the return leave in the state to allow reuse.
* This is useful for code that wants to repeatable use a FutureCallback to convert
* an asynchronous API to a blocking API.
- * @return
- * @throws IOException
+ * @throws IOException if exception was caught during blocking, or callback was cancelled
*/
public void block() throws IOException
{
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
index 95e4f50..a947c36 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
@@ -31,6 +31,8 @@
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import org.eclipse.jetty.util.resource.Resource;
+
/* ------------------------------------------------------------------------------- */
/**
@@ -788,7 +790,7 @@
return ByteBuffer.wrap(array, offset, length);
}
- public static ByteBuffer toBuffer(File file) throws IOException
+ public static ByteBuffer toMappedBuffer(File file) throws IOException
{
try (RandomAccessFile raf = new RandomAccessFile(file, "r"))
{
@@ -796,6 +798,29 @@
}
}
+ public static ByteBuffer toBuffer(Resource resource,boolean direct) throws IOException
+ {
+ int len=(int)resource.length();
+ if (len<0)
+ throw new IllegalArgumentException("invalid resource: "+String.valueOf(resource)+" len="+len);
+
+ ByteBuffer buffer = direct?BufferUtil.allocateDirect(len):BufferUtil.allocate(len);
+
+ int pos=BufferUtil.flipToFill(buffer);
+ if (resource.getFile()!=null)
+ BufferUtil.readFrom(resource.getFile(),buffer);
+ else
+ {
+ try (InputStream is = resource.getInputStream();)
+ {
+ BufferUtil.readFrom(is,len,buffer);
+ }
+ }
+ BufferUtil.flipToFlush(buffer,pos);
+
+ return buffer;
+ }
+
public static String toSummaryString(ByteBuffer buffer)
{
if (buffer == null)
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java
index ea541a5..a90daca 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java
@@ -34,8 +34,6 @@
package org.eclipse.jetty.util;
-import org.eclipse.jetty.util.log.Log;
-
/**
* <p>A callback abstraction that handles completed/failed events of asynchronous operations.</p>
*
@@ -70,7 +68,6 @@
@Override
public void failed(Throwable x)
{
- Log.getLogger(this.getClass()).warn(x);
}
}
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueue.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueue.java
deleted file mode 100644
index ca9a109..0000000
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueue.java
+++ /dev/null
@@ -1,418 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.util;
-
-import java.util.Collection;
-import java.util.Objects;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLongArray;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Common functionality for a blocking version of {@link ConcurrentArrayQueue}.
- *
- * @see Unbounded
- * @see Bounded
- * @param <E>
- */
-public abstract class ConcurrentArrayBlockingQueue<E> extends ConcurrentArrayQueue<E> implements BlockingQueue<E>
-{
- private final Lock _lock = new ReentrantLock();
- private final Condition _consumer = _lock.newCondition();
-
- public ConcurrentArrayBlockingQueue(int blockSize)
- {
- super(blockSize);
- }
-
- @Override
- public E poll()
- {
- E result = super.poll();
- if (result != null && decrementAndGetSize() > 0)
- signalConsumer();
- return result;
- }
-
- @Override
- public boolean remove(Object o)
- {
- boolean result = super.remove(o);
- if (result && decrementAndGetSize() > 0)
- signalConsumer();
- return result;
- }
-
- protected abstract int decrementAndGetSize();
-
- protected void signalConsumer()
- {
- final Lock lock = _lock;
- lock.lock();
- try
- {
- _consumer.signal();
- }
- finally
- {
- lock.unlock();
- }
- }
-
- @Override
- public E take() throws InterruptedException
- {
- while (true)
- {
- E result = poll();
- if (result != null)
- return result;
-
- final Lock lock = _lock;
- lock.lockInterruptibly();
- try
- {
- if (size() == 0)
- {
- _consumer.await();
- }
- }
- finally
- {
- lock.unlock();
- }
- }
- }
-
- @Override
- public E poll(long timeout, TimeUnit unit) throws InterruptedException
- {
- long nanos = unit.toNanos(timeout);
-
- while (true)
- {
- // TODO should reduce nanos if we spin here
-
- E result = poll();
- if (result != null)
- return result;
-
- final Lock lock = _lock;
- lock.lockInterruptibly();
- try
- {
- if (size() == 0)
- {
- if (nanos <= 0)
- return null;
- nanos = _consumer.awaitNanos(nanos);
- }
- }
- finally
- {
- lock.unlock();
- }
- }
- }
-
- @Override
- public int drainTo(Collection<? super E> c)
- {
- return drainTo(c, Integer.MAX_VALUE);
- }
-
- @Override
- public int drainTo(Collection<? super E> c, int maxElements)
- {
- if (c == this)
- throw new IllegalArgumentException();
-
- int added = 0;
- while (added < maxElements)
- {
- E element = poll();
- if (element == null)
- break;
- c.add(element);
- ++added;
- }
- return added;
- }
-
- /**
- * An unbounded, blocking version of {@link ConcurrentArrayQueue}.
- *
- * @param <E>
- */
- public static class Unbounded<E> extends ConcurrentArrayBlockingQueue<E>
- {
- private static final int SIZE_LEFT_OFFSET = MemoryUtils.getLongsPerCacheLine() - 1;
- private static final int SIZE_RIGHT_OFFSET = SIZE_LEFT_OFFSET + MemoryUtils.getLongsPerCacheLine();
-
- private final AtomicLongArray _sizes = new AtomicLongArray(SIZE_RIGHT_OFFSET+1);
-
- public Unbounded()
- {
- this(DEFAULT_BLOCK_SIZE);
- }
-
- public Unbounded(int blockSize)
- {
- super(blockSize);
- }
-
- @Override
- public boolean offer(E item)
- {
- boolean result = super.offer(item);
- if (result && getAndIncrementSize() == 0)
- signalConsumer();
- return result;
- }
-
- private int getAndIncrementSize()
- {
- long sizeRight = _sizes.getAndIncrement(SIZE_RIGHT_OFFSET);
- long sizeLeft = _sizes.get(SIZE_LEFT_OFFSET);
- return (int)(sizeRight - sizeLeft);
- }
-
- @Override
- protected int decrementAndGetSize()
- {
- long sizeLeft = _sizes.incrementAndGet(SIZE_LEFT_OFFSET);
- long sizeRight = _sizes.get(SIZE_RIGHT_OFFSET);
- return (int)(sizeRight - sizeLeft);
- }
-
- @Override
- public int size()
- {
- long sizeLeft = _sizes.get(SIZE_LEFT_OFFSET);
- long sizeRight = _sizes.get(SIZE_RIGHT_OFFSET);
- return (int)(sizeRight - sizeLeft);
- }
-
- @Override
- public int remainingCapacity()
- {
- return Integer.MAX_VALUE;
- }
-
- @Override
- public void put(E element) throws InterruptedException
- {
- offer(element);
- }
-
- @Override
- public boolean offer(E element, long timeout, TimeUnit unit) throws InterruptedException
- {
- return offer(element);
- }
- }
-
- /**
- * A bounded, blocking version of {@link ConcurrentArrayQueue}.
- *
- * @param <E>
- */
- public static class Bounded<E> extends ConcurrentArrayBlockingQueue<E>
- {
- private final AtomicInteger _size = new AtomicInteger();
- private final Lock _lock = new ReentrantLock();
- private final Condition _producer = _lock.newCondition();
- private final int _capacity;
-
- public Bounded(int capacity)
- {
- this(DEFAULT_BLOCK_SIZE, capacity);
- }
-
- public Bounded(int blockSize, int capacity)
- {
- super(blockSize);
- this._capacity = capacity;
- }
-
- @Override
- public boolean offer(E item)
- {
- while (true)
- {
- int size = size();
- int nextSize = size + 1;
-
- if (nextSize > _capacity)
- return false;
-
- if (_size.compareAndSet(size, nextSize))
- {
- if (super.offer(item))
- {
- if (size == 0)
- signalConsumer();
- return true;
- }
- else
- {
- decrementAndGetSize();
- }
- }
- }
- }
-
- @Override
- public E poll()
- {
- E result = super.poll();
- if (result != null)
- signalProducer();
- return result;
- }
-
- @Override
- public boolean remove(Object o)
- {
- boolean result = super.remove(o);
- if (result)
- signalProducer();
- return result;
- }
-
- @Override
- protected int decrementAndGetSize()
- {
- return _size.decrementAndGet();
- }
-
- @Override
- public int size()
- {
- return _size.get();
- }
-
- @Override
- public int remainingCapacity()
- {
- return _capacity - size();
- }
-
- @Override
- public void put(E item) throws InterruptedException
- {
- item = Objects.requireNonNull(item);
-
- while (true)
- {
- final Lock lock = _lock;
- lock.lockInterruptibly();
- try
- {
- if (size() == _capacity)
- _producer.await();
- }
- finally
- {
- lock.unlock();
- }
- if (offer(item))
- break;
- }
- }
-
- @Override
- public boolean offer(E item, long timeout, TimeUnit unit) throws InterruptedException
- {
- item = Objects.requireNonNull(item);
-
- long nanos = unit.toNanos(timeout);
- while (true)
- {
- final Lock lock = _lock;
- lock.lockInterruptibly();
- try
- {
- if (size() == _capacity)
- {
- if (nanos <= 0)
- return false;
- nanos = _producer.awaitNanos(nanos);
- }
- }
- finally
- {
- lock.unlock();
- }
- if (offer(item))
- break;
- }
-
- return true;
- }
-
- @Override
- public int drainTo(Collection<? super E> c, int maxElements)
- {
- int result = super.drainTo(c, maxElements);
- if (result > 0)
- signalProducers();
- return result;
- }
-
- @Override
- public void clear()
- {
- super.clear();
- signalProducers();
- }
-
- private void signalProducer()
- {
- final Lock lock = _lock;
- lock.lock();
- try
- {
- _producer.signal();
- }
- finally
- {
- lock.unlock();
- }
- }
-
- private void signalProducers()
- {
- final Lock lock = _lock;
- lock.lock();
- try
- {
- _producer.signalAll();
- }
- finally
- {
- lock.unlock();
- }
- }
- }
-}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java b/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java
index e5dbf1f..62f5e2c 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java
@@ -18,23 +18,19 @@
package org.eclipse.jetty.util;
-import java.nio.ByteBuffer;
-import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
-import java.util.Timer;
-import java.util.TimerTask;
/* ------------------------------------------------------------ */
/** Date Format Cache.
* Computes String representations of Dates and caches
- * the results so that subsequent requests within the same minute
+ * the results so that subsequent requests within the same second
* will be fast.
*
- * Only format strings that contain either "ss" or "ss.SSS" are
- * handled.
+ * Only format strings that contain either "ss". Sub second formatting is
+ * not handled.
*
* The timezone of the date may be included as an ID with the "zzz"
* format string or as an offset with the "ZZZ" format string.
@@ -49,30 +45,15 @@
public static final String DEFAULT_FORMAT="EEE MMM dd HH:mm:ss zzz yyyy";
private final String _formatString;
- private String _tzFormatString;
- private SimpleDateFormat _tzFormat;
+ private final String _tzFormatString;
+ private final SimpleDateFormat _tzFormat;
+ private final Locale _locale ;
private volatile Tick _tick;
- private Locale _locale = null;
- private DateFormatSymbols _dfs = null;
-
- private static Timer __timer;
-
-
- public static Timer getTimer()
- {
- synchronized (DateCache.class)
- {
- if (__timer==null)
- __timer=new Timer("DateCache",true);
- return __timer;
- }
- }
-
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
- private static class Tick
+ public static class Tick
{
final long _seconds;
final String _string;
@@ -99,85 +80,28 @@
*/
public DateCache(String format)
{
- _formatString=format;
- setTimeZone(TimeZone.getDefault());
-
- synchronized (DateCache.class)
- {
- long now=System.currentTimeMillis();
- long tick=1000*((now/1000)+1)-now;
- formatNow();
- getTimer().scheduleAtFixedRate(new TimerTask()
- {
- @Override
- public void run()
- {
- formatNow();
- }
- },
- tick,
- 1000);
- }
+ this(format,null,TimeZone.getDefault());
}
/* ------------------------------------------------------------ */
public DateCache(String format,Locale l)
{
- this(format);
+ this(format,l,TimeZone.getDefault());
+ }
+
+ /* ------------------------------------------------------------ */
+ public DateCache(String format,Locale l,String tz)
+ {
+ this(format,l,TimeZone.getTimeZone(tz));
+ }
+
+ /* ------------------------------------------------------------ */
+ public DateCache(String format,Locale l,TimeZone tz)
+ {
+ _formatString=format;
_locale = l;
- setTimeZone(TimeZone.getDefault());
- }
-
- /* ------------------------------------------------------------ */
- public DateCache(String format,DateFormatSymbols s)
- {
- this(format);
- _dfs = s;
- setTimeZone(TimeZone.getDefault());
- }
+
- /* ------------------------------------------------------------ */
- /** Set the timezone.
- * @param tz TimeZone
- */
- public void setTimeZone(TimeZone tz)
- {
- setTzFormatString(tz);
- if( _locale != null )
- {
- _tzFormat=new SimpleDateFormat(_tzFormatString,_locale);
- }
- else if( _dfs != null )
- {
- _tzFormat=new SimpleDateFormat(_tzFormatString,_dfs);
- }
- else
- {
- _tzFormat=new SimpleDateFormat(_tzFormatString);
- }
- _tzFormat.setTimeZone(tz);
- _tick=null;
- }
-
- /* ------------------------------------------------------------ */
- public TimeZone getTimeZone()
- {
- return _tzFormat.getTimeZone();
- }
-
- /* ------------------------------------------------------------ */
- /** Set the timezone.
- * @param timeZoneId TimeZoneId the ID of the zone as used by
- * TimeZone.getTimeZone(id)
- */
- public void setTimeZoneID(String timeZoneId)
- {
- setTimeZone(TimeZone.getTimeZone(timeZoneId));
- }
-
- /* ------------------------------------------------------------ */
- private void setTzFormatString(final TimeZone tz )
- {
int zIndex = _formatString.indexOf( "ZZZ" );
if( zIndex >= 0 )
{
@@ -196,7 +120,7 @@
sb.append( '-' );
}
- int raw = tzOffset / (1000*60); // Convert to seconds
+ int raw = tzOffset / (1000*60); // Convert to seconds
int hr = raw / 60;
int min = raw % 60;
@@ -213,8 +137,26 @@
}
else
_tzFormatString=_formatString;
+
+ if( _locale != null )
+ {
+ _tzFormat=new SimpleDateFormat(_tzFormatString,_locale);
+ }
+ else
+ {
+ _tzFormat=new SimpleDateFormat(_tzFormatString);
+ }
+ _tzFormat.setTimeZone(tz);
+
_tick=null;
}
+
+
+ /* ------------------------------------------------------------ */
+ public TimeZone getTimeZone()
+ {
+ return _tzFormat.getTimeZone();
+ }
/* ------------------------------------------------------------ */
@@ -243,6 +185,8 @@
/* ------------------------------------------------------------ */
/** Format a date according to our stored formatter.
+ * If it happens to be in the same second as the last formatNow
+ * call, then the format is reused.
* @param inDate
* @return Formatted date
*/
@@ -267,51 +211,58 @@
}
/* ------------------------------------------------------------ */
- public String now()
+ /** Format a date according to our stored formatter.
+ * The passed time is expected to be close to the current time, so it is
+ * compared to the last value passed and if it is within the same second,
+ * the format is reused. Otherwise a new cached format is created.
+ * @param now
+ * @return Formatted date
+ */
+ public String formatNow(long now)
{
- return _tick._string;
+ long seconds = now / 1000;
+
+ Tick tick=_tick;
+
+ // Is this the cached time
+ if (tick!=null && tick._seconds==seconds)
+ return tick._string;
+ return formatTick(now)._string;
}
/* ------------------------------------------------------------ */
- protected void formatNow()
+ public String now()
{
- long now = System.currentTimeMillis();
+ return formatNow(System.currentTimeMillis());
+ }
+
+ /* ------------------------------------------------------------ */
+ public Tick tick()
+ {
+ return formatTick(System.currentTimeMillis());
+ }
+
+ /* ------------------------------------------------------------ */
+ protected Tick formatTick(long now)
+ {
long seconds = now / 1000;
+ // Synchronize to protect _tzFormat
synchronized (this)
{
- String s= _tzFormat.format(new Date(now));
- _tick=new Tick(seconds,s);
+ // recheck the tick, to save multiple formats
+ if (_tick==null || _tick._seconds!=seconds)
+ {
+ String s= _tzFormat.format(new Date(now));
+ return _tick=new Tick(seconds,s);
+ }
+ return _tick;
}
}
/* ------------------------------------------------------------ */
- /** Format to string buffer.
- * @param inDate Date the format
- * @param buffer StringBuilder
- */
- public void format(long inDate, StringBuilder buffer)
- {
- buffer.append(format(inDate));
- }
-
- /* ------------------------------------------------------------ */
public String getFormatString()
{
return _formatString;
}
-
- /* ------------------------------------------------------------ */
- private volatile ByteBuffer _buffer;
- private volatile Object _last;
- public synchronized ByteBuffer formatBuffer(long date)
- {
- String d = format(date);
- if (d==_last)
- return _buffer;
- _last=d;
- _buffer=BufferUtil.toBuffer(d);
-
- return _buffer;
- }
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ExecutorCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ExecutorCallback.java
deleted file mode 100644
index 3380640..0000000
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ExecutorCallback.java
+++ /dev/null
@@ -1,134 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.util;
-
-import java.util.concurrent.Executor;
-
-public abstract class ExecutorCallback implements Callback
-{
- private final ForkInvoker<Void> _invoker;
- private final Executor _executor;
- private final Runnable _onComplete=new Runnable()
- {
- @Override
- public void run()
- {
- onCompleted();
- }
- };
-
- public ExecutorCallback(Executor executor)
- {
- this(executor, 4);
- }
-
- public ExecutorCallback(Executor executor, int maxRecursion)
- {
- _executor = executor;
- _invoker = maxRecursion>0?new ExecutorCallbackInvoker(maxRecursion):null;
- if (_executor==null)
- throw new IllegalArgumentException();
- }
-
- @Override
- public void succeeded()
- {
- // Should we execute?
- if (_invoker==null)
- {
- _executor.execute(_onComplete);
- }
- else if (alwaysDispatchCompletion())
- {
- _invoker.fork(null);
- }
- else
- {
- _invoker.invoke(null);
- }
- }
-
- protected abstract void onCompleted();
-
- @Override
- public void failed(final Throwable x)
- {
- // Always execute failure
- Runnable runnable = new Runnable()
- {
- @Override
- public void run()
- {
- onFailed(x);
- }
-
- @Override
- public String toString()
- {
- return String.format("ExecutorCallback@%x{%s}", hashCode(), x);
- }
- };
-
- if (_executor == null)
- new Thread(runnable).start();
- else
- _executor.execute(runnable);
- }
-
- protected void onFailed(Throwable x)
- {
- }
-
- protected boolean alwaysDispatchCompletion()
- {
- return _executor != null;
- }
-
- @Override
- public String toString()
- {
- return String.format("%s@%x", getClass(), hashCode());
- }
-
- private class ExecutorCallbackInvoker extends ForkInvoker<Void> implements Runnable
- {
- private ExecutorCallbackInvoker(int maxInvocations)
- {
- super(maxInvocations);
- }
-
- @Override
- public void fork(Void arg)
- {
- _executor.execute(this);
- }
-
- @Override
- public void call(Void arg)
- {
- onCompleted();
- }
-
- @Override
- public void run()
- {
- onCompleted();
- }
- }
-}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Fields.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Fields.java
index 6581557..8964ae5 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Fields.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Fields.java
@@ -18,11 +18,13 @@
package org.eclipse.jetty.util;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -82,7 +84,7 @@
if (obj == null || getClass() != obj.getClass())
return false;
Fields that = (Fields)obj;
- if (size() != that.size())
+ if (getSize() != that.getSize())
return false;
if (caseSensitive != that.caseSensitive)
return false;
@@ -105,11 +107,11 @@
/**
* @return a set of field names
*/
- public Set<String> names()
+ public Set<String> getNames()
{
Set<String> result = new LinkedHashSet<>();
for (Field field : fields.values())
- result.add(field.name());
+ result.add(field.getName());
return result;
}
@@ -141,14 +143,14 @@
}
/**
- * <p>Inserts or replaces the given {@link Field}, mapped to the {@link Field#name() field's name}</p>
+ * <p>Inserts or replaces the given {@link Field}, mapped to the {@link Field#getName() field's name}</p>
*
* @param field the field to put
*/
public void put(Field field)
{
if (field != null)
- fields.put(normalizeName(field.name()), field);
+ fields.put(normalizeName(field.getName()), field);
}
/**
@@ -170,7 +172,7 @@
}
else
{
- field = new Field(field.name(), field.values(), value);
+ field = new Field(field.getName(), field.getValues(), value);
fields.put(key, field);
}
}
@@ -206,7 +208,7 @@
/**
* @return the number of fields
*/
- public int size()
+ public int getSize()
{
return fields.size();
}
@@ -233,19 +235,20 @@
public static class Field
{
private final String name;
- private final String[] values;
+ private final List<String> values;
- private Field(String name, String value)
+ public Field(String name, String value)
{
- this(name, new String[]{value});
+ this(name, Collections.singletonList(value));
}
- private Field(String name, String[] values, String... moreValues)
+ private Field(String name, List<String> values, String... moreValues)
{
this.name = name;
- this.values = new String[values.length + moreValues.length];
- System.arraycopy(values, 0, this.values, 0, values.length);
- System.arraycopy(moreValues, 0, this.values, values.length, moreValues.length);
+ List<String> list = new ArrayList<>(values.size() + moreValues.length);
+ list.addAll(values);
+ list.addAll(Arrays.asList(moreValues));
+ this.values = Collections.unmodifiableList(list);
}
public boolean equals(Field that, boolean caseSensitive)
@@ -256,7 +259,7 @@
return false;
if (caseSensitive)
return equals(that);
- return name.equalsIgnoreCase(that.name) && Arrays.equals(values, that.values);
+ return name.equalsIgnoreCase(that.name) && values.equals(that.values);
}
@Override
@@ -267,21 +270,21 @@
if (obj == null || getClass() != obj.getClass())
return false;
Field that = (Field)obj;
- return name.equals(that.name) && Arrays.equals(values, that.values);
+ return name.equals(that.name) && values.equals(that.values);
}
@Override
public int hashCode()
{
int result = name.hashCode();
- result = 31 * result + Arrays.hashCode(values);
+ result = 31 * result + values.hashCode();
return result;
}
/**
* @return the field's name
*/
- public String name()
+ public String getName()
{
return name;
}
@@ -289,29 +292,29 @@
/**
* @return the first field's value
*/
- public String value()
+ public String getValue()
{
- return values[0];
+ return values.get(0);
}
/**
- * <p>Attempts to convert the result of {@link #value()} to an integer,
+ * <p>Attempts to convert the result of {@link #getValue()} to an integer,
* returning it if the conversion is successful; returns null if the
- * result of {@link #value()} is null.</p>
+ * result of {@link #getValue()} is null.</p>
*
- * @return the result of {@link #value()} converted to an integer, or null
+ * @return the result of {@link #getValue()} converted to an integer, or null
* @throws NumberFormatException if the conversion fails
*/
- public Integer valueAsInt()
+ public Integer getValueAsInt()
{
- final String value = value();
+ final String value = getValue();
return value == null ? null : Integer.valueOf(value);
}
/**
* @return the field's values
*/
- public String[] values()
+ public List<String> getValues()
{
return values;
}
@@ -321,13 +324,13 @@
*/
public boolean hasMultipleValues()
{
- return values.length > 1;
+ return values.size() > 1;
}
@Override
public String toString()
{
- return Arrays.toString(values);
+ return String.format("%s=%s", name, values);
}
}
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IO.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IO.java
index c1e501b..82277c7 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/IO.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IO.java
@@ -54,17 +54,6 @@
/* ------------------------------------------------------------------- */
public static final int bufferSize = 64*1024;
-
- /* ------------------------------------------------------------------- */
- // TODO get rid of this singleton!
- private static class Singleton {
- static final QueuedThreadPool __pool=new QueuedThreadPool();
- static
- {
- try{__pool.start();}
- catch(Exception e){LOG.warn(e); System.exit(1);}
- }
- }
/* ------------------------------------------------------------------- */
static class Job implements Runnable
@@ -120,23 +109,6 @@
/* ------------------------------------------------------------------- */
/** Copy Stream in to Stream out until EOF or exception.
- * in own thread
- */
- public static void copyThread(InputStream in, OutputStream out)
- {
- try{
- Job job=new Job(in,out);
- if (!Singleton.__pool.dispatch(job))
- job.run();
- }
- catch(Exception e)
- {
- LOG.warn(e);
- }
- }
-
- /* ------------------------------------------------------------------- */
- /** Copy Stream in to Stream out until EOF or exception.
*/
public static void copy(InputStream in, OutputStream out)
throws IOException
@@ -145,24 +117,6 @@
}
/* ------------------------------------------------------------------- */
- /** Copy Stream in to Stream out until EOF or exception
- * in own thread
- */
- public static void copyThread(Reader in, Writer out)
- {
- try
- {
- Job job=new Job(in,out);
- if (!Singleton.__pool.dispatch(job))
- job.run();
- }
- catch(Exception e)
- {
- LOG.warn(e);
- }
- }
-
- /* ------------------------------------------------------------------- */
/** Copy Reader to Writer out until EOF or exception.
*/
public static void copy(Reader in, Writer out)
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java
index f9b2527..f93128f 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java
@@ -23,7 +23,7 @@
/* ------------------------------------------------------------ */
/** Iterating Callback.
- * <p>This specialised callback is used when breaking up an
+ * <p>This specialized callback is used when breaking up an
* asynchronous task into smaller asynchronous tasks. A typical pattern
* is that a successful callback is used to schedule the next sub task, but
* if that task completes quickly and uses the calling thread to callback
@@ -36,19 +36,16 @@
* <p>This callback is passed to the asynchronous handling of each sub
* task and a call the {@link #succeeded()} on this call back represents
* completion of the subtask. Only once all the subtasks are completed is
- * the {@link Callback#succeeded()} method called on the {@link Callback} instance
- * passed the the {@link #IteratingCallback(Callback)} constructor.</p>
+ * the {#completed()} method called.</p>
*
*/
public abstract class IteratingCallback implements Callback
{
private enum State { WAITING, ITERATING, SUCCEEDED, FAILED };
private final AtomicReference<State> _state = new AtomicReference<>(State.WAITING);
- private final Callback _callback;
- public IteratingCallback(Callback callback)
+ public IteratingCallback()
{
- _callback=callback;
}
/* ------------------------------------------------------------ */
@@ -63,6 +60,8 @@
*/
abstract protected boolean process() throws Exception;
+ abstract protected void completed();
+
/* ------------------------------------------------------------ */
/** This method is called initially to start processing and
* is then called by subsequent sub task success to continue
@@ -80,10 +79,10 @@
// Make some progress by calling process()
if (process())
{
- // A true return indicates we are finished a no further callbacks
+ // A true return indicates we are finished and no further callbacks
// are scheduled. So we must still be ITERATING.
if (_state.compareAndSet(State.ITERATING,State.SUCCEEDED))
- _callback.succeeded();
+ completed();
else
throw new IllegalStateException("Already "+_state.get());
return;
@@ -93,7 +92,7 @@
else if (_state.compareAndSet(State.ITERATING,State.WAITING))
// no callback yet, so break the loop and wait for it
break;
-
+
// The callback must have happened and we are either WAITING already or FAILED
// the loop test will work out which
}
@@ -103,7 +102,8 @@
failed(e);
}
}
-
+
+ /* ------------------------------------------------------------ */
@Override
public void succeeded()
{
@@ -132,7 +132,13 @@
}
}
}
-
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Derivations of this method should always call super.failed(x)
+ * to check the state before handling the failure.
+ * @see org.eclipse.jetty.util.Callback#failed(java.lang.Throwable)
+ */
@Override
public void failed(Throwable x)
{
@@ -151,11 +157,8 @@
continue;
default:
- throw new IllegalStateException("Already "+_state.get());
+ throw new IllegalStateException("Already "+_state.get(),x);
}
}
-
- _callback.failed(x);
}
-
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingNestedCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingNestedCallback.java
new file mode 100644
index 0000000..9415f16
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingNestedCallback.java
@@ -0,0 +1,68 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+
+/* ------------------------------------------------------------ */
+/** Iterating Nested Callback.
+ * <p>This specialized callback is used when breaking up an
+ * asynchronous task into smaller asynchronous tasks. A typical pattern
+ * is that a successful callback is used to schedule the next sub task, but
+ * if that task completes quickly and uses the calling thread to callback
+ * the success notification, this can result in a growing stack depth.
+ * </p>
+ * <p>To avoid this issue, this callback uses an AtomicBoolean to note
+ * if the success callback has been called during the processing of a
+ * sub task, and if so then the processing iterates rather than recurses.
+ * </p>
+ * <p>This callback is passed to the asynchronous handling of each sub
+ * task and a call the {@link #succeeded()} on this call back represents
+ * completion of the subtask. Only once all the subtasks are completed is
+ * the {@link Callback#succeeded()} method called on the {@link Callback} instance
+ * passed the the {@link #IteratingNestedCallback(Callback)} constructor.</p>
+ *
+ */
+public abstract class IteratingNestedCallback extends IteratingCallback
+{
+ final Callback _callback;
+
+ public IteratingNestedCallback(Callback callback)
+ {
+ _callback=callback;
+ }
+
+ @Override
+ protected void completed()
+ {
+ _callback.succeeded();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ super.failed(x);
+ _callback.failed(x);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x",getClass().getSimpleName(),hashCode());
+ }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java
index d79365c..805a310 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java
@@ -30,7 +30,7 @@
pkg.getImplementationVersion() != null)
VERSION = pkg.getImplementationVersion();
else
- VERSION = System.getProperty("jetty.version", "9.0.z-SNAPSHOT");
+ VERSION = System.getProperty("jetty.version", "9.1.z-SNAPSHOT");
}
private Jetty()
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Loader.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Loader.java
index d75600b..4a320f7 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Loader.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Loader.java
@@ -46,79 +46,69 @@
public class Loader
{
/* ------------------------------------------------------------ */
- public static URL getResource(Class<?> loadClass,String name, boolean checkParents)
+ public static URL getResource(Class<?> loadClass,String name)
{
URL url =null;
- ClassLoader loader=Thread.currentThread().getContextClassLoader();
- while (url==null && loader!=null )
- {
- url=loader.getResource(name);
- loader=(url==null&&checkParents)?loader.getParent():null;
- }
+ ClassLoader context_loader=Thread.currentThread().getContextClassLoader();
+ if (context_loader!=null)
+ url=context_loader.getResource(name);
- loader=loadClass==null?null:loadClass.getClassLoader();
- while (url==null && loader!=null )
+ if (url==null && loadClass!=null)
{
- url=loader.getResource(name);
- loader=(url==null&&checkParents)?loader.getParent():null;
- }
+ ClassLoader load_loader=loadClass.getClassLoader();
+ if (load_loader!=null && load_loader!=context_loader)
+ url=load_loader.getResource(name);
+ }
if (url==null)
- {
url=ClassLoader.getSystemResource(name);
- }
return url;
}
/* ------------------------------------------------------------ */
- @SuppressWarnings("rawtypes")
- public static Class loadClass(Class loadClass,String name)
- throws ClassNotFoundException
- {
- return loadClass(loadClass,name,false);
- }
-
- /* ------------------------------------------------------------ */
/** Load a class.
*
* @param loadClass
* @param name
- * @param checkParents If true, try loading directly from parent classloaders.
* @return Class
* @throws ClassNotFoundException
*/
@SuppressWarnings("rawtypes")
- public static Class loadClass(Class loadClass,String name,boolean checkParents)
+ public static Class loadClass(Class loadClass,String name)
throws ClassNotFoundException
{
ClassNotFoundException ex=null;
Class<?> c =null;
- ClassLoader loader=Thread.currentThread().getContextClassLoader();
- while (c==null && loader!=null )
+ ClassLoader context_loader=Thread.currentThread().getContextClassLoader();
+ if (context_loader!=null )
{
- try { c=loader.loadClass(name); }
- catch (ClassNotFoundException e) {if(ex==null)ex=e;}
- loader=(c==null&&checkParents)?loader.getParent():null;
- }
+ try { c=context_loader.loadClass(name); }
+ catch (ClassNotFoundException e) {ex=e;}
+ }
- loader=loadClass==null?null:loadClass.getClassLoader();
- while (c==null && loader!=null )
+ if (c==null && loadClass!=null)
{
- try { c=loader.loadClass(name); }
- catch (ClassNotFoundException e) {if(ex==null)ex=e;}
- loader=(c==null&&checkParents)?loader.getParent():null;
- }
+ ClassLoader load_loader=loadClass.getClassLoader();
+ if (load_loader!=null && load_loader!=context_loader)
+ {
+ try { c=load_loader.loadClass(name); }
+ catch (ClassNotFoundException e) {if(ex==null)ex=e;}
+ }
+ }
if (c==null)
{
try { c=Class.forName(name); }
- catch (ClassNotFoundException e) {if(ex==null)ex=e;}
+ catch (ClassNotFoundException e)
+ {
+ if(ex!=null)
+ throw ex;
+ throw e;
+ }
}
- if (c!=null)
- return c;
- throw ex;
+ return c;
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiMap.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiMap.java
index 40ae7a9..c778322 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiMap.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiMap.java
@@ -228,8 +228,8 @@
/**
* Merge values.
*
- * @param the
- * map to overlay on top of this one, merging together values if needed.
+ * @param map
+ * the map to overlay on top of this one, merging together values if needed.
* @return true if an existing key was merged with potentially new values, false if either no change was made, or there were only new keys.
*/
public boolean addAllValues(MultiMap<V> map)
@@ -284,7 +284,7 @@
* <p>
* NOTE: This is a SLOW operation, and is actively discouraged.
* @param value
- * @return
+ * @return true if contains simple value
*/
public boolean containsSimpleValue(V value)
{
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java
index b09edfb..c5ec817 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java
@@ -217,6 +217,16 @@
}
}
+
+ /**
+ * @see javax.servlet.http.Part#getSubmittedFileName()
+ */
+ @Override
+ public String getSubmittedFileName()
+ {
+ return getContentDispositionFilename();
+ }
+
public byte[] getBytes()
{
if (_bout!=null)
@@ -302,7 +312,6 @@
/**
* Get the file, if any, the data has been written to.
- * @return
*/
public File getFile ()
{
@@ -344,8 +353,6 @@
/**
* Get the already parsed parts.
- *
- * @return
*/
public Collection<Part> getParsedParts()
{
@@ -392,7 +399,6 @@
/**
* Parse, if necessary, the multipart data and return the list of Parts.
*
- * @return
* @throws IOException
* @throws ServletException
*/
@@ -415,7 +421,6 @@
* Get the named Part.
*
* @param name
- * @return
* @throws IOException
* @throws ServletException
*/
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
index 9889481..10752a1 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
@@ -19,7 +19,6 @@
package org.eclipse.jetty.util;
import java.io.UnsupportedEncodingException;
-import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java
index 1b09521..35a2d03 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java
@@ -49,7 +49,6 @@
/* ------------------------------------------------------------ */
/** Get and exact match from a String key
* @param s The key
- * @return
*/
public V get(String s);
@@ -58,7 +57,6 @@
* @param s The key
* @param offset The offset within the string of the key
* @param len the length of the key
- * @return
*/
public V get(String s,int offset,int len);
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java b/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java
index 48277bc..2c90292 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java
@@ -85,6 +85,11 @@
{
}
+ public UrlEncoded(String query)
+ {
+ decodeTo(query,this,ENCODING,-1);
+ }
+
/* ----------------------------------------------------------------- */
public void decode(String query)
{
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedObject.java b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedObject.java
index b51f2af..5db7c53 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedObject.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedObject.java
@@ -39,8 +39,6 @@
{
/**
* Description of the Managed Object
- *
- * @return
*/
String value() default "Not Specified";
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedOperation.java b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedOperation.java
index b9ff025..85f7526 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedOperation.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedOperation.java
@@ -48,8 +48,6 @@
{
/**
* Description of the Managed Object
- *
- * @return
*/
String value() default "Not Specified";
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/Name.java b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/Name.java
index 5d7f1b7..9118a62 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/Name.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/Name.java
@@ -39,13 +39,11 @@
{
/**
* the name of the parameter
- * @return
*/
String value();
/**
* the description of the parameter
- * @return
*/
String description() default "";
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Container.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Container.java
index ba83470..7ed8203 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Container.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Container.java
@@ -53,20 +53,19 @@
* Removes the given bean.
* If the bean is-a {@link Listener}, then also do an implicit {@link #removeEventListener(Listener)}.
* @return whether the bean was removed
- * @see #removeBeans()
*/
public boolean removeBean(Object o);
/**
* Add an event listener.
- * @see Container#addBean(Object), which also adds listeners if the bean is-a Listener
+ * @see Container#addBean(Object)
* @param listener
*/
public void addEventListener(Listener listener);
/**
* Remove an event listener.
- * @see Container#removeBean(Object), which also adds listeners if the bean is-a Listener
+ * @see Container#removeBean(Object)
* @param listener
*/
public void removeEventListener(Listener listener);
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
index 7897225..b0ca5d4 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
@@ -27,7 +27,6 @@
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
-import org.eclipse.jetty.util.component.Container.Listener;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/AbstractLogger.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/AbstractLogger.java
index bdbd00e..0acf4e3 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/AbstractLogger.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/AbstractLogger.java
@@ -75,4 +75,10 @@
}
return true;
}
+
+ public void debug(String msg, long arg)
+ {
+ if (isDebugEnabled())
+ debug(msg,new Long(arg));
+ }
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/JavaUtilLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/JavaUtilLog.java
index d01305f..c9047bc 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/JavaUtilLog.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/JavaUtilLog.java
@@ -111,6 +111,12 @@
_logger.log(Level.FINE,format(msg, args));
}
+ public void debug(String msg, long arg)
+ {
+ if (_logger.isLoggable(Level.FINE))
+ _logger.log(Level.FINE,format(msg, arg));
+ }
+
public void debug(Throwable thrown)
{
debug("", thrown);
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java
index 6a26c73..686d18b 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java
@@ -89,7 +89,7 @@
* configuration of the Log class in situations where access to the System.properties are
* either too late or just impossible.
*/
- URL testProps = Loader.getResource(Log.class,"jetty-logging.properties",true);
+ URL testProps = Loader.getResource(Log.class,"jetty-logging.properties");
if (testProps != null)
{
InputStream in = null;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Logger.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Logger.java
index afe405c..2eb40f0 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Logger.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Logger.java
@@ -85,6 +85,15 @@
* @param args the optional arguments
*/
public void debug(String msg, Object... args);
+
+
+ /**
+ * Formats and logs at debug level.
+ * avoids autoboxing of integers
+ * @param msg the formatting string
+ * @param value long value
+ */
+ public void debug(String msg, long value);
/**
* Logs the given Throwable information at debug level
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/LoggerLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/LoggerLog.java
index 7909ced..5747b46 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/LoggerLog.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/LoggerLog.java
@@ -151,6 +151,7 @@
}
}
+
public void debug(String msg, Object... args)
{
if (!_debug)
@@ -186,6 +187,21 @@
}
}
+ public void debug(String msg, long value)
+ {
+ if (!_debug)
+ return;
+
+ try
+ {
+ _debugMAA.invoke(_logger, new Object[]{new Long(value)});
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
public void ignore(Throwable ignored)
{
if (Log.isIgnored())
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java
index d9ebd17..3c944d6 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java
@@ -88,6 +88,12 @@
{
_logger.debug(msg, args);
}
+
+ public void debug(String msg, long arg)
+ {
+ if (isDebugEnabled())
+ _logger.debug(msg, new Object[]{new Long(arg)});
+ }
public void debug(Throwable thrown)
{
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java
index 1439b8a..652fc81 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java
@@ -519,6 +519,16 @@
}
}
+ public void debug(String msg, long arg)
+ {
+ if (isDebugEnabled())
+ {
+ StringBuilder buffer = new StringBuilder(64);
+ format(buffer,":DBUG:",msg,arg);
+ (_stderr==null?System.err:_stderr).println(buffer);
+ }
+ }
+
public void debug(Throwable thrown)
{
debug("",thrown);
@@ -538,7 +548,7 @@
{
long now = System.currentTimeMillis();
int ms=(int)(now%1000);
- String d = _dateCache.now();
+ String d = _dateCache.formatNow(now);
tag(buffer,d,ms,level);
format(buffer,msg,args);
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/BadResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/BadResource.java
index 410ae2c..5ee7a2d 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/BadResource.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/BadResource.java
@@ -22,7 +22,6 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.net.URL;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/EmptyResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/EmptyResource.java
new file mode 100644
index 0000000..a922add
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/EmptyResource.java
@@ -0,0 +1,130 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.channels.ReadableByteChannel;
+
+/**
+ * EmptyResource
+ *
+ * Represents a resource that does does not refer to any file, url, jar etc.
+ */
+public class EmptyResource extends Resource
+{
+ public static final Resource INSTANCE = new EmptyResource();
+
+ private EmptyResource()
+ {
+ }
+
+ @Override
+ public boolean isContainedIn(Resource r) throws MalformedURLException
+ {
+ return false;
+ }
+
+ @Override
+ public void close()
+ {
+ }
+
+ @Override
+ public boolean exists()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isDirectory()
+ {
+ return false;
+ }
+
+ @Override
+ public long lastModified()
+ {
+ return 0;
+ }
+
+ @Override
+ public long length()
+ {
+ return 0;
+ }
+
+ @Override
+ public URL getURL()
+ {
+ return null;
+ }
+
+ @Override
+ public File getFile() throws IOException
+ {
+ return null;
+ }
+
+ @Override
+ public String getName()
+ {
+ return null;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException
+ {
+ return null;
+ }
+
+ @Override
+ public ReadableByteChannel getReadableByteChannel() throws IOException
+ {
+ return null;
+ }
+
+ @Override
+ public boolean delete() throws SecurityException
+ {
+ return false;
+ }
+
+ @Override
+ public boolean renameTo(Resource dest) throws SecurityException
+ {
+ return false;
+ }
+
+ @Override
+ public String[] list()
+ {
+ return null;
+ }
+
+ @Override
+ public Resource addPath(String path) throws IOException, MalformedURLException
+ {
+ return null;
+ }
+
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java
index d6503cc..eb924d9 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java
@@ -32,8 +32,6 @@
import java.nio.file.StandardOpenOption;
import java.security.Permission;
-import javax.management.RuntimeErrorException;
-
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
@@ -56,7 +54,7 @@
/* ------------------------------------------------------------ */
private final File _file;
private final String _uri;
- private final URL _alias;
+ private final URI _alias;
/* -------------------------------------------------------- */
public FileResource(URL url)
@@ -115,16 +113,7 @@
{
// URI and File URI are different. Is it just an encoding difference?
if (!file_uri.toString().equals(URIUtil.decodePath(uri.toString())))
- {
- try
- {
- _alias=_file.toURI().toURL();
- }
- catch (MalformedURLException e)
- {
- throw new IllegalArgumentException(e);
- }
- }
+ _alias=_file.toURI();
else
_alias=checkAlias(_file);
}
@@ -155,7 +144,7 @@
}
/* -------------------------------------------------------- */
- private static URL checkAlias(File file)
+ private static URI checkAlias(File file)
{
try
{
@@ -165,7 +154,7 @@
if (!abs.equals(can))
{
LOG.debug("ALIAS abs={} can={}",abs,can);
- return new File(can).toURI().toURL();
+ return new File(can).toURI();
}
}
catch(IOException e)
@@ -174,7 +163,7 @@
LOG.debug(e);
try
{
- return new URL("http://eclipse.org/bad/canonical/alias");
+ return new URI("http://eclipse.org/bad/canonical/alias");
}
catch(Exception e2)
{
@@ -225,7 +214,7 @@
/* ------------------------------------------------------------ */
@Override
- public URL getAlias()
+ public URI getAlias()
{
return _alias;
}
@@ -421,6 +410,12 @@
throw new IllegalStateException(e);
}
}
+
+ @Override
+ public URI getURI()
+ {
+ return _file.toURI();
+ }
@Override
public String toString()
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
index f4b11a9..536b780 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
@@ -29,7 +29,9 @@
import java.net.URL;
import java.nio.channels.ReadableByteChannel;
import java.text.DateFormat;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Date;
import org.eclipse.jetty.util.B64Code;
@@ -261,7 +263,7 @@
/* ------------------------------------------------------------ */
/** Find a classpath resource.
* The {@link java.lang.Class#getResource(String)} method is used to lookup the resource. If it is not
- * found, then the {@link Loader#getResource(Class, String, boolean)} method is used.
+ * found, then the {@link Loader#getResource(Class, String)} method is used.
* If it is still not found, then {@link ClassLoader#getSystemResource(String)} is used.
* Unlike {@link ClassLoader#getSystemResource(String)} this method does not check for normal resources.
* @param name The relative name of the resource
@@ -275,7 +277,7 @@
URL url=Resource.class.getResource(name);
if (url==null)
- url=Loader.getResource(Resource.class,name,checkParents);
+ url=Loader.getResource(Resource.class,name);
if (url==null)
return null;
return newResource(url,useCaches);
@@ -471,7 +473,7 @@
/**
* @return The canonical Alias of this resource or null if none.
*/
- public URL getAlias()
+ public URI getAlias()
{
return null;
}
@@ -663,6 +665,34 @@
}
/* ------------------------------------------------------------ */
+ public Collection<Resource> getAllResources()
+ {
+ try
+ {
+ ArrayList<Resource> deep=new ArrayList<>();
+ {
+ String[] list=list();
+ if (list!=null)
+ {
+ for (String i:list)
+ {
+ Resource r=addPath(i);
+ if (r.isDirectory())
+ deep.addAll(r.getAllResources());
+ else
+ deep.add(r);
+ }
+ }
+ }
+ return deep;
+ }
+ catch(Exception e)
+ {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
/** Generate a properly encoded URL from a {@link File} instance.
* @param file Target file.
* @return URL of the target file.
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java
index 4bb59a4..283a7c8 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java
@@ -21,7 +21,6 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java
index 4d2d911..85ae39f 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java
@@ -23,6 +23,8 @@
/* ------------------------------------------------------------ */
/**
+ * Constraint
+ *
* Describe an auth and/or data constraint.
*
*
@@ -65,6 +67,8 @@
public final static String NONE = "NONE";
public final static String ANY_ROLE = "*";
+
+ public final static String ANY_AUTH = "**"; //Servlet Spec 3.1 pg 140
/* ------------------------------------------------------------ */
private String _name;
@@ -74,6 +78,8 @@
private int _dataConstraint = DC_UNSET;
private boolean _anyRole = false;
+
+ private boolean _anyAuth = false;
private boolean _authenticate = false;
@@ -119,9 +125,15 @@
{
_roles = roles;
_anyRole = false;
+ _anyAuth = false;
if (roles != null)
- for (int i = roles.length; !_anyRole && i-- > 0;)
+ {
+ for (int i = roles.length; i-- > 0;)
+ {
_anyRole |= ANY_ROLE.equals(roles[i]);
+ _anyAuth |= ANY_AUTH.equals(roles[i]);
+ }
+ }
}
/* ------------------------------------------------------------ */
@@ -132,6 +144,16 @@
{
return _anyRole;
}
+
+
+ /* ------------------------------------------------------------ */
+ /** Servlet Spec 3.1, pg 140
+ * @return True if any authenticated user is permitted (ie a role "**" was specified in the constraint).
+ */
+ public boolean isAnyAuth()
+ {
+ return _anyAuth;
+ }
/* ------------------------------------------------------------ */
/**
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java
index 8ce30d7..f7d34c8 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java
@@ -22,7 +22,6 @@
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
index 9869566..e13fda0 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
@@ -44,6 +44,7 @@
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+
import javax.net.ssl.CertPathTrustManagerParameters;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
@@ -1390,7 +1391,7 @@
{
try
{
- javax.security.cert.X509Certificate javaxCerts[]=sslSession.getPeerCertificateChain();
+ Certificate[] javaxCerts=sslSession.getPeerCertificates();
if (javaxCerts==null||javaxCerts.length==0)
return null;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
index c5bb1d1..cd72d83 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
@@ -129,10 +129,10 @@
jobs.offer(noop);
// try to jobs complete naturally for half our stop time
- long stopby = System.currentTimeMillis() + timeout / 2;
+ long stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
for (Thread thread : _threads)
{
- long canwait = stopby - System.currentTimeMillis();
+ long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
if (canwait > 0)
thread.join(canwait);
}
@@ -145,10 +145,10 @@
thread.interrupt();
// wait again for the other half of our stop time
- stopby = System.currentTimeMillis() + timeout / 2;
+ stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
for (Thread thread : _threads)
{
- long canwait = stopby - System.currentTimeMillis();
+ long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
if (canwait > 0)
thread.join(canwait);
}
@@ -242,7 +242,7 @@
}
/**
- * @param name Name of the BoundedThreadPool to use when naming Threads.
+ * @param name Name of this thread pool to use when naming threads.
*/
public void setName(String name)
{
@@ -303,7 +303,7 @@
}
/**
- * @return The name of the BoundedThreadPool.
+ * @return The name of the this thread pool
*/
@ManagedAttribute("name of the thread pool")
public String getName()
@@ -351,18 +351,11 @@
{
_detailedDump = detailedDump;
}
-
- @Override
- public boolean dispatch(Runnable job)
- {
- LOG.debug("{} dispatched {}", this, job);
- return isRunning() && _jobs.offer(job);
- }
-
+
@Override
public void execute(Runnable job)
{
- if (!dispatch(job))
+ if (!isRunning() || !_jobs.offer(job))
{
LOG.warn("{} rejected {}", this, job);
throw new RejectedExecutionException(job.toString());
@@ -562,8 +555,8 @@
if (size > _minThreads)
{
long last = _lastShrink.get();
- long now = System.currentTimeMillis();
- if (last == 0 || (now - last) > _idleTimeout)
+ long now = System.nanoTime();
+ if (last == 0 || (now - last) > TimeUnit.MILLISECONDS.toNanos(_idleTimeout))
{
shrink = _lastShrink.compareAndSet(last, now) &&
_threadsStarted.compareAndSet(size, size - 1);
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java
index b5e4681..dd08aca 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.util.thread;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPool.java
index d88e931..31f5488 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPool.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPool.java
@@ -27,19 +27,14 @@
/* ------------------------------------------------------------ */
/** ThreadPool.
*
+ * A specialization of Executor interface that provides reporting methods (eg {@link #getThreads()})
+ * and the option of configuration methods (e.g. @link {@link SizedThreadPool#setMaxThreads(int)}).
*
*/
@ManagedObject("Pool of Threads")
public interface ThreadPool extends Executor
{
/* ------------------------------------------------------------ */
- /**
- * @deprecated use {@link Executor#execute(Runnable)}
- */
- @Deprecated
- public abstract boolean dispatch(Runnable job);
-
- /* ------------------------------------------------------------ */
/**
* Blocks until the thread pool is {@link LifeCycle#stop stopped}.
*/
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Timeout.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Timeout.java
deleted file mode 100644
index 1045a89..0000000
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Timeout.java
+++ /dev/null
@@ -1,381 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.util.thread;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-/* ------------------------------------------------------------ */
-/** Timeout queue.
- * This class implements a timeout queue for timers that are at least as likely to be cancelled as they are to expire.
- * Unlike the util timeout class, the duration of the timeouts is shared by all scheduled tasks and if the duration
- * is changed, this affects all scheduled tasks.
- * <p>
- * The nested class Task should be extended by users of this class to obtain call back notification of
- * expires.
- */
-@Deprecated
-public class Timeout
-{
- private static final Logger LOG = Log.getLogger(Timeout.class);
- private Object _lock;
- private long _duration;
- private volatile long _now=System.currentTimeMillis();
- private Task _head=new Task();
-
- /* ------------------------------------------------------------ */
- public Timeout()
- {
- _lock=new Object();
- _head._timeout=this;
- }
-
- /* ------------------------------------------------------------ */
- public Timeout(Object lock)
- {
- _lock=lock;
- _head._timeout=this;
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @return Returns the duration.
- */
- public long getDuration()
- {
- return _duration;
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @param duration The duration to set.
- */
- public void setDuration(long duration)
- {
- _duration = duration;
- }
-
- /* ------------------------------------------------------------ */
- public long setNow()
- {
- return _now=System.currentTimeMillis();
- }
-
- /* ------------------------------------------------------------ */
- public long getNow()
- {
- return _now;
- }
-
- /* ------------------------------------------------------------ */
- public void setNow(long now)
- {
- _now=now;
- }
-
- /* ------------------------------------------------------------ */
- /** Get an expired tasks.
- * This is called instead of {@link #tick()} to obtain the next
- * expired Task, but without calling it's {@link Task#expire()} or
- * {@link Task#expired()} methods.
- *
- * @return the next expired task or null.
- */
- public Task expired()
- {
- synchronized (_lock)
- {
- long _expiry = _now-_duration;
-
- if (_head._next!=_head)
- {
- Task task = _head._next;
- if (task._timestamp>_expiry)
- return null;
-
- task.unlink();
- task._expired=true;
- return task;
- }
- return null;
- }
- }
-
- /* ------------------------------------------------------------ */
- public void tick()
- {
- final long expiry = _now-_duration;
-
- Task task=null;
- while (true)
- {
- try
- {
- synchronized (_lock)
- {
- task= _head._next;
- if (task==_head || task._timestamp>expiry)
- break;
- task.unlink();
- task._expired=true;
- task.expire();
- }
-
- task.expired();
- }
- catch(Throwable th)
- {
- LOG.warn(Log.EXCEPTION,th);
- }
- }
- }
-
- /* ------------------------------------------------------------ */
- public void tick(long now)
- {
- _now=now;
- tick();
- }
-
- /* ------------------------------------------------------------ */
- public void schedule(Task task)
- {
- schedule(task,0L);
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @param task
- * @param delay A delay in addition to the default duration of the timeout
- */
- public void schedule(Task task,long delay)
- {
- synchronized (_lock)
- {
- if (task._timestamp!=0)
- {
- task.unlink();
- task._timestamp=0;
- }
- task._timeout=this;
- task._expired=false;
- task._delay=delay;
- task._timestamp = _now+delay;
-
- Task last=_head._prev;
- while (last!=_head)
- {
- if (last._timestamp <= task._timestamp)
- break;
- last=last._prev;
- }
- last.link(task);
- }
- }
-
-
- /* ------------------------------------------------------------ */
- public void cancelAll()
- {
- synchronized (_lock)
- {
- _head._next=_head._prev=_head;
- }
- }
-
- /* ------------------------------------------------------------ */
- public boolean isEmpty()
- {
- synchronized (_lock)
- {
- return _head._next==_head;
- }
- }
-
- /* ------------------------------------------------------------ */
- public long getTimeToNext()
- {
- synchronized (_lock)
- {
- if (_head._next==_head)
- return -1;
- long to_next = _duration+_head._next._timestamp-_now;
- return to_next<0?0:to_next;
- }
- }
-
- /* ------------------------------------------------------------ */
- @Override
- public String toString()
- {
- StringBuffer buf = new StringBuffer();
- buf.append(super.toString());
-
- Task task = _head._next;
- while (task!=_head)
- {
- buf.append("-->");
- buf.append(task);
- task=task._next;
- }
-
- return buf.toString();
- }
-
- /* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- /** Task.
- * The base class for scheduled timeouts. This class should be
- * extended to implement the expire() method, which is called if the
- * timeout expires.
- *
- *
- *
- */
- public static class Task
- {
- Task _next;
- Task _prev;
- Timeout _timeout;
- long _delay;
- long _timestamp=0;
- boolean _expired=false;
-
- /* ------------------------------------------------------------ */
- protected Task()
- {
- _next=_prev=this;
- }
-
- /* ------------------------------------------------------------ */
- public long getTimestamp()
- {
- return _timestamp;
- }
-
- /* ------------------------------------------------------------ */
- public long getAge()
- {
- final Timeout t = _timeout;
- if (t!=null)
- {
- final long now=t._now;
- if (now!=0 && _timestamp!=0)
- return now-_timestamp;
- }
- return 0;
- }
-
- /* ------------------------------------------------------------ */
- private void unlink()
- {
- _next._prev=_prev;
- _prev._next=_next;
- _next=_prev=this;
- _expired=false;
- }
-
- /* ------------------------------------------------------------ */
- private void link(Task task)
- {
- Task next_next = _next;
- _next._prev=task;
- _next=task;
- _next._next=next_next;
- _next._prev=this;
- }
-
- /* ------------------------------------------------------------ */
- /** Schedule the task on the given timeout.
- * The task exiry will be called after the timeout duration.
- * @param timer
- */
- public void schedule(Timeout timer)
- {
- timer.schedule(this);
- }
-
- /* ------------------------------------------------------------ */
- /** Schedule the task on the given timeout.
- * The task exiry will be called after the timeout duration.
- * @param timer
- */
- public void schedule(Timeout timer, long delay)
- {
- timer.schedule(this,delay);
- }
-
- /* ------------------------------------------------------------ */
- /** Reschedule the task on the current timeout.
- * The task timeout is rescheduled as if it had been cancelled and
- * scheduled on the current timeout.
- */
- public void reschedule()
- {
- Timeout timeout = _timeout;
- if (timeout!=null)
- timeout.schedule(this,_delay);
- }
-
- /* ------------------------------------------------------------ */
- /** Cancel the task.
- * Remove the task from the timeout.
- */
- public void cancel()
- {
- Timeout timeout = _timeout;
- if (timeout!=null)
- {
- synchronized (timeout._lock)
- {
- unlink();
- _timestamp=0;
- }
- }
- }
-
- /* ------------------------------------------------------------ */
- public boolean isExpired() { return _expired; }
-
- /* ------------------------------------------------------------ */
- public boolean isScheduled() { return _next!=this; }
-
- /* ------------------------------------------------------------ */
- /** Expire task.
- * This method is called when the timeout expires. It is called
- * in the scope of the synchronize block (on this) that sets
- * the {@link #isExpired()} state to true.
- * @see #expired() For an unsynchronized callback.
- */
- protected void expire(){}
-
- /* ------------------------------------------------------------ */
- /** Expire task.
- * This method is called when the timeout expires. It is called
- * outside of any synchronization scope and may be delayed.
- *
- */
- public void expired(){}
-
- }
-
-}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/B64CodeTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/B64CodeTest.java
index 05033fa..2e4983d 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/B64CodeTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/B64CodeTest.java
@@ -19,12 +19,12 @@
package org.eclipse.jetty.util;
+import java.nio.charset.StandardCharsets;
+
import junit.framework.Assert;
import org.junit.Test;
-import java.nio.charset.StandardCharsets;
-
public class B64CodeTest
{
String text = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.";
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/BenchmarkHelper.java b/jetty-util/src/test/java/org/eclipse/jetty/util/BenchmarkHelper.java
index 49e7195..0f0ced9 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/BenchmarkHelper.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/BenchmarkHelper.java
@@ -39,14 +39,14 @@
private final CompilationMXBean jitCompiler;
private final MemoryMXBean heapMemory;
private final AtomicInteger starts = new AtomicInteger();
- private volatile MemoryPoolMXBean youngMemoryPool;
- private volatile MemoryPoolMXBean survivorMemoryPool;
- private volatile MemoryPoolMXBean oldMemoryPool;
- private volatile boolean hasMemoryPools;
+ private final MemoryPoolMXBean youngMemoryPool;
+ private final MemoryPoolMXBean survivorMemoryPool;
+ private final MemoryPoolMXBean oldMemoryPool;
+ private final boolean hasMemoryPools;
+ private final GarbageCollectorMXBean youngCollector;
+ private final GarbageCollectorMXBean oldCollector;
+ private final boolean hasCollectors;
private volatile ScheduledFuture<?> memoryPoller;
- private volatile GarbageCollectorMXBean youngCollector;
- private volatile GarbageCollectorMXBean oldCollector;
- private volatile boolean hasCollectors;
private volatile ScheduledExecutorService scheduler;
private volatile boolean polling;
private volatile long lastYoungUsed;
@@ -70,35 +70,47 @@
this.heapMemory = ManagementFactory.getMemoryMXBean();
List<MemoryPoolMXBean> memoryPools = ManagementFactory.getMemoryPoolMXBeans();
+ MemoryPoolMXBean ymp=null;
+ MemoryPoolMXBean smp=null;
+ MemoryPoolMXBean omp=null;
+
for (MemoryPoolMXBean memoryPool : memoryPools)
{
if ("PS Eden Space".equals(memoryPool.getName()) ||
"Par Eden Space".equals(memoryPool.getName()) ||
"G1 Eden".equals(memoryPool.getName()))
- youngMemoryPool = memoryPool;
+ ymp = memoryPool;
else if ("PS Survivor Space".equals(memoryPool.getName()) ||
"Par Survivor Space".equals(memoryPool.getName()) ||
"G1 Survivor".equals(memoryPool.getName()))
- survivorMemoryPool = memoryPool;
+ smp = memoryPool;
else if ("PS Old Gen".equals(memoryPool.getName()) ||
"CMS Old Gen".equals(memoryPool.getName()) ||
"G1 Old Gen".equals(memoryPool.getName()))
- oldMemoryPool = memoryPool;
+ omp = memoryPool;
}
+ youngMemoryPool=ymp;
+ survivorMemoryPool=smp;
+ oldMemoryPool=omp;
+
hasMemoryPools = youngMemoryPool != null && survivorMemoryPool != null && oldMemoryPool != null;
List<GarbageCollectorMXBean> garbageCollectors = ManagementFactory.getGarbageCollectorMXBeans();
+ GarbageCollectorMXBean yc=null;
+ GarbageCollectorMXBean oc=null;
for (GarbageCollectorMXBean garbageCollector : garbageCollectors)
{
if ("PS Scavenge".equals(garbageCollector.getName()) ||
"ParNew".equals(garbageCollector.getName()) ||
"G1 Young Generation".equals(garbageCollector.getName()))
- youngCollector = garbageCollector;
+ yc = garbageCollector;
else if ("PS MarkSweep".equals(garbageCollector.getName()) ||
"ConcurrentMarkSweep".equals(garbageCollector.getName()) ||
"G1 Old Generation".equals(garbageCollector.getName()))
- oldCollector = garbageCollector;
+ oc = garbageCollector;
}
+ youngCollector=yc;
+ oldCollector=oc;
hasCollectors = youngCollector != null && oldCollector != null;
}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java
index b6c85f3..9904364 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java
@@ -19,6 +19,11 @@
package org.eclipse.jetty.util;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.BufferOverflowException;
@@ -32,11 +37,6 @@
import org.junit.Ignore;
import org.junit.Test;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
public class BufferUtilTest
{
@Test
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueueUnboundedTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueueUnboundedTest.java
deleted file mode 100644
index 8545f91..0000000
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueueUnboundedTest.java
+++ /dev/null
@@ -1,166 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.util;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.toolchain.test.TestTracker;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-
-public class ConcurrentArrayBlockingQueueUnboundedTest extends ConcurrentArrayQueueTest
-{
- @Rule
- public final TestTracker tracker = new TestTracker();
-
- @Override
- protected ConcurrentArrayBlockingQueue<Integer> newConcurrentArrayQueue(int blockSize)
- {
- return new ConcurrentArrayBlockingQueue.Unbounded<>(blockSize);
- }
-
- @Test
- public void testOfferTake() throws Exception
- {
- ConcurrentArrayBlockingQueue<Integer> queue = newConcurrentArrayQueue(32);
- Integer item = 1;
- Assert.assertTrue(queue.offer(item));
- Integer result = queue.take();
- Assert.assertSame(item, result);
- }
-
- @Test
- public void testTimedPollOffer() throws Exception
- {
- final ConcurrentArrayBlockingQueue<Integer> queue = newConcurrentArrayQueue(32);
-
- final long timeout = 1000;
- final Integer item = 1;
- new Thread()
- {
- @Override
- public void run()
- {
- try
- {
- TimeUnit.MILLISECONDS.sleep(timeout);
- queue.offer(item);
- }
- catch (InterruptedException x)
- {
- x.printStackTrace();
- }
- }
- }.start();
-
- Integer result = queue.poll(2 * timeout, TimeUnit.MILLISECONDS);
- Assert.assertNotNull(result);
- }
-
- @Test
- public void testConcurrentOfferTake() throws Exception
- {
- final ConcurrentArrayBlockingQueue<Integer> queue = newConcurrentArrayQueue(512);
- int readerCount = 16;
- final int factor = 2;
- int writerCount = readerCount * factor;
- final int iterations = 4096;
- for (int runs = 0; runs < 16; ++runs)
- {
- ExecutorService executor = Executors.newFixedThreadPool(readerCount + writerCount);
- List<Future<Integer>> readers = new ArrayList<>();
- for (int i = 0; i < readerCount / 2; ++i)
- {
- final int reader = i;
- readers.add(executor.submit(new Callable<Integer>()
- {
- @Override
- public Integer call() throws Exception
- {
- int sum = 0;
- for (int j = 0; j < iterations * factor; ++j)
- sum += queue.take();
- //System.err.println("Taking reader " + reader + " completed: " + sum);
- return sum;
- }
- }));
- readers.add(executor.submit(new Callable<Integer>()
- {
- @Override
- public Integer call() throws Exception
- {
- int sum = 0;
- for (int j = 0; j < iterations * factor; ++j)
- sum += queue.poll(5, TimeUnit.SECONDS);
- //System.err.println("Polling Reader " + reader + " completed: " + sum);
- return sum;
- }
- }));
- }
- for (int i = 0; i < writerCount; ++i)
- {
- final int writer = i;
- executor.submit(new Callable<Object>()
- {
- @Override
- public Object call() throws Exception
- {
- for (int j = 0; j < iterations; ++j)
- queue.offer(1);
- //System.err.println("Writer " + writer + " completed");
- return null;
- }
- });
- }
-
- int sum = 0;
- for (Future<Integer> result : readers)
- sum += result.get();
-
- Assert.assertEquals(writerCount * iterations, sum);
- Assert.assertTrue(queue.isEmpty());
- }
- }
-
- @Test
- public void testDrain() throws Exception
- {
- final ConcurrentArrayBlockingQueue<Integer> queue = newConcurrentArrayQueue(512);
- List<Integer> chunk1 = Arrays.asList(1, 2);
- List<Integer> chunk2 = Arrays.asList(3, 4, 5);
- queue.addAll(chunk1);
- queue.addAll(chunk2);
-
- List<Integer> drainer1 = new ArrayList<>();
- queue.drainTo(drainer1, chunk1.size());
- List<Integer> drainer2 = new ArrayList<>();
- queue.drainTo(drainer2, chunk2.size());
-
- Assert.assertEquals(chunk1, drainer1);
- Assert.assertEquals(chunk2, drainer2);
- }
-}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java
index 32bda5c..6bbac87 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java
@@ -44,14 +44,13 @@
{
//@WAS: Test t = new Test("org.eclipse.jetty.util.DateCache");
// 012345678901234567890123456789
- DateCache dc = new DateCache("EEE, dd MMM yyyy HH:mm:ss zzz ZZZ",Locale.US);
- dc.setTimeZone(TimeZone.getTimeZone("GMT"));
+ DateCache dc = new DateCache("EEE, dd MMM yyyy HH:mm:ss zzz ZZZ",Locale.US,TimeZone.getTimeZone("GMT"));
Thread.sleep(2000);
long now=System.currentTimeMillis();
long end=now+3000;
- String f=dc.format(now);
+ String f=dc.formatNow(now);
String last=f;
int hits=0;
@@ -60,7 +59,7 @@
while (now<end)
{
last=f;
- f=dc.format(now);
+ f=dc.formatNow(now);
// System.err.printf("%s %s%n",f,last==f);
if (last==f)
hits++;
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiMapTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiMapTest.java
index 0c69c9b..2efe3cf 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiMapTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiMapTest.java
@@ -122,7 +122,7 @@
}
/**
- * Tests {@link MultiMap#putValues(String, Object...)}
+ * Tests {@link MultiMap#putValues(String, String...)}
*/
@Test
public void testPutValues_StringArray()
@@ -138,7 +138,7 @@
}
/**
- * Tests {@link MultiMap#putValues(String, Object...)}
+ * Tests {@link MultiMap#putValues(String, String...)}
*/
@Test
public void testPutValues_VarArgs()
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
index a352d12..e5148cf 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
@@ -18,8 +18,17 @@
package org.eclipse.jetty.util;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -589,7 +598,7 @@
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1));
- assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getContentDispositionFilename(), is("Taken on Aug 22 \\ 2012.jpg"));
+ assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("Taken on Aug 22 \\ 2012.jpg"));
}
@Test
@@ -611,7 +620,7 @@
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1));
- assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getContentDispositionFilename(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
+ assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
}
@Test
@@ -632,7 +641,7 @@
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(1));
- assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getContentDispositionFilename(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
+ assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
}
public void testMulti ()
@@ -683,7 +692,7 @@
assertFalse(f2.exists()); //2nd written file was explicitly deleted
MultiPart stuff = (MultiPart)mpis.getPart("stuff");
- assertThat(stuff.getContentDispositionFilename(), is(filename));
+ assertThat(stuff.getSubmittedFileName(), is(filename));
assertThat(stuff.getContentType(),is("text/plain"));
assertThat(stuff.getHeader("Content-Type"),is("text/plain"));
assertThat(stuff.getHeaders("content-type").size(),is(1));
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/QueueBenchmarkTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/QueueBenchmarkTest.java
index 750c288..046aff5 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/QueueBenchmarkTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/QueueBenchmarkTest.java
@@ -87,8 +87,6 @@
final int iterations = 16 * 1024 * 1024;
final List<Queue<Runnable>> queues = new ArrayList<>();
- queues.add(new ConcurrentArrayBlockingQueue.Unbounded<Runnable>());
- queues.add(new ConcurrentArrayBlockingQueue.Bounded<Runnable>(iterations * writers));
queues.add(new LinkedBlockingQueue<Runnable>());
queues.add(new ArrayBlockingQueue<Runnable>(iterations * writers));
queues.add(new BlockingArrayQueue<Runnable>(iterations * writers));
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java
index 6503ee3..52f2c4c 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java
@@ -22,11 +22,11 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import java.nio.charset.StandardCharsets;
+
import org.junit.Assert;
import org.junit.Test;
-import java.nio.charset.StandardCharsets;
-
public class StringUtilTest
{
@Test
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
index d1ff2fb..cdc8660 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
@@ -23,7 +23,6 @@
import java.util.Arrays;
import java.util.Collection;
-import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/URITest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/URITest.java
index 42b6b61..91178ed 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/URITest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/URITest.java
@@ -20,7 +20,6 @@
import static org.junit.Assert.assertEquals;
-import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.junit.Test;
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderTest.java
index 9967074..859f1b0 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderTest.java
@@ -21,11 +21,11 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import java.nio.charset.StandardCharsets;
+
import org.junit.Assert;
import org.junit.Test;
-import java.nio.charset.StandardCharsets;
-
public class Utf8StringBuilderTest
{
@Test
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileResourceTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileResourceTest.java
index 5c754c5..153eacb 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileResourceTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileResourceTest.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.util.resource;
import static org.hamcrest.Matchers.is;
+import static org.junit.Assume.assumeTrue;
import java.io.File;
import java.io.IOException;
@@ -30,13 +31,11 @@
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.TestingDir;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.UrlEncoded;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
-import static org.junit.Assume.assumeTrue;
public class FileResourceTest
{
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceAliasTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceAliasTest.java
index 546d80a..be7287b 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceAliasTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceAliasTest.java
@@ -25,13 +25,9 @@
import java.io.File;
import java.net.MalformedURLException;
-import java.net.URISyntaxException;
-
-import junit.framework.Assert;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.toolchain.test.TestingDir;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java
index efe7e84..5d9cc9d 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java
@@ -30,9 +30,9 @@
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
-
import java.util.zip.ZipFile;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
@@ -297,10 +297,20 @@
assertTrue(entries.contains("numbers"));
IO.delete(extract);
-
-
}
+ /* ------------------------------------------------------------ */
+ @Test
+ public void testJarFileGetAllResoures()
+ throws Exception
+ {
+ String s = "jar:"+__userURL+"TestData/test.zip!/subdir/";
+ Resource r = Resource.newResource(s);
+ Collection<Resource> deep=r.getAllResources();
+
+ assertEquals(4, deep.size());
+ }
+
@Test
public void testJarFileIsContainedIn ()
throws Exception
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java
index bd92209..e58d7a9 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java
@@ -18,6 +18,12 @@
package org.eclipse.jetty.util.ssl;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
@@ -32,12 +38,6 @@
import org.junit.Before;
import org.junit.Test;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
public class SslContextFactoryTest
{
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java
index f84e866..c2361cf 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java
@@ -88,7 +88,7 @@
waitForIdle(tp,5);
RunningJob job=new RunningJob();
- tp.dispatch(job);
+ tp.execute(job);
waitForIdle(tp,4);
waitForThreads(tp,5);
@@ -104,14 +104,14 @@
for (int i=0;i<jobs.length;i++)
{
jobs[i]=new RunningJob();
- tp.dispatch(jobs[i]);
+ tp.execute(jobs[i]);
}
waitForIdle(tp,1);
waitForThreads(tp,6);
job=new RunningJob();
- tp.dispatch(job);
+ tp.execute(job);
waitForIdle(tp,1);
waitForThreads(tp,7);
@@ -135,7 +135,7 @@
for (int i=0;i<jobs.length;i++)
{
jobs[i]=new RunningJob();
- tp.dispatch(jobs[i]);
+ tp.execute(jobs[i]);
}
waitForIdle(tp,0);
@@ -182,10 +182,10 @@
waitForThreads(tp,2);
sleep.set(200);
- tp.dispatch(job);
- tp.dispatch(job);
+ tp.execute(job);
+ tp.execute(job);
for (int i=0;i<20;i++)
- tp.dispatch(job);
+ tp.execute(job);
waitForThreads(tp,10);
waitForIdle(tp,0);
@@ -193,7 +193,7 @@
sleep.set(5);
for (int i=0;i<500;i++)
{
- tp.dispatch(job);
+ tp.execute(job);
Thread.sleep(10);
}
waitForThreads(tp,2);
@@ -206,7 +206,7 @@
QueuedThreadPool tp= new QueuedThreadPool();
tp.setStopTimeout(500);
tp.start();
- tp.dispatch(new Runnable(){
+ tp.execute(new Runnable(){
public void run () {
while (true) {
try {
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/TimeoutTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/TimeoutTest.java
deleted file mode 100644
index 4005b2d..0000000
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/TimeoutTest.java
+++ /dev/null
@@ -1,266 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.util.thread;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicIntegerArray;
-
-import org.junit.Before;
-import org.junit.Test;
-
-
-public class TimeoutTest
-{
- private boolean _stress=Boolean.getBoolean("STRESS");
-
- Object lock = new Object();
- Timeout timeout = new Timeout(null);
- Timeout.Task[] tasks;
-
- @Before
- public void setUp() throws Exception
- {
- timeout=new Timeout(lock);
- tasks= new Timeout.Task[10];
-
- for (int i=0;i<tasks.length;i++)
- {
- tasks[i]=new Timeout.Task();
- timeout.setNow(1000+i*100);
- timeout.schedule(tasks[i]);
- }
- timeout.setNow(100);
- }
-
- /* ------------------------------------------------------------ */
- @Test
- public void testExpiry()
- {
- timeout.setDuration(200);
- timeout.setNow(1500);
- timeout.tick();
-
- for (int i=0;i<tasks.length;i++)
- {
- assertEquals("isExpired "+i,i<4, tasks[i].isExpired());
- }
- }
-
- /* ------------------------------------------------------------ */
- @Test
- public void testCancel()
- {
- timeout.setDuration(200);
- timeout.setNow(1700);
-
- for (int i=0;i<tasks.length;i++)
- if ((i+1)%2==0)
- tasks[i].cancel();
-
- timeout.tick();
-
- for (int i=0;i<tasks.length;i++)
- {
- assertEquals("isExpired "+i,i%2==0 && i<6, tasks[i].isExpired());
- }
- }
-
- /* ------------------------------------------------------------ */
- @Test
- public void testTouch()
- {
- timeout.setDuration(200);
- timeout.setNow(1350);
- timeout.schedule(tasks[2]);
-
- timeout.setNow(1500);
- timeout.tick();
- for (int i=0;i<tasks.length;i++)
- {
- assertEquals("isExpired "+i,i!=2 && i<4, tasks[i].isExpired());
- }
-
- timeout.setNow(1550);
- timeout.tick();
- for (int i=0;i<tasks.length;i++)
- {
- assertEquals("isExpired "+i, i<4, tasks[i].isExpired());
- }
- }
-
-
- /* ------------------------------------------------------------ */
- @Test
- public void testDelay()
- {
- Timeout.Task task = new Timeout.Task();
-
- timeout.setNow(1100);
- timeout.schedule(task, 300);
- timeout.setDuration(200);
-
- timeout.setNow(1300);
- timeout.tick();
- assertEquals("delay", false, task.isExpired());
-
- timeout.setNow(1500);
- timeout.tick();
- assertEquals("delay", false, task.isExpired());
-
- timeout.setNow(1700);
- timeout.tick();
- assertEquals("delay", true, task.isExpired());
- }
-
- /* ------------------------------------------------------------ */
- @Test
- public void testStress() throws Exception
- {
- if ( !_stress )
- return;
-
- final int LOOP=250;
- final AtomicBoolean running=new AtomicBoolean(true);
- final AtomicIntegerArray count = new AtomicIntegerArray( 4 );
-
-
- timeout.setNow(System.currentTimeMillis());
- timeout.setDuration(500);
-
- // Start a ticker thread that will tick over the timer frequently.
- Thread ticker = new Thread()
- {
- @Override
- public void run()
- {
- while (running.get())
- {
- try
- {
- // use lock.wait so we have a memory barrier and
- // have no funny optimisation issues.
- synchronized (lock)
- {
- lock.wait(30);
- }
- Thread.sleep(30);
- timeout.tick(System.currentTimeMillis());
- }
- catch(Exception e)
- {
- e.printStackTrace();
- }
- }
- }
- };
- ticker.start();
-
- // start lots of test threads
- for (int i=0;i<LOOP;i++)
- {
- //
- Thread th = new Thread()
- {
- @Override
- public void run()
- {
- // count how many threads were started (should == LOOP)
- int once = (int) 10 + count.incrementAndGet( 0 )%50;
-
- // create a task for this thread
- Timeout.Task task = new Timeout.Task()
- {
- @Override
- public void expired()
- {
- // count the number of expires
- count.incrementAndGet( 2 );
- }
- };
-
- // this thread will loop and each loop with schedule a
- // task with a delay on top of the timeouts duration
- // mostly this thread will then cancel the task
- // But once it will wait and the task will expire
-
-
- // do the looping until we are stopped
- int loop=0;
- while (running.get())
- {
- try
- {
- long delay=1000;
- long wait=100-once;
-
- if (loop++==once)
- {
- // THIS loop is the one time we wait longer than the delay
- count.incrementAndGet( 1 );
- delay=200;
- wait=1000;
- }
-
- timeout.schedule(task,delay);
-
- // do the wait
- Thread.sleep(wait);
-
- // cancel task (which may have expired)
- task.cancel();
- }
- catch(Exception e)
- {
- e.printStackTrace();
- }
- }
- count.incrementAndGet(3);
- }
- };
- th.start();
- }
-
- long start=System.currentTimeMillis();
-
- // run test until all threads are started
- while (count.get(0)<LOOP && (System.currentTimeMillis()-start)<20000)
- Thread.sleep(50);
- // run test until all expires initiated
- while (count.get(1)<LOOP && (System.currentTimeMillis()-start)<20000)
- Thread.sleep(50);
-
- // run test until all expires initiated
- while (count.get(2)<LOOP && (System.currentTimeMillis()-start)<20000)
- Thread.sleep(50);
-
- running.set(false);
-
- // run test until all threads complete
- while (count.get(3)<LOOP && (System.currentTimeMillis()-start)<20000)
- Thread.sleep(50);
-
- // check the counts
- assertEquals("count threads", LOOP,count.get( 0 ));
- assertEquals("count once waits",LOOP,count.get(1 ));
- assertEquals("count expires",LOOP,count.get(2));
- assertEquals("done",LOOP,count.get(3));
- }
-}
diff --git a/jetty-util/src/test/resources/TestData/WindowsDir.zip b/jetty-util/src/test/resources/TestData/WindowsDir.zip
new file mode 100644
index 0000000..26f2357
--- /dev/null
+++ b/jetty-util/src/test/resources/TestData/WindowsDir.zip
Binary files differ
diff --git a/jetty-webapp/pom.xml b/jetty-webapp/pom.xml
index 0a5186b..a5b71e6 100644
--- a/jetty-webapp/pom.xml
+++ b/jetty-webapp/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-webapp</artifactId>
@@ -45,6 +45,23 @@
</executions>
</plugin>
<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <descriptorRef>config</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
@@ -55,7 +72,7 @@
</goals>
<configuration>
<instructions>
- <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
+ <Import-Package>javax.servlet.*;version="[2.6.0,3.2]",*</Import-Package>
</instructions>
</configuration>
</execution>
diff --git a/jetty-webapp/src/main/config/etc/webdefault.xml b/jetty-webapp/src/main/config/etc/webdefault.xml
index 7c25cf4..15ff487 100644
--- a/jetty-webapp/src/main/config/etc/webdefault.xml
+++ b/jetty-webapp/src/main/config/etc/webdefault.xml
@@ -1,4 +1,10 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app
+ xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_1.xsd"
+ metadata-complete="false"
+ version="3.1">
<!-- ===================================================================== -->
<!-- This file contains the default descriptor for web applications. -->
@@ -17,13 +23,6 @@
<!-- by the jetty.xml file. -->
<!-- -->
<!-- ===================================================================== -->
-<web-app
- xmlns="http://java.sun.com/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
- metadata-complete="true"
- version="2.5"
->
<description>
Default web.xml file.
@@ -169,11 +168,11 @@
</init-param>
<init-param>
<param-name>gzip</param-name>
- <param-value>true</param-value>
+ <param-value>false</param-value>
</init-param>
<init-param>
<param-name>etags</param-name>
- <param-value>true</param-value>
+ <param-value>false</param-value>
</init-param>
<init-param>
<param-name>useFileMappedBuffer</param-name>
@@ -285,9 +284,7 @@
<!-- If you get an error reporting that jikes can't use UTF-8 encoding, -->
<!-- try setting the init parameter "javaEncoding" to "ISO-8859-1". -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <servlet
- id="jsp"
- >
+ <servlet id="jsp">
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
@@ -525,6 +522,13 @@
</web-resource-collection>
<auth-constraint/>
</security-constraint>
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Enable everything but TRACE</web-resource-name>
+ <url-pattern>/</url-pattern>
+ <http-method-ommission>TRACE</http-method-ommission>
+ </web-resource-collection>
+ </security-constraint>
</web-app>
diff --git a/jetty-webapp/src/main/config/modules/webapp.mod b/jetty-webapp/src/main/config/modules/webapp.mod
new file mode 100644
index 0000000..6bb37ef
--- /dev/null
+++ b/jetty-webapp/src/main/config/modules/webapp.mod
@@ -0,0 +1,10 @@
+#
+# WebApp Support Module
+#
+
+[depend]
+servlet
+security
+
+[lib]
+lib/jetty-webapp-${jetty.version}.jar
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/FragmentConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/FragmentConfiguration.java
index eb81292..ff28d05 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/FragmentConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/FragmentConfiguration.java
@@ -19,7 +19,7 @@
package org.eclipse.jetty.webapp;
-import java.util.List;
+import java.util.Map;
import org.eclipse.jetty.util.resource.Resource;
@@ -70,18 +70,18 @@
public void findWebFragments (final WebAppContext context, final MetaData metaData) throws Exception
{
@SuppressWarnings("unchecked")
- List<Resource> frags = (List<Resource>)context.getAttribute(FRAGMENT_RESOURCES);
+ Map<Resource, Resource> frags = (Map<Resource,Resource>)context.getAttribute(FRAGMENT_RESOURCES);
if (frags!=null)
{
- for (Resource frag : frags)
+ for (Resource key : frags.keySet())
{
- if (frag.isDirectory()) //tolerate the case where the library is a directory, not a jar. useful for OSGi for example
+ if (key.isDirectory()) //tolerate the case where the library is a directory, not a jar. useful for OSGi for example
{
- metaData.addFragment(frag, Resource.newResource(frag.getURL()+"/META-INF/web-fragment.xml"));
+ metaData.addFragment(key, frags.get(key));
}
else //the standard case: a jar most likely inside WEB-INF/lib
{
- metaData.addFragment(frag, Resource.newResource("jar:"+frag.getURL()+"!/META-INF/web-fragment.xml"));
+ metaData.addFragment(key, frags.get(key));
}
}
}
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java
index c5978f8..0014801 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java
@@ -28,6 +28,7 @@
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.EmptyResource;
import org.eclipse.jetty.util.resource.Resource;
@@ -43,18 +44,18 @@
private static final Logger LOG = Log.getLogger(MetaData.class);
public static final String ORDERED_LIBS = "javax.servlet.context.orderedLibs";
+ public static final Resource NON_FRAG_RESOURCE = EmptyResource.INSTANCE;
protected Map<String, OriginInfo> _origins =new HashMap<String,OriginInfo>();
protected WebDescriptor _webDefaultsRoot;
protected WebDescriptor _webXmlRoot;
protected final List<WebDescriptor> _webOverrideRoots=new ArrayList<WebDescriptor>();
- protected boolean _metaDataComplete;
- protected final List<DiscoveredAnnotation> _annotations = new ArrayList<DiscoveredAnnotation>();
+ protected boolean _metaDataComplete;
protected final List<DescriptorProcessor> _descriptorProcessors = new ArrayList<DescriptorProcessor>();
protected final List<FragmentDescriptor> _webFragmentRoots = new ArrayList<FragmentDescriptor>();
protected final Map<String,FragmentDescriptor> _webFragmentNameMap = new HashMap<String,FragmentDescriptor>();
protected final Map<Resource, FragmentDescriptor> _webFragmentResourceMap = new HashMap<Resource, FragmentDescriptor>();
- protected final Map<Resource, List<DiscoveredAnnotation>> _webFragmentAnnotations = new HashMap<Resource, List<DiscoveredAnnotation>>();
+ protected final Map<Resource, List<DiscoveredAnnotation>> _annotations = new HashMap<Resource, List<DiscoveredAnnotation>>();
protected final List<Resource> _webInfClasses = new ArrayList<Resource>();
protected final List<Resource> _webInfJars = new ArrayList<Resource>();
protected final List<Resource> _orderedWebInfJars = new ArrayList<Resource>();
@@ -135,7 +136,7 @@
_webFragmentRoots.clear();
_webFragmentNameMap.clear();
_webFragmentResourceMap.clear();
- _webFragmentAnnotations.clear();
+ _annotations.clear();
_webInfJars.clear();
_orderedWebInfJars.clear();
_orderedContainerResources.clear();
@@ -276,45 +277,47 @@
return;
for (DiscoveredAnnotation a:annotations)
{
- Resource r = a.getResource();
- if (r == null || !_webInfJars.contains(r))
- _annotations.add(a);
- else
- addDiscoveredAnnotation(a.getResource(), a);
-
+ addDiscoveredAnnotation(a);
}
}
-
-
- public void addDiscoveredAnnotation(Resource resource, DiscoveredAnnotation annotation)
- {
- List<DiscoveredAnnotation> list = _webFragmentAnnotations.get(resource);
- if (list == null)
- {
- list = new ArrayList<DiscoveredAnnotation>();
- _webFragmentAnnotations.put(resource, list);
- }
- list.add(annotation);
- }
-
- public void addDiscoveredAnnotations(Resource resource, List<DiscoveredAnnotation> annotations)
+
+ /**
+ * Add an annotation that has been discovered on a class, method or field within a resource
+ * eg a jar or dir.
+ *
+ * This method is synchronized as it is anticipated that it may be called by many threads
+ * during the annotation scanning phase.
+ *
+ * @param annotation
+ */
+ public synchronized void addDiscoveredAnnotation (DiscoveredAnnotation annotation)
{
- List<DiscoveredAnnotation> list = _webFragmentAnnotations.get(resource);
+ if (annotation == null)
+ return;
+
+ //if no resource associated with an annotation or the resource is not one of the WEB-INF/lib jars,
+ //map it to empty resource
+ Resource resource = annotation.getResource();
+ if (resource == null || !_webInfJars.contains(resource))
+ resource = EmptyResource.INSTANCE;
+
+ List<DiscoveredAnnotation> list = _annotations.get(resource);
if (list == null)
{
list = new ArrayList<DiscoveredAnnotation>();
- _webFragmentAnnotations.put(resource, list);
+ _annotations.put(resource, list);
}
-
- list.addAll(annotations);
+ list.add(annotation);
}
+
public void addDescriptorProcessor(DescriptorProcessor p)
{
_descriptorProcessors.add(p);
}
+
public void orderFragments ()
{
//if we have already ordered them don't do it again
@@ -373,13 +376,20 @@
}
}
- for (DiscoveredAnnotation a:_annotations)
+ //get an apply the annotations that are not associated with a fragment (and hence for
+ //which no ordering applies
+ List<DiscoveredAnnotation> nonFragAnnotations = _annotations.get(NON_FRAG_RESOURCE);
+ if (nonFragAnnotations != null)
{
- LOG.debug("apply {}",a);
- a.apply();
+ for (DiscoveredAnnotation a:nonFragAnnotations)
+ {
+ LOG.debug("apply {}",a);
+ a.apply();
+ }
}
-
-
+
+ //apply the annotations that are associated with a fragment, according to the
+ //established ordering
List<Resource> resources = getOrderedWebInfJars();
for (Resource r:resources)
{
@@ -393,7 +403,7 @@
}
}
- List<DiscoveredAnnotation> fragAnnotations = _webFragmentAnnotations.get(r);
+ List<DiscoveredAnnotation> fragAnnotations = _annotations.get(r);
if (fragAnnotations != null)
{
for (DiscoveredAnnotation a:fragAnnotations)
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java
index df913b5..a4c541f 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java
@@ -21,9 +21,10 @@
import java.net.URI;
import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.jar.JarEntry;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -45,57 +46,51 @@
public static final String METAINF_TLDS = TagLibConfiguration.TLD_RESOURCES;
public static final String METAINF_FRAGMENTS = FragmentConfiguration.FRAGMENT_RESOURCES;
- public static final String METAINF_RESOURCES = WebInfConfiguration.RESOURCE_URLS;
+ public static final String METAINF_RESOURCES = WebInfConfiguration.RESOURCE_DIRS;
@Override
public void preConfigure(final WebAppContext context) throws Exception
{
- //Merge all container and webinf lib jars to look for META-INF resources
-
+ //Merge all container and webinf lib jars to look for META-INF resources
ArrayList<Resource> jars = new ArrayList<Resource>();
jars.addAll(context.getMetaData().getContainerResources());
jars.addAll(context.getMetaData().getWebInfJars());
- JarScanner scanner = new JarScanner()
- {
- public void processEntry(URI jarUri, JarEntry entry)
- {
- try
- {
- MetaInfConfiguration.this.processEntry(context,jarUri,entry);
- }
- catch (Exception e)
- {
- LOG.warn("Problem processing jar entry " + entry, e);
- }
- }
- };
-
-
//Scan jars for META-INF information
if (jars != null)
{
- URI[] uris = new URI[jars.size()];
- int i=0;
for (Resource r : jars)
{
- uris[i++] = r.getURI();
+ URI uri = r.getURI();
+ Resource fragXml = Resource.newResource("jar:"+uri+"!/META-INF/web-fragment.xml");
+ if (fragXml.exists())
+ {
+ //add mapping for resource->fragment
+ Map<Resource, Resource> fragments = (Map<Resource,Resource>)context.getAttribute(METAINF_FRAGMENTS);
+ if (fragments == null)
+ {
+ fragments = new HashMap<Resource, Resource>();
+ context.setAttribute(METAINF_FRAGMENTS, fragments);
+ }
+ fragments.put(r, fragXml);
+ }
+
+ Resource resourcesDir = Resource.newResource("jar:"+uri+"!/META-INF/resources");
+ if (resourcesDir.exists())
+ {
+ //add resources dir
+ Set<Resource> dirs = (Set<Resource>)context.getAttribute(METAINF_RESOURCES);
+ if (dirs == null)
+ {
+ dirs = new HashSet<Resource>();
+ context.setAttribute(METAINF_RESOURCES, dirs);
+ }
+ dirs.add(resourcesDir);
+ }
}
- scanner.scan(null, uris, true);
}
}
- @Override
- public void configure(WebAppContext context) throws Exception
- {
-
- }
-
- @Override
- public void deconfigure(WebAppContext context) throws Exception
- {
-
- }
-
+
@Override
public void postConfigure(WebAppContext context) throws Exception
{
@@ -103,50 +98,4 @@
context.setAttribute(METAINF_RESOURCES, null);
context.setAttribute(METAINF_TLDS, null);
}
-
- public void addResource (WebAppContext context, String attribute, Resource jar)
- {
- @SuppressWarnings("unchecked")
- List<Resource> list = (List<Resource>)context.getAttribute(attribute);
- if (list==null)
- {
- list=new ArrayList<Resource>();
- context.setAttribute(attribute,list);
- }
- if (!list.contains(jar))
- list.add(jar);
- }
-
-
- protected void processEntry(WebAppContext context, URI jarUri, JarEntry entry)
- {
- String name = entry.getName();
-
- if (!name.startsWith("META-INF/"))
- return;
-
- try
- {
- if (name.equals("META-INF/web-fragment.xml") && context.isConfigurationDiscovered())
- {
- addResource(context,METAINF_FRAGMENTS,Resource.newResource(jarUri));
- }
- else if (name.equals("META-INF/resources/") && context.isConfigurationDiscovered())
- {
- addResource(context,METAINF_RESOURCES,Resource.newResource("jar:"+jarUri+"!/META-INF/resources"));
- }
- else
- {
- String lcname = name.toLowerCase(Locale.ENGLISH);
- if (lcname.endsWith(".tld"))
- {
- addResource(context,METAINF_TLDS,Resource.newResource("jar:"+jarUri+"!/"+name));
- }
- }
- }
- catch(Exception e)
- {
- context.getServletContext().log(jarUri+"!/"+name,e);
- }
- }
}
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java
index 0858fe9..71d7fb5 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java
@@ -26,7 +26,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
import java.util.Set;
import javax.servlet.DispatcherType;
@@ -42,11 +41,10 @@
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.Holder;
import org.eclipse.jetty.servlet.JspPropertyGroupServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletContextHandler.JspConfig;
import org.eclipse.jetty.servlet.ServletContextHandler.JspPropertyGroup;
import org.eclipse.jetty.servlet.ServletContextHandler.TagLib;
+import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.util.ArrayUtil;
@@ -92,6 +90,7 @@
registerVisitor("filter-mapping", this.getClass().getDeclaredMethod("visitFilterMapping", __signature));
registerVisitor("listener", this.getClass().getDeclaredMethod("visitListener", __signature));
registerVisitor("distributable", this.getClass().getDeclaredMethod("visitDistributable", __signature));
+ registerVisitor("deny-uncovered-http-methods", this.getClass().getDeclaredMethod("visitDenyUncoveredHttpMethods", __signature));
}
catch (Exception e)
{
@@ -126,8 +125,7 @@
{
String name = node.getString("param-name", false, true);
String value = node.getString("param-value", false, true);
- Origin o = context.getMetaData().getOrigin("context-param."+name);
- switch (o)
+ switch (context.getMetaData().getOrigin("context-param."+name))
{
case NotSet:
{
@@ -212,36 +210,36 @@
XmlParser.Node paramNode = (XmlParser.Node) iParamsIter.next();
String pname = paramNode.getString("param-name", false, true);
String pvalue = paramNode.getString("param-value", false, true);
+ String originName = servlet_name+".servlet.init-param."+pname;
- Origin origin = context.getMetaData().getOrigin(servlet_name+".servlet.init-param."+pname);
-
- switch (origin)
+ Descriptor originDescriptor = context.getMetaData().getOriginDescriptor(originName);
+ switch (context.getMetaData().getOrigin(originName))
{
case NotSet:
{
//init-param not already set, so set it
-
holder.setInitParameter(pname, pvalue);
- context.getMetaData().setOrigin(servlet_name+".servlet.init-param."+pname, descriptor);
+ context.getMetaData().setOrigin(originName, descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
- //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
+ //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override as long as it is from a different descriptor
+ //ie ignore setting more than once within the same descriptor
//otherwise just ignore it
- if (!(descriptor instanceof FragmentDescriptor))
+ if (!(descriptor instanceof FragmentDescriptor) && (descriptor!=originDescriptor))
{
holder.setInitParameter(pname, pvalue);
- context.getMetaData().setOrigin(servlet_name+".servlet.init-param."+pname, descriptor);
+ context.getMetaData().setOrigin(originName, descriptor);
}
break;
}
case WebFragment:
{
//previously set by a web-fragment, make sure that the value matches, otherwise its an error
- if (!holder.getInitParameter(pname).equals(pvalue))
+ if ((descriptor != originDescriptor) && !holder.getInitParameter(pname).equals(pvalue))
throw new IllegalStateException("Mismatching init-param "+pname+"="+pvalue+" in "+descriptor.getResource());
break;
}
@@ -282,9 +280,7 @@
if (servlet_class != null)
{
((WebDescriptor)descriptor).addClassName(servlet_class);
-
- Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.servlet-class");
- switch (o)
+ switch (context.getMetaData().getOrigin(servlet_name+".servlet.servlet-class"))
{
case NotSet:
{
@@ -349,8 +345,7 @@
}
}
- Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.load-on-startup");
- switch (o)
+ switch (context.getMetaData().getOrigin(servlet_name+".servlet.load-on-startup"))
{
case NotSet:
{
@@ -390,8 +385,7 @@
if (roleName != null && roleName.length() > 0 && roleLink != null && roleLink.length() > 0)
{
if (LOG.isDebugEnabled()) LOG.debug("link role " + roleName + " to " + roleLink + " for " + this);
- Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.role-name."+roleName);
- switch (o)
+ switch (context.getMetaData().getOrigin(servlet_name+".servlet.role-name."+roleName))
{
case NotSet:
{
@@ -434,8 +428,7 @@
if (roleName != null)
{
- Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.run-as");
- switch (o)
+ switch (context.getMetaData().getOrigin(servlet_name+".servlet.run-as"))
{
case NotSet:
{
@@ -471,8 +464,7 @@
if (async!=null)
{
boolean val = async.length()==0||Boolean.valueOf(async);
- Origin o =context.getMetaData().getOrigin(servlet_name+".servlet.async-supported");
- switch (o)
+ switch (context.getMetaData().getOrigin(servlet_name+".servlet.async-supported"))
{
case NotSet:
{
@@ -507,8 +499,7 @@
if (enabled!=null)
{
boolean is_enabled = enabled.length()==0||Boolean.valueOf(enabled);
- Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.enabled");
- switch (o)
+ switch (context.getMetaData().getOrigin(servlet_name+".servlet.enabled"))
{
case NotSet:
{
@@ -556,8 +547,7 @@
(maxRequest==null||"".equals(maxRequest)?-1L:Long.parseLong(maxRequest)),
(threshold==null||"".equals(threshold)?0:Integer.parseInt(threshold)));
- Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.multipart-config");
- switch (o)
+ switch (context.getMetaData().getOrigin(servlet_name+".servlet.multipart-config"))
{
case NotSet:
{
@@ -614,24 +604,21 @@
// <servlet-mapping> declared in web.xml overrides the mapping for the servlet specified in the web-fragment.xml
String servlet_name = node.getString("servlet-name", false, true);
- Origin origin = context.getMetaData().getOrigin(servlet_name+".servlet.mappings");
-
- switch (origin)
+ switch (context.getMetaData().getOrigin(servlet_name+".servlet.mappings"))
{
case NotSet:
{
//no servlet mappings
context.getMetaData().setOrigin(servlet_name+".servlet.mappings", descriptor);
- ServletMapping mapping = addServletMapping(servlet_name, node, context, descriptor);
- mapping.setDefault(context.getMetaData().getOrigin(servlet_name+".servlet.mappings") == Origin.WebDefaults);
+ addServletMapping(servlet_name, node, context, descriptor);
break;
}
- case WebXml:
case WebDefaults:
+ case WebXml:
case WebOverride:
{
//previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
- //otherwise just ignore it
+ //otherwise just ignore it as web.xml takes precedence (pg 8-81 5.g.vi)
if (!(descriptor instanceof FragmentDescriptor))
{
addServletMapping(servlet_name, node, context, descriptor);
@@ -713,8 +700,7 @@
String name = cookieConfig.getString("name", false, true);
if (name != null)
{
- Origin o = context.getMetaData().getOrigin("cookie-config.name");
- switch (o)
+ switch (context.getMetaData().getOrigin("cookie-config.name"))
{
case NotSet:
{
@@ -749,8 +735,7 @@
String domain = cookieConfig.getString("domain", false, true);
if (domain != null)
{
- Origin o = context.getMetaData().getOrigin("cookie-config.domain");
- switch (o)
+ switch (context.getMetaData().getOrigin("cookie-config.domain"))
{
case NotSet:
{
@@ -785,8 +770,7 @@
String path = cookieConfig.getString("path", false, true);
if (path != null)
{
- Origin o = context.getMetaData().getOrigin("cookie-config.path");
- switch (o)
+ switch (context.getMetaData().getOrigin("cookie-config.path"))
{
case NotSet:
{
@@ -821,8 +805,7 @@
String comment = cookieConfig.getString("comment", false, true);
if (comment != null)
{
- Origin o = context.getMetaData().getOrigin("cookie-config.comment");
- switch (o)
+ switch (context.getMetaData().getOrigin("cookie-config.comment"))
{
case NotSet:
{
@@ -858,8 +841,7 @@
if (tNode != null)
{
boolean httpOnly = Boolean.parseBoolean(tNode.toString(false,true));
- Origin o = context.getMetaData().getOrigin("cookie-config.http-only");
- switch (o)
+ switch (context.getMetaData().getOrigin("cookie-config.http-only"))
{
case NotSet:
{
@@ -895,8 +877,7 @@
if (tNode != null)
{
boolean secure = Boolean.parseBoolean(tNode.toString(false,true));
- Origin o = context.getMetaData().getOrigin("cookie-config.secure");
- switch (o)
+ switch (context.getMetaData().getOrigin("cookie-config.secure"))
{
case NotSet:
{
@@ -932,8 +913,7 @@
if (tNode != null)
{
int maxAge = Integer.parseInt(tNode.toString(false,true));
- Origin o = context.getMetaData().getOrigin("cookie-config.max-age");
- switch (o)
+ switch (context.getMetaData().getOrigin("cookie-config.max-age"))
{
case NotSet:
{
@@ -981,8 +961,7 @@
String mimeType = node.getString("mime-type", false, true);
if (extension != null)
{
- Origin o = context.getMetaData().getOrigin("extension."+extension);
- switch (o)
+ switch (context.getMetaData().getOrigin("extension."+extension))
{
case NotSet:
{
@@ -1021,8 +1000,7 @@
*/
protected void visitWelcomeFileList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
- Origin o = context.getMetaData().getOrigin("welcome-file-list");
- switch (o)
+ switch (context.getMetaData().getOrigin("welcome-file-list"))
{
case NotSet:
{
@@ -1078,8 +1056,7 @@
if (encoding != null)
{
- Origin o = context.getMetaData().getOrigin("locale-encoding."+locale);
- switch (o)
+ switch (context.getMetaData().getOrigin("locale-encoding."+locale))
{
case NotSet:
{
@@ -1132,11 +1109,8 @@
String location = node.getString("location", false, true);
ErrorPageErrorHandler handler = (ErrorPageErrorHandler)context.getErrorHandler();
-
-
- Origin o = context.getMetaData().getOrigin("error."+error);
- switch (o)
+ switch (context.getMetaData().getOrigin("error."+error))
{
case NotSet:
{
@@ -1207,7 +1181,8 @@
{
ServletMapping mapping = new ServletMapping();
mapping.setServletName(servletName);
-
+ mapping.setDefault(descriptor instanceof DefaultsDescriptor);
+
List<String> paths = new ArrayList<String>();
Iterator<XmlParser.Node> iter = node.iterator("url-pattern");
while (iter.hasNext())
@@ -1510,8 +1485,7 @@
if (method != null)
{
//handle auth-method merge
- Origin o = context.getMetaData().getOrigin("auth-method");
- switch (o)
+ switch (context.getMetaData().getOrigin("auth-method"))
{
case NotSet:
{
@@ -1544,8 +1518,7 @@
//handle realm-name merge
XmlParser.Node name = node.get("realm-name");
String nameStr = (name == null ? "default" : name.toString(false, true));
- o = context.getMetaData().getOrigin("realm-name");
- switch (o)
+ switch (context.getMetaData().getOrigin("realm-name"))
{
case NotSet:
{
@@ -1590,8 +1563,7 @@
errorPageName = errorPage.toString(false, true);
//handle form-login-page
- o = context.getMetaData().getOrigin("form-login-page");
- switch (o)
+ switch (context.getMetaData().getOrigin("form-login-page"))
{
case NotSet:
{
@@ -1622,8 +1594,7 @@
}
//handle form-error-page
- o = context.getMetaData().getOrigin("form-error-page");
- switch (o)
+ switch (context.getMetaData().getOrigin("form-error-page"))
{
case NotSet:
{
@@ -1696,8 +1667,7 @@
{
((WebDescriptor)descriptor).addClassName(filter_class);
- Origin o = context.getMetaData().getOrigin(name+".filter.filter-class");
- switch (o)
+ switch (context.getMetaData().getOrigin(name+".filter.filter-class"))
{
case NotSet:
{
@@ -1726,7 +1696,6 @@
break;
}
}
-
}
Iterator<XmlParser.Node> iter = node.iterator("init-param");
@@ -1736,8 +1705,7 @@
String pname = paramNode.getString("param-name", false, true);
String pvalue = paramNode.getString("param-value", false, true);
- Origin origin = context.getMetaData().getOrigin(name+".filter.init-param."+pname);
- switch (origin)
+ switch (context.getMetaData().getOrigin(name+".filter.init-param."+pname))
{
case NotSet:
{
@@ -1775,8 +1743,7 @@
if (async!=null)
{
boolean val = async.length()==0||Boolean.valueOf(async);
- Origin o = context.getMetaData().getOrigin(name+".filter.async-supported");
- switch (o)
+ switch (context.getMetaData().getOrigin(name+".filter.async-supported"))
{
case NotSet:
{
@@ -1806,7 +1773,6 @@
}
}
}
-
}
/**
@@ -1820,13 +1786,8 @@
//filter-mappings are always additive, whether from web xml descriptors (web.xml/web-default.xml/web-override.xml) or web-fragments.
//Maintenance update 3.0a to spec:
// Updated 8.2.3.g.v to say <servlet-mapping> elements are additive across web-fragments.
-
-
String filter_name = node.getString("filter-name", false, true);
-
- Origin origin = context.getMetaData().getOrigin(filter_name+".filter.mappings");
-
- switch (origin)
+ switch (context.getMetaData().getOrigin(filter_name+".filter.mappings"))
{
case NotSet:
{
@@ -1914,6 +1875,21 @@
//Servlet Spec 3.0 p.74 distributable only if all fragments are distributable
((WebDescriptor)descriptor).setDistributable(true);
}
+
+
+ /**
+ * Servlet spec 3.1. When present in web.xml, this means that http methods that are
+ * not covered by security constraints should have access denied.
+ *
+ * See section 13.8.4, pg 145
+ * @param context
+ * @param descriptor
+ * @param node
+ */
+ protected void visitDenyUncoveredHttpMethods(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+ {
+ ((ConstraintAware)context.getSecurityHandler()).setDenyUncoveredHttpMethods(true);
+ }
/**
* @param context
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java
index 4456a0e..48b6b00 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java
@@ -369,13 +369,13 @@
finally
{
if(taglib11==null)
- taglib11=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd",true);
+ taglib11=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd");
if(taglib12==null)
- taglib12=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd",true);
+ taglib12=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd");
if(taglib20==null)
- taglib20=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd",true);
+ taglib20=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd");
if(taglib21==null)
- taglib21=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd",true);
+ taglib21=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd");
}
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java
index 3235b52..4e3cf83 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java
@@ -336,8 +336,15 @@
{
URL url= null;
boolean tried_parent= false;
- boolean system_class=_context.isSystemClass(name);
- boolean server_class=_context.isServerClass(name);
+
+ //If the resource is a class name with .class suffix, strip it off before comparison
+ //as the server and system patterns are specified without a .class suffix
+ String tmp = name;
+ if (tmp != null && tmp.endsWith(".class"))
+ tmp = tmp.substring(0, tmp.length()-6);
+
+ boolean system_class=_context.isSystemClass(tmp);
+ boolean server_class=_context.isServerClass(tmp);
if (system_class && server_class)
return null;
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java
index 8daa1c6..11e862e 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java
@@ -31,7 +31,6 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
@@ -41,6 +40,7 @@
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionIdListener;
import javax.servlet.http.HttpSessionListener;
import org.eclipse.jetty.security.ConstraintAware;
@@ -61,7 +61,6 @@
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
-import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
@@ -154,6 +153,8 @@
private String[] _contextWhiteList = null;
private File _tmpDir;
+ private boolean _persistTmpDir = false;
+
private String _war;
private String _extraClasspath;
private Throwable _unavailableException;
@@ -655,13 +656,13 @@
/* ------------------------------------------------------------ */
/** Add to the list of Server classes.
- * @see #setServerClasses(String[])
- * @see http://www.eclipse.org/jetty/documentation/current/jetty-classloading.html
* @param classOrPackage A fully qualified class name (eg com.foo.MyClass)
* or a qualified package name ending with '.' (eg com.foo.). If the class
* or package has '-' it is excluded from the server classes and order is thus
* important when added system class patterns. This argument may also be a comma
* separated list of classOrPackage patterns.
+ * @see #setServerClasses(String[])
+ * @see <a href="http://www.eclipse.org/jetty/documentation/current/jetty-classloading.html">Jetty Documentation: Classloading</a>
*/
public void addServerClass(String classOrPackage)
{
@@ -673,13 +674,13 @@
/* ------------------------------------------------------------ */
/** Prepend to the list of Server classes.
- * @see #setServerClasses(String[])
- * @see http://www.eclipse.org/jetty/documentation/current/jetty-classloading.html
* @param classOrPackage A fully qualified class name (eg com.foo.MyClass)
* or a qualified package name ending with '.' (eg com.foo.). If the class
* or package has '-' it is excluded from the server classes and order is thus
* important when added system class patterns. This argument may also be a comma
* separated list of classOrPackage patterns.
+ * @see #setServerClasses(String[])
+ * @see <a href="http://www.eclipse.org/jetty/documentation/current/jetty-classloading.html">Jetty Documentation: Classloading</a>
*/
public void prependServerClass(String classOrPackage)
{
@@ -705,13 +706,13 @@
/* ------------------------------------------------------------ */
/** Add to the list of System classes.
- * @see http://www.eclipse.org/jetty/documentation/current/jetty-classloading.html
- * @see #setSystemClasses(String[])
* @param classOrPackage A fully qualified class name (eg com.foo.MyClass)
* or a qualified package name ending with '.' (eg com.foo.). If the class
* or package has '-' it is excluded from the system classes and order is thus
* important when added system class patterns. This argument may also be a comma
* separated list of classOrPackage patterns.
+ * @see #setSystemClasses(String[])
+ * @see <a href="http://www.eclipse.org/jetty/documentation/current/jetty-classloading.html">Jetty Documentation: Classloading</a>
*/
public void addSystemClass(String classOrPackage)
{
@@ -724,13 +725,13 @@
/* ------------------------------------------------------------ */
/** Prepend to the list of System classes.
- * @see #setSystemClasses(String[])
- * @see http://www.eclipse.org/jetty/documentation/current/jetty-classloading.html
* @param classOrPackage A fully qualified class name (eg com.foo.MyClass)
* or a qualified package name ending with '.' (eg com.foo.). If the class
* or package has '-' it is excluded from the system classes and order is thus
* important when added system class patterns.This argument may also be a comma
* separated list of classOrPackage patterns.
+ * @see #setSystemClasses(String[])
+ * @see <a href="http://www.eclipse.org/jetty/documentation/current/jetty-classloading.html">Jetty Documentation: Classloading</a>
*/
public void prependSystemClass(String classOrPackage)
{
@@ -1058,7 +1059,8 @@
if ((listener instanceof HttpSessionActivationListener)
|| (listener instanceof HttpSessionAttributeListener)
|| (listener instanceof HttpSessionBindingListener)
- || (listener instanceof HttpSessionListener))
+ || (listener instanceof HttpSessionListener)
+ || (listener instanceof HttpSessionIdListener))
{
if (_sessionHandler!=null)
_sessionHandler.addEventListener(listener);
@@ -1072,7 +1074,8 @@
if ((listener instanceof HttpSessionActivationListener)
|| (listener instanceof HttpSessionAttributeListener)
|| (listener instanceof HttpSessionBindingListener)
- || (listener instanceof HttpSessionListener))
+ || (listener instanceof HttpSessionListener)
+ || (listener instanceof HttpSessionIdListener))
{
if (_sessionHandler!=null)
_sessionHandler.removeEventListener(listener);
@@ -1220,7 +1223,7 @@
LOG.warn(e);
}
_tmpDir=dir;
- setAttribute(TEMPDIR,_tmpDir);
+ setAttribute(TEMPDIR,_tmpDir);
}
/* ------------------------------------------------------------ */
@@ -1230,6 +1233,27 @@
return _tmpDir;
}
+ /**
+ * If true the temp directory for this
+ * webapp will be kept when the webapp stops. Otherwise,
+ * it will be deleted.
+ *
+ * @param delete
+ */
+ public void setPersistTempDirectory(boolean persist)
+ {
+ _persistTmpDir = persist;
+ }
+
+ /**
+ * @return
+ */
+ public boolean isPersistTempDirectory()
+ {
+ return _persistTmpDir;
+ }
+
+
/* ------------------------------------------------------------ */
/**
* @param war The war to set as a file name or URL
@@ -1326,7 +1350,6 @@
@Override
public Set<String> setServletSecurity(Dynamic registration, ServletSecurityElement servletSecurityElement)
{
-
Set<String> unchangedURLMappings = new HashSet<String>();
//From javadoc for ServletSecurityElement:
/*
@@ -1361,6 +1384,7 @@
List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath(registration.getName(), pathSpec, servletSecurityElement);
for (ConstraintMapping m:mappings)
((ConstraintAware)getSecurityHandler()).addConstraintMapping(m);
+ ((ConstraintAware)getSecurityHandler()).checkPathsWithUncoveredHttpMethods();
getMetaData().setOrigin("constraint.url."+pathSpec, Origin.API);
break;
}
@@ -1384,6 +1408,7 @@
constraintMappings.addAll(freshMappings);
((ConstraintSecurityHandler)getSecurityHandler()).setConstraintMappings(constraintMappings);
+ ((ConstraintAware)getSecurityHandler()).checkPathsWithUncoveredHttpMethods();
break;
}
}
@@ -1398,6 +1423,32 @@
/* ------------------------------------------------------------ */
public class Context extends ServletContextHandler.Context
{
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void checkListener(Class<? extends EventListener> listener) throws IllegalStateException
+ {
+ try
+ {
+ super.checkListener(listener);
+ }
+ catch (IllegalArgumentException e)
+ {
+ //not one of the standard servlet listeners, check our extended session listener types
+ boolean ok = false;
+ for (Class l:SessionHandler.SESSION_LISTENER_TYPES)
+ {
+ if (l.isAssignableFrom(listener))
+ {
+ ok = true;
+ break;
+ }
+ }
+ if (!ok)
+ throw new IllegalArgumentException("Inappropriate listener type "+listener.getName());
+ }
+ }
+
/* ------------------------------------------------------------ */
@Override
public URL getResource(String path) throws MalformedURLException
@@ -1444,7 +1495,6 @@
}
}
-
}
/* ------------------------------------------------------------ */
@@ -1452,5 +1502,4 @@
{
return _metadata;
}
-
}
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebDescriptor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebDescriptor.java
index 602c063..4b65a81 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebDescriptor.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebDescriptor.java
@@ -69,19 +69,19 @@
{
XmlParser xmlParser=new XmlParser();
//set up cache of DTDs and schemas locally
- URL dtd22=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_2.dtd",true);
- URL dtd23=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_3.dtd",true);
- URL j2ee14xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_1_4.xsd",true);
- URL webapp24xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_4.xsd",true);
- URL webapp25xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_5.xsd",true);
- URL webapp30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_3_0.xsd",true);
- URL webcommon30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-common_3_0.xsd",true);
- URL webfragment30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-fragment_3_0.xsd",true);
- URL schemadtd=Loader.getResource(Servlet.class,"javax/servlet/resources/XMLSchema.dtd",true);
- URL xmlxsd=Loader.getResource(Servlet.class,"javax/servlet/resources/xml.xsd",true);
- URL webservice11xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_web_services_client_1_1.xsd",true);
- URL webservice12xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_2.xsd",true);
- URL datatypesdtd=Loader.getResource(Servlet.class,"javax/servlet/resources/datatypes.dtd",true);
+ URL dtd22=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_2.dtd");
+ URL dtd23=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_3.dtd");
+ URL j2ee14xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_1_4.xsd");
+ URL webapp24xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_4.xsd");
+ URL webapp25xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_5.xsd");
+ URL webapp30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_3_0.xsd");
+ URL webcommon30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-common_3_0.xsd");
+ URL webfragment30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-fragment_3_0.xsd");
+ URL schemadtd=Loader.getResource(Servlet.class,"javax/servlet/resources/XMLSchema.dtd");
+ URL xmlxsd=Loader.getResource(Servlet.class,"javax/servlet/resources/xml.xsd");
+ URL webservice11xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_web_services_client_1_1.xsd");
+ URL webservice12xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_2.xsd");
+ URL datatypesdtd=Loader.getResource(Servlet.class,"javax/servlet/resources/datatypes.dtd");
URL jsp20xsd = null;
URL jsp21xsd = null;
@@ -98,8 +98,8 @@
}
finally
{
- if (jsp20xsd == null) jsp20xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_0.xsd", true);
- if (jsp21xsd == null) jsp21xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_1.xsd", true);
+ if (jsp20xsd == null) jsp20xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_0.xsd");
+ if (jsp21xsd == null) jsp21xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_1.xsd");
}
redirect(xmlParser,"web-app_2_2.dtd",dtd22);
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
index d0d4085..1a04e0d 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
@@ -27,6 +27,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
@@ -54,18 +55,16 @@
* If set, to a list of URLs, these resources are added to the context
* resource base as a resource collection.
*/
- public static final String RESOURCE_URLS = "org.eclipse.jetty.resources";
+ public static final String RESOURCE_DIRS = "org.eclipse.jetty.resources";
+
protected Resource _preUnpackBaseResource;
+
+
@Override
public void preConfigure(final WebAppContext context) throws Exception
{
- // Look for a work directory
- File work = findWorkDirectory(context);
- if (work != null)
- makeTempDirectory(work, context, false);
-
//Make a temp directory for the webapp if one is not already set
resolveTempDirectory(context);
@@ -140,7 +139,7 @@
}
}
webInfJarNameMatcher.match(webInfPattern, uris, true); //null is inclusive, no pattern == all jars match
-
+
//No pattern to appy to classes, just add to metadata
context.getMetaData().setWebInfClassesDirs(findClassDirs(context));
}
@@ -175,7 +174,7 @@
// Look for extra resource
@SuppressWarnings("unchecked")
- List<Resource> resources = (List<Resource>)context.getAttribute(RESOURCE_URLS);
+ Set<Resource> resources = (Set<Resource>)context.getAttribute(RESOURCE_DIRS);
if (resources!=null)
{
Resource[] collection=new Resource[resources.size()+1];
@@ -190,20 +189,16 @@
@Override
public void deconfigure(WebAppContext context) throws Exception
{
- // delete temp directory if we had to create it or if it isn't called work
- Boolean tmpdirConfigured = (Boolean)context.getAttribute(TEMPDIR_CONFIGURED);
-
- if (context.getTempDirectory()!=null && (tmpdirConfigured == null || !tmpdirConfigured.booleanValue()) && !isTempWorkDirectory(context.getTempDirectory()))
+ //if we're not persisting the temp dir contents delete it
+ if (!context.isPersistTempDirectory())
{
IO.delete(context.getTempDirectory());
- context.setTempDirectory(null);
-
- //clear out the context attributes for the tmp dir only if we had to
- //create the tmp dir
- context.setAttribute(TEMPDIR_CONFIGURED, null);
- context.setAttribute(WebAppContext.TEMPDIR, null);
}
-
+
+ //if it wasn't explicitly configured by the user, then unset it
+ Boolean tmpdirConfigured = (Boolean)context.getAttribute(TEMPDIR_CONFIGURED);
+ if (tmpdirConfigured != null && !tmpdirConfigured)
+ context.setTempDirectory(null);
//reset the base resource back to what it was before we did any unpacking of resources
context.setBaseResource(_preUnpackBaseResource);
@@ -236,51 +231,44 @@
* <p>A. Try to use an explicit directory specifically for this webapp:</p>
* <ol>
* <li>
- * Iff an explicit directory is set for this webapp, use it. Do NOT set
- * delete on exit.
+ * Iff an explicit directory is set for this webapp, use it. Set delete on
+ * exit depends on value of persistTempDirectory.
* </li>
* <li>
* Iff javax.servlet.context.tempdir context attribute is set for
- * this webapp && exists && writeable, then use it. Do NOT set delete on exit.
+ * this webapp && exists && writeable, then use it. Set delete on exit depends on
+ * value of persistTempDirectory.
* </li>
* </ol>
*
* <p>B. Create a directory based on global settings. The new directory
- * will be called "Jetty_"+host+"_"+port+"__"+context+"_"+virtualhost
- * Work out where to create this directory:
- * <ol>
- * <li>
- * Iff $(jetty.home)/work exists create the directory there. Do NOT
- * set delete on exit. Do NOT delete contents if dir already exists.
- * </li>
- * <li>
- * Iff WEB-INF/work exists create the directory there. Do NOT set
- * delete on exit. Do NOT delete contents if dir already exists.
- * </li>
- * <li>
- * Else create dir in $(java.io.tmpdir). Set delete on exit. Delete
- * contents if dir already exists.
- * </li>
- * </ol>
+ * will be called "Jetty-"+host+"-"+port+"__"+context+"-"+virtualhost+"-"+randomdigits+".dir"
+ * </p>
+ * <p>
+ * If the user has specified the context attribute org.eclipse.jetty.webapp.basetempdir, the
+ * directory specified by this attribute will be the parent of the temp dir created. Otherwise,
+ * the parent dir is $(java.io.tmpdir). Set delete on exit depends on value of persistTempDirectory.
+ * </p>
*/
public void resolveTempDirectory (WebAppContext context)
+ throws Exception
{
- //If a tmp directory is already set, we're done
+ //If a tmp directory is already set we should use it
File tmpDir = context.getTempDirectory();
- if (tmpDir != null && tmpDir.isDirectory() && tmpDir.canWrite())
+ if (tmpDir != null)
{
- context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE);
- return; // Already have a suitable tmp dir configured
+ configureTempDirectory(tmpDir, context);
+ context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE); //the tmp dir was set explicitly
+ return;
}
-
- // No temp directory configured, try to establish one.
- // First we check the context specific, javax.servlet specified, temp directory attribute
+ // No temp directory configured, try to establish one via the javax.servlet.context.tempdir.
File servletTmpDir = asFile(context.getAttribute(WebAppContext.TEMPDIR));
- if (servletTmpDir != null && servletTmpDir.isDirectory() && servletTmpDir.canWrite())
+ if (servletTmpDir != null)
{
// Use as tmpDir
tmpDir = servletTmpDir;
+ configureTempDirectory(tmpDir, context);
// Ensure Attribute has File object
context.setAttribute(WebAppContext.TEMPDIR,tmpDir);
// Set as TempDir in context.
@@ -288,60 +276,25 @@
return;
}
- try
+ //We need to make a temp dir. Check if the user has set a directory to use instead
+ //of java.io.tmpdir as the parent of the dir
+ File baseTemp = asFile(context.getAttribute(WebAppContext.BASETEMPDIR));
+ if (baseTemp != null && baseTemp.isDirectory() && baseTemp.canWrite())
{
- // Put the tmp dir in the work directory if we had one
- File work = new File(System.getProperty("jetty.home"),"work");
- if (work.exists() && work.canWrite() && work.isDirectory())
- {
- makeTempDirectory(work, context, false); //make a tmp dir inside work, don't delete if it exists
- }
- else
- {
- File baseTemp = asFile(context.getAttribute(WebAppContext.BASETEMPDIR));
- if (baseTemp != null && baseTemp.isDirectory() && baseTemp.canWrite())
- {
- // Use baseTemp directory (allow the funky Jetty_0_0_0_0.. subdirectory logic to kick in
- makeTempDirectory(baseTemp,context,false);
- }
- else
- {
- makeTempDirectory(new File(System.getProperty("java.io.tmpdir")),context,true); //make a tmpdir, delete if it already exists
- }
- }
+ //Make a temp directory as a child of the given base dir
+ makeTempDirectory(baseTemp,context);
}
- catch(Exception e)
+ else
{
- tmpDir=null;
- LOG.ignore(e);
- }
-
- //Third ... Something went wrong trying to make the tmp directory, just make
- //a jvm managed tmp directory
- if (context.getTempDirectory() == null)
- {
- try
- {
- // Last resort
- tmpDir=File.createTempFile("JettyContext","");
- if (tmpDir.exists())
- IO.delete(tmpDir);
- tmpDir.mkdir();
- tmpDir.deleteOnExit();
- context.setTempDirectory(tmpDir);
- }
- catch(IOException e)
- {
- tmpDir = null;
- throw new IllegalStateException("Cannot create tmp dir in "+System.getProperty("java.io.tmpdir")+ " for context "+context,e);
- }
+ //Make a temp directory in java.io.tmpdir
+ makeTempDirectory(new File(System.getProperty("java.io.tmpdir")),context);
}
}
/**
* Given an Object, return File reference for object.
* Typically used to convert anonymous Object from getAttribute() calls to a File object.
- * @param fileattr the file attribute to analyze and return from (supports type File and type String, all others return null)
+ * @param fileattr the file attribute to analyze and return from (supports type File and type String, all others return null
* @return the File object, null if null, or null if not a File or String
*/
private File asFile(Object fileattr)
@@ -363,45 +316,47 @@
- public void makeTempDirectory (File parent, WebAppContext context, boolean deleteExisting)
- throws IOException
+ public void makeTempDirectory (File parent, WebAppContext context)
+ throws Exception
{
- if (parent != null && parent.exists() && parent.canWrite() && parent.isDirectory())
+ if (parent == null || !parent.exists() || !parent.canWrite() || !parent.isDirectory())
+ throw new IllegalStateException("Parent for temp dir not configured correctly: "+(parent==null?"null":"writeable="+parent.canWrite()));
+
+ String temp = getCanonicalNameForWebAppTmpDir(context);
+ File tmpDir = File.createTempFile(temp, ".dir", parent);
+ //delete the file that was created
+ tmpDir.delete();
+ //and make a directory of the same name
+ tmpDir.mkdirs();
+ configureTempDirectory(tmpDir, context);
+
+ if(LOG.isDebugEnabled())
+ LOG.debug("Set temp dir "+tmpDir);
+ context.setTempDirectory(tmpDir);
+ }
+
+ private void configureTempDirectory (File dir, WebAppContext context)
+ {
+ if (dir == null)
+ throw new IllegalArgumentException("Null temp dir");
+
+ //if dir exists and we don't want it persisted, delete it
+ if (dir.exists() && !context.isPersistTempDirectory())
{
- String temp = getCanonicalNameForWebAppTmpDir(context);
- File tmpDir = new File(parent,temp);
-
- if (deleteExisting && tmpDir.exists())
- {
- if (!IO.delete(tmpDir))
- {
- if(LOG.isDebugEnabled())LOG.debug("Failed to delete temp dir "+tmpDir);
- }
-
- //If we can't delete the existing tmp dir, create a new one
- if (tmpDir.exists())
- {
- String old=tmpDir.toString();
- tmpDir=File.createTempFile(temp+"_","");
- if (tmpDir.exists())
- IO.delete(tmpDir);
- LOG.warn("Can't reuse "+old+", using "+tmpDir);
- }
- }
-
- if (!tmpDir.exists())
- tmpDir.mkdir();
-
- //If the parent is not a work directory
- if (!isTempWorkDirectory(tmpDir))
- {
- tmpDir.deleteOnExit();
- }
-
- if(LOG.isDebugEnabled())
- LOG.debug("Set temp dir "+tmpDir);
- context.setTempDirectory(tmpDir);
+ if (!IO.delete(dir))
+ throw new IllegalStateException("Failed to delete temp dir "+dir);
}
+
+ //if it doesn't exist make it
+ if (!dir.exists())
+ dir.mkdirs();
+
+ if (!context.isPersistTempDirectory())
+ dir.deleteOnExit();
+
+ //is it useable
+ if (!dir.canWrite() || !dir.isDirectory())
+ throw new IllegalStateException("Temp dir "+dir+" not useable: writeable="+dir.canWrite()+", dir="+dir.isDirectory());
}
@@ -564,45 +519,17 @@
}
- public File findWorkDirectory (WebAppContext context) throws IOException
- {
- if (context.getBaseResource() != null)
- {
- Resource web_inf = context.getWebInf();
- if (web_inf !=null && web_inf.exists())
- {
- return new File(web_inf.getFile(),"work");
- }
- }
- return null;
- }
-
-
- /**
- * Check if the tmpDir itself is called "work", or if the tmpDir
- * is in a directory called "work".
- * @return true if File is a temporary or work directory
- */
- public boolean isTempWorkDirectory (File tmpDir)
- {
- if (tmpDir == null)
- return false;
- if (tmpDir.getName().equalsIgnoreCase("work"))
- return true;
- File t = tmpDir.getParentFile();
- if (t == null)
- return false;
- return (t.getName().equalsIgnoreCase("work"));
- }
/**
* Create a canonical name for a webapp temp directory.
* The form of the name is:
- * <code>"Jetty_"+host+"_"+port+"__"+resourceBase+"_"+context+"_"+virtualhost+base36_hashcode_of_whole_string</code>
+ * <code>"jetty-"+host+"-"+port+"-"+resourceBase+"-_"+context+"-"+virtualhost+"-"+randomdigits+".dir"</code>
*
* host and port uniquely identify the server
* context and virtual host uniquely identify the webapp
+ * randomdigits ensure every tmp directory is unique
+ *
* @return the canonical name for the webapp temp directory
*/
public static String getCanonicalNameForWebAppTmpDir (WebAppContext context)
@@ -695,6 +622,7 @@
}
canonicalName.append("-");
+
return canonicalName.toString();
}
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java
index 776eecb..90d5bd8 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java
@@ -22,7 +22,6 @@
import java.net.MalformedURLException;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
-import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/OrderingTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/OrderingTest.java
index 5e5eec3..ea29e37 100644
--- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/OrderingTest.java
+++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/OrderingTest.java
@@ -24,7 +24,6 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.channels.ReadableByteChannel;
diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppClassLoaderTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppClassLoaderTest.java
index 41c6413..179a904 100644
--- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppClassLoaderTest.java
+++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppClassLoaderTest.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.webapp;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.net.URL;
@@ -121,9 +122,24 @@
assertTrue(canLoadClass("org.acme.webapp.ClassInJarA"));
assertTrue(canLoadClass("org.acme.webapp.ClassInJarB"));
assertTrue(canLoadClass("org.acme.other.ClassInClassesC"));
-
assertTrue(cantLoadClass("org.eclipse.jetty.webapp.Configuration"));
assertTrue(cantLoadClass("org.eclipse.jetty.webapp.JarScanner"));
+
+ oldSysC=_context.getSystemClasses();
+ newSysC=new String[oldSysC.length+1];
+ newSysC[0]="org.acme.webapp.ClassInJarA";
+ System.arraycopy(oldSysC,0,newSysC,1,oldSysC.length);
+ _context.setSystemClasses(newSysC);
+
+ assertNotNull(_loader.getResource("org/acme/webapp/ClassInJarA.class"));
+ _context.setSystemClasses(oldSysC);
+
+ oldServC=_context.getServerClasses();
+ newServC=new String[oldServC.length+1];
+ newServC[0]="org.acme.webapp.ClassInJarA";
+ System.arraycopy(oldServC,0,newServC,1,oldServC.length);
+ _context.setServerClasses(newServC);
+ assertNotNull(_loader.getResource("org/acme/webapp/ClassInJarA.class"));
}
@Test
diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java
index bd82a50..3f240f5 100644
--- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java
+++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java
@@ -20,7 +20,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
diff --git a/jetty-websocket/README.txt b/jetty-websocket/README.TXT
similarity index 100%
rename from jetty-websocket/README.txt
rename to jetty-websocket/README.TXT
diff --git a/jetty-websocket/javax-websocket-client-impl/pom.xml b/jetty-websocket/javax-websocket-client-impl/pom.xml
new file mode 100644
index 0000000..f50927f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/pom.xml
@@ -0,0 +1,76 @@
+<?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.websocket</groupId>
+ <artifactId>websocket-parent</artifactId>
+ <version>9.1.1-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>javax-websocket-client-impl</artifactId>
+ <name>Jetty :: Websocket :: javax.websocket :: Client Implementation</name>
+
+ <properties>
+ <bundle-symbolic-name>${project.groupId}.javax.websocket</bundle-symbolic-name>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>websocket-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.websocket</groupId>
+ <artifactId>javax.websocket-client-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>websocket-server</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-test-helper</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <version>1.1</version>
+ <executions>
+ <execution>
+ <id>ban-java-servlet-api</id>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <bannedDependencies>
+ <includes>
+ <include>javax.servlet</include>
+ <include>servletapi</include>
+ <include>org.eclipse.jetty.orbit:javax.servlet</include>
+ <include>org.mortbay.jetty:servlet-api</include>
+ <include>jetty:servlet-api</include>
+ </includes>
+ </bannedDependencies>
+ </rules>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/AbstractJsrRemote.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/AbstractJsrRemote.java
new file mode 100644
index 0000000..6f1184e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/AbstractJsrRemote.java
@@ -0,0 +1,194 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Future;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.SendHandler;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.WebSocketRemoteEndpoint;
+import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
+import org.eclipse.jetty.websocket.common.message.MessageOutputStream;
+import org.eclipse.jetty.websocket.common.message.MessageWriter;
+import org.eclipse.jetty.websocket.jsr356.encoders.EncodeFailedFuture;
+
+public abstract class AbstractJsrRemote implements RemoteEndpoint
+{
+ private static final Logger LOG = Log.getLogger(AbstractJsrRemote.class);
+
+ protected final JsrSession session;
+ protected final WebSocketRemoteEndpoint jettyRemote;
+ protected final EncoderFactory encoders;
+
+ protected AbstractJsrRemote(JsrSession session)
+ {
+ this.session = session;
+ if (!(session.getRemote() instanceof WebSocketRemoteEndpoint))
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Unexpected implementation [");
+ err.append(session.getRemote().getClass().getName());
+ err.append("]. Expected an instanceof [");
+ err.append(WebSocketRemoteEndpoint.class.getName());
+ err.append("]");
+ throw new IllegalStateException(err.toString());
+ }
+ this.jettyRemote = (WebSocketRemoteEndpoint)session.getRemote();
+ this.encoders = session.getEncoderFactory();
+ }
+
+ protected void assertMessageNotNull(Object data)
+ {
+ if (data == null)
+ {
+ throw new IllegalArgumentException("message cannot be null");
+ }
+ }
+
+ protected void assertSendHandlerNotNull(SendHandler handler)
+ {
+ if (handler == null)
+ {
+ throw new IllegalArgumentException("SendHandler cannot be null");
+ }
+ }
+
+ @Override
+ public void flushBatch() throws IOException
+ {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public boolean getBatchingAllowed()
+ {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @SuppressWarnings(
+ { "rawtypes", "unchecked" })
+ public Future<Void> sendObjectViaFuture(Object data)
+ {
+ assertMessageNotNull(data);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendObject({})",data);
+ }
+
+ Encoder encoder = encoders.getEncoderFor(data.getClass());
+ if (encoder == null)
+ {
+ throw new IllegalArgumentException("No encoder for type: " + data.getClass());
+ }
+
+ if (encoder instanceof Encoder.Text)
+ {
+ Encoder.Text etxt = (Encoder.Text)encoder;
+ try
+ {
+ String msg = etxt.encode(data);
+ return jettyRemote.sendStringByFuture(msg);
+ }
+ catch (EncodeException e)
+ {
+ return new EncodeFailedFuture(data,etxt,Encoder.Text.class,e);
+ }
+ }
+ else if (encoder instanceof Encoder.TextStream)
+ {
+ Encoder.TextStream etxt = (Encoder.TextStream)encoder;
+ FutureWriteCallback callback = new FutureWriteCallback();
+ try (MessageWriter writer = new MessageWriter(session))
+ {
+ writer.setCallback(callback);
+ etxt.encode(data,writer);
+ return callback;
+ }
+ catch (EncodeException | IOException e)
+ {
+ return new EncodeFailedFuture(data,etxt,Encoder.Text.class,e);
+ }
+ }
+ else if (encoder instanceof Encoder.Binary)
+ {
+ Encoder.Binary ebin = (Encoder.Binary)encoder;
+ try
+ {
+ ByteBuffer buf = ebin.encode(data);
+ return jettyRemote.sendBytesByFuture(buf);
+ }
+ catch (EncodeException e)
+ {
+ return new EncodeFailedFuture(data,ebin,Encoder.Binary.class,e);
+ }
+ }
+ else if (encoder instanceof Encoder.BinaryStream)
+ {
+ Encoder.BinaryStream ebin = (Encoder.BinaryStream)encoder;
+ FutureWriteCallback callback = new FutureWriteCallback();
+ try (MessageOutputStream out = new MessageOutputStream(session))
+ {
+ out.setCallback(callback);
+ ebin.encode(data,out);
+ return callback;
+ }
+ catch (EncodeException | IOException e)
+ {
+ return new EncodeFailedFuture(data,ebin,Encoder.Binary.class,e);
+ }
+ }
+
+ throw new IllegalArgumentException("Unknown encoder type: " + encoder);
+ }
+
+ @Override
+ public void sendPing(ByteBuffer data) throws IOException, IllegalArgumentException
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendPing({})",BufferUtil.toDetailString(data));
+ }
+ jettyRemote.sendPing(data);
+ }
+
+ @Override
+ public void sendPong(ByteBuffer data) throws IOException, IllegalArgumentException
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendPong({})",BufferUtil.toDetailString(data));
+ }
+ jettyRemote.sendPong(data);
+ }
+
+ @Override
+ public void setBatchingAllowed(boolean allowed) throws IOException
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/BasicEndpointConfig.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/BasicEndpointConfig.java
new file mode 100644
index 0000000..72a3271
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/BasicEndpointConfig.java
@@ -0,0 +1,63 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Basic EndpointConfig (used when no EndpointConfig is provided or discovered)
+ */
+public class BasicEndpointConfig implements EndpointConfig
+{
+ private List<Class<? extends Decoder>> decoders;
+ private List<Class<? extends Encoder>> encoders;
+ private Map<String, Object> userProperties;
+
+ public BasicEndpointConfig()
+ {
+ decoders = Collections.emptyList();
+ encoders = Collections.emptyList();
+ userProperties = new HashMap<>();
+ }
+
+ @Override
+ public List<Class<? extends Decoder>> getDecoders()
+ {
+ return decoders;
+ }
+
+ @Override
+ public List<Class<? extends Encoder>> getEncoders()
+ {
+ return encoders;
+ }
+
+ @Override
+ public Map<String, Object> getUserProperties()
+ {
+ return userProperties;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java
new file mode 100644
index 0000000..a3a6bde
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java
@@ -0,0 +1,353 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.Extension;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.thread.ShutdownThread;
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.client.io.UpgradeListener;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.client.AnnotatedClientEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
+import org.eclipse.jetty.websocket.jsr356.client.SimpleEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.decoders.PrimitiveDecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.encoders.PrimitiveEncoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEventDriverFactory;
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+
+/**
+ * Container for Client use of the javax.websocket API.
+ * <p>
+ * This should be specific to a JVM if run in a standalone mode. or specific to a WebAppContext if running on the Jetty server.
+ */
+public class ClientContainer extends ContainerLifeCycle implements WebSocketContainer
+{
+ /** Tracking all primitive decoders for the container */
+ private final DecoderFactory decoderFactory;
+ /** Tracking all primitive encoders for the container */
+ private final EncoderFactory encoderFactory;
+
+ /** Tracking for all declared Client endpoints */
+ private final Map<Class<?>, EndpointMetadata> endpointClientMetadataCache;
+ /** The jetty websocket client in use for this container */
+ private WebSocketClient client;
+
+ public ClientContainer()
+ {
+ this(null);
+ }
+
+ public ClientContainer(Executor executor)
+ {
+ endpointClientMetadataCache = new ConcurrentHashMap<>();
+ decoderFactory = new DecoderFactory(PrimitiveDecoderMetadataSet.INSTANCE);
+ encoderFactory = new EncoderFactory(PrimitiveEncoderMetadataSet.INSTANCE);
+
+ EmptyClientEndpointConfig empty = new EmptyClientEndpointConfig();
+ decoderFactory.init(empty);
+ encoderFactory.init(empty);
+
+ client = new WebSocketClient(executor);
+ client.setEventDriverFactory(new JsrEventDriverFactory(client.getPolicy()));
+ client.setSessionFactory(new JsrSessionFactory(this));
+ addBean(client);
+ }
+
+ private Session connect(EndpointInstance instance, URI path) throws IOException
+ {
+ Objects.requireNonNull(instance,"EndpointInstance cannot be null");
+ Objects.requireNonNull(path,"Path cannot be null");
+
+ ClientEndpointConfig config = (ClientEndpointConfig)instance.getConfig();
+ ClientUpgradeRequest req = new ClientUpgradeRequest();
+ UpgradeListener upgradeListener = null;
+
+ for (Extension ext : config.getExtensions())
+ {
+ req.addExtensions(new JsrExtensionConfig(ext));
+ }
+
+ if (config.getPreferredSubprotocols().size() > 0)
+ {
+ req.setSubProtocols(config.getPreferredSubprotocols());
+ }
+
+ if (config.getConfigurator() != null)
+ {
+ upgradeListener = new JsrUpgradeListener(config.getConfigurator());
+ }
+
+ Future<org.eclipse.jetty.websocket.api.Session> futSess = client.connect(instance,path,req,upgradeListener);
+ try
+ {
+ return (JsrSession)futSess.get();
+ }
+ catch (InterruptedException e)
+ {
+ throw new IOException("Connect failure",e);
+ }
+ catch (ExecutionException e)
+ {
+ // Unwrap Actual Cause
+ Throwable cause = e.getCause();
+
+ if (cause instanceof IOException)
+ {
+ // Just rethrow
+ throw (IOException)cause;
+ }
+ else
+ {
+ throw new IOException("Connect failure",cause);
+ }
+ }
+ }
+
+ @Override
+ public Session connectToServer(Class<? extends Endpoint> endpointClass, ClientEndpointConfig config, URI path) throws DeploymentException, IOException
+ {
+ EndpointInstance instance = newClientEndpointInstance(endpointClass,config);
+ return connect(instance,path);
+ }
+
+ @Override
+ public Session connectToServer(Class<?> annotatedEndpointClass, URI path) throws DeploymentException, IOException
+ {
+ EndpointInstance instance = newClientEndpointInstance(annotatedEndpointClass,null);
+ return connect(instance,path);
+ }
+
+ @Override
+ public Session connectToServer(Endpoint endpoint, ClientEndpointConfig config, URI path) throws DeploymentException, IOException
+ {
+ EndpointInstance instance = newClientEndpointInstance(endpoint,config);
+ return connect(instance,path);
+ }
+
+ @Override
+ public Session connectToServer(Object endpoint, URI path) throws DeploymentException, IOException
+ {
+ EndpointInstance instance = newClientEndpointInstance(endpoint,null);
+ return connect(instance,path);
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ ShutdownThread.register(client);
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ endpointClientMetadataCache.clear();
+ ShutdownThread.deregister(client);
+ super.doStop();
+ }
+
+ public WebSocketClient getClient()
+ {
+ return client;
+ }
+
+ public EndpointMetadata getClientEndpointMetadata(Class<?> endpoint)
+ {
+ EndpointMetadata metadata = null;
+
+ synchronized (endpointClientMetadataCache)
+ {
+ metadata = endpointClientMetadataCache.get(endpoint);
+
+ if (metadata != null)
+ {
+ return metadata;
+ }
+
+ ClientEndpoint anno = endpoint.getAnnotation(ClientEndpoint.class);
+ if (anno != null)
+ {
+ // Annotated takes precedence here
+ AnnotatedClientEndpointMetadata annoMetadata = new AnnotatedClientEndpointMetadata(this,endpoint);
+ AnnotatedEndpointScanner<ClientEndpoint, ClientEndpointConfig> scanner = new AnnotatedEndpointScanner<>(annoMetadata);
+ scanner.scan();
+ metadata = annoMetadata;
+ }
+ else if (Endpoint.class.isAssignableFrom(endpoint))
+ {
+ // extends Endpoint
+ @SuppressWarnings("unchecked")
+ Class<? extends Endpoint> eendpoint = (Class<? extends Endpoint>)endpoint;
+ metadata = new SimpleEndpointMetadata(eendpoint);
+ }
+ else
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Not a recognized websocket [");
+ err.append(endpoint.getName());
+ err.append("] does not extend @").append(ClientEndpoint.class.getName());
+ err.append(" or extend from ").append(Endpoint.class.getName());
+ throw new InvalidWebSocketException("Unable to identify as valid Endpoint: " + endpoint);
+ }
+
+ endpointClientMetadataCache.put(endpoint,metadata);
+ return metadata;
+ }
+ }
+
+ public DecoderFactory getDecoderFactory()
+ {
+ return decoderFactory;
+ }
+
+ @Override
+ public long getDefaultAsyncSendTimeout()
+ {
+ return client.getAsyncWriteTimeout();
+ }
+
+ @Override
+ public int getDefaultMaxBinaryMessageBufferSize()
+ {
+ return client.getMaxBinaryMessageBufferSize();
+ }
+
+ @Override
+ public long getDefaultMaxSessionIdleTimeout()
+ {
+ return client.getMaxIdleTimeout();
+ }
+
+ @Override
+ public int getDefaultMaxTextMessageBufferSize()
+ {
+ return client.getMaxTextMessageBufferSize();
+ }
+
+ public EncoderFactory getEncoderFactory()
+ {
+ return encoderFactory;
+ }
+
+ @Override
+ public Set<Extension> getInstalledExtensions()
+ {
+ Set<Extension> ret = new HashSet<>();
+ ExtensionFactory extensions = client.getExtensionFactory();
+
+ for (String name : extensions.getExtensionNames())
+ {
+ ret.add(new JsrExtension(name));
+ }
+
+ return ret;
+ }
+
+ /**
+ * Used in {@link Session#getOpenSessions()}
+ */
+ public Set<Session> getOpenSessions()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ private EndpointInstance newClientEndpointInstance(Class<?> endpointClass, ClientEndpointConfig config)
+ {
+ try
+ {
+ return newClientEndpointInstance(endpointClass.newInstance(),config);
+ }
+ catch (InstantiationException | IllegalAccessException e)
+ {
+ throw new InvalidWebSocketException("Unable to instantiate websocket: " + endpointClass.getClass());
+ }
+ }
+
+ public EndpointInstance newClientEndpointInstance(Object endpoint, ClientEndpointConfig config)
+ {
+ EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass());
+ ClientEndpointConfig cec = config;
+ if (config == null)
+ {
+ if (metadata instanceof AnnotatedClientEndpointMetadata)
+ {
+ cec = ((AnnotatedClientEndpointMetadata)metadata).getConfig();
+ }
+ else
+ {
+ cec = new EmptyClientEndpointConfig();
+ }
+ }
+ return new EndpointInstance(endpoint,cec,metadata);
+ }
+
+ @Override
+ public void setAsyncSendTimeout(long ms)
+ {
+ client.setAsyncWriteTimeout(ms);
+ }
+
+ @Override
+ public void setDefaultMaxBinaryMessageBufferSize(int max)
+ {
+ // overall message limit (used in non-streaming)
+ client.getPolicy().setMaxBinaryMessageSize(max);
+ // incoming streaming buffer size
+ client.setMaxBinaryMessageBufferSize(max);
+ }
+
+ @Override
+ public void setDefaultMaxSessionIdleTimeout(long ms)
+ {
+ client.setMaxIdleTimeout(ms);
+ }
+
+ @Override
+ public void setDefaultMaxTextMessageBufferSize(int max)
+ {
+ // overall message limit (used in non-streaming)
+ client.getPolicy().setMaxTextMessageSize(max);
+ // incoming streaming buffer size
+ client.setMaxTextMessageBufferSize(max);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/Configurable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/Configurable.java
new file mode 100644
index 0000000..6f8f7d6
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/Configurable.java
@@ -0,0 +1,29 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import javax.websocket.EndpointConfig;
+
+/**
+ * Tag indicating a component that needs to be configured.
+ */
+public interface Configurable
+{
+ public void init(EndpointConfig config);
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ConfigurationException.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ConfigurationException.java
new file mode 100644
index 0000000..aa4b9c5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ConfigurationException.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import org.eclipse.jetty.websocket.api.WebSocketException;
+
+public class ConfigurationException extends WebSocketException
+{
+ private static final long serialVersionUID = 3026803845657799372L;
+
+ public ConfigurationException(String message)
+ {
+ super(message);
+ }
+
+ public ConfigurationException(String message, Throwable cause)
+ {
+ super(message,cause);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/DecoderFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/DecoderFactory.java
new file mode 100644
index 0000000..5c9c0bb
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/DecoderFactory.java
@@ -0,0 +1,178 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
+
+/**
+ * Factory for {@link DecoderMetadata}
+ * <p>
+ * Relies on search order of parent {@link DecoderFactory} instances as such.
+ * <ul>
+ * <li>From Static DecoderMetadataSet (based on data in annotations and static EndpointConfig)</li>
+ * <li>From Composite DecoderMetadataSet (based static and instance specific EndpointConfig)</li>
+ * <li>Container declared DecoderMetadataSet (primitives)</li>
+ * </ul>
+ */
+public class DecoderFactory implements Configurable
+{
+ public static class Wrapper implements Configurable
+ {
+ private final Decoder decoder;
+ private final DecoderMetadata metadata;
+
+ private Wrapper(Decoder decoder, DecoderMetadata metadata)
+ {
+ this.decoder = decoder;
+ this.metadata = metadata;
+ }
+
+ public Decoder getDecoder()
+ {
+ return decoder;
+ }
+
+ public DecoderMetadata getMetadata()
+ {
+ return metadata;
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ this.decoder.init(config);
+ }
+ }
+
+ private static final Logger LOG = Log.getLogger(DecoderFactory.class);
+
+ private final DecoderMetadataSet metadatas;
+ private DecoderFactory parentFactory;
+ private Map<Class<?>, Wrapper> activeWrappers;
+
+ public DecoderFactory(DecoderMetadataSet metadatas)
+ {
+ this.metadatas = metadatas;
+ this.activeWrappers = new ConcurrentHashMap<>();
+ }
+
+ public DecoderFactory(DecoderMetadataSet metadatas, DecoderFactory parentFactory)
+ {
+ this(metadatas);
+ this.parentFactory = parentFactory;
+ }
+
+ public Decoder getDecoderFor(Class<?> type)
+ {
+ Wrapper wrapper = getWrapperFor(type);
+ if (wrapper == null)
+ {
+ return null;
+ }
+ return wrapper.decoder;
+ }
+
+ public DecoderMetadata getMetadataFor(Class<?> type)
+ {
+ LOG.debug("getMetadataFor({})",type);
+ DecoderMetadata metadata = metadatas.getMetadataByType(type);
+
+ if (metadata != null)
+ {
+ return metadata;
+ }
+
+ if (parentFactory != null)
+ {
+ return parentFactory.getMetadataFor(type);
+ }
+
+ return null;
+ }
+
+ public Wrapper getWrapperFor(Class<?> type)
+ {
+ synchronized (activeWrappers)
+ {
+ Wrapper wrapper = activeWrappers.get(type);
+
+ // Try parent (if needed)
+ if ((wrapper == null) && (parentFactory != null))
+ {
+ wrapper = parentFactory.getWrapperFor(type);
+ }
+
+ if (wrapper == null)
+ {
+ // Attempt to create Wrapper on demand
+ DecoderMetadata metadata = metadatas.getMetadataByType(type);
+ if (metadata == null)
+ {
+ return null;
+ }
+ wrapper = newWrapper(metadata);
+ // track wrapper
+ activeWrappers.put(type,wrapper);
+ }
+
+ return wrapper;
+ }
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ LOG.debug("init({})",config);
+ // Instantiate all declared decoders
+ for (DecoderMetadata metadata : metadatas)
+ {
+ Wrapper wrapper = newWrapper(metadata);
+ activeWrappers.put(metadata.getObjectType(),wrapper);
+ }
+
+ // Initialize all decoders
+ for (Wrapper wrapper : activeWrappers.values())
+ {
+ wrapper.decoder.init(config);
+ }
+ }
+
+ public Wrapper newWrapper(DecoderMetadata metadata)
+ {
+ Class<? extends Decoder> decoderClass = metadata.getCoderClass();
+ try
+ {
+ Decoder decoder = decoderClass.newInstance();
+ return new Wrapper(decoder,metadata);
+ }
+ catch (InstantiationException | IllegalAccessException e)
+ {
+ throw new IllegalStateException("Unable to instantiate Decoder: " + decoderClass.getName());
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/EncoderFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/EncoderFactory.java
new file mode 100644
index 0000000..0e6527f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/EncoderFactory.java
@@ -0,0 +1,172 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
+
+/**
+ * Represents all of the declared {@link Encoder}s that the Container is aware of.
+ */
+public class EncoderFactory implements Configurable
+{
+ public static class Wrapper implements Configurable
+ {
+ private final Encoder encoder;
+ private final EncoderMetadata metadata;
+
+ private Wrapper(Encoder encoder, EncoderMetadata metadata)
+ {
+ this.encoder = encoder;
+ this.metadata = metadata;
+ }
+
+ public Encoder getEncoder()
+ {
+ return encoder;
+ }
+
+ public EncoderMetadata getMetadata()
+ {
+ return metadata;
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ this.encoder.init(config);
+ }
+ }
+
+ private static final Logger LOG = Log.getLogger(EncoderFactory.class);
+
+ private final EncoderMetadataSet metadatas;
+ private EncoderFactory parentFactory;
+ private Map<Class<?>, Wrapper> activeWrappers;
+
+ public EncoderFactory(EncoderMetadataSet metadatas)
+ {
+ this.metadatas = metadatas;
+ this.activeWrappers = new ConcurrentHashMap<>();
+ }
+
+ public EncoderFactory(EncoderMetadataSet metadatas, EncoderFactory parentFactory)
+ {
+ this(metadatas);
+ this.parentFactory = parentFactory;
+ }
+
+ public Encoder getEncoderFor(Class<?> type)
+ {
+ Wrapper wrapper = getWrapperFor(type);
+ if (wrapper == null)
+ {
+ return null;
+ }
+ return wrapper.encoder;
+ }
+
+ public EncoderMetadata getMetadataFor(Class<?> type)
+ {
+ LOG.debug("getMetadataFor({})",type);
+ EncoderMetadata metadata = metadatas.getMetadataByType(type);
+
+ if (metadata != null)
+ {
+ return metadata;
+ }
+
+ if (parentFactory != null)
+ {
+ return parentFactory.getMetadataFor(type);
+ }
+
+ return null;
+ }
+
+ public Wrapper getWrapperFor(Class<?> type)
+ {
+ synchronized (activeWrappers)
+ {
+ Wrapper wrapper = activeWrappers.get(type);
+
+ // Try parent (if needed)
+ if ((wrapper == null) && (parentFactory != null))
+ {
+ wrapper = parentFactory.getWrapperFor(type);
+ }
+
+ if (wrapper == null)
+ {
+ // Attempt to create Wrapper on demand
+ EncoderMetadata metadata = metadatas.getMetadataByType(type);
+ if (metadata == null)
+ {
+ return null;
+ }
+ wrapper = newWrapper(metadata);
+ // track wrapper
+ activeWrappers.put(type,wrapper);
+ }
+
+ return wrapper;
+ }
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ LOG.debug("init({})",config);
+
+ // Instantiate all declared encoders
+ for (EncoderMetadata metadata : metadatas)
+ {
+ Wrapper wrapper = newWrapper(metadata);
+ activeWrappers.put(metadata.getObjectType(),wrapper);
+ }
+
+ // Initialize all encoders
+ for (Wrapper wrapper : activeWrappers.values())
+ {
+ wrapper.encoder.init(config);
+ }
+ }
+
+ private Wrapper newWrapper(EncoderMetadata metadata)
+ {
+ Class<? extends Encoder> encoderClass = metadata.getCoderClass();
+ try
+ {
+ Encoder encoder = encoderClass.newInstance();
+ return new Wrapper(encoder,metadata);
+ }
+ catch (InstantiationException | IllegalAccessException e)
+ {
+ throw new IllegalStateException("Unable to instantiate Encoder: " + encoderClass.getName());
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/InitException.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/InitException.java
new file mode 100644
index 0000000..7bb67b8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/InitException.java
@@ -0,0 +1,42 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+/**
+ * Exception during initialization of the Endpoint
+ */
+public class InitException extends IllegalStateException
+{
+ private static final long serialVersionUID = -4691138423037387558L;
+
+ public InitException(String s)
+ {
+ super(s);
+ }
+
+ public InitException(String message, Throwable cause)
+ {
+ super(message,cause);
+ }
+
+ public InitException(Throwable cause)
+ {
+ super(cause);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JettyClientContainerProvider.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JettyClientContainerProvider.java
new file mode 100644
index 0000000..3463ae8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JettyClientContainerProvider.java
@@ -0,0 +1,43 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.WebSocketContainer;
+
+/**
+ * Client {@link ContainerProvider} implementation
+ */
+public class JettyClientContainerProvider extends ContainerProvider
+{
+ @Override
+ protected WebSocketContainer getContainer()
+ {
+ ClientContainer container = new ClientContainer();
+ try
+ {
+ container.start();
+ return container;
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException("Unable to start Client Container",e);
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrAsyncRemote.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrAsyncRemote.java
new file mode 100644
index 0000000..9021065
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrAsyncRemote.java
@@ -0,0 +1,197 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Future;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.message.MessageOutputStream;
+import org.eclipse.jetty.websocket.common.message.MessageWriter;
+import org.eclipse.jetty.websocket.common.util.TextUtil;
+import org.eclipse.jetty.websocket.jsr356.messages.SendHandlerWriteCallback;
+
+public class JsrAsyncRemote extends AbstractJsrRemote implements RemoteEndpoint.Async
+{
+ static final Logger LOG = Log.getLogger(JsrAsyncRemote.class);
+
+ protected JsrAsyncRemote(JsrSession session)
+ {
+ super(session);
+ }
+
+ @Override
+ public long getSendTimeout()
+ {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public Future<Void> sendBinary(ByteBuffer data)
+ {
+ assertMessageNotNull(data);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendBinary({})",BufferUtil.toDetailString(data));
+ }
+ return jettyRemote.sendBytesByFuture(data);
+ }
+
+ @Override
+ public void sendBinary(ByteBuffer data, SendHandler handler)
+ {
+ assertMessageNotNull(data);
+ assertSendHandlerNotNull(handler);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendBinary({},{})",BufferUtil.toDetailString(data),handler);
+ }
+ WebSocketFrame frame = new BinaryFrame().setPayload(data).setFin(true);
+ jettyRemote.sendFrame(frame,new SendHandlerWriteCallback(handler));
+ }
+
+ @Override
+ public Future<Void> sendObject(Object data)
+ {
+ return sendObjectViaFuture(data);
+ }
+
+ @SuppressWarnings(
+ { "rawtypes", "unchecked" })
+ @Override
+ public void sendObject(Object data, SendHandler handler)
+ {
+ assertMessageNotNull(data);
+ assertSendHandlerNotNull(handler);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendObject({},{})",data,handler);
+ }
+
+ Encoder encoder = encoders.getEncoderFor(data.getClass());
+ if (encoder == null)
+ {
+ throw new IllegalArgumentException("No encoder for type: " + data.getClass());
+ }
+
+ if (encoder instanceof Encoder.Text)
+ {
+ Encoder.Text etxt = (Encoder.Text)encoder;
+ try
+ {
+ String msg = etxt.encode(data);
+ sendText(msg,handler);
+ return;
+ }
+ catch (EncodeException e)
+ {
+ handler.onResult(new SendResult(e));
+ }
+ }
+ else if (encoder instanceof Encoder.TextStream)
+ {
+ Encoder.TextStream etxt = (Encoder.TextStream)encoder;
+ SendHandlerWriteCallback callback = new SendHandlerWriteCallback(handler);
+ try (MessageWriter writer = new MessageWriter(session))
+ {
+ writer.setCallback(callback);
+ etxt.encode(data,writer);
+ return;
+ }
+ catch (EncodeException | IOException e)
+ {
+ handler.onResult(new SendResult(e));
+ }
+ }
+ else if (encoder instanceof Encoder.Binary)
+ {
+ Encoder.Binary ebin = (Encoder.Binary)encoder;
+ try
+ {
+ ByteBuffer buf = ebin.encode(data);
+ sendBinary(buf,handler);
+ return;
+ }
+ catch (EncodeException e)
+ {
+ handler.onResult(new SendResult(e));
+ }
+ }
+ else if (encoder instanceof Encoder.BinaryStream)
+ {
+ Encoder.BinaryStream ebin = (Encoder.BinaryStream)encoder;
+ SendHandlerWriteCallback callback = new SendHandlerWriteCallback(handler);
+ try (MessageOutputStream out = new MessageOutputStream(session))
+ {
+ out.setCallback(callback);
+ ebin.encode(data,out);
+ return;
+ }
+ catch (EncodeException | IOException e)
+ {
+ handler.onResult(new SendResult(e));
+ }
+ }
+
+ throw new IllegalArgumentException("Unknown encoder type: " + encoder);
+ }
+
+ @Override
+ public Future<Void> sendText(String text)
+ {
+ assertMessageNotNull(text);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendText({})",TextUtil.hint(text));
+ }
+ return jettyRemote.sendStringByFuture(text);
+ }
+
+ @Override
+ public void sendText(String text, SendHandler handler)
+ {
+ assertMessageNotNull(text);
+ assertSendHandlerNotNull(handler);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendText({},{})",TextUtil.hint(text),handler);
+ }
+ WebSocketFrame frame = new TextFrame().setPayload(text).setFin(true);
+ jettyRemote.sendFrame(frame,new SendHandlerWriteCallback(handler));
+ }
+
+ @Override
+ public void setSendTimeout(long timeoutmillis)
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrBasicRemote.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrBasicRemote.java
new file mode 100644
index 0000000..3851c63
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrBasicRemote.java
@@ -0,0 +1,120 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import javax.websocket.EncodeException;
+import javax.websocket.RemoteEndpoint;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.message.MessageOutputStream;
+import org.eclipse.jetty.websocket.common.message.MessageWriter;
+import org.eclipse.jetty.websocket.common.util.TextUtil;
+
+public class JsrBasicRemote extends AbstractJsrRemote implements RemoteEndpoint.Basic
+{
+ private static final Logger LOG = Log.getLogger(JsrBasicRemote.class);
+
+ protected JsrBasicRemote(JsrSession session)
+ {
+ super(session);
+ }
+
+ @Override
+ public OutputStream getSendStream() throws IOException
+ {
+ return new MessageOutputStream(session);
+ }
+
+ @Override
+ public Writer getSendWriter() throws IOException
+ {
+ return new MessageWriter(session);
+ }
+
+ @Override
+ public void sendBinary(ByteBuffer data) throws IOException
+ {
+ assertMessageNotNull(data);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendBinary({})",BufferUtil.toDetailString(data));
+ }
+ jettyRemote.sendBytes(data);
+ }
+
+ @Override
+ public void sendBinary(ByteBuffer partialByte, boolean isLast) throws IOException
+ {
+ assertMessageNotNull(partialByte);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendBinary({},{})",BufferUtil.toDetailString(partialByte),isLast);
+ }
+ jettyRemote.sendPartialBytes(partialByte,isLast);
+ }
+
+ @Override
+ public void sendObject(Object data) throws IOException, EncodeException
+ {
+ Future<Void> fut = sendObjectViaFuture(data);
+ try
+ {
+ fut.get(); // block till done
+ }
+ catch (ExecutionException e)
+ {
+ throw new IOException("Failed to write object",e.getCause());
+ }
+ catch (InterruptedException e)
+ {
+ throw new IOException("Failed to write object",e);
+ }
+ }
+
+ @Override
+ public void sendText(String text) throws IOException
+ {
+ assertMessageNotNull(text);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendText({})",TextUtil.hint(text));
+ }
+ jettyRemote.sendString(text);
+ }
+
+ @Override
+ public void sendText(String partialMessage, boolean isLast) throws IOException
+ {
+ assertMessageNotNull(partialMessage);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendText({},{})",TextUtil.hint(partialMessage),isLast);
+ }
+ jettyRemote.sendPartialString(partialMessage,isLast);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrExtension.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrExtension.java
new file mode 100644
index 0000000..633418c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrExtension.java
@@ -0,0 +1,112 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.Extension;
+
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+
+public class JsrExtension implements Extension
+{
+ private static class JsrParameter implements Extension.Parameter
+ {
+ private String name;
+ private String value;
+
+ private JsrParameter(String key, String value)
+ {
+ this.name = key;
+ this.value = value;
+ }
+
+ @Override
+ public String getName()
+ {
+ return this.name;
+ }
+
+ @Override
+ public String getValue()
+ {
+ return this.value;
+ }
+ }
+
+ private final String name;
+ private List<Parameter> parameters = new ArrayList<>();
+
+ /**
+ * A configured extension
+ */
+ public JsrExtension(ExtensionConfig cfg)
+ {
+ this.name = cfg.getName();
+ if (cfg.getParameters() != null)
+ {
+ for (Map.Entry<String, String> entry : cfg.getParameters().entrySet())
+ {
+ parameters.add(new JsrParameter(entry.getKey(),entry.getValue()));
+ }
+ }
+ }
+
+ /**
+ * A potential (unconfigured) extension
+ */
+ public JsrExtension(String name)
+ {
+ this.name = name;
+ }
+
+ @Override
+ public String getName()
+ {
+ return name;
+ }
+
+ @Override
+ public List<Parameter> getParameters()
+ {
+ return parameters;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder();
+ str.append(name);
+ for (Parameter param : parameters)
+ {
+ str.append(';');
+ str.append(param.getName());
+ String value = param.getValue();
+ if (value != null)
+ {
+ str.append('=');
+ QuoteUtil.quoteIfNeeded(str,value,";=");
+ }
+ }
+ return str.toString();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrExtensionConfig.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrExtensionConfig.java
new file mode 100644
index 0000000..1e01dd3
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrExtensionConfig.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import javax.websocket.Extension;
+
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+
+public class JsrExtensionConfig extends ExtensionConfig
+{
+ public JsrExtensionConfig(Extension ext)
+ {
+ super(ext.getName());
+ for (Extension.Parameter param : ext.getParameters())
+ {
+ this.setParameter(param.getName(),param.getValue());
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrHandshakeResponse.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrHandshakeResponse.java
new file mode 100644
index 0000000..0ce1597
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrHandshakeResponse.java
@@ -0,0 +1,42 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.HandshakeResponse;
+
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+
+public class JsrHandshakeResponse implements HandshakeResponse
+{
+ private final Map<String, List<String>> headers;
+
+ public JsrHandshakeResponse(UpgradeResponse response)
+ {
+ this.headers = response.getHeaders();
+ }
+
+ @Override
+ public Map<String, List<String>> getHeaders()
+ {
+ return this.headers;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrPongMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrPongMessage.java
new file mode 100644
index 0000000..a4edcf9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrPongMessage.java
@@ -0,0 +1,39 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.PongMessage;
+
+public class JsrPongMessage implements PongMessage
+{
+ private final ByteBuffer data;
+
+ public JsrPongMessage(ByteBuffer buf)
+ {
+ this.data = buf;
+ }
+
+ @Override
+ public ByteBuffer getApplicationData()
+ {
+ return data;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSession.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSession.java
new file mode 100644
index 0000000..14476bd
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSession.java
@@ -0,0 +1,375 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+import java.net.URI;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import javax.websocket.CloseReason;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Extension;
+import javax.websocket.MessageHandler;
+import javax.websocket.RemoteEndpoint.Async;
+import javax.websocket.RemoteEndpoint.Basic;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.jsr356.endpoints.AbstractJsrEventDriver;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.MessageHandlerMetadata;
+
+/**
+ * Session for the JSR.
+ */
+public class JsrSession extends WebSocketSession implements javax.websocket.Session, Configurable
+{
+ private static final Logger LOG = Log.getLogger(JsrSession.class);
+ private final ClientContainer container;
+ private final String id;
+ private final EndpointConfig config;
+ private final EndpointMetadata metadata;
+ private final DecoderFactory decoderFactory;
+ private final EncoderFactory encoderFactory;
+ /** Factory for MessageHandlers */
+ private final MessageHandlerFactory messageHandlerFactory;
+ /** Array of MessageHandlerWrappers, indexed by {@link MessageType#ordinal()} */
+ private final MessageHandlerWrapper wrappers[];
+ private Set<MessageHandler> messageHandlerSet;
+ private List<Extension> negotiatedExtensions;
+ private Map<String, String> pathParameters = new HashMap<>();
+ private JsrAsyncRemote asyncRemote;
+ private JsrBasicRemote basicRemote;
+
+ public JsrSession(URI requestURI, EventDriver websocket, LogicalConnection connection, ClientContainer container, String id)
+ {
+ super(requestURI,websocket,connection);
+ if (!(websocket instanceof AbstractJsrEventDriver))
+ {
+ throw new IllegalArgumentException("Cannot use, not a JSR WebSocket: " + websocket);
+ }
+ AbstractJsrEventDriver jsr = (AbstractJsrEventDriver)websocket;
+ this.config = jsr.getConfig();
+ this.metadata = jsr.getMetadata();
+ this.container = container;
+ this.id = id;
+ this.decoderFactory = new DecoderFactory(metadata.getDecoders(),container.getDecoderFactory());
+ this.encoderFactory = new EncoderFactory(metadata.getEncoders(),container.getEncoderFactory());
+ this.messageHandlerFactory = new MessageHandlerFactory();
+ this.wrappers = new MessageHandlerWrapper[MessageType.values().length];
+ this.messageHandlerSet = new HashSet<>();
+
+ }
+
+ @Override
+ public void addMessageHandler(MessageHandler handler) throws IllegalStateException
+ {
+ Objects.requireNonNull(handler,"MessageHandler cannot be null");
+
+ synchronized (wrappers)
+ {
+ for (MessageHandlerMetadata metadata : messageHandlerFactory.getMetadata(handler.getClass()))
+ {
+ DecoderFactory.Wrapper wrapper = decoderFactory.getWrapperFor(metadata.getMessageClass());
+ if (wrapper == null)
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Unable to find decoder for type <");
+ err.append(metadata.getMessageClass().getName());
+ err.append("> used in <");
+ err.append(metadata.getHandlerClass().getName());
+ err.append(">");
+ throw new IllegalStateException(err.toString());
+ }
+
+ MessageType key = wrapper.getMetadata().getMessageType();
+ MessageHandlerWrapper other = wrappers[key.ordinal()];
+ if (other != null)
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Encountered duplicate MessageHandler handling message type <");
+ err.append(wrapper.getMetadata().getObjectType().getName());
+ err.append(">, ").append(metadata.getHandlerClass().getName());
+ err.append("<");
+ err.append(metadata.getMessageClass().getName());
+ err.append("> and ");
+ err.append(other.getMetadata().getHandlerClass().getName());
+ err.append("<");
+ err.append(other.getMetadata().getMessageClass().getName());
+ err.append("> both implement this message type");
+ throw new IllegalStateException(err.toString());
+ }
+ else
+ {
+ MessageHandlerWrapper handlerWrapper = new MessageHandlerWrapper(handler,metadata,wrapper);
+ wrappers[key.ordinal()] = handlerWrapper;
+ }
+ }
+
+ // Update handlerSet
+ updateMessageHandlerSet();
+ }
+ }
+
+ @Override
+ public void close(CloseReason closeReason) throws IOException
+ {
+ close(closeReason.getCloseCode().getCode(),closeReason.getReasonPhrase());
+ }
+
+ @Override
+ public Async getAsyncRemote()
+ {
+ if (asyncRemote == null)
+ {
+ asyncRemote = new JsrAsyncRemote(this);
+ }
+ return asyncRemote;
+ }
+
+ @Override
+ public Basic getBasicRemote()
+ {
+ if (basicRemote == null)
+ {
+ basicRemote = new JsrBasicRemote(this);
+ }
+ return basicRemote;
+ }
+
+ @Override
+ public WebSocketContainer getContainer()
+ {
+ return this.container;
+ }
+
+ public DecoderFactory getDecoderFactory()
+ {
+ return decoderFactory;
+ }
+
+ public EncoderFactory getEncoderFactory()
+ {
+ return encoderFactory;
+ }
+
+ public EndpointConfig getEndpointConfig()
+ {
+ return config;
+ }
+
+ public EndpointMetadata getEndpointMetadata()
+ {
+ return metadata;
+ }
+
+ @Override
+ public String getId()
+ {
+ return this.id;
+ }
+
+ @Override
+ public int getMaxBinaryMessageBufferSize()
+ {
+ return getPolicy().getMaxBinaryMessageSize();
+ }
+
+ @Override
+ public long getMaxIdleTimeout()
+ {
+ return getPolicy().getIdleTimeout();
+ }
+
+ @Override
+ public int getMaxTextMessageBufferSize()
+ {
+ return getPolicy().getMaxTextMessageSize();
+ }
+
+ public MessageHandlerFactory getMessageHandlerFactory()
+ {
+ return messageHandlerFactory;
+ }
+
+ @Override
+ public Set<MessageHandler> getMessageHandlers()
+ {
+ // Always return copy of set, as it is common to iterate and remove from the real set.
+ return new HashSet<MessageHandler>(messageHandlerSet);
+ }
+
+ public MessageHandlerWrapper getMessageHandlerWrapper(MessageType type)
+ {
+ synchronized (wrappers)
+ {
+ return wrappers[type.ordinal()];
+ }
+ }
+
+ @Override
+ public List<Extension> getNegotiatedExtensions()
+ {
+ if (negotiatedExtensions == null)
+ {
+ negotiatedExtensions = new ArrayList<Extension>();
+ for (ExtensionConfig cfg : getUpgradeResponse().getExtensions())
+ {
+ negotiatedExtensions.add(new JsrExtension(cfg));
+ }
+ }
+ return negotiatedExtensions;
+ }
+
+ @Override
+ public String getNegotiatedSubprotocol()
+ {
+ String acceptedSubProtocol = getUpgradeResponse().getAcceptedSubProtocol();
+ if (acceptedSubProtocol == null)
+ {
+ return "";
+ }
+ return acceptedSubProtocol;
+ }
+
+ @Override
+ public Set<Session> getOpenSessions()
+ {
+ return container.getOpenSessions();
+ }
+
+ @Override
+ public Map<String, String> getPathParameters()
+ {
+ return Collections.unmodifiableMap(pathParameters);
+ }
+
+ @Override
+ public String getQueryString()
+ {
+ return getUpgradeRequest().getRequestURI().getQuery();
+ }
+
+ @Override
+ public Map<String, List<String>> getRequestParameterMap()
+ {
+ return getUpgradeRequest().getParameterMap();
+ }
+
+ @Override
+ public Principal getUserPrincipal()
+ {
+ return getUpgradeRequest().getUserPrincipal();
+ }
+
+ @Override
+ public Map<String, Object> getUserProperties()
+ {
+ return config.getUserProperties();
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ // Initialize encoders
+ encoderFactory.init(config);
+ // Initialize decoders
+ decoderFactory.init(config);
+ }
+
+ @Override
+ public void removeMessageHandler(MessageHandler handler)
+ {
+ synchronized (wrappers)
+ {
+ try
+ {
+ for (MessageHandlerMetadata metadata : messageHandlerFactory.getMetadata(handler.getClass()))
+ {
+ DecoderMetadata decoder = decoderFactory.getMetadataFor(metadata.getMessageClass());
+ MessageType key = decoder.getMessageType();
+ wrappers[key.ordinal()] = null;
+ }
+ updateMessageHandlerSet();
+ }
+ catch (IllegalStateException e)
+ {
+ LOG.warn("Unable to identify MessageHandler: " + handler.getClass().getName(),e);
+ }
+ }
+ }
+
+ @Override
+ public void setMaxBinaryMessageBufferSize(int length)
+ {
+ getPolicy().setMaxBinaryMessageSize(length);
+ getPolicy().setMaxBinaryMessageBufferSize(length);
+ }
+
+ @Override
+ public void setMaxIdleTimeout(long milliseconds)
+ {
+ getPolicy().setIdleTimeout(milliseconds);
+ }
+
+ @Override
+ public void setMaxTextMessageBufferSize(int length)
+ {
+ getPolicy().setMaxTextMessageSize(length);
+ getPolicy().setMaxTextMessageBufferSize(length);
+ }
+
+ public void setPathParameters(Map<String, String> pathParams)
+ {
+ this.pathParameters.clear();
+ if (pathParams != null)
+ {
+ this.pathParameters.putAll(pathParams);
+ }
+ }
+
+ private void updateMessageHandlerSet()
+ {
+ messageHandlerSet.clear();
+ for (MessageHandlerWrapper wrapper : wrappers)
+ {
+ if (wrapper == null)
+ {
+ // skip empty
+ continue;
+ }
+ messageHandlerSet.add(wrapper.getHandler());
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionFactory.java
new file mode 100644
index 0000000..7a5c1c5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionFactory.java
@@ -0,0 +1,56 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.net.URI;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.SessionFactory;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.jsr356.endpoints.AbstractJsrEventDriver;
+
+public class JsrSessionFactory implements SessionFactory
+{
+ private AtomicLong idgen = new AtomicLong(0);
+ private final ClientContainer container;
+
+ public JsrSessionFactory(ClientContainer container)
+ {
+ this.container = container;
+ }
+
+ @Override
+ public WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection)
+ {
+ return new JsrSession(requestURI,websocket,connection,container,getNextId());
+ }
+
+ public String getNextId()
+ {
+ return String.format("websocket-%d",idgen.incrementAndGet());
+ }
+
+ @Override
+ public boolean supports(EventDriver websocket)
+ {
+ return (websocket instanceof AbstractJsrEventDriver);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrUpgradeListener.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrUpgradeListener.java
new file mode 100644
index 0000000..7d339c1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrUpgradeListener.java
@@ -0,0 +1,63 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.ClientEndpointConfig.Configurator;
+
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.client.io.UpgradeListener;
+
+public class JsrUpgradeListener implements UpgradeListener
+{
+ private Configurator configurator;
+
+ public JsrUpgradeListener(Configurator configurator)
+ {
+ this.configurator = configurator;
+ }
+
+ @Override
+ public void onHandshakeRequest(UpgradeRequest request)
+ {
+ if (configurator == null)
+ {
+ return;
+ }
+
+ Map<String, List<String>> headers = request.getHeaders();
+ configurator.beforeRequest(headers);
+ request.setHeaders(headers);
+ }
+
+ @Override
+ public void onHandshakeResponse(UpgradeResponse response)
+ {
+ if (configurator == null)
+ {
+ return;
+ }
+
+ JsrHandshakeResponse hr = new JsrHandshakeResponse(response);
+ configurator.afterResponse(hr);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactory.java
new file mode 100644
index 0000000..1dd6ead
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactory.java
@@ -0,0 +1,86 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.websocket.MessageHandler;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+import org.eclipse.jetty.websocket.jsr356.metadata.MessageHandlerMetadata;
+
+/**
+ * Factory for {@link MessageHandlerMetadata}
+ */
+public class MessageHandlerFactory
+{
+ private static final Logger LOG = Log.getLogger(MessageHandlerFactory.class);
+ /** Registered MessageHandlers at this level */
+ private Map<Class<? extends MessageHandler>, List<MessageHandlerMetadata>> registered;
+
+ public MessageHandlerFactory()
+ {
+ registered = new ConcurrentHashMap<>();
+ }
+
+ public List<MessageHandlerMetadata> getMetadata(Class<? extends MessageHandler> handler) throws IllegalStateException
+ {
+ LOG.debug("getMetadata({})",handler);
+ List<MessageHandlerMetadata> ret = registered.get(handler);
+ if (ret != null)
+ {
+ return ret;
+ }
+
+ return register(handler);
+ }
+
+ public List<MessageHandlerMetadata> register(Class<? extends MessageHandler> handler)
+ {
+ List<MessageHandlerMetadata> metadatas = new ArrayList<>();
+
+ boolean partial = false;
+
+ if (MessageHandler.Partial.class.isAssignableFrom(handler))
+ {
+ LOG.debug("supports Partial: {}",handler);
+ partial = true;
+ Class<?> onMessageClass = ReflectUtils.findGenericClassFor(handler,MessageHandler.Partial.class);
+ LOG.debug("Partial message class: {}",onMessageClass);
+ metadatas.add(new MessageHandlerMetadata(handler,onMessageClass,partial));
+ }
+
+ if (MessageHandler.Whole.class.isAssignableFrom(handler))
+ {
+ LOG.debug("supports Whole: {}",handler.getName());
+ partial = false;
+ Class<?> onMessageClass = ReflectUtils.findGenericClassFor(handler,MessageHandler.Whole.class);
+ LOG.debug("Whole message class: {}",onMessageClass);
+ metadatas.add(new MessageHandlerMetadata(handler,onMessageClass,partial));
+ }
+
+ registered.put(handler,metadatas);
+ return metadatas;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerWrapper.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerWrapper.java
new file mode 100644
index 0000000..891bde1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerWrapper.java
@@ -0,0 +1,84 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import javax.websocket.MessageHandler;
+import javax.websocket.MessageHandler.Partial;
+import javax.websocket.MessageHandler.Whole;
+
+import org.eclipse.jetty.websocket.jsr356.metadata.MessageHandlerMetadata;
+
+/**
+ * Expose a {@link MessageHandler} instance along with its associated {@link MessageHandlerMetadata} and {@link DecoderFactory.Wrapper}
+ */
+public class MessageHandlerWrapper
+{
+ private final MessageHandler handler;
+ private final MessageHandlerMetadata metadata;
+ private final DecoderFactory.Wrapper decoder;
+
+ public MessageHandlerWrapper(MessageHandler handler, MessageHandlerMetadata metadata, DecoderFactory.Wrapper decoder)
+ {
+ this.handler = handler;
+ this.metadata = metadata;
+ this.decoder = decoder;
+ }
+
+ public DecoderFactory.Wrapper getDecoder()
+ {
+ return decoder;
+ }
+
+ public MessageHandler getHandler()
+ {
+ return handler;
+ }
+
+ public MessageHandlerMetadata getMetadata()
+ {
+ return metadata;
+ }
+
+ public boolean isMessageType(Class<?> msgType)
+ {
+ return msgType.isAssignableFrom(metadata.getMessageClass());
+ }
+
+ /**
+ * Flag for a onMessage() that wants partial messages.
+ * <p>
+ * This indicates the use of MessageHandler.{@link Partial}.
+ *
+ * @return true for use of MessageHandler.{@link Partial}, false for use of MessageHandler.{@link Whole}
+ */
+ public boolean wantsPartialMessages()
+ {
+ return metadata.isPartialSupported();
+ }
+
+ /**
+ * Flag for a onMessage() method that wants MessageHandler.{@link Whole} with a Decoder that is based on {@link TextStream} or {@link BinaryStream}
+ *
+ * @return true for Streaming based Decoder, false for normal decoder for whole messages.
+ */
+ public boolean wantsStreams()
+ {
+ return decoder.getMetadata().isStreamed();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageType.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageType.java
new file mode 100644
index 0000000..4e1d952
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageType.java
@@ -0,0 +1,31 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+/**
+ * Basic Message Type enum.
+ * <p>
+ * The list of options mirrors the registration limits for "websocket message type" defined in JSR-356 / PFD1 section 2.1.3 "Receiving Messages".
+ */
+public enum MessageType
+{
+ TEXT,
+ BINARY,
+ PONG;
+}
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointMetadata.java
new file mode 100644
index 0000000..776e5ef
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointMetadata.java
@@ -0,0 +1,138 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.annotation.Annotation;
+import java.util.LinkedList;
+
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+
+/**
+ * Static reference to a specific annotated classes metadata.
+ *
+ * @param <T>
+ * the annotation this metadata is based off of
+ */
+public abstract class AnnotatedEndpointMetadata<T extends Annotation, C extends EndpointConfig> implements EndpointMetadata
+{
+ /**
+ * Callable for @{@link OnOpen} annotation.
+ */
+ public OnOpenCallable onOpen;
+
+ /**
+ * Callable for @{@link OnClose} annotation
+ */
+ public OnCloseCallable onClose;
+
+ /**
+ * Callable for @{@link OnError} annotation
+ */
+ public OnErrorCallable onError;
+
+ /**
+ * Callable for @{@link OnMessage} annotation dealing with Text Message Format
+ */
+ public OnMessageTextCallable onText;
+
+ /**
+ * Callable for @{@link OnMessage} annotation dealing with Text Streaming Message Format
+ */
+ public OnMessageTextStreamCallable onTextStream;
+
+ /**
+ * Callable for @{@link OnMessage} annotation dealing with Binary Message Format
+ */
+ public OnMessageBinaryCallable onBinary;
+
+ /**
+ * Callable for @{@link OnMessage} annotation dealing with Binary Streaming Message Format
+ */
+ public OnMessageBinaryStreamCallable onBinaryStream;
+
+ /**
+ * Callable for @{@link OnMessage} annotation dealing with Pong Message Format
+ */
+ public OnMessagePongCallable onPong;
+
+ private final Class<?> endpointClass;
+ private DecoderMetadataSet decoders;
+ private EncoderMetadataSet encoders;
+
+ protected AnnotatedEndpointMetadata(Class<?> endpointClass)
+ {
+ this.endpointClass = endpointClass;
+ this.decoders = new DecoderMetadataSet();
+ this.encoders = new EncoderMetadataSet();
+ }
+
+ public void customizeParamsOnClose(LinkedList<IJsrParamId> params)
+ {
+ /* do nothing */
+ }
+
+ public void customizeParamsOnError(LinkedList<IJsrParamId> params)
+ {
+ /* do nothing */
+ }
+
+ public void customizeParamsOnMessage(LinkedList<IJsrParamId> params)
+ {
+ for (DecoderMetadata metadata : decoders)
+ {
+ params.add(new JsrParamIdDecoder(metadata));
+ }
+ }
+
+ public void customizeParamsOnOpen(LinkedList<IJsrParamId> params)
+ {
+ /* do nothing */
+ }
+
+ public abstract T getAnnotation();
+
+ public abstract C getConfig();
+
+ @Override
+ public DecoderMetadataSet getDecoders()
+ {
+ return decoders;
+ }
+
+ @Override
+ public EncoderMetadataSet getEncoders()
+ {
+ return encoders;
+ }
+
+ @Override
+ public Class<?> getEndpointClass()
+ {
+ return endpointClass;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointScanner.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointScanner.java
new file mode 100644
index 0000000..5d96eec
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointScanner.java
@@ -0,0 +1,205 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.events.annotated.AbstractMethodAnnotationScanner;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+
+public class AnnotatedEndpointScanner<T extends Annotation, C extends EndpointConfig> extends AbstractMethodAnnotationScanner<AnnotatedEndpointMetadata<T, C>>
+{
+ private static final Logger LOG = Log.getLogger(AnnotatedEndpointScanner.class);
+
+ private final LinkedList<IJsrParamId> paramsOnOpen;
+ private final LinkedList<IJsrParamId> paramsOnClose;
+ private final LinkedList<IJsrParamId> paramsOnError;
+ private final LinkedList<IJsrParamId> paramsOnMessage;
+ private final AnnotatedEndpointMetadata<T, C> metadata;
+
+ public AnnotatedEndpointScanner(AnnotatedEndpointMetadata<T, C> metadata)
+ {
+ this.metadata = metadata;
+
+ paramsOnOpen = new LinkedList<>();
+ paramsOnClose = new LinkedList<>();
+ paramsOnError = new LinkedList<>();
+ paramsOnMessage = new LinkedList<>();
+
+ metadata.customizeParamsOnOpen(paramsOnOpen);
+ paramsOnOpen.add(JsrParamIdOnOpen.INSTANCE);
+
+ metadata.customizeParamsOnClose(paramsOnClose);
+ paramsOnClose.add(JsrParamIdOnClose.INSTANCE);
+
+ metadata.customizeParamsOnError(paramsOnError);
+ paramsOnError.add(JsrParamIdOnError.INSTANCE);
+
+ metadata.customizeParamsOnMessage(paramsOnMessage);
+ paramsOnMessage.add(JsrParamIdText.INSTANCE);
+ paramsOnMessage.add(JsrParamIdBinary.INSTANCE);
+ paramsOnMessage.add(JsrParamIdPong.INSTANCE);
+ }
+
+ private void assertNotDuplicate(JsrCallable callable, Class<? extends Annotation> methodAnnotationClass, Class<?> pojo, Method method)
+ {
+ if (callable != null)
+ {
+ // Duplicate annotation detected
+ StringBuilder err = new StringBuilder();
+ err.append("Encountered duplicate method annotations @");
+ err.append(methodAnnotationClass.getSimpleName());
+ err.append(" on ");
+ err.append(ReflectUtils.toString(pojo,callable.getMethod()));
+ err.append(" and ");
+ err.append(ReflectUtils.toString(pojo,method));
+
+ throw new InvalidSignatureException(err.toString());
+ }
+ }
+
+ @Override
+ public void onMethodAnnotation(AnnotatedEndpointMetadata<T, C> metadata, Class<?> pojo, Method method, Annotation annotation)
+ {
+ LOG.debug("onMethodAnnotation({}, {}, {}, {})",metadata,pojo,method,annotation);
+
+ if (isAnnotation(annotation,OnOpen.class))
+ {
+ assertIsPublicNonStatic(method);
+ assertIsReturn(method,Void.TYPE);
+ assertNotDuplicate(metadata.onOpen,OnOpen.class,pojo,method);
+ OnOpenCallable onopen = new OnOpenCallable(pojo,method);
+ visitMethod(onopen,pojo,method,paramsOnOpen,OnOpen.class);
+ metadata.onOpen = onopen;
+ return;
+ }
+
+ if (isAnnotation(annotation,OnClose.class))
+ {
+ assertIsPublicNonStatic(method);
+ assertIsReturn(method,Void.TYPE);
+ assertNotDuplicate(metadata.onClose,OnClose.class,pojo,method);
+ OnCloseCallable onclose = new OnCloseCallable(pojo,method);
+ visitMethod(onclose,pojo,method,paramsOnClose,OnClose.class);
+ metadata.onClose = onclose;
+ return;
+ }
+
+ if (isAnnotation(annotation,OnError.class))
+ {
+ assertIsPublicNonStatic(method);
+ assertIsReturn(method,Void.TYPE);
+ assertNotDuplicate(metadata.onError,OnError.class,pojo,method);
+ OnErrorCallable onerror = new OnErrorCallable(pojo,method);
+ visitMethod(onerror,pojo,method,paramsOnError,OnError.class);
+ metadata.onError = onerror;
+ return;
+ }
+
+ if (isAnnotation(annotation,OnMessage.class))
+ {
+ assertIsPublicNonStatic(method);
+ // assertIsReturn(method,Void.TYPE); // no validation, it can be any return type
+ OnMessageCallable onmessage = new OnMessageCallable(pojo,method);
+ visitMethod(onmessage,pojo,method,paramsOnMessage,OnMessage.class);
+
+ Param param = onmessage.getMessageObjectParam();
+ switch (param.role)
+ {
+ case MESSAGE_BINARY:
+ metadata.onBinary = new OnMessageBinaryCallable(onmessage);
+ break;
+ case MESSAGE_BINARY_STREAM:
+ metadata.onBinaryStream = new OnMessageBinaryStreamCallable(onmessage);
+ break;
+ case MESSAGE_TEXT:
+ metadata.onText = new OnMessageTextCallable(onmessage);
+ break;
+ case MESSAGE_TEXT_STREAM:
+ metadata.onTextStream = new OnMessageTextStreamCallable(onmessage);
+ break;
+ case MESSAGE_PONG:
+ metadata.onPong = new OnMessagePongCallable(onmessage);
+ break;
+ default:
+ StringBuilder err = new StringBuilder();
+ err.append("An unrecognized message type <");
+ err.append(param.type);
+ err.append(">: does not meet specified type categories of [TEXT, BINARY, DECODER, or PONG]");
+ throw new InvalidSignatureException(err.toString());
+ }
+ }
+ }
+
+ public AnnotatedEndpointMetadata<T, C> scan()
+ {
+ scanMethodAnnotations(metadata,metadata.getEndpointClass());
+ return metadata;
+ }
+
+ private void visitMethod(JsrCallable callable, Class<?> pojo, Method method, LinkedList<IJsrParamId> paramIds,
+ Class<? extends Annotation> methodAnnotationClass)
+ {
+ // Identify all of the parameters
+ for (Param param : callable.getParams())
+ {
+ if (!visitParam(callable,param,paramIds))
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Encountered unknown parameter type <");
+ err.append(param.type.getName());
+ err.append("> on @");
+ err.append(methodAnnotationClass.getSimpleName());
+ err.append(" annotated method: ");
+ err.append(ReflectUtils.toString(pojo,method));
+
+ throw new InvalidSignatureException(err.toString());
+ }
+ }
+ }
+
+ private boolean visitParam(JsrCallable callable, Param param, List<IJsrParamId> paramIds)
+ {
+ for (IJsrParamId paramId : paramIds)
+ {
+ LOG.debug("{}.process()",paramId);
+ if (paramId.process(param,callable))
+ {
+ // Successfully identified
+ LOG.debug("Identified: {}",param);
+ return true;
+ }
+ }
+
+ // Failed identification as a known parameter
+ return false;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrMethod.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrMethod.java
new file mode 100644
index 0000000..0e3f86b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrMethod.java
@@ -0,0 +1,84 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+
+import javax.websocket.Decoder;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+
+public interface IJsrMethod
+{
+ /**
+ * Indicate that partial message support is desired
+ */
+ void enablePartialMessageSupport();
+
+ /**
+ * Get the fully qualifed method name {classname}.{methodname}({params}) suitable for using in error messages.
+ *
+ * @return the fully qualified method name for end users
+ */
+ String getFullyQualifiedMethodName();
+
+ /**
+ * Get the Decoder to use for message decoding
+ *
+ * @return the decoder class to use for message decoding
+ */
+ Class<? extends Decoder> getMessageDecoder();
+
+ /**
+ * The type of message this method can handle
+ *
+ * @return the message type if @{@link OnMessage} annotated, null if unknown/unspecified
+ */
+ MessageType getMessageType();
+
+ /**
+ * The reflected method
+ *
+ * @return the method itself
+ */
+ Method getMethod();
+
+ /**
+ * Indicator that partial message support is enabled
+ *
+ * @return true if enabled
+ */
+ boolean isPartialMessageSupportEnabled();
+
+ /**
+ * The message decoder class to use.
+ *
+ * @param decoderClass
+ */
+ void setMessageDecoder(Class<? extends Decoder> decoderClass);
+
+ /**
+ * The type of message this method can handle
+ *
+ * @param type
+ * the type of message
+ */
+ void setMessageType(MessageType type);
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrParamId.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrParamId.java
new file mode 100644
index 0000000..9654f2b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrParamId.java
@@ -0,0 +1,43 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+
+/**
+ * JSR-356 Parameter Identification processing.
+ */
+public interface IJsrParamId
+{
+ /**
+ * Process the potential parameter.
+ * <p>
+ * If known to be a valid parameter, bind a role to it.
+ *
+ * @param param
+ * the parameter being processed
+ * @param callable
+ * the callable this param belongs to (used to obtain extra state about the callable that might impact decision making)
+ *
+ * @return true if processed, false if not processed
+ * @throws InvalidSignatureException
+ * if a violation of the signature rules occurred
+ */
+ boolean process(Param param, JsrCallable callable) throws InvalidSignatureException;
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrCallable.java
new file mode 100644
index 0000000..0261677
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrCallable.java
@@ -0,0 +1,175 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+public abstract class JsrCallable extends CallableMethod
+{
+ protected final Param[] params;
+ protected final Object[] args;
+ protected int idxSession = -1;
+ protected int idxConfig = -1;
+
+ public JsrCallable(Class<?> pojo, Method method)
+ {
+ super(pojo,method);
+
+ Class<?> ptypes[] = method.getParameterTypes();
+ Annotation pannos[][] = method.getParameterAnnotations();
+ int len = ptypes.length;
+ params = new Param[len];
+ for (int i = 0; i < len; i++)
+ {
+ params[i] = new Param(i,ptypes[i],pannos[i]);
+ }
+
+ args = new Object[len];
+ }
+
+ /**
+ * Copy Constructor
+ */
+ public JsrCallable(JsrCallable copy)
+ {
+ this(copy.getPojo(),copy.getMethod());
+ this.idxSession = copy.idxSession;
+ this.idxConfig = copy.idxConfig;
+ System.arraycopy(copy.params,0,this.params,0,params.length);
+ System.arraycopy(copy.args,0,this.args,0,args.length);
+ }
+
+ protected void assertRoleRequired(int index, String description)
+ {
+ if (index < 0)
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Unable to find parameter with role [");
+ err.append(description).append("] in method: ");
+ err.append(ReflectUtils.toString(pojo,method));
+ throw new InvalidSignatureException(err.toString());
+ }
+ }
+
+ /**
+ * Search the list of parameters for first one matching the role specified.
+ *
+ * @param role
+ * the role to look for
+ * @return the index for the role specified (or -1 if not found)
+ */
+ protected int findIndexForRole(Role role)
+ {
+ Param param = findParamForRole(role);
+ if (param != null)
+ {
+ return param.index;
+ }
+ return -1;
+ }
+
+ /**
+ * Find first param for specified role.
+ *
+ * @param role
+ * the role specified
+ * @return the param (or null if not found)
+ */
+ protected Param findParamForRole(Role role)
+ {
+ for (Param param : params)
+ {
+ if (param.role == role)
+ {
+ return param;
+ }
+ }
+ return null;
+ }
+
+ public Param[] getParams()
+ {
+ return params;
+ }
+
+ public void init(JsrSession session)
+ {
+ // Default for the session.
+ // Session is an optional parameter (always)
+ idxSession = findIndexForRole(Param.Role.SESSION);
+ if (idxSession >= 0)
+ {
+ args[idxSession] = session;
+ }
+
+ // Optional EndpointConfig
+ idxConfig = findIndexForRole(Param.Role.ENDPOINT_CONFIG);
+ if (idxConfig >= 0)
+ {
+ args[idxConfig] = session.getEndpointConfig();
+ }
+
+ // Default for the path parameters
+ // PathParam's are optional parameters (always)
+ Map<String, String> pathParams = session.getPathParameters();
+ if ((pathParams != null) && (pathParams.size() > 0))
+ {
+ for (Param param : params)
+ {
+ if (param.role == Role.PATH_PARAM)
+ {
+ int idx = param.index;
+ String rawvalue = pathParams.get(param.getPathParamName());
+
+ Decoder decoder = session.getDecoderFactory().getDecoderFor(param.type);
+ if (decoder instanceof Decoder.Text<?>)
+ {
+ Decoder.Text<?> textDecoder = (Decoder.Text<?>)decoder;
+ try
+ {
+ args[idx] = textDecoder.decode(rawvalue);
+ }
+ catch (DecodeException e)
+ {
+ session.notifyError(e);
+ }
+ }
+ else
+ {
+ throw new InvalidWebSocketException("PathParam decoders must use Decoder.Text");
+ }
+ }
+ }
+ }
+ }
+
+ public abstract void setDecoderClass(Class<? extends Decoder> decoderClass);
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrEvents.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrEvents.java
new file mode 100644
index 0000000..2d0d284
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrEvents.java
@@ -0,0 +1,292 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.annotation.Annotation;
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+import javax.websocket.CloseReason;
+import javax.websocket.DecodeException;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.RemoteEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+
+/**
+ * The live event methods found for a specific Annotated Endpoint
+ */
+public class JsrEvents<T extends Annotation, C extends EndpointConfig>
+{
+ private static final Logger LOG = Log.getLogger(JsrEvents.class);
+ private final AnnotatedEndpointMetadata<T, C> metadata;
+
+ /**
+ * Callable for @{@link OnOpen} annotation.
+ */
+ private final OnOpenCallable onOpen;
+
+ /**
+ * Callable for @{@link OnClose} annotation
+ */
+ private final OnCloseCallable onClose;
+
+ /**
+ * Callable for @{@link OnError} annotation
+ */
+ private final OnErrorCallable onError;
+
+ /**
+ * Callable for @{@link OnMessage} annotation dealing with Text Message Format
+ */
+ private final OnMessageTextCallable onText;
+
+ /**
+ * Callable for @{@link OnMessage} annotation dealing with Text Streaming Message Format
+ */
+ private final OnMessageTextStreamCallable onTextStream;
+
+ /**
+ * Callable for @{@link OnMessage} annotation dealing with Binary Message Format
+ */
+ private final OnMessageBinaryCallable onBinary;
+
+ /**
+ * Callable for @{@link OnMessage} annotation dealing with Binary Streaming Message Format
+ */
+ private final OnMessageBinaryStreamCallable onBinaryStream;
+
+ /**
+ * Callable for @{@link OnMessage} annotation dealing with Pong Message Format
+ */
+ private OnMessagePongCallable onPong;
+
+ /**
+ * The Request Parameters (from resolved javax.websocket.server.PathParam entries)
+ */
+ private Map<String, String> pathParameters;
+
+ public JsrEvents(AnnotatedEndpointMetadata<T, C> metadata)
+ {
+ this.metadata = metadata;
+ this.onOpen = (metadata.onOpen == null)?null:new OnOpenCallable(metadata.onOpen);
+ this.onClose = (metadata.onClose == null)?null:new OnCloseCallable(metadata.onClose);
+ this.onError = (metadata.onError == null)?null:new OnErrorCallable(metadata.onError);
+ this.onBinary = (metadata.onBinary == null)?null:new OnMessageBinaryCallable(metadata.onBinary);
+ this.onBinaryStream = (metadata.onBinaryStream == null)?null:new OnMessageBinaryStreamCallable(metadata.onBinaryStream);
+ this.onText = (metadata.onText == null)?null:new OnMessageTextCallable(metadata.onText);
+ this.onTextStream = (metadata.onTextStream == null)?null:new OnMessageTextStreamCallable(metadata.onTextStream);
+ this.onPong = (metadata.onPong == null)?null:new OnMessagePongCallable(metadata.onPong);
+ }
+
+ public void callBinary(RemoteEndpoint.Async endpoint, Object websocket, ByteBuffer buf, boolean fin) throws DecodeException
+ {
+ if (onBinary == null)
+ {
+ return;
+ }
+
+ Object ret = onBinary.call(websocket,buf,fin);
+ if (ret != null)
+ {
+ LOG.debug("returning: {}",ret);
+ endpoint.sendObject(ret);
+ }
+ }
+
+ public void callBinaryStream(RemoteEndpoint.Async endpoint, Object websocket, InputStream stream) throws DecodeException, IOException
+ {
+ if (onBinaryStream == null)
+ {
+ return;
+ }
+
+ Object ret = onBinaryStream.call(websocket,stream);
+ if (ret != null)
+ {
+ LOG.debug("returning: {}",ret);
+ endpoint.sendObject(ret);
+ }
+ }
+
+ public void callClose(Object websocket, CloseReason close)
+ {
+ if (onClose == null)
+ {
+ return;
+ }
+ onClose.call(websocket,close);
+ }
+
+ public void callError(Object websocket, Throwable cause)
+ {
+ if (onError == null)
+ {
+ return;
+ }
+ onError.call(websocket,cause);
+ }
+
+ public void callOpen(Object websocket, EndpointConfig config)
+ {
+ if (onOpen == null)
+ {
+ return;
+ }
+ onOpen.call(websocket,config);
+ }
+
+ public void callPong(RemoteEndpoint.Async endpoint, Object websocket, ByteBuffer pong) throws DecodeException, IOException
+ {
+ if (onPong == null)
+ {
+ return;
+ }
+
+ Object ret = onPong.call(websocket,pong);
+ if (ret != null)
+ {
+ LOG.debug("returning: {}",ret);
+ endpoint.sendObject(ret);
+ }
+ }
+
+ public void callText(RemoteEndpoint.Async endpoint, Object websocket, String text, boolean fin) throws DecodeException
+ {
+ if (onText == null)
+ {
+ return;
+ }
+ Object ret = onText.call(websocket,text,fin);
+ if (ret != null)
+ {
+ LOG.debug("returning: {}",ret);
+ endpoint.sendObject(ret);
+ }
+ }
+
+ public void callTextStream(RemoteEndpoint.Async endpoint, Object websocket, Reader reader) throws DecodeException, IOException
+ {
+ if (onTextStream == null)
+ {
+ return;
+ }
+ Object ret = onTextStream.call(websocket,reader);
+ if (ret != null)
+ {
+ LOG.debug("returning: {}",ret);
+ endpoint.sendObject(ret);
+ }
+ }
+
+ public AnnotatedEndpointMetadata<T, C> getMetadata()
+ {
+ return metadata;
+ }
+
+ public boolean hasBinary()
+ {
+ return (onBinary != null);
+ }
+
+ public boolean hasBinaryStream()
+ {
+ return (onBinaryStream != null);
+ }
+
+ public boolean hasText()
+ {
+ return (onText != null);
+ }
+
+ public boolean hasTextStream()
+ {
+ return (onTextStream != null);
+ }
+
+ public void init(JsrSession session)
+ {
+ session.setPathParameters(pathParameters);
+
+ if (onOpen != null)
+ {
+ onOpen.init(session);
+ }
+ if (onClose != null)
+ {
+ onClose.init(session);
+ }
+ if (onError != null)
+ {
+ onError.init(session);
+ }
+ if (onText != null)
+ {
+ onText.init(session);
+ }
+ if (onTextStream != null)
+ {
+ onTextStream.init(session);
+ }
+ if (onBinary != null)
+ {
+ onBinary.init(session);
+ }
+ if (onBinaryStream != null)
+ {
+ onBinaryStream.init(session);
+ }
+ if (onPong != null)
+ {
+ onPong.init(session);
+ }
+ }
+
+ public boolean isBinaryPartialSupported()
+ {
+ if (onBinary == null)
+ {
+ return false;
+ }
+ return onBinary.isPartialMessageSupported();
+ }
+
+ public boolean isTextPartialSupported()
+ {
+ if (onText == null)
+ {
+ return false;
+ }
+ return onText.isPartialMessageSupported();
+ }
+
+ public void setPathParameters(Map<String, String> pathParameters)
+ {
+ this.pathParameters = pathParameters;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBase.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBase.java
new file mode 100644
index 0000000..19bfdb2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBase.java
@@ -0,0 +1,51 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Common base for Parameter Identification in JSR Callable methods
+ */
+public abstract class JsrParamIdBase implements IJsrParamId
+{
+ @Override
+ public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+ {
+ // Session parameter (optional)
+ if (param.type.isAssignableFrom(Session.class))
+ {
+ param.bind(Role.SESSION);
+ return true;
+ }
+
+ // Endpoint Config (optional)
+ if (param.type.isAssignableFrom(EndpointConfig.class))
+ {
+ param.bind(Role.ENDPOINT_CONFIG);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBinary.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBinary.java
new file mode 100644
index 0000000..ce204a3
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBinary.java
@@ -0,0 +1,73 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+import org.eclipse.jetty.websocket.jsr356.decoders.ByteArrayDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.ByteBufferDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.InputStreamDecoder;
+
+/**
+ * Param handling for static Binary @{@link OnMessage} parameters.
+ */
+public class JsrParamIdBinary extends JsrParamIdOnMessage implements IJsrParamId
+{
+ public static final IJsrParamId INSTANCE = new JsrParamIdBinary();
+
+ @Override
+ public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+ {
+ if (super.process(param,callable))
+ {
+ // Found common roles
+ return true;
+ }
+
+ if (param.type.isAssignableFrom(ByteBuffer.class))
+ {
+ param.bind(Role.MESSAGE_BINARY);
+ callable.setDecoderClass(ByteBufferDecoder.class);
+ return true;
+ }
+
+ if (param.type.isAssignableFrom(byte[].class))
+ {
+ param.bind(Role.MESSAGE_BINARY);
+ callable.setDecoderClass(ByteArrayDecoder.class);
+ return true;
+ }
+
+ // Streaming
+ if (param.type.isAssignableFrom(InputStream.class))
+ {
+ assertPartialMessageSupportDisabled(param,callable);
+ param.bind(Role.MESSAGE_BINARY_STREAM);
+ callable.setDecoderClass(InputStreamDecoder.class);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoder.java
new file mode 100644
index 0000000..8942eec
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoder.java
@@ -0,0 +1,78 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import javax.websocket.Decoder;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+
+/**
+ * Param handling for Text or Binary @{@link OnMessage} parameters declared as {@link Decoder}s
+ */
+public class JsrParamIdDecoder extends JsrParamIdOnMessage implements IJsrParamId
+{
+ private final DecoderMetadata metadata;
+
+ public JsrParamIdDecoder(DecoderMetadata metadata)
+ {
+ this.metadata = metadata;
+ }
+
+ @Override
+ public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+ {
+ if (param.type.isAssignableFrom(metadata.getObjectType()))
+ {
+ assertPartialMessageSupportDisabled(param,callable);
+
+ switch (metadata.getMessageType())
+ {
+ case TEXT:
+ if (metadata.isStreamed())
+ {
+ param.bind(Role.MESSAGE_TEXT_STREAM);
+ }
+ else
+ {
+ param.bind(Role.MESSAGE_TEXT);
+ }
+ break;
+ case BINARY:
+ if (metadata.isStreamed())
+ {
+ param.bind(Role.MESSAGE_BINARY_STREAM);
+ }
+ else
+ {
+ param.bind(Role.MESSAGE_BINARY);
+ }
+ break;
+ case PONG:
+ param.bind(Role.MESSAGE_PONG);
+ break;
+ }
+ callable.setDecoderClass(metadata.getCoderClass());
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnClose.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnClose.java
new file mode 100644
index 0000000..7b271a4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnClose.java
@@ -0,0 +1,50 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Param handling for @{@link OnClose} parameters.
+ */
+public class JsrParamIdOnClose extends JsrParamIdBase implements IJsrParamId
+{
+ public static final IJsrParamId INSTANCE = new JsrParamIdOnClose();
+
+ @Override
+ public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+ {
+ if (super.process(param,callable))
+ {
+ // Found common roles
+ return true;
+ }
+
+ if (param.type.isAssignableFrom(CloseReason.class))
+ {
+ param.bind(Role.CLOSE_REASON);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnError.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnError.java
new file mode 100644
index 0000000..765f7b4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnError.java
@@ -0,0 +1,49 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import javax.websocket.OnError;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Param handling for @{@link OnError} parameters.
+ */
+public class JsrParamIdOnError extends JsrParamIdBase implements IJsrParamId
+{
+ public static final IJsrParamId INSTANCE = new JsrParamIdOnError();
+
+ @Override
+ public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+ {
+ if (super.process(param,callable))
+ {
+ // Found common roles
+ return true;
+ }
+
+ if (param.type.isAssignableFrom(Throwable.class))
+ {
+ param.bind(Role.ERROR_CAUSE);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnMessage.java
new file mode 100644
index 0000000..c9bb86b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnMessage.java
@@ -0,0 +1,40 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+
+public abstract class JsrParamIdOnMessage extends JsrParamIdBase implements IJsrParamId
+{
+ protected void assertPartialMessageSupportDisabled(Param param, JsrCallable callable)
+ {
+ if (callable instanceof OnMessageCallable)
+ {
+ OnMessageCallable onmessage = (OnMessageCallable)callable;
+ if (onmessage.isPartialMessageSupported())
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Unable to support parameter type <");
+ err.append(param.type.getName()).append("> in conjunction with the partial message indicator boolean.");
+ err.append(" Only type <String> is supported with partial message boolean indicator.");
+ throw new InvalidSignatureException(err.toString());
+ }
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnOpen.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnOpen.java
new file mode 100644
index 0000000..1340187
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnOpen.java
@@ -0,0 +1,50 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Param handling for @{@link OnOpen} parameters.
+ */
+public class JsrParamIdOnOpen extends JsrParamIdBase implements IJsrParamId
+{
+ public static final IJsrParamId INSTANCE = new JsrParamIdOnOpen();
+
+ @Override
+ public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+ {
+ if (super.process(param,callable))
+ {
+ // Found common roles
+ return true;
+ }
+
+ if (param.type.isAssignableFrom(EndpointConfig.class))
+ {
+ param.bind(Role.ENDPOINT_CONFIG);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdPong.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdPong.java
new file mode 100644
index 0000000..2f7d093
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdPong.java
@@ -0,0 +1,49 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import javax.websocket.PongMessage;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+import org.eclipse.jetty.websocket.jsr356.decoders.PongMessageDecoder;
+
+public class JsrParamIdPong extends JsrParamIdOnMessage implements IJsrParamId
+{
+ public static final IJsrParamId INSTANCE = new JsrParamIdPong();
+
+ @Override
+ public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+ {
+ if (super.process(param,callable))
+ {
+ // Found common roles
+ return true;
+ }
+
+ if (param.type.isAssignableFrom(PongMessage.class))
+ {
+ assertPartialMessageSupportDisabled(param,callable);
+ param.bind(Role.MESSAGE_PONG);
+ callable.setDecoderClass(PongMessageDecoder.class);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdText.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdText.java
new file mode 100644
index 0000000..d7eb8f9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdText.java
@@ -0,0 +1,160 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.io.Reader;
+
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+import org.eclipse.jetty.websocket.jsr356.decoders.BooleanDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.ByteDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.CharacterDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.DoubleDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.FloatDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.IntegerDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.LongDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.ReaderDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.ShortDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.StringDecoder;
+
+/**
+ * Param handling for static Text @{@link OnMessage} parameters
+ */
+public class JsrParamIdText extends JsrParamIdOnMessage implements IJsrParamId
+{
+ public static final IJsrParamId INSTANCE = new JsrParamIdText();
+
+ private boolean isMessageRoleAssigned(JsrCallable callable)
+ {
+ if (callable instanceof OnMessageCallable)
+ {
+ OnMessageCallable onmessage = (OnMessageCallable)callable;
+ return onmessage.isMessageRoleAssigned();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+ {
+ if (super.process(param,callable))
+ {
+ // Found common roles
+ return true;
+ }
+
+ // String for whole message
+ if (param.type.isAssignableFrom(String.class))
+ {
+ param.bind(Role.MESSAGE_TEXT);
+ callable.setDecoderClass(StringDecoder.class);
+ return true;
+ }
+
+ // Java primitive or class equivalent to receive the whole message converted to that type
+ if (param.type.isAssignableFrom(Boolean.class))
+ {
+ assertPartialMessageSupportDisabled(param,callable);
+ param.bind(Role.MESSAGE_TEXT);
+ callable.setDecoderClass(BooleanDecoder.class);
+ return true;
+ }
+ if (param.type.isAssignableFrom(Byte.class) || (param.type == Byte.TYPE))
+ {
+ assertPartialMessageSupportDisabled(param,callable);
+ param.bind(Role.MESSAGE_TEXT);
+ callable.setDecoderClass(ByteDecoder.class);
+ return true;
+ }
+ if (param.type.isAssignableFrom(Character.class) || (param.type == Character.TYPE))
+ {
+ assertPartialMessageSupportDisabled(param,callable);
+ param.bind(Role.MESSAGE_TEXT);
+ callable.setDecoderClass(CharacterDecoder.class);
+ return true;
+ }
+ if (param.type.isAssignableFrom(Double.class) || (param.type == Double.TYPE))
+ {
+ assertPartialMessageSupportDisabled(param,callable);
+ param.bind(Role.MESSAGE_TEXT);
+ callable.setDecoderClass(DoubleDecoder.class);
+ return true;
+ }
+ if (param.type.isAssignableFrom(Float.class) || (param.type == Float.TYPE))
+ {
+ assertPartialMessageSupportDisabled(param,callable);
+ param.bind(Role.MESSAGE_TEXT);
+ callable.setDecoderClass(FloatDecoder.class);
+ return true;
+ }
+ if (param.type.isAssignableFrom(Integer.class) || (param.type == Integer.TYPE))
+ {
+ assertPartialMessageSupportDisabled(param,callable);
+ param.bind(Role.MESSAGE_TEXT);
+ callable.setDecoderClass(IntegerDecoder.class);
+ return true;
+ }
+ if (param.type.isAssignableFrom(Long.class) || (param.type == Long.TYPE))
+ {
+ assertPartialMessageSupportDisabled(param,callable);
+ param.bind(Role.MESSAGE_TEXT);
+ callable.setDecoderClass(LongDecoder.class);
+ return true;
+ }
+ if (param.type.isAssignableFrom(Short.class) || (param.type == Short.TYPE))
+ {
+ assertPartialMessageSupportDisabled(param,callable);
+ param.bind(Role.MESSAGE_TEXT);
+ callable.setDecoderClass(ShortDecoder.class);
+ return true;
+ }
+
+ // Streaming
+ if (param.type.isAssignableFrom(Reader.class))
+ {
+ assertPartialMessageSupportDisabled(param,callable);
+ param.bind(Role.MESSAGE_TEXT_STREAM);
+ callable.setDecoderClass(ReaderDecoder.class);
+ return true;
+ }
+
+ /*
+ * boolean primitive.
+ *
+ * can be used for either: 1) a boolean message type 2) a partial message indicator flag
+ */
+ if (param.type == Boolean.TYPE)
+ {
+ if (isMessageRoleAssigned(callable))
+ {
+ param.bind(Role.MESSAGE_PARTIAL_FLAG);
+ }
+ else
+ {
+ param.bind(Role.MESSAGE_TEXT);
+ callable.setDecoderClass(BooleanDecoder.class);
+ }
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnCloseCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnCloseCallable.java
new file mode 100644
index 0000000..f854aaa
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnCloseCallable.java
@@ -0,0 +1,90 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.Decoder;
+import javax.websocket.OnClose;
+
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnClose} annotated methods
+ */
+public class OnCloseCallable extends JsrCallable
+{
+ private int idxCloseReason = -1;
+
+ public OnCloseCallable(Class<?> pojo, Method method)
+ {
+ super(pojo,method);
+ }
+
+ public OnCloseCallable(OnCloseCallable copy)
+ {
+ super(copy);
+ this.idxCloseReason = copy.idxCloseReason;
+ }
+
+ public void call(Object endpoint, CloseInfo close)
+ {
+ this.call(endpoint,close.getStatusCode(),close.getReason());
+ }
+
+ public void call(Object endpoint, CloseReason closeReason)
+ {
+ // Close Reason is an optional parameter
+ if (idxCloseReason >= 0)
+ {
+ // convert to javax.websocket.CloseReason
+ super.args[idxCloseReason] = closeReason;
+ }
+ super.call(endpoint,super.args);
+ }
+
+ public void call(Object endpoint, int statusCode, String reason)
+ {
+ // Close Reason is an optional parameter
+ if (idxCloseReason >= 0)
+ {
+ // convert to javax.websocket.CloseReason
+ CloseReason jsrclose = new CloseReason(CloseCodes.getCloseCode(statusCode),reason);
+ super.args[idxCloseReason] = jsrclose;
+ }
+ super.call(endpoint,super.args);
+ }
+
+ @Override
+ public void init(JsrSession session)
+ {
+ idxCloseReason = findIndexForRole(Role.CLOSE_REASON);
+ super.init(session);
+ }
+
+ @Override
+ public void setDecoderClass(Class<? extends Decoder> decoderClass)
+ {
+ /* ignore, not relevant for onClose */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnErrorCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnErrorCallable.java
new file mode 100644
index 0000000..c9489b7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnErrorCallable.java
@@ -0,0 +1,75 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+
+import javax.websocket.Decoder;
+import javax.websocket.OnError;
+
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+
+/**
+ * Callable for {@link OnError} annotated methods
+ */
+public class OnErrorCallable extends JsrCallable
+{
+ private int idxThrowable = -1;
+
+ public OnErrorCallable(Class<?> pojo, Method method)
+ {
+ super(pojo,method);
+ }
+
+ public OnErrorCallable(OnErrorCallable copy)
+ {
+ super(copy);
+ this.idxThrowable = copy.idxThrowable;
+ }
+
+ public void call(Object endpoint, Throwable cause)
+ {
+ if (idxThrowable == (-1))
+ {
+ idxThrowable = findIndexForRole(Param.Role.ERROR_CAUSE);
+ assertRoleRequired(idxThrowable,"Throwable");
+ }
+
+ if (idxThrowable >= 0)
+ {
+ super.args[idxThrowable] = cause;
+ }
+ super.call(endpoint,super.args);
+ }
+
+ @Override
+ public void init(JsrSession session)
+ {
+ idxThrowable = findIndexForRole(Param.Role.ERROR_CAUSE);
+ assertRoleRequired(idxThrowable,"Throwable");
+ super.init(session);
+ }
+
+ @Override
+ public void setDecoderClass(Class<? extends Decoder> decoderClass)
+ {
+ /* ignore, not relevant for onClose */
+ }
+
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryCallable.java
new file mode 100644
index 0000000..5c0c834
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryCallable.java
@@ -0,0 +1,74 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnMessage} annotated methods with a whole or partial binary messages.
+ * <p>
+ * Not for use with {@link InputStream} based {@link OnMessage} method objects.
+ *
+ * @see Binary
+ */
+public class OnMessageBinaryCallable extends OnMessageCallable
+{
+ private Decoder.Binary<?> binaryDecoder;
+
+ public OnMessageBinaryCallable(Class<?> pojo, Method method)
+ {
+ super(pojo,method);
+ }
+
+ /**
+ * Copy Constructor
+ */
+ public OnMessageBinaryCallable(OnMessageCallable copy)
+ {
+ super(copy);
+ }
+
+ public Object call(Object endpoint, ByteBuffer buf, boolean partialFlag) throws DecodeException
+ {
+ super.args[idxMessageObject] = binaryDecoder.decode(buf);
+ if (idxPartialMessageFlag >= 0)
+ {
+ super.args[idxPartialMessageFlag] = partialFlag;
+ }
+ return super.call(endpoint,super.args);
+ }
+
+ @Override
+ public void init(JsrSession session)
+ {
+ idxMessageObject = findIndexForRole(Role.MESSAGE_BINARY);
+ assertRoleRequired(idxMessageObject,"Binary Message Object");
+ super.init(session);
+ assertDecoderRequired();
+ binaryDecoder = (Decoder.Binary<?>)getDecoder();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryStreamCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryStreamCallable.java
new file mode 100644
index 0000000..6fad465
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryStreamCallable.java
@@ -0,0 +1,70 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+//import java.io.IOException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnMessage} annotated methods for {@link InputStream} based binary message objects
+ *
+ * @see BinaryStream
+ */
+public class OnMessageBinaryStreamCallable extends OnMessageCallable
+{
+ private Decoder.BinaryStream<?> binaryDecoder;
+
+ public OnMessageBinaryStreamCallable(Class<?> pojo, Method method)
+ {
+ super(pojo,method);
+ }
+
+ /**
+ * Copy Constructor
+ */
+ public OnMessageBinaryStreamCallable(OnMessageCallable copy)
+ {
+ super(copy);
+ }
+
+ public Object call(Object endpoint, InputStream stream) throws DecodeException, IOException
+ {
+ super.args[idxMessageObject] = binaryDecoder.decode(stream);
+ return super.call(endpoint,super.args);
+ }
+
+ @Override
+ public void init(JsrSession session)
+ {
+ idxMessageObject = findIndexForRole(Role.MESSAGE_BINARY_STREAM);
+ assertRoleRequired(idxMessageObject,"Binary InputStream Message Object");
+ super.init(session);
+ assertDecoderRequired();
+ binaryDecoder = (Decoder.BinaryStream<?>)getDecoder();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageCallable.java
new file mode 100644
index 0000000..44f9fec
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageCallable.java
@@ -0,0 +1,175 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+import org.eclipse.jetty.websocket.jsr356.EncoderFactory;
+import org.eclipse.jetty.websocket.jsr356.InitException;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+public class OnMessageCallable extends JsrCallable
+{
+ protected final Class<?> returnType;
+ protected Encoder returnEncoder;
+ protected Class<? extends Decoder> decoderClass;
+ protected Decoder decoder;
+ protected int idxPartialMessageFlag = -1;
+ protected int idxMessageObject = -1;
+ protected boolean messageRoleAssigned = false;
+
+ public OnMessageCallable(Class<?> pojo, Method method)
+ {
+ super(pojo,method);
+ this.returnType = method.getReturnType();
+ }
+
+ public OnMessageCallable(OnMessageCallable copy)
+ {
+ super(copy);
+ this.returnType = copy.returnType;
+ this.decoderClass = copy.decoderClass;
+ this.decoder = copy.decoder;
+ this.idxPartialMessageFlag = copy.idxPartialMessageFlag;
+ this.idxMessageObject = copy.idxMessageObject;
+ }
+
+ protected void assertDecoderRequired()
+ {
+ if (getDecoder() == null)
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Unable to find a valid ");
+ err.append(Decoder.class.getName());
+ err.append(" for parameter #");
+ Param param = params[idxMessageObject];
+ err.append(param.index);
+ err.append(" [").append(param.type).append("] in method: ");
+ err.append(ReflectUtils.toString(pojo,method));
+ throw new InvalidSignatureException(err.toString());
+ }
+ }
+
+ private int findMessageObjectIndex()
+ {
+ int index = -1;
+
+ for (Param.Role role : Param.Role.getMessageRoles())
+ {
+ index = findIndexForRole(role);
+ if (index >= 0)
+ {
+ return index;
+ }
+ }
+
+ return -1;
+ }
+
+ public Decoder getDecoder()
+ {
+ return decoder;
+ }
+
+ public Class<? extends Decoder> getDecoderClass()
+ {
+ return decoderClass;
+ }
+
+ public Param getMessageObjectParam()
+ {
+ if (idxMessageObject < 0)
+ {
+ idxMessageObject = findMessageObjectIndex();
+
+ if (idxMessageObject < 0)
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("A message type must be specified [TEXT, BINARY, DECODER, or PONG] : ");
+ err.append(ReflectUtils.toString(pojo,method));
+ throw new InvalidSignatureException(err.toString());
+ }
+ }
+
+ return super.params[idxMessageObject];
+ }
+
+ public Encoder getReturnEncoder()
+ {
+ return returnEncoder;
+ }
+
+ public Class<?> getReturnType()
+ {
+ return returnType;
+ }
+
+ @Override
+ public void init(JsrSession session)
+ {
+ super.init(session);
+ idxPartialMessageFlag = findIndexForRole(Role.MESSAGE_PARTIAL_FLAG);
+
+ EncoderFactory.Wrapper encoderWrapper = session.getEncoderFactory().getWrapperFor(returnType);
+ if (encoderWrapper != null)
+ {
+ this.returnEncoder = encoderWrapper.getEncoder();
+ }
+
+ if (decoderClass != null)
+ {
+ try
+ {
+ this.decoder = decoderClass.newInstance();
+ }
+ catch (InstantiationException | IllegalAccessException e)
+ {
+ throw new InitException("Unable to create decoder: " + decoderClass.getName(),e);
+ }
+ }
+ }
+
+ public boolean isMessageRoleAssigned()
+ {
+ return messageRoleAssigned;
+ }
+
+ public boolean isPartialMessageSupported()
+ {
+ return (idxPartialMessageFlag >= 0);
+ }
+
+ @Override
+ public void setDecoderClass(Class<? extends Decoder> decoderClass)
+ {
+ this.decoderClass = decoderClass;
+ messageRoleAssigned = true;
+ }
+
+ public void setPartialMessageFlag(Param param)
+ {
+ idxPartialMessageFlag = param.index;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessagePongCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessagePongCallable.java
new file mode 100644
index 0000000..8ac8656
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessagePongCallable.java
@@ -0,0 +1,63 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+
+import javax.websocket.DecodeException;
+import javax.websocket.OnMessage;
+import javax.websocket.PongMessage;
+
+import org.eclipse.jetty.websocket.jsr356.JsrPongMessage;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnMessage} annotated methods with a {@link PongMessage} message object.
+ */
+public class OnMessagePongCallable extends OnMessageCallable
+{
+ public OnMessagePongCallable(Class<?> pojo, Method method)
+ {
+ super(pojo,method);
+ }
+
+ /**
+ * Copy Constructor
+ */
+ public OnMessagePongCallable(OnMessageCallable copy)
+ {
+ super(copy);
+ }
+
+ public Object call(Object endpoint, ByteBuffer buf) throws DecodeException
+ {
+ super.args[idxMessageObject] = new JsrPongMessage(buf);
+ return super.call(endpoint,super.args);
+ }
+
+ @Override
+ public void init(JsrSession session)
+ {
+ idxMessageObject = findIndexForRole(Role.MESSAGE_PONG);
+ assertRoleRequired(idxMessageObject,"Pong Message Object");
+ super.init(session);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextCallable.java
new file mode 100644
index 0000000..441e2b1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextCallable.java
@@ -0,0 +1,74 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.io.Reader;
+import java.lang.reflect.Method;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnMessage} annotated methods with a whole or partial text messages.
+ * <p>
+ * Not for use with {@link Reader} based {@link OnMessage} method objects.
+ *
+ * @see Text
+ */
+public class OnMessageTextCallable extends OnMessageCallable
+{
+ private Decoder.Text<?> textDecoder;
+
+ public OnMessageTextCallable(Class<?> pojo, Method method)
+ {
+ super(pojo,method);
+ }
+
+ /**
+ * Copy Constructor
+ */
+ public OnMessageTextCallable(OnMessageCallable copy)
+ {
+ super(copy);
+ }
+
+ public Object call(Object endpoint, String str, boolean partialFlag) throws DecodeException
+ {
+ super.args[idxMessageObject] = textDecoder.decode(str);
+ if (idxPartialMessageFlag >= 0)
+ {
+ super.args[idxPartialMessageFlag] = partialFlag;
+ }
+ return super.call(endpoint,super.args);
+ }
+
+ @Override
+ public void init(JsrSession session)
+ {
+ idxMessageObject = findIndexForRole(Role.MESSAGE_TEXT);
+ assertRoleRequired(idxMessageObject,"Text Message Object");
+ super.init(session);
+ assertDecoderRequired();
+ textDecoder = (Decoder.Text<?>)getDecoder();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextStreamCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextStreamCallable.java
new file mode 100644
index 0000000..2dcd28d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextStreamCallable.java
@@ -0,0 +1,69 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.lang.reflect.Method;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnMessage} annotated methods for {@link Reader} based text message objects
+ *
+ * @see TextStream
+ */
+public class OnMessageTextStreamCallable extends OnMessageCallable
+{
+ private Decoder.TextStream<?> textDecoder;
+
+ public OnMessageTextStreamCallable(Class<?> pojo, Method method)
+ {
+ super(pojo,method);
+ }
+
+ /**
+ * Copy Constructor
+ */
+ public OnMessageTextStreamCallable(OnMessageCallable copy)
+ {
+ super(copy);
+ }
+
+ public Object call(Object endpoint, Reader reader) throws DecodeException, IOException
+ {
+ super.args[idxMessageObject] = textDecoder.decode(reader);
+ return super.call(endpoint,super.args);
+ }
+
+ @Override
+ public void init(JsrSession session)
+ {
+ idxMessageObject = findIndexForRole(Role.MESSAGE_TEXT_STREAM);
+ assertRoleRequired(idxMessageObject,"Text Reader Message Object");
+ super.init(session);
+ assertDecoderRequired();
+ textDecoder = (Decoder.TextStream<?>)getDecoder();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnOpenCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnOpenCallable.java
new file mode 100644
index 0000000..388bee6
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnOpenCallable.java
@@ -0,0 +1,70 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnOpen} annotated methods
+ */
+public class OnOpenCallable extends JsrCallable
+{
+ private int idxEndpointConfig = -1;
+
+ public OnOpenCallable(Class<?> pojo, Method method)
+ {
+ super(pojo,method);
+ }
+
+ public OnOpenCallable(OnOpenCallable copy)
+ {
+ super(copy);
+ this.idxEndpointConfig = copy.idxEndpointConfig;
+ }
+
+ public void call(Object endpoint, EndpointConfig config)
+ {
+ // EndpointConfig is an optional parameter
+ if (idxEndpointConfig >= 0)
+ {
+ super.args[idxEndpointConfig] = config;
+ }
+ super.call(endpoint,super.args);
+ }
+
+ @Override
+ public void init(JsrSession session)
+ {
+ idxEndpointConfig = findIndexForRole(Role.ENDPOINT_CONFIG);
+ super.init(session);
+ }
+
+ @Override
+ public void setDecoderClass(Class<? extends Decoder> decoderClass)
+ {
+ /* ignore, not relevant for onClose */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/Param.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/Param.java
new file mode 100644
index 0000000..f5068ad
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/Param.java
@@ -0,0 +1,135 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+
+public class Param
+{
+ /**
+ * The various roles of the known parameters.
+ */
+ public static enum Role
+ {
+ SESSION,
+ ENDPOINT_CONFIG,
+ CLOSE_REASON,
+ ERROR_CAUSE,
+ MESSAGE_TEXT,
+ MESSAGE_TEXT_STREAM,
+ MESSAGE_BINARY,
+ MESSAGE_BINARY_STREAM,
+ MESSAGE_PONG,
+ MESSAGE_PARTIAL_FLAG,
+ PATH_PARAM;
+
+ private static Role[] messageRoles;
+
+ static
+ {
+ messageRoles = new Role[]
+ { MESSAGE_TEXT, MESSAGE_TEXT_STREAM, MESSAGE_BINARY, MESSAGE_BINARY_STREAM, MESSAGE_PONG, };
+ }
+
+ public static Role[] getMessageRoles()
+ {
+ return messageRoles;
+ }
+ }
+
+ public int index;
+ public Class<?> type;
+ private transient Map<Class<? extends Annotation>, Annotation> annotations;
+
+ /*
+ * The bound role for this parameter.
+ */
+ public Role role = null;
+ private String pathParamName = null;
+
+ public Param(int idx, Class<?> type, Annotation[] annos)
+ {
+ this.index = idx;
+ this.type = type;
+ if (annos != null)
+ {
+ this.annotations = new HashMap<>();
+ for (Annotation anno : annos)
+ {
+ this.annotations.put(anno.annotationType(),anno);
+ }
+ }
+ }
+
+ public void bind(Role role)
+ {
+ this.role = role;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
+ {
+ if (this.annotations == null)
+ {
+ return null;
+ }
+
+ return (A)this.annotations.get(annotationClass);
+ }
+
+ public String getPathParamName()
+ {
+ return this.pathParamName;
+ }
+
+ public boolean isValid()
+ {
+ return this.role != null;
+ }
+
+ public void setPathParamName(String name)
+ {
+ this.pathParamName = name;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder();
+ str.append("Param[");
+ str.append("index=").append(index);
+ str.append(",type=").append(ReflectUtils.toShortName(type));
+ str.append(",role=").append(role);
+ if (pathParamName != null)
+ {
+ str.append(",pathParamName=").append(pathParamName);
+ }
+ str.append(']');
+ return str.toString();
+ }
+
+ public void unbind()
+ {
+ this.role = null;
+ }
+}
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/AnnotatedClientEndpointConfig.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/AnnotatedClientEndpointConfig.java
new file mode 100644
index 0000000..1175f3d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/AnnotatedClientEndpointConfig.java
@@ -0,0 +1,112 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.client;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+import javax.websocket.Extension;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+
+public class AnnotatedClientEndpointConfig implements ClientEndpointConfig
+{
+ private final List<Class<? extends Decoder>> decoders;
+ private final List<Class<? extends Encoder>> encoders;
+ private final List<Extension> extensions;
+ private final List<String> preferredSubprotocols;
+ private final Configurator configurator;
+ private Map<String, Object> userProperties;
+
+ public AnnotatedClientEndpointConfig(ClientEndpoint anno)
+ {
+ this.decoders = Collections.unmodifiableList(Arrays.asList(anno.decoders()));
+ this.encoders = Collections.unmodifiableList(Arrays.asList(anno.encoders()));
+ this.preferredSubprotocols = Collections.unmodifiableList(Arrays.asList(anno.subprotocols()));
+
+ // no extensions declared in annotation
+ this.extensions = Collections.emptyList();
+ // no userProperties in annotation
+ this.userProperties = new HashMap<>();
+
+ if (anno.configurator() == null)
+ {
+ this.configurator = EmptyConfigurator.INSTANCE;
+ }
+ else
+ {
+ try
+ {
+ this.configurator = anno.configurator().newInstance();
+ }
+ catch (InstantiationException | IllegalAccessException e)
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Unable to instantiate ClientEndpoint.configurator() of ");
+ err.append(anno.configurator().getName());
+ err.append(" defined as annotation in ");
+ err.append(anno.getClass().getName());
+ throw new InvalidWebSocketException(err.toString(),e);
+ }
+ }
+ }
+
+ @Override
+ public Configurator getConfigurator()
+ {
+ return configurator;
+ }
+
+ @Override
+ public List<Class<? extends Decoder>> getDecoders()
+ {
+ return decoders;
+ }
+
+ @Override
+ public List<Class<? extends Encoder>> getEncoders()
+ {
+ return encoders;
+ }
+
+ @Override
+ public List<Extension> getExtensions()
+ {
+ return extensions;
+ }
+
+ @Override
+ public List<String> getPreferredSubprotocols()
+ {
+ return preferredSubprotocols;
+ }
+
+ @Override
+ public Map<String, Object> getUserProperties()
+ {
+ return userProperties;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/AnnotatedClientEndpointMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/AnnotatedClientEndpointMetadata.java
new file mode 100644
index 0000000..70f6d53
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/AnnotatedClientEndpointMetadata.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.client;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.jsr356.ClientContainer;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointMetadata;
+
+public class AnnotatedClientEndpointMetadata extends AnnotatedEndpointMetadata<ClientEndpoint, ClientEndpointConfig>
+{
+ private final ClientEndpoint endpoint;
+ private final AnnotatedClientEndpointConfig config;
+
+ public AnnotatedClientEndpointMetadata(ClientContainer container, Class<?> websocket)
+ {
+ super(websocket);
+
+ ClientEndpoint anno = websocket.getAnnotation(ClientEndpoint.class);
+ if (anno == null)
+ {
+ throw new InvalidWebSocketException(String.format("Unsupported WebSocket object [%s], missing @%s annotation",websocket.getName(),
+ ClientEndpoint.class.getName()));
+ }
+
+ this.endpoint = anno;
+ this.config = new AnnotatedClientEndpointConfig(anno);
+
+ getDecoders().addAll(anno.decoders());
+ getEncoders().addAll(anno.encoders());
+ }
+
+ @Override
+ public ClientEndpoint getAnnotation()
+ {
+ return endpoint;
+ }
+
+ @Override
+ public ClientEndpointConfig getConfig()
+ {
+ return config;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/EmptyClientEndpointConfig.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/EmptyClientEndpointConfig.java
new file mode 100644
index 0000000..f3363d4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/EmptyClientEndpointConfig.java
@@ -0,0 +1,85 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.client;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+import javax.websocket.Extension;
+
+public class EmptyClientEndpointConfig implements ClientEndpointConfig
+{
+ private final List<Class<? extends Decoder>> decoders;
+ private final List<Class<? extends Encoder>> encoders;
+ private final List<Extension> extensions;
+ private final List<String> preferredSubprotocols;
+ private final Configurator configurator;
+ private Map<String, Object> userProperties;
+
+ public EmptyClientEndpointConfig()
+ {
+ this.decoders = new ArrayList<>();
+ this.encoders = new ArrayList<>();
+ this.preferredSubprotocols = new ArrayList<>();
+ this.extensions = new ArrayList<>();
+ this.userProperties = new HashMap<>();
+ this.configurator = EmptyConfigurator.INSTANCE;
+ }
+
+ @Override
+ public Configurator getConfigurator()
+ {
+ return configurator;
+ }
+
+ @Override
+ public List<Class<? extends Decoder>> getDecoders()
+ {
+ return decoders;
+ }
+
+ @Override
+ public List<Class<? extends Encoder>> getEncoders()
+ {
+ return encoders;
+ }
+
+ @Override
+ public List<Extension> getExtensions()
+ {
+ return extensions;
+ }
+
+ @Override
+ public List<String> getPreferredSubprotocols()
+ {
+ return preferredSubprotocols;
+ }
+
+ @Override
+ public Map<String, Object> getUserProperties()
+ {
+ return userProperties;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/EmptyConfigurator.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/EmptyConfigurator.java
new file mode 100644
index 0000000..776ab89
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/EmptyConfigurator.java
@@ -0,0 +1,42 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.client;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.HandshakeResponse;
+
+public class EmptyConfigurator extends ClientEndpointConfig.Configurator
+{
+ public static final EmptyConfigurator INSTANCE = new EmptyConfigurator();
+
+ @Override
+ public void afterResponse(HandshakeResponse hr)
+ {
+ // do nothing
+ }
+
+ @Override
+ public void beforeRequest(Map<String, List<String>> headers)
+ {
+ // do nothing
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/JsrClientEndpointImpl.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/JsrClientEndpointImpl.java
new file mode 100644
index 0000000..80b6b4b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/JsrClientEndpointImpl.java
@@ -0,0 +1,72 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.client;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.DeploymentException;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverImpl;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrEvents;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrAnnotatedEventDriver;
+
+/**
+ * Event Driver for classes annotated with @{@link ClientEndpoint}
+ */
+public class JsrClientEndpointImpl implements EventDriverImpl
+{
+ @Override
+ public EventDriver create(Object websocket, WebSocketPolicy policy) throws DeploymentException
+ {
+ if (!(websocket instanceof EndpointInstance))
+ {
+ throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),EndpointInstance.class.getName()));
+ }
+
+ EndpointInstance ei = (EndpointInstance)websocket;
+ AnnotatedClientEndpointMetadata metadata = (AnnotatedClientEndpointMetadata)ei.getMetadata();
+ JsrEvents<ClientEndpoint, ClientEndpointConfig> events = new JsrEvents<>(metadata);
+
+ return new JsrAnnotatedEventDriver(policy,ei,events);
+ }
+
+ @Override
+ public String describeRule()
+ {
+ return "class is annotated with @" + ClientEndpoint.class.getName();
+ }
+
+ @Override
+ public boolean supports(Object websocket)
+ {
+ if (!(websocket instanceof EndpointInstance))
+ {
+ return false;
+ }
+
+ EndpointInstance ei = (EndpointInstance)websocket;
+ Object endpoint = ei.getEndpoint();
+
+ ClientEndpoint anno = endpoint.getClass().getAnnotation(ClientEndpoint.class);
+ return (anno != null);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/SimpleEndpointMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/SimpleEndpointMetadata.java
new file mode 100644
index 0000000..cdd0eda
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/SimpleEndpointMetadata.java
@@ -0,0 +1,60 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.client;
+
+import javax.websocket.Endpoint;
+
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+
+/**
+ * Basic {@link EndpointMetadata} for an WebSocket that extends from {@link Endpoint}
+ */
+public class SimpleEndpointMetadata implements EndpointMetadata
+{
+ private final Class<?> endpointClass;
+ private DecoderMetadataSet decoders;
+ private EncoderMetadataSet encoders;
+
+ public SimpleEndpointMetadata(Class<? extends Endpoint> endpointClass)
+ {
+ this.endpointClass = endpointClass;
+ this.decoders = new DecoderMetadataSet();
+ this.encoders = new EncoderMetadataSet();
+ }
+
+ @Override
+ public DecoderMetadataSet getDecoders()
+ {
+ return decoders;
+ }
+
+ @Override
+ public EncoderMetadataSet getEncoders()
+ {
+ return encoders;
+ }
+
+ @Override
+ public Class<?> getEndpointClass()
+ {
+ return endpointClass;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/AbstractDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/AbstractDecoder.java
new file mode 100644
index 0000000..3dd9afc
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/AbstractDecoder.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+public abstract class AbstractDecoder implements Decoder
+{
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/BooleanDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/BooleanDecoder.java
new file mode 100644
index 0000000..bedc5c1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/BooleanDecoder.java
@@ -0,0 +1,49 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link Boolean} decoder.
+ * <p>
+ * Note: delegates to {@link Boolean#parseBoolean(String)} and will only support "true" and "false" as boolean values.
+ */
+public class BooleanDecoder extends AbstractDecoder implements Decoder.Text<Boolean>
+{
+ public static final BooleanDecoder INSTANCE = new BooleanDecoder();
+
+ @Override
+ public Boolean decode(String s) throws DecodeException
+ {
+ return Boolean.parseBoolean(s);
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ if (s == null)
+ {
+ return false;
+ }
+ return (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("false"));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteArrayDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteArrayDecoder.java
new file mode 100644
index 0000000..71b62db
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteArrayDecoder.java
@@ -0,0 +1,43 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class ByteArrayDecoder extends AbstractDecoder implements Decoder.Binary<byte[]>
+{
+ public static final ByteArrayDecoder INSTANCE = new ByteArrayDecoder();
+
+ @Override
+ public byte[] decode(ByteBuffer bytes) throws DecodeException
+ {
+ return BufferUtil.toArray(bytes);
+ }
+
+ @Override
+ public boolean willDecode(ByteBuffer bytes)
+ {
+ return true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteBufferDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteBufferDecoder.java
new file mode 100644
index 0000000..d1fcb91
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteBufferDecoder.java
@@ -0,0 +1,41 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+public class ByteBufferDecoder extends AbstractDecoder implements Decoder.Binary<ByteBuffer>
+{
+ public static final ByteBufferDecoder INSTANCE = new ByteBufferDecoder();
+
+ @Override
+ public ByteBuffer decode(ByteBuffer bytes) throws DecodeException
+ {
+ return bytes;
+ }
+
+ @Override
+ public boolean willDecode(ByteBuffer bytes)
+ {
+ return true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteDecoder.java
new file mode 100644
index 0000000..3e3936e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteDecoder.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link Byte} decoder
+ */
+public class ByteDecoder extends AbstractDecoder implements Decoder.Text<Byte>
+{
+ public static final ByteDecoder INSTANCE = new ByteDecoder();
+
+ @Override
+ public Byte decode(String s) throws DecodeException
+ {
+ try
+ {
+ return Byte.parseByte(s);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new DecodeException(s,"Unable to parse Byte",e);
+ }
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ if (s == null)
+ {
+ return false;
+ }
+ try
+ {
+ Byte.parseByte(s);
+ return true;
+ }
+ catch (NumberFormatException e)
+ {
+ return false;
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/CharacterDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/CharacterDecoder.java
new file mode 100644
index 0000000..3cc476f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/CharacterDecoder.java
@@ -0,0 +1,52 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link Character} decoder
+ */
+public class CharacterDecoder extends AbstractDecoder implements Decoder.Text<Character>
+{
+ public static final CharacterDecoder INSTANCE = new CharacterDecoder();
+
+ @Override
+ public Character decode(String s) throws DecodeException
+ {
+ return Character.valueOf(s.charAt(0));
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ if (s == null)
+ {
+ return false;
+ }
+ if (s.length() == 1)
+ {
+ return true;
+ }
+ // can only parse 1 character
+ return false;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/DoubleDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/DoubleDecoder.java
new file mode 100644
index 0000000..e6bd246
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/DoubleDecoder.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link Double} to decoder
+ */
+public class DoubleDecoder extends AbstractDecoder implements Decoder.Text<Double>
+{
+ public static final DoubleDecoder INSTANCE = new DoubleDecoder();
+
+ @Override
+ public Double decode(String s) throws DecodeException
+ {
+ try
+ {
+ return Double.parseDouble(s);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new DecodeException(s,"Unable to parse double",e);
+ }
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ if (s == null)
+ {
+ return false;
+ }
+ try
+ {
+ Double.parseDouble(s);
+ return true;
+ }
+ catch (NumberFormatException e)
+ {
+ return false;
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/FloatDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/FloatDecoder.java
new file mode 100644
index 0000000..d8e0790
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/FloatDecoder.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+/**
+ * Default implementation of the Text Message to {@link Float} decoder
+ */
+public class FloatDecoder extends AbstractDecoder implements Decoder.Text<Float>
+{
+ public static final FloatDecoder INSTANCE = new FloatDecoder();
+
+ @Override
+ public Float decode(String s) throws DecodeException
+ {
+ try
+ {
+ Float val = Float.parseFloat(s);
+ if (val.isNaN())
+ {
+ throw new DecodeException(s,"NaN");
+ }
+ return val;
+ }
+ catch (NumberFormatException e)
+ {
+ throw new DecodeException(s,"Unable to parse float",e);
+ }
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ if (s == null)
+ {
+ return false;
+ }
+ try
+ {
+ Float val = Float.parseFloat(s);
+ return (!val.isNaN());
+ }
+ catch (NumberFormatException e)
+ {
+ return false;
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/InputStreamDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/InputStreamDecoder.java
new file mode 100644
index 0000000..47f37bd
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/InputStreamDecoder.java
@@ -0,0 +1,45 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+public class InputStreamDecoder implements Decoder.BinaryStream<InputStream>
+{
+ @Override
+ public InputStream decode(InputStream is) throws DecodeException, IOException
+ {
+ return is;
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/IntegerDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/IntegerDecoder.java
new file mode 100644
index 0000000..3dda52a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/IntegerDecoder.java
@@ -0,0 +1,63 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link Integer} decoder
+ */
+public class IntegerDecoder extends AbstractDecoder implements Decoder.Text<Integer>
+{
+ public static final IntegerDecoder INSTANCE = new IntegerDecoder();
+
+ @Override
+ public Integer decode(String s) throws DecodeException
+ {
+ try
+ {
+ return Integer.parseInt(s);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new DecodeException(s,"Unable to parse Integer",e);
+ }
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ if (s == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ Integer.parseInt(s);
+ return true;
+ }
+ catch (NumberFormatException e)
+ {
+ return false;
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/LongDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/LongDecoder.java
new file mode 100644
index 0000000..f42901e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/LongDecoder.java
@@ -0,0 +1,61 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+/**
+ * Default implementation of the Text Message to {@link Long} decoder
+ */
+public class LongDecoder extends AbstractDecoder implements Decoder.Text<Long>
+{
+ public static final LongDecoder INSTANCE = new LongDecoder();
+
+ @Override
+ public Long decode(String s) throws DecodeException
+ {
+ try
+ {
+ return Long.parseLong(s);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new DecodeException(s,"Unable to parse Long",e);
+ }
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ if (s == null)
+ {
+ return false;
+ }
+ try
+ {
+ Long.parseLong(s);
+ return true;
+ }
+ catch (NumberFormatException e)
+ {
+ return false;
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/PongMessageDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/PongMessageDecoder.java
new file mode 100644
index 0000000..c81454e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/PongMessageDecoder.java
@@ -0,0 +1,61 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.PongMessage;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class PongMessageDecoder extends AbstractDecoder implements Decoder.Binary<PongMessage>
+{
+ private static class PongMsg implements PongMessage
+ {
+ private final ByteBuffer bytes;
+
+ public PongMsg(ByteBuffer buf)
+ {
+ int len = buf.remaining();
+ this.bytes = ByteBuffer.allocate(len);
+ BufferUtil.put(buf,this.bytes);
+ BufferUtil.flipToFlush(this.bytes,0);
+ }
+
+ @Override
+ public ByteBuffer getApplicationData()
+ {
+ return this.bytes;
+ }
+ }
+
+ @Override
+ public PongMessage decode(ByteBuffer bytes) throws DecodeException
+ {
+ return new PongMsg(bytes);
+ }
+
+ @Override
+ public boolean willDecode(ByteBuffer bytes)
+ {
+ return true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/PrimitiveDecoderMetadataSet.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/PrimitiveDecoderMetadataSet.java
new file mode 100644
index 0000000..e746a76
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/PrimitiveDecoderMetadataSet.java
@@ -0,0 +1,70 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
+
+public class PrimitiveDecoderMetadataSet extends DecoderMetadataSet
+{
+ public static final DecoderMetadataSet INSTANCE = new PrimitiveDecoderMetadataSet();
+
+ public PrimitiveDecoderMetadataSet()
+ {
+ boolean streamed = false;
+ // TEXT based - Classes Based
+ MessageType msgType = MessageType.TEXT;
+ register(Boolean.class,BooleanDecoder.class,msgType,streamed);
+ register(Byte.class,ByteDecoder.class,msgType,streamed);
+ register(Character.class,CharacterDecoder.class,msgType,streamed);
+ register(Double.class,DoubleDecoder.class,msgType,streamed);
+ register(Float.class,FloatDecoder.class,msgType,streamed);
+ register(Integer.class,IntegerDecoder.class,msgType,streamed);
+ register(Long.class,LongDecoder.class,msgType,streamed);
+ register(Short.class,ShortDecoder.class,msgType,streamed);
+ register(String.class,StringDecoder.class,msgType,streamed);
+
+ // TEXT based - Primitive Types
+ msgType = MessageType.TEXT;
+ register(Boolean.TYPE,BooleanDecoder.class,msgType,streamed);
+ register(Byte.TYPE,ByteDecoder.class,msgType,streamed);
+ register(Character.TYPE,CharacterDecoder.class,msgType,streamed);
+ register(Double.TYPE,DoubleDecoder.class,msgType,streamed);
+ register(Float.TYPE,FloatDecoder.class,msgType,streamed);
+ register(Integer.TYPE,IntegerDecoder.class,msgType,streamed);
+ register(Long.TYPE,LongDecoder.class,msgType,streamed);
+ register(Short.TYPE,ShortDecoder.class,msgType,streamed);
+
+ // BINARY based
+ msgType = MessageType.BINARY;
+ register(ByteBuffer.class,ByteBufferDecoder.class,msgType,streamed);
+ register(byte[].class,ByteArrayDecoder.class,msgType,streamed);
+
+ // STREAMING based
+ streamed = true;
+ msgType = MessageType.TEXT;
+ register(Reader.class,ReaderDecoder.class,msgType,streamed);
+ msgType = MessageType.BINARY;
+ register(InputStream.class,InputStreamDecoder.class,msgType,streamed);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ReaderDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ReaderDecoder.java
new file mode 100644
index 0000000..2a36c88
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ReaderDecoder.java
@@ -0,0 +1,45 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+public class ReaderDecoder implements Decoder.TextStream<Reader>
+{
+ @Override
+ public Reader decode(Reader reader) throws DecodeException, IOException
+ {
+ return reader;
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ShortDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ShortDecoder.java
new file mode 100644
index 0000000..f4348f9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ShortDecoder.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link Short} decoder
+ */
+public class ShortDecoder extends AbstractDecoder implements Decoder.Text<Short>
+{
+ public static final ShortDecoder INSTANCE = new ShortDecoder();
+
+ @Override
+ public Short decode(String s) throws DecodeException
+ {
+ try
+ {
+ return Short.parseShort(s);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new DecodeException(s,"Unable to parse Short",e);
+ }
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ if (s == null)
+ {
+ return false;
+ }
+ try
+ {
+ Short.parseShort(s);
+ return true;
+ }
+ catch (NumberFormatException e)
+ {
+ return false;
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/StringDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/StringDecoder.java
new file mode 100644
index 0000000..70f6887
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/StringDecoder.java
@@ -0,0 +1,43 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link String} decoder
+ */
+public class StringDecoder extends AbstractDecoder implements Decoder.Text<String>
+{
+ public static final StringDecoder INSTANCE = new StringDecoder();
+
+ @Override
+ public String decode(String s) throws DecodeException
+ {
+ return s;
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ return true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/AbstractEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/AbstractEncoder.java
new file mode 100644
index 0000000..93e4104
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/AbstractEncoder.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+public abstract class AbstractEncoder implements Encoder
+{
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/BooleanEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/BooleanEncoder.java
new file mode 100644
index 0000000..f2cf552
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/BooleanEncoder.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Boolean} to {@link Text} Message encoder
+ */
+public class BooleanEncoder extends AbstractEncoder implements Encoder.Text<Boolean>
+{
+ @Override
+ public String encode(Boolean object) throws EncodeException
+ {
+ if (object == null)
+ {
+ return null;
+ }
+ return object.toString();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteArrayEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteArrayEncoder.java
new file mode 100644
index 0000000..a1f934a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteArrayEncoder.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+public class ByteArrayEncoder implements Encoder.Binary<byte[]>
+{
+ @Override
+ public void destroy()
+ {
+ /* do nothing */
+ }
+
+ @Override
+ public ByteBuffer encode(byte[] object) throws EncodeException
+ {
+ return ByteBuffer.wrap(object);
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ /* do nothing */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteBufferEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteBufferEncoder.java
new file mode 100644
index 0000000..bee78b7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteBufferEncoder.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+public class ByteBufferEncoder implements Encoder.Binary<ByteBuffer>
+{
+ @Override
+ public void destroy()
+ {
+ /* do nothing */
+ }
+
+ @Override
+ public ByteBuffer encode(ByteBuffer object) throws EncodeException
+ {
+ return object;
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ /* do nothing */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteEncoder.java
new file mode 100644
index 0000000..193bb5a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteEncoder.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Byte} to {@link Text} Message encoder
+ */
+public class ByteEncoder extends AbstractEncoder implements Encoder.Text<Byte>
+{
+ @Override
+ public String encode(Byte object) throws EncodeException
+ {
+ if (object == null)
+ {
+ return null;
+ }
+ return object.toString();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/CharacterEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/CharacterEncoder.java
new file mode 100644
index 0000000..1a0f1a2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/CharacterEncoder.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Character} to {@link Text} Message encoder
+ */
+public class CharacterEncoder extends AbstractEncoder implements Encoder.Text<Character>
+{
+ @Override
+ public String encode(Character object) throws EncodeException
+ {
+ if (object == null)
+ {
+ return null;
+ }
+ return object.toString();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryEncoder.java
new file mode 100644
index 0000000..77a4f89
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryEncoder.java
@@ -0,0 +1,33 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+public class DefaultBinaryEncoder extends AbstractEncoder implements Encoder.Binary<ByteBuffer>
+{
+ @Override
+ public ByteBuffer encode(ByteBuffer message) throws EncodeException
+ {
+ return message;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryStreamEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryStreamEncoder.java
new file mode 100644
index 0000000..8eab691
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryStreamEncoder.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class DefaultBinaryStreamEncoder extends AbstractEncoder implements Encoder.BinaryStream<ByteBuffer>
+{
+ @Override
+ public void encode(ByteBuffer message, OutputStream out) throws EncodeException, IOException
+ {
+ BufferUtil.writeTo(message,out);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextEncoder.java
new file mode 100644
index 0000000..e3e52a4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextEncoder.java
@@ -0,0 +1,31 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+public class DefaultTextEncoder extends AbstractEncoder implements Encoder.Text<String>
+{
+ @Override
+ public String encode(String message) throws EncodeException
+ {
+ return message;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextStreamEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextStreamEncoder.java
new file mode 100644
index 0000000..72ac319
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextStreamEncoder.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+public class DefaultTextStreamEncoder extends AbstractEncoder implements Encoder.TextStream<String>
+{
+ @Override
+ public void encode(String message, Writer writer) throws EncodeException, IOException
+ {
+ writer.append(message);
+ writer.flush();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DoubleEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DoubleEncoder.java
new file mode 100644
index 0000000..ab40aac
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DoubleEncoder.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Double} to {@link Text} Message encoder
+ */
+public class DoubleEncoder extends AbstractEncoder implements Encoder.Text<Double>
+{
+ @Override
+ public String encode(Double object) throws EncodeException
+ {
+ if (object == null)
+ {
+ return null;
+ }
+ return object.toString();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/EncodeFailedFuture.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/EncodeFailedFuture.java
new file mode 100644
index 0000000..3841144
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/EncodeFailedFuture.java
@@ -0,0 +1,71 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.websocket.Encoder;
+
+/**
+ * A <code>Future<Void></code> that is already failed as a result of an Encode error
+ */
+public class EncodeFailedFuture implements Future<Void>
+{
+ private final String msg;
+ private final Throwable cause;
+
+ public EncodeFailedFuture(Object data, Encoder encoder, Class<?> encoderType, Throwable cause)
+ {
+ this.msg = String.format("Unable to encode %s using %s as %s",data.getClass().getName(),encoder.getClass().getName(),encoderType.getName());
+ this.cause = cause;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning)
+ {
+ return false;
+ }
+
+ @Override
+ public Void get() throws InterruptedException, ExecutionException
+ {
+ throw new ExecutionException(msg,cause);
+ }
+
+ @Override
+ public Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
+ {
+ throw new ExecutionException(msg,cause);
+ }
+
+ @Override
+ public boolean isCancelled()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isDone()
+ {
+ return true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/FloatEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/FloatEncoder.java
new file mode 100644
index 0000000..60bb4a1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/FloatEncoder.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+/**
+ * Default encoder for {@link Float} to {@link Text} Message encoder
+ */
+public class FloatEncoder extends AbstractEncoder implements Encoder.Text<Float>
+{
+ @Override
+ public String encode(Float object) throws EncodeException
+ {
+ if (object == null)
+ {
+ return null;
+ }
+ return object.toString();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/IntegerEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/IntegerEncoder.java
new file mode 100644
index 0000000..fd4afe9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/IntegerEncoder.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Integer} to {@link Text} Message encoder
+ */
+public class IntegerEncoder extends AbstractEncoder implements Encoder.Text<Integer>
+{
+ @Override
+ public String encode(Integer object) throws EncodeException
+ {
+ if (object == null)
+ {
+ return null;
+ }
+ return object.toString();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/LongEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/LongEncoder.java
new file mode 100644
index 0000000..6a5dfe7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/LongEncoder.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Long} to {@link Text} Message encoder
+ */
+public class LongEncoder extends AbstractEncoder implements Encoder.Text<Long>
+{
+ @Override
+ public String encode(Long object) throws EncodeException
+ {
+ if (object == null)
+ {
+ return null;
+ }
+ return object.toString();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/PrimitiveEncoderMetadataSet.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/PrimitiveEncoderMetadataSet.java
new file mode 100644
index 0000000..89dc869
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/PrimitiveEncoderMetadataSet.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
+
+public class PrimitiveEncoderMetadataSet extends EncoderMetadataSet
+{
+ public static final EncoderMetadataSet INSTANCE = new PrimitiveEncoderMetadataSet();
+
+ public PrimitiveEncoderMetadataSet()
+ {
+ boolean streamed = false;
+ // TEXT based - Classes Based
+ MessageType msgType = MessageType.TEXT;
+ register(Boolean.class,BooleanEncoder.class,msgType,streamed);
+ register(Byte.class,ByteEncoder.class,msgType,streamed);
+ register(Character.class,CharacterEncoder.class,msgType,streamed);
+ register(Double.class,DoubleEncoder.class,msgType,streamed);
+ register(Float.class,FloatEncoder.class,msgType,streamed);
+ register(Integer.class,IntegerEncoder.class,msgType,streamed);
+ register(Long.class,LongEncoder.class,msgType,streamed);
+ register(Short.class,ShortEncoder.class,msgType,streamed);
+ register(String.class,StringEncoder.class,msgType,streamed);
+
+ // TEXT based - Primitive Types
+ msgType = MessageType.TEXT;
+ register(Boolean.TYPE,BooleanEncoder.class,msgType,streamed);
+ register(Byte.TYPE,ByteEncoder.class,msgType,streamed);
+ register(Character.TYPE,CharacterEncoder.class,msgType,streamed);
+ register(Double.TYPE,DoubleEncoder.class,msgType,streamed);
+ register(Float.TYPE,FloatEncoder.class,msgType,streamed);
+ register(Integer.TYPE,IntegerEncoder.class,msgType,streamed);
+ register(Long.TYPE,LongEncoder.class,msgType,streamed);
+ register(Short.TYPE,ShortEncoder.class,msgType,streamed);
+
+ // BINARY based
+ msgType = MessageType.BINARY;
+ register(ByteBuffer.class,ByteBufferEncoder.class,msgType,streamed);
+ register(byte[].class,ByteArrayEncoder.class,msgType,streamed);
+
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ShortEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ShortEncoder.java
new file mode 100644
index 0000000..14150df
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ShortEncoder.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Short} to {@link Text} Message encoder
+ */
+public class ShortEncoder extends AbstractEncoder implements Encoder.Text<Short>
+{
+ @Override
+ public String encode(Short object) throws EncodeException
+ {
+ if (object == null)
+ {
+ return null;
+ }
+ return object.toString();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/StringEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/StringEncoder.java
new file mode 100644
index 0000000..ff7f443
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/StringEncoder.java
@@ -0,0 +1,34 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link String} to {@link Text} Message encoder
+ */
+public class StringEncoder extends AbstractEncoder implements Encoder.Text<String>
+{
+ @Override
+ public String encode(String object) throws EncodeException
+ {
+ return object;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java
new file mode 100644
index 0000000..8eb4091
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java
@@ -0,0 +1,115 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import java.util.Map;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCode;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.events.AbstractEventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+
+public abstract class AbstractJsrEventDriver extends AbstractEventDriver implements EventDriver
+{
+ protected final EndpointMetadata metadata;
+ protected final EndpointConfig config;
+ protected JsrSession jsrsession;
+ private boolean hasCloseBeenCalled = false;
+
+ public AbstractJsrEventDriver(WebSocketPolicy policy, EndpointInstance endpointInstance)
+ {
+ super(policy,endpointInstance.getEndpoint());
+ this.config = endpointInstance.getConfig();
+ this.metadata = endpointInstance.getMetadata();
+ }
+
+ public EndpointConfig getConfig()
+ {
+ return config;
+ }
+
+ public Session getJsrSession()
+ {
+ return this.jsrsession;
+ }
+
+ public EndpointMetadata getMetadata()
+ {
+ return metadata;
+ }
+
+ public abstract void init(JsrSession jsrsession);
+
+ @Override
+ public final void onClose(CloseInfo close)
+ {
+ if (hasCloseBeenCalled)
+ {
+ // avoid duplicate close events (possible when using harsh Session.disconnect())
+ return;
+ }
+ hasCloseBeenCalled = true;
+
+ CloseCode closecode = CloseCodes.getCloseCode(close.getStatusCode());
+ CloseReason closereason = new CloseReason(closecode,close.getReason());
+ onClose(closereason);
+ }
+
+ protected abstract void onClose(CloseReason closereason);
+
+ @Override
+ public void onFrame(Frame frame)
+ {
+ /* Ignored, not supported by JSR-356 */
+ }
+
+ @Override
+ public final void openSession(WebSocketSession session)
+ {
+ // Cast should be safe, as it was created by JsrSessionFactory
+ this.jsrsession = (JsrSession)session;
+
+ // Allow jsr session to init
+ this.jsrsession.init(config);
+
+ // Allow event driver to init itself
+ init(jsrsession);
+
+ // Allow end-user socket to adjust configuration
+ super.openSession(session);
+ }
+
+ public void setEndpointconfig(EndpointConfig endpointconfig)
+ {
+ throw new RuntimeException("Why are you reconfiguring the endpoint?");
+ // this.config = endpointconfig;
+ }
+
+ public abstract void setPathParameters(Map<String, String> pathParameters);
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/EndpointInstance.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/EndpointInstance.java
new file mode 100644
index 0000000..3aa5575
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/EndpointInstance.java
@@ -0,0 +1,58 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+
+/**
+ * Associate a JSR Endpoint with its optional {@link EndpointConfig}
+ */
+public class EndpointInstance
+{
+ /** The instance of the Endpoint */
+ private final Object endpoint;
+ /** The instance specific configuration for the Endpoint */
+ private final EndpointConfig config;
+ /** The metadata for this endpoint */
+ private final EndpointMetadata metadata;
+
+ public EndpointInstance(Object endpoint, EndpointConfig config, EndpointMetadata metadata)
+ {
+ this.endpoint = endpoint;
+ this.config = config;
+ this.metadata = metadata;
+ }
+
+ public EndpointConfig getConfig()
+ {
+ return config;
+ }
+
+ public Object getEndpoint()
+ {
+ return endpoint;
+ }
+
+ public EndpointMetadata getMetadata()
+ {
+ return metadata;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedEventDriver.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedEventDriver.java
new file mode 100644
index 0000000..cc55e2e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedEventDriver.java
@@ -0,0 +1,363 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+import javax.websocket.CloseReason;
+import javax.websocket.DecodeException;
+import javax.websocket.MessageHandler.Whole;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.message.MessageInputStream;
+import org.eclipse.jetty.websocket.common.message.MessageReader;
+import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage;
+import org.eclipse.jetty.websocket.common.message.SimpleTextMessage;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrEvents;
+import org.eclipse.jetty.websocket.jsr356.messages.BinaryPartialOnMessage;
+import org.eclipse.jetty.websocket.jsr356.messages.TextPartialOnMessage;
+
+/**
+ * Base implementation for JSR-356 Annotated event drivers.
+ */
+public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver implements EventDriver
+{
+ private static final Logger LOG = Log.getLogger(JsrAnnotatedEventDriver.class);
+ private final JsrEvents<?, ?> events;
+
+ public JsrAnnotatedEventDriver(WebSocketPolicy policy, EndpointInstance endpointInstance, JsrEvents<?, ?> events)
+ {
+ super(policy,endpointInstance);
+ this.events = events;
+ }
+
+ @Override
+ public void init(JsrSession jsrsession)
+ {
+ this.events.init(jsrsession);
+ }
+
+ /**
+ * Entry point for all incoming binary frames.
+ */
+ @Override
+ public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("onBinaryFrame({}, {})",BufferUtil.toDetailString(buffer),fin);
+ LOG.debug("events.onBinary={}",events.hasBinary());
+ LOG.debug("events.onBinaryStream={}",events.hasBinaryStream());
+ }
+ boolean handled = false;
+
+ if (events.hasBinary())
+ {
+ handled = true;
+ if (activeMessage == null)
+ {
+ if (events.isBinaryPartialSupported())
+ {
+ // Partial Message Support (does not use messageAppender)
+ LOG.debug("Partial Binary Message: fin={}",fin);
+ activeMessage = new BinaryPartialOnMessage(this);
+ }
+ else
+ {
+ // Whole Message Support
+ LOG.debug("Whole Binary Message");
+ activeMessage = new SimpleBinaryMessage(this);
+ }
+ }
+ }
+
+ if (events.hasBinaryStream())
+ {
+ handled = true;
+ // Streaming Message Support
+ if (activeMessage == null)
+ {
+ LOG.debug("Binary Message InputStream");
+ final MessageInputStream stream = new MessageInputStream(session.getConnection());
+ activeMessage = stream;
+
+ // Always dispatch streaming read to another thread.
+ dispatch(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ events.callBinaryStream(jsrsession.getAsyncRemote(),websocket,stream);
+ }
+ catch (DecodeException | IOException e)
+ {
+ onFatalError(e);
+ }
+ }
+ });
+ }
+ }
+
+ LOG.debug("handled = {}",handled);
+
+ // Process any active MessageAppender
+ if (handled && (activeMessage != null))
+ {
+ appendMessage(buffer,fin);
+ }
+ }
+
+ /**
+ * Entry point for binary frames destined for {@link Whole}
+ */
+ @Override
+ public void onBinaryMessage(byte[] data)
+ {
+ if (data == null)
+ {
+ return;
+ }
+
+ ByteBuffer buf = ByteBuffer.wrap(data);
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("onBinaryMessage({})",BufferUtil.toDetailString(buf));
+ }
+
+ try
+ {
+ // FIN is always true here
+ events.callBinary(jsrsession.getAsyncRemote(),websocket,buf,true);
+ }
+ catch (DecodeException e)
+ {
+ onFatalError(e);
+ }
+ }
+
+ @Override
+ protected void onClose(CloseReason closereason)
+ {
+ events.callClose(websocket,closereason);
+ }
+
+ @Override
+ public void onConnect()
+ {
+ events.callOpen(websocket,config);
+ }
+
+ @Override
+ public void onError(Throwable cause)
+ {
+ events.callError(websocket,cause);
+ }
+
+ private void onFatalError(Throwable t)
+ {
+ onError(t);
+ }
+
+ @Override
+ public void onFrame(Frame frame)
+ {
+ /* Ignored in JSR-356 */
+ }
+
+ @Override
+ public void onInputStream(InputStream stream)
+ {
+ try
+ {
+ events.callBinaryStream(jsrsession.getAsyncRemote(),websocket,stream);
+ }
+ catch (DecodeException | IOException e)
+ {
+ onFatalError(e);
+ }
+ }
+
+ public void onPartialBinaryMessage(ByteBuffer buffer, boolean fin)
+ {
+ try
+ {
+ events.callBinary(jsrsession.getAsyncRemote(),websocket,buffer,fin);
+ }
+ catch (DecodeException e)
+ {
+ onFatalError(e);
+ }
+ }
+
+ public void onPartialTextMessage(String message, boolean fin)
+ {
+ try
+ {
+ events.callText(jsrsession.getAsyncRemote(),websocket,message,fin);
+ }
+ catch (DecodeException e)
+ {
+ onFatalError(e);
+ }
+ }
+
+ @Override
+ public void onPong(ByteBuffer buffer)
+ {
+ try
+ {
+ events.callPong(jsrsession.getAsyncRemote(),websocket,buffer);
+ }
+ catch (DecodeException | IOException e)
+ {
+ onFatalError(e);
+ }
+ }
+
+ @Override
+ public void onReader(Reader reader)
+ {
+ try
+ {
+ events.callTextStream(jsrsession.getAsyncRemote(),websocket,reader);
+ }
+ catch (DecodeException | IOException e)
+ {
+ onFatalError(e);
+ }
+ }
+
+ /**
+ * Entry point for all incoming text frames.
+ */
+ @Override
+ public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("onTextFrame({}, {})",BufferUtil.toDetailString(buffer),fin);
+ LOG.debug("events.hasText={}",events.hasText());
+ LOG.debug("events.hasTextStream={}",events.hasTextStream());
+ }
+
+ boolean handled = false;
+
+ if (events.hasText())
+ {
+ handled = true;
+ if (activeMessage == null)
+ {
+ if (events.isTextPartialSupported())
+ {
+ // Partial Message Support
+ LOG.debug("Partial Text Message: fin={}",fin);
+ activeMessage = new TextPartialOnMessage(this);
+ }
+ else
+ {
+ // Whole Message Support
+ LOG.debug("Whole Text Message");
+ activeMessage = new SimpleTextMessage(this);
+ }
+ }
+ }
+
+ if (events.hasTextStream())
+ {
+ handled = true;
+ // Streaming Message Support
+ if (activeMessage == null)
+ {
+ LOG.debug("Text Message Writer");
+
+ final MessageReader stream = new MessageReader(new MessageInputStream(session.getConnection()));
+ activeMessage = stream;
+
+ // Always dispatch streaming read to another thread.
+ dispatch(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ events.callTextStream(jsrsession.getAsyncRemote(),websocket,stream);
+ }
+ catch (DecodeException | IOException e)
+ {
+ onFatalError(e);
+ }
+ }
+ });
+ }
+ }
+
+ LOG.debug("handled = {}",handled);
+
+ // Process any active MessageAppender
+ if (handled && (activeMessage != null))
+ {
+ appendMessage(buffer,fin);
+ }
+ }
+
+ /**
+ * Entry point for whole text messages
+ */
+ @Override
+ public void onTextMessage(String message)
+ {
+ LOG.debug("onText({})",message);
+
+ try
+ {
+ // FIN is always true here
+ events.callText(jsrsession.getAsyncRemote(),websocket,message,true);
+ }
+ catch (DecodeException e)
+ {
+ onFatalError(e);
+ }
+ }
+
+ @Override
+ public void setPathParameters(Map<String, String> pathParameters)
+ {
+ events.setPathParameters(pathParameters);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[websocket=%s]",this.getClass().getSimpleName(),websocket);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointEventDriver.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointEventDriver.java
new file mode 100644
index 0000000..d0e49d3
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointEventDriver.java
@@ -0,0 +1,227 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+import javax.websocket.CloseReason;
+import javax.websocket.Endpoint;
+import javax.websocket.MessageHandler;
+import javax.websocket.MessageHandler.Whole;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.message.MessageInputStream;
+import org.eclipse.jetty.websocket.common.message.MessageReader;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper;
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.messages.BinaryPartialMessage;
+import org.eclipse.jetty.websocket.jsr356.messages.BinaryWholeMessage;
+import org.eclipse.jetty.websocket.jsr356.messages.TextPartialMessage;
+import org.eclipse.jetty.websocket.jsr356.messages.TextWholeMessage;
+
+/**
+ * EventDriver for websocket that extend from {@link javax.websocket.Endpoint}
+ */
+public class JsrEndpointEventDriver extends AbstractJsrEventDriver implements EventDriver
+{
+ private static final Logger LOG = Log.getLogger(JsrEndpointEventDriver.class);
+
+ private final Endpoint endpoint;
+ private Map<String, String> pathParameters;
+
+ public JsrEndpointEventDriver(WebSocketPolicy policy, EndpointInstance endpointInstance)
+ {
+ super(policy,endpointInstance);
+ this.endpoint = (Endpoint)endpointInstance.getEndpoint();
+ }
+
+ @Override
+ public void init(JsrSession jsrsession)
+ {
+ jsrsession.setPathParameters(pathParameters);
+ }
+
+ @Override
+ public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException
+ {
+ if (activeMessage == null)
+ {
+ final MessageHandlerWrapper wrapper = jsrsession.getMessageHandlerWrapper(MessageType.BINARY);
+ if (wrapper == null)
+ {
+ LOG.debug("No BINARY MessageHandler declared");
+ return;
+ }
+ if (wrapper.wantsPartialMessages())
+ {
+ activeMessage = new BinaryPartialMessage(wrapper);
+ }
+ else if (wrapper.wantsStreams())
+ {
+ final MessageInputStream stream = new MessageInputStream(session.getConnection());
+ activeMessage = stream;
+ dispatch(new Runnable()
+ {
+ @SuppressWarnings("unchecked")
+ @Override
+ public void run()
+ {
+ MessageHandler.Whole<InputStream> handler = (Whole<InputStream>)wrapper.getHandler();
+ handler.onMessage(stream);
+ }
+ });
+ }
+ else
+ {
+ activeMessage = new BinaryWholeMessage(this,wrapper);
+ }
+ }
+
+ activeMessage.appendMessage(buffer,fin);
+
+ if (fin)
+ {
+ activeMessage.messageComplete();
+ activeMessage = null;
+ }
+ }
+
+ @Override
+ public void onBinaryMessage(byte[] data)
+ {
+ /* Ignored, handled by BinaryWholeMessage */
+ }
+
+ @Override
+ protected void onClose(CloseReason closereason)
+ {
+ endpoint.onClose(this.jsrsession,closereason);
+ }
+
+ @Override
+ public void onConnect()
+ {
+ LOG.debug("onConnect({}, {})",jsrsession,config);
+ try
+ {
+ endpoint.onOpen(jsrsession,config);
+ }
+ catch (Throwable t)
+ {
+ LOG.warn("Uncaught exception",t);
+ }
+ }
+
+ @Override
+ public void onError(Throwable cause)
+ {
+ endpoint.onError(jsrsession,cause);
+ }
+
+ @Override
+ public void onFrame(Frame frame)
+ {
+ /* Ignored, not supported by JSR-356 */
+ }
+
+ @Override
+ public void onInputStream(InputStream stream)
+ {
+ /* Ignored, handled by BinaryStreamMessage */
+ }
+
+ @Override
+ public void onReader(Reader reader)
+ {
+ /* Ignored, handled by TextStreamMessage */
+ }
+
+ @Override
+ public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException
+ {
+ if (activeMessage == null)
+ {
+ final MessageHandlerWrapper wrapper = jsrsession.getMessageHandlerWrapper(MessageType.TEXT);
+ if (wrapper == null)
+ {
+ LOG.debug("No TEXT MessageHandler declared");
+ return;
+ }
+ if (wrapper.wantsPartialMessages())
+ {
+ activeMessage = new TextPartialMessage(wrapper);
+ }
+ else if (wrapper.wantsStreams())
+ {
+ final MessageReader stream = new MessageReader(new MessageInputStream(session.getConnection()));
+ activeMessage = stream;
+
+ dispatch(new Runnable()
+ {
+ @SuppressWarnings("unchecked")
+ @Override
+ public void run()
+ {
+ MessageHandler.Whole<Reader> handler = (Whole<Reader>)wrapper.getHandler();
+ handler.onMessage(stream);
+ }
+ });
+ }
+ else
+ {
+ activeMessage = new TextWholeMessage(this,wrapper);
+ }
+ }
+
+ activeMessage.appendMessage(buffer,fin);
+
+ if (fin)
+ {
+ activeMessage.messageComplete();
+ activeMessage = null;
+ }
+ }
+
+ @Override
+ public void onTextMessage(String message)
+ {
+ /* Ignored, handled by TextWholeMessage */
+ }
+
+ @Override
+ public void setPathParameters(Map<String, String> pathParameters)
+ {
+ this.pathParameters = pathParameters;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[%s]",JsrEndpointEventDriver.class.getSimpleName(),endpoint.getClass().getName());
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointImpl.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointImpl.java
new file mode 100644
index 0000000..f14a650
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointImpl.java
@@ -0,0 +1,57 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverImpl;
+
+public class JsrEndpointImpl implements EventDriverImpl
+{
+ @Override
+ public EventDriver create(Object websocket, WebSocketPolicy policy)
+ {
+ if (!(websocket instanceof EndpointInstance))
+ {
+ throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),EndpointInstance.class.getName()));
+ }
+
+ return new JsrEndpointEventDriver(policy,(EndpointInstance)websocket);
+ }
+
+ @Override
+ public String describeRule()
+ {
+ return "class extends " + javax.websocket.Endpoint.class.getName();
+ }
+
+ @Override
+ public boolean supports(Object websocket)
+ {
+ if (!(websocket instanceof EndpointInstance))
+ {
+ return false;
+ }
+
+ EndpointInstance ei = (EndpointInstance)websocket;
+ Object endpoint = ei.getEndpoint();
+
+ return (endpoint instanceof javax.websocket.Endpoint);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEventDriverFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEventDriverFactory.java
new file mode 100644
index 0000000..ce95a04
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEventDriverFactory.java
@@ -0,0 +1,52 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
+import org.eclipse.jetty.websocket.jsr356.client.JsrClientEndpointImpl;
+
+public class JsrEventDriverFactory extends EventDriverFactory
+{
+ public JsrEventDriverFactory(WebSocketPolicy policy)
+ {
+ super(policy);
+
+ clearImplementations();
+ // Classes that extend javax.websocket.Endpoint
+ addImplementation(new JsrEndpointImpl());
+ // Classes annotated with @javax.websocket.ClientEndpoint
+ addImplementation(new JsrClientEndpointImpl());
+ }
+
+ /**
+ * Unwrap ConfiguredEndpoint for end-user.
+ */
+ @Override
+ protected String getClassName(Object websocket)
+ {
+ if (websocket instanceof EndpointInstance)
+ {
+ EndpointInstance ce = (EndpointInstance)websocket;
+ return ce.getEndpoint().getClass().getName();
+ }
+
+ return websocket.getClass().getName();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialMessage.java
new file mode 100644
index 0000000..db98eca
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialMessage.java
@@ -0,0 +1,80 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.websocket.MessageHandler;
+import javax.websocket.MessageHandler.Partial;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.common.message.MessageAppender;
+import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper;
+
+/**
+ * Partial BINARY MessageAppender for MessageHandler.Partial interface
+ */
+public class BinaryPartialMessage implements MessageAppender
+{
+ private final MessageHandlerWrapper msgWrapper;
+ private final MessageHandler.Partial<Object> partialHandler;
+
+ @SuppressWarnings("unchecked")
+ public BinaryPartialMessage(MessageHandlerWrapper wrapper)
+ {
+ this.msgWrapper = wrapper;
+ this.partialHandler = (Partial<Object>)wrapper.getHandler();
+ }
+
+ @Override
+ public void appendMessage(ByteBuffer payload, boolean isLast) throws IOException
+ {
+ // No decoders for Partial messages per JSR-356 (PFD1 spec)
+
+ // Supported Partial<> Type #1: ByteBuffer
+ if (msgWrapper.isMessageType(ByteBuffer.class))
+ {
+ partialHandler.onMessage(payload.slice(),isLast);
+ return;
+ }
+
+ // Supported Partial<> Type #2: byte[]
+ if (msgWrapper.isMessageType(byte[].class))
+ {
+ partialHandler.onMessage(BufferUtil.toArray(payload),isLast);
+ return;
+ }
+
+ StringBuilder err = new StringBuilder();
+ err.append(msgWrapper.getHandler().getClass());
+ err.append(" does not implement an expected ");
+ err.append(MessageHandler.Partial.class.getName());
+ err.append(" of type ");
+ err.append(ByteBuffer.class.getName());
+ err.append(" or byte[]");
+ throw new IllegalStateException(err.toString());
+ }
+
+ @Override
+ public void messageComplete()
+ {
+ /* nothing to do here */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialOnMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialOnMessage.java
new file mode 100644
index 0000000..21c60a5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialOnMessage.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.common.message.MessageAppender;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrAnnotatedEventDriver;
+
+/**
+ * Partial BINARY MessageAppender for @{@link OnMessage} annotated methods
+ */
+public class BinaryPartialOnMessage implements MessageAppender
+{
+ private final JsrAnnotatedEventDriver driver;
+ private boolean finished;
+
+ public BinaryPartialOnMessage(JsrAnnotatedEventDriver driver)
+ {
+ this.driver = driver;
+ this.finished = false;
+ }
+
+ @Override
+ public void appendMessage(ByteBuffer payload, boolean isLast) throws IOException
+ {
+ if (finished)
+ {
+ throw new IOException("Cannot append to finished buffer");
+ }
+ if (payload == null)
+ {
+ driver.onPartialBinaryMessage(BufferUtil.EMPTY_BUFFER,isLast);
+ }
+ else
+ {
+ driver.onPartialBinaryMessage(payload,isLast);
+ }
+ }
+
+ @Override
+ public void messageComplete()
+ {
+ finished = true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryWholeMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryWholeMessage.java
new file mode 100644
index 0000000..b1da55b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryWholeMessage.java
@@ -0,0 +1,67 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.Decoder.Binary;
+import javax.websocket.MessageHandler;
+import javax.websocket.MessageHandler.Whole;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage;
+import org.eclipse.jetty.websocket.jsr356.DecoderFactory;
+import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper;
+
+public class BinaryWholeMessage extends SimpleBinaryMessage
+{
+ private final MessageHandlerWrapper msgWrapper;
+ private final MessageHandler.Whole<Object> wholeHandler;
+
+ @SuppressWarnings("unchecked")
+ public BinaryWholeMessage(EventDriver onEvent, MessageHandlerWrapper wrapper)
+ {
+ super(onEvent);
+ this.msgWrapper = wrapper;
+ this.wholeHandler = (Whole<Object>)wrapper.getHandler();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void messageComplete()
+ {
+ super.finished = true;
+
+ byte data[] = out.toByteArray();
+
+ DecoderFactory.Wrapper decoder = msgWrapper.getDecoder();
+ Decoder.Binary<Object> binaryDecoder = (Binary<Object>)decoder.getDecoder();
+ try
+ {
+ Object obj = binaryDecoder.decode(BufferUtil.toBuffer(data));
+ wholeHandler.onMessage(obj);
+ }
+ catch (DecodeException e)
+ {
+ throw new WebSocketException("Unable to decode binary data",e);
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/SendHandlerWriteCallback.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/SendHandlerWriteCallback.java
new file mode 100644
index 0000000..b33f64c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/SendHandlerWriteCallback.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+
+import org.eclipse.jetty.websocket.api.WriteCallback;
+
+public class SendHandlerWriteCallback implements WriteCallback
+{
+ private final SendHandler sendHandler;
+
+ public SendHandlerWriteCallback(SendHandler sendHandler)
+ {
+ this.sendHandler = sendHandler;
+ }
+
+ @Override
+ public void writeFailed(Throwable x)
+ {
+ sendHandler.onResult(new SendResult(x));
+ }
+
+ @Override
+ public void writeSuccess()
+ {
+ sendHandler.onResult(new SendResult());
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialMessage.java
new file mode 100644
index 0000000..23934c4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialMessage.java
@@ -0,0 +1,59 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.websocket.MessageHandler;
+import javax.websocket.MessageHandler.Partial;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.common.message.MessageAppender;
+import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper;
+
+/**
+ * Partial TEXT MessageAppender for MessageHandler.Partial interface
+ */
+public class TextPartialMessage implements MessageAppender
+{
+ @SuppressWarnings("unused")
+ private final MessageHandlerWrapper msgWrapper;
+ private final MessageHandler.Partial<String> partialHandler;
+
+ @SuppressWarnings("unchecked")
+ public TextPartialMessage(MessageHandlerWrapper wrapper)
+ {
+ this.msgWrapper = wrapper;
+ this.partialHandler = (Partial<String>)wrapper.getHandler();
+ }
+
+ @Override
+ public void appendMessage(ByteBuffer payload, boolean isLast) throws IOException
+ {
+ // No decoders for Partial messages per JSR-356 (PFD1 spec)
+ partialHandler.onMessage(BufferUtil.toUTF8String(payload.slice()),isLast);
+ }
+
+ @Override
+ public void messageComplete()
+ {
+ /* nothing to do here */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialOnMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialOnMessage.java
new file mode 100644
index 0000000..12b4e7b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialOnMessage.java
@@ -0,0 +1,67 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.common.message.MessageAppender;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrAnnotatedEventDriver;
+
+/**
+ * Partial TEXT MessageAppender for @{@link OnMessage} annotated methods
+ */
+public class TextPartialOnMessage implements MessageAppender
+{
+ private final JsrAnnotatedEventDriver driver;
+ private boolean finished;
+
+ public TextPartialOnMessage(JsrAnnotatedEventDriver driver)
+ {
+ this.driver = driver;
+ this.finished = false;
+ }
+
+ @Override
+ public void appendMessage(ByteBuffer payload, boolean isLast) throws IOException
+ {
+ if (finished)
+ {
+ throw new IOException("Cannot append to finished buffer");
+ }
+ if (payload == null)
+ {
+ driver.onPartialTextMessage("",isLast);
+ }
+ else
+ {
+ String text = BufferUtil.toUTF8String(payload);
+ driver.onPartialTextMessage(text,isLast);
+ }
+ }
+
+ @Override
+ public void messageComplete()
+ {
+ finished = true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextWholeMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextWholeMessage.java
new file mode 100644
index 0000000..f98be92
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextWholeMessage.java
@@ -0,0 +1,63 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.MessageHandler;
+import javax.websocket.MessageHandler.Whole;
+
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.message.SimpleTextMessage;
+import org.eclipse.jetty.websocket.jsr356.DecoderFactory;
+import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper;
+
+public class TextWholeMessage extends SimpleTextMessage
+{
+ private final MessageHandlerWrapper msgWrapper;
+ private final MessageHandler.Whole<Object> wholeHandler;
+
+ @SuppressWarnings("unchecked")
+ public TextWholeMessage(EventDriver onEvent, MessageHandlerWrapper wrapper)
+ {
+ super(onEvent);
+ this.msgWrapper = wrapper;
+ this.wholeHandler = (Whole<Object>)wrapper.getHandler();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void messageComplete()
+ {
+ finished = true;
+
+ DecoderFactory.Wrapper decoder = msgWrapper.getDecoder();
+ Decoder.Text<Object> textDecoder = (Decoder.Text<Object>)decoder.getDecoder();
+ try
+ {
+ Object obj = textDecoder.decode(utf.toString());
+ wholeHandler.onMessage(obj);
+ }
+ catch (DecodeException e)
+ {
+ throw new WebSocketException("Unable to decode text data",e);
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadata.java
new file mode 100644
index 0000000..32b2706
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadata.java
@@ -0,0 +1,69 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+
+/**
+ * The immutable base metadata for a coder ({@link Decoder} or {@link Encoder}
+ *
+ * @param <T>
+ * the specific type of coder ({@link Decoder} or {@link Encoder}
+ */
+public abstract class CoderMetadata<T>
+{
+ /** The class for the Coder */
+ private final Class<? extends T> coderClass;
+ /** The Class that the Decoder declares it decodes */
+ private final Class<?> objType;
+ /** The Basic type of message the decoder handles */
+ private final MessageType messageType;
+ /** Flag indicating if Decoder is for streaming (or not) */
+ private final boolean streamed;
+
+ public CoderMetadata(Class<? extends T> coderClass, Class<?> objType, MessageType messageType, boolean streamed)
+ {
+ this.objType = objType;
+ this.coderClass = coderClass;
+ this.messageType = messageType;
+ this.streamed = streamed;
+ }
+
+ public Class<? extends T> getCoderClass()
+ {
+ return this.coderClass;
+ }
+
+ public MessageType getMessageType()
+ {
+ return messageType;
+ }
+
+ public Class<?> getObjectType()
+ {
+ return objType;
+ }
+
+ public boolean isStreamed()
+ {
+ return streamed;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadataSet.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadataSet.java
new file mode 100644
index 0000000..52ce2e5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadataSet.java
@@ -0,0 +1,259 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+
+/**
+ * An durable collection of {@link CoderMetadata}.
+ * <p>
+ * This is a write-only collection, and cannot be modified once initialized.
+ *
+ * @param <T>
+ * The type of coder ({@link Decoder} or {@link Encoder}
+ * @param <M>
+ * The metadata for the coder
+ */
+public abstract class CoderMetadataSet<T, M extends CoderMetadata<T>> implements Iterable<M>
+{
+ /**
+ * Collection of metadatas
+ */
+ private final List<M> metadatas;
+ /**
+ * Collection of declared Coder classes
+ */
+ private final List<Class<? extends T>> coders;
+ /**
+ * Mapping of supported Type to metadata list index
+ */
+ private final Map<Class<?>, Integer> typeMap;
+ /**
+ * Mapping of Coder class to list of supported metadata
+ */
+ private final Map<Class<? extends T>, List<Integer>> implMap;
+
+ protected CoderMetadataSet()
+ {
+ metadatas = new ArrayList<>();
+ coders = new ArrayList<>();
+ typeMap = new ConcurrentHashMap<>();
+ implMap = new ConcurrentHashMap<>();
+ }
+
+ public void add(Class<? extends T> coder)
+ {
+ List<M> metadatas = discover(coder);
+ trackMetadata(metadatas);
+ }
+
+ public List<M> addAll(Class<? extends T>[] coders)
+ {
+ List<M> metadatas = new ArrayList<>();
+
+ for (Class<? extends T> coder : coders)
+ {
+ metadatas.addAll(discover(coder));
+ }
+
+ trackMetadata(metadatas);
+ return metadatas;
+ }
+
+ public List<M> addAll(List<Class<? extends T>> coders)
+ {
+ List<M> metadatas = new ArrayList<>();
+
+ for (Class<? extends T> coder : coders)
+ {
+ metadatas.addAll(discover(coder));
+ }
+
+ trackMetadata(metadatas);
+ return metadatas;
+ }
+
+ /**
+ * Coder Specific discovery of Metadata for a specific coder.
+ *
+ * @param coder
+ * the coder to discover metadata in.
+ * @return the list of metadata discovered
+ * @throws InvalidWebSocketException
+ * if unable to discover some metadata. Sucha as: a duplicate {@link CoderMetadata#getObjectType()} encountered, , or if unable to find the
+ * concrete generic class reference for the coder, or if the provided coder is not valid per spec.
+ */
+ protected abstract List<M> discover(Class<? extends T> coder);
+
+ public Class<? extends T> getCoder(Class<?> type)
+ {
+ M metadata = getMetadataByType(type);
+ if (metadata == null)
+ {
+ return null;
+ }
+ return metadata.getCoderClass();
+ }
+
+ public List<Class<? extends T>> getList()
+ {
+ return coders;
+ }
+
+ public List<M> getMetadataByImplementation(Class<? extends T> clazz)
+ {
+ List<Integer> indexes = implMap.get(clazz);
+ if (indexes == null)
+ {
+ return null;
+ }
+ List<M> ret = new ArrayList<>();
+ for (Integer idx : indexes)
+ {
+ ret.add(metadatas.get(idx));
+ }
+ return ret;
+ }
+
+ public M getMetadataByType(Class<?> type)
+ {
+ Integer idx = typeMap.get(type);
+ if (idx == null)
+ {
+ // Quick lookup failed, try slower lookup via isAssignable instead
+ idx = getMetadataByAssignableType(type);
+ if (idx != null)
+ {
+ // add new entry map
+ typeMap.put(type,idx);
+ }
+ }
+
+ // If idx is STILL null, we've got no match
+ if (idx == null)
+ {
+ return null;
+ }
+ return metadatas.get(idx);
+ }
+
+ private Integer getMetadataByAssignableType(Class<?> type)
+ {
+ for (Map.Entry<Class<?>, Integer> entry : typeMap.entrySet())
+ {
+ if (entry.getKey().isAssignableFrom(type))
+ {
+ return entry.getValue();
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public Iterator<M> iterator()
+ {
+ return metadatas.iterator();
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append(this.getClass().getSimpleName());
+ builder.append("[metadatas=");
+ builder.append(metadatas.size());
+ builder.append(",coders=");
+ builder.append(coders.size());
+ builder.append("]");
+ return builder.toString();
+ }
+
+ protected void trackMetadata(List<M> metadatas)
+ {
+ for (M metadata : metadatas)
+ {
+ trackMetadata(metadata);
+ }
+ }
+
+ protected void trackMetadata(M metadata)
+ {
+ synchronized (metadatas)
+ {
+ // Validate
+ boolean duplicate = false;
+
+ // Is this metadata already declared?
+ if (metadatas.contains(metadata))
+ {
+ duplicate = true;
+ }
+
+ // Is this type already declared?
+ Class<?> type = metadata.getObjectType();
+ if (typeMap.containsKey(type))
+ {
+ duplicate = true;
+ }
+
+ if (duplicate)
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Duplicate decoder for type: ");
+ err.append(type);
+ err.append(" (class ").append(metadata.getCoderClass().getName());
+
+ // Get prior one
+ M dup = getMetadataByType(type);
+ err.append(" duplicates ");
+ err.append(dup.getCoderClass().getName());
+ err.append(")");
+ throw new IllegalStateException(err.toString());
+ }
+
+ // Track
+ Class<? extends T> coderClass = metadata.getCoderClass();
+ int newidx = metadatas.size();
+ metadatas.add(metadata);
+ coders.add(coderClass);
+ typeMap.put(type,newidx);
+
+ List<Integer> indexes = implMap.get(coderClass);
+ if (indexes == null)
+ {
+ indexes = new ArrayList<>();
+ }
+ if (indexes.contains(newidx))
+ {
+ // possible duplicate, TODO: how?
+ }
+ indexes.add(newidx);
+ implMap.put(coderClass,indexes);
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadata.java
new file mode 100644
index 0000000..3c01df0
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadata.java
@@ -0,0 +1,34 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+
+/**
+ * Immutable Metadata for a {@link Decoder}
+ */
+public class DecoderMetadata extends CoderMetadata<Decoder>
+{
+ public DecoderMetadata(Class<? extends Decoder> coderClass, Class<?> objType, MessageType messageType, boolean streamed)
+ {
+ super(coderClass,objType,messageType,streamed);
+ }
+}
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSet.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSet.java
new file mode 100644
index 0000000..0307b3c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSet.java
@@ -0,0 +1,92 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+
+public class DecoderMetadataSet extends CoderMetadataSet<Decoder, DecoderMetadata>
+{
+ @Override
+ protected List<DecoderMetadata> discover(Class<? extends Decoder> decoder)
+ {
+ List<DecoderMetadata> metadatas = new ArrayList<>();
+
+ if (Decoder.Binary.class.isAssignableFrom(decoder))
+ {
+ Class<?> objType = getDecoderType(decoder,Decoder.Binary.class);
+ metadatas.add(new DecoderMetadata(decoder,objType,MessageType.BINARY,false));
+ }
+ if (Decoder.BinaryStream.class.isAssignableFrom(decoder))
+ {
+ Class<?> objType = getDecoderType(decoder,Decoder.BinaryStream.class);
+ metadatas.add(new DecoderMetadata(decoder,objType,MessageType.BINARY,true));
+ }
+ if (Decoder.Text.class.isAssignableFrom(decoder))
+ {
+ Class<?> objType = getDecoderType(decoder,Decoder.Text.class);
+ metadatas.add(new DecoderMetadata(decoder,objType,MessageType.TEXT,false));
+ }
+ if (Decoder.TextStream.class.isAssignableFrom(decoder))
+ {
+ Class<?> objType = getDecoderType(decoder,Decoder.TextStream.class);
+ metadatas.add(new DecoderMetadata(decoder,objType,MessageType.TEXT,true));
+ }
+
+ if (!ReflectUtils.isDefaultConstructable(decoder))
+ {
+ throw new InvalidSignatureException("Decoder must have public, no-args constructor: " + decoder.getName());
+ }
+
+ if (metadatas.size() <= 0)
+ {
+ throw new InvalidSignatureException("Not a valid Decoder class: " + decoder.getName());
+ }
+
+ return metadatas;
+ }
+
+ private Class<?> getDecoderType(Class<? extends Decoder> decoder, Class<?> interfaceClass)
+ {
+ Class<?> decoderClass = ReflectUtils.findGenericClassFor(decoder,interfaceClass);
+ if (decoderClass == null)
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Invalid type declared for interface ");
+ err.append(interfaceClass.getName());
+ err.append(" on class ");
+ err.append(decoder);
+ throw new InvalidWebSocketException(err.toString());
+ }
+ return decoderClass;
+ }
+
+ protected final void register(Class<?> type, Class<? extends Decoder> decoder, MessageType msgType, boolean streamed)
+ {
+ DecoderMetadata metadata = new DecoderMetadata(decoder,type,msgType,streamed);
+ trackMetadata(metadata);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DuplicateCoderException.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DuplicateCoderException.java
new file mode 100644
index 0000000..9758d49
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DuplicateCoderException.java
@@ -0,0 +1,41 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+
+/**
+ * Thrown when a duplicate coder is encountered when attempting to identify a Endpoint's metadata ( {@link Decoder} or {@link Encoder})
+ */
+public class DuplicateCoderException extends InvalidWebSocketException
+{
+ private static final long serialVersionUID = -3049181444035417170L;
+
+ public DuplicateCoderException(String message)
+ {
+ super(message);
+ }
+
+ public DuplicateCoderException(String message, Throwable cause)
+ {
+ super(message,cause);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadata.java
new file mode 100644
index 0000000..8a43564
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadata.java
@@ -0,0 +1,34 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import javax.websocket.Encoder;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+
+/**
+ * Immutable Metadata for a {@link Encoder}
+ */
+public class EncoderMetadata extends CoderMetadata<Encoder>
+{
+ public EncoderMetadata(Class<? extends Encoder> coderClass, Class<?> objType, MessageType messageType, boolean streamed)
+ {
+ super(coderClass,objType,messageType,streamed);
+ }
+}
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSet.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSet.java
new file mode 100644
index 0000000..5f1b279
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSet.java
@@ -0,0 +1,92 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.Encoder;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+
+public class EncoderMetadataSet extends CoderMetadataSet<Encoder, EncoderMetadata>
+{
+ @Override
+ protected List<EncoderMetadata> discover(Class<? extends Encoder> encoder)
+ {
+ List<EncoderMetadata> metadatas = new ArrayList<>();
+
+ if (Encoder.Binary.class.isAssignableFrom(encoder))
+ {
+ Class<?> objType = getEncoderType(encoder,Encoder.Binary.class);
+ metadatas.add(new EncoderMetadata(encoder,objType,MessageType.BINARY,false));
+ }
+ if (Encoder.BinaryStream.class.isAssignableFrom(encoder))
+ {
+ Class<?> objType = getEncoderType(encoder,Encoder.BinaryStream.class);
+ metadatas.add(new EncoderMetadata(encoder,objType,MessageType.BINARY,true));
+ }
+ if (Encoder.Text.class.isAssignableFrom(encoder))
+ {
+ Class<?> objType = getEncoderType(encoder,Encoder.Text.class);
+ metadatas.add(new EncoderMetadata(encoder,objType,MessageType.TEXT,false));
+ }
+ if (Encoder.TextStream.class.isAssignableFrom(encoder))
+ {
+ Class<?> objType = getEncoderType(encoder,Encoder.TextStream.class);
+ metadatas.add(new EncoderMetadata(encoder,objType,MessageType.TEXT,true));
+ }
+
+ if (!ReflectUtils.isDefaultConstructable(encoder))
+ {
+ throw new InvalidSignatureException("Encoder must have public, no-args constructor: " + encoder.getName());
+ }
+
+ if (metadatas.size() <= 0)
+ {
+ throw new InvalidSignatureException("Not a valid Encoder class: " + encoder.getName() + " implements no " + Encoder.class.getName() + " interfaces");
+ }
+
+ return metadatas;
+ }
+
+ private Class<?> getEncoderType(Class<? extends Encoder> encoder, Class<?> interfaceClass)
+ {
+ Class<?> decoderClass = ReflectUtils.findGenericClassFor(encoder,interfaceClass);
+ if (decoderClass == null)
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Invalid type declared for interface ");
+ err.append(interfaceClass.getName());
+ err.append(" on class ");
+ err.append(encoder);
+ throw new InvalidWebSocketException(err.toString());
+ }
+ return decoderClass;
+ }
+
+ protected final void register(Class<?> type, Class<? extends Encoder> encoder, MessageType msgType, boolean streamed)
+ {
+ EncoderMetadata metadata = new EncoderMetadata(encoder,type,msgType,streamed);
+ trackMetadata(metadata);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EndpointMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EndpointMetadata.java
new file mode 100644
index 0000000..a6ba012
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EndpointMetadata.java
@@ -0,0 +1,28 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+public interface EndpointMetadata
+{
+ public DecoderMetadataSet getDecoders();
+
+ public EncoderMetadataSet getEncoders();
+
+ public Class<?> getEndpointClass();
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/MessageHandlerMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/MessageHandlerMetadata.java
new file mode 100644
index 0000000..e5cdc82
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/MessageHandlerMetadata.java
@@ -0,0 +1,71 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import javax.websocket.MessageHandler;
+
+/**
+ * An immutable metadata for a {@link MessageHandler}, representing a single interface on a message handling class.
+ * <p>
+ * A message handling class can contain more than 1 valid {@link MessageHandler} interface, this will result in multiple {@link MessageHandlerMetadata}
+ * instances, each tracking one of the {@link MessageHandler} interfaces declared.
+ */
+public class MessageHandlerMetadata
+{
+ /**
+ * The implemented MessageHandler class.
+ * <p>
+ * Commonly a end-user provided class, with 1 or more implemented {@link MessageHandler} interfaces
+ */
+ private final Class<? extends MessageHandler> handlerClass;
+ /**
+ * Indicator if this is a {@link MessageHandler.Partial} or {@link MessageHandler.Whole} interface.
+ * <p>
+ * True for MessageHandler.Partial, other wise its a MessageHandler.Whole
+ */
+ private final boolean isPartialSupported;
+ /**
+ * The class type that this specific interface's generic implements.
+ * <p>
+ * Or said another way, the first parameter type on this interface's onMessage() method.
+ */
+ private final Class<?> messageClass;
+
+ public MessageHandlerMetadata(Class<? extends MessageHandler> handlerClass, Class<?> messageClass, boolean partial)
+ {
+ this.handlerClass = handlerClass;
+ this.isPartialSupported = partial;
+ this.messageClass = messageClass;
+ }
+
+ public Class<? extends MessageHandler> getHandlerClass()
+ {
+ return handlerClass;
+ }
+
+ public Class<?> getMessageClass()
+ {
+ return messageClass;
+ }
+
+ public boolean isPartialSupported()
+ {
+ return isPartialSupported;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/utils/Primitives.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/utils/Primitives.java
new file mode 100644
index 0000000..f72be7a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/utils/Primitives.java
@@ -0,0 +1,77 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.utils;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class Primitives
+{
+ private static final Map<Class<?>, Class<?>> PRIMITIVE_CLASS_MAP;
+ private static final Map<Class<?>, Class<?>> CLASS_PRIMITIVE_MAP;
+
+ static
+ {
+ Map<Class<?>, Class<?>> primitives = new HashMap<>();
+
+ // Map of classes to primitive types
+ primitives.put(Boolean.class,Boolean.TYPE);
+ primitives.put(Byte.class,Byte.TYPE);
+ primitives.put(Character.class,Character.TYPE);
+ primitives.put(Double.class,Double.TYPE);
+ primitives.put(Float.class,Float.TYPE);
+ primitives.put(Integer.class,Integer.TYPE);
+ primitives.put(Long.class,Long.TYPE);
+ primitives.put(Short.class,Short.TYPE);
+ primitives.put(Void.class,Void.TYPE);
+
+ CLASS_PRIMITIVE_MAP = Collections.unmodifiableMap(primitives);
+
+ // Map of primitive types to classes
+ Map<Class<?>, Class<?>> types = new HashMap<>();
+ for (Map.Entry<Class<?>, Class<?>> classEntry : primitives.entrySet())
+ {
+ types.put(classEntry.getValue(),classEntry.getKey());
+ }
+
+ PRIMITIVE_CLASS_MAP = Collections.unmodifiableMap(types);
+ }
+
+ public static Class<?> getPrimitiveClass(Class<?> primitiveType)
+ {
+ return PRIMITIVE_CLASS_MAP.get(primitiveType);
+ }
+
+ public static Set<Class<?>> getPrimitiveClasses()
+ {
+ return CLASS_PRIMITIVE_MAP.keySet();
+ }
+
+ public static Set<Class<?>> getPrimitives()
+ {
+ return PRIMITIVE_CLASS_MAP.keySet();
+ }
+
+ public static Class<?> getPrimitiveType(Class<?> primitiveClass)
+ {
+ return CLASS_PRIMITIVE_MAP.get(primitiveClass);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/resources/META-INF/services/javax.websocket.ContainerProvider b/jetty-websocket/javax-websocket-client-impl/src/main/resources/META-INF/services/javax.websocket.ContainerProvider
new file mode 100644
index 0000000..e6dffe7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/resources/META-INF/services/javax.websocket.ContainerProvider
@@ -0,0 +1 @@
+org.eclipse.jetty.websocket.jsr356.JettyClientContainerProvider
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEchoClient.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEchoClient.java
new file mode 100644
index 0000000..56bdcd9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEchoClient.java
@@ -0,0 +1,60 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+@ClientEndpoint
+public class AnnotatedEchoClient
+{
+ private Session session = null;
+ public CloseReason close = null;
+ public MessageQueue messageQueue = new MessageQueue();
+
+ public void onClose(CloseReason close)
+ {
+ this.close = close;
+ }
+
+ @OnMessage
+ public void onMessage(String message)
+ {
+ this.messageQueue.offer(message);
+ }
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ public void sendText(String text) throws IOException
+ {
+ if (session != null)
+ {
+ session.getBasicRemote().sendText(text);
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEchoTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEchoTest.java
new file mode 100644
index 0000000..01d9940
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEchoTest.java
@@ -0,0 +1,89 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class AnnotatedEchoTest
+{
+ private static Server server;
+ private static EchoHandler handler;
+ private static URI serverUri;
+
+ @BeforeClass
+ public static void startServer() throws Exception
+ {
+ server = new Server();
+ ServerConnector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ handler = new EchoHandler();
+
+ ContextHandler context = new ContextHandler();
+ context.setContextPath("/");
+ context.setHandler(handler);
+ server.setHandler(context);
+
+ // Start Server
+ server.start();
+
+ String host = connector.getHost();
+ if (host == null)
+ {
+ host = "localhost";
+ }
+ int port = connector.getLocalPort();
+ serverUri = new URI(String.format("ws://%s:%d/",host,port));
+ }
+
+ @AfterClass
+ public static void stopServer()
+ {
+ try
+ {
+ server.stop();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace(System.err);
+ }
+ }
+
+ @Test
+ public void testEcho() throws Exception
+ {
+ WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+ AnnotatedEchoClient echoer = new AnnotatedEchoClient();
+ Session session = container.connectToServer(echoer,serverUri);
+ session.getBasicRemote().sendText("Echo");
+ echoer.messageQueue.awaitMessages(1,1000,TimeUnit.MILLISECONDS);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/ConfiguratorTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/ConfiguratorTest.java
new file mode 100644
index 0000000..a0f8fb3
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/ConfiguratorTest.java
@@ -0,0 +1,134 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.ClientEndpointConfig.Configurator;
+import javax.websocket.ContainerProvider;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests of {@link Configurator}
+ */
+public class ConfiguratorTest
+{
+ public class TrackingConfigurator extends ClientEndpointConfig.Configurator
+ {
+ public HandshakeResponse response;
+ public Map<String, List<String>> request;
+
+ @Override
+ public void afterResponse(HandshakeResponse hr)
+ {
+ this.response = hr;
+ }
+
+ @Override
+ public void beforeRequest(Map<String, List<String>> headers)
+ {
+ this.request = headers;
+ }
+ }
+
+ private static Server server;
+ private static EchoHandler handler;
+ private static URI serverUri;
+
+ @BeforeClass
+ public static void startServer() throws Exception
+ {
+ server = new Server();
+ ServerConnector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ handler = new EchoHandler();
+
+ ContextHandler context = new ContextHandler();
+ context.setContextPath("/");
+ context.setHandler(handler);
+ server.setHandler(context);
+
+ // Start Server
+ server.start();
+
+ String host = connector.getHost();
+ if (host == null)
+ {
+ host = "localhost";
+ }
+ int port = connector.getLocalPort();
+ serverUri = new URI(String.format("ws://%s:%d/",host,port));
+ }
+
+ @AfterClass
+ public static void stopServer()
+ {
+ try
+ {
+ server.stop();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace(System.err);
+ }
+ }
+
+ @Test
+ public void testEndpointHandshakeInfo() throws Exception
+ {
+ WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+ EndpointEchoClient echoer = new EndpointEchoClient();
+
+ // Build Config
+ ClientEndpointConfig.Builder cfgbldr = ClientEndpointConfig.Builder.create();
+ TrackingConfigurator configurator = new TrackingConfigurator();
+ cfgbldr.configurator(configurator);
+ ClientEndpointConfig config = cfgbldr.build();
+
+ // Connect
+ Session session = container.connectToServer(echoer,config,serverUri);
+
+ // Send Simple Message
+ session.getBasicRemote().sendText("Echo");
+
+ // Wait for echo
+ echoer.textCapture.messageQueue.awaitMessages(1,1000,TimeUnit.MILLISECONDS);
+
+ // Validate client side configurator use
+ Assert.assertThat("configurator.request",configurator.request,notNullValue());
+ Assert.assertThat("configurator.response",configurator.response,notNullValue());
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/DecoderFactoryTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/DecoderFactoryTest.java
new file mode 100644
index 0000000..e0b959b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/DecoderFactoryTest.java
@@ -0,0 +1,107 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+import java.util.Date;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.decoders.ByteArrayDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.ByteBufferDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.DateDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.IntegerDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.LongDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.PrimitiveDecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.decoders.StringDecoder;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.samples.Fruit;
+import org.eclipse.jetty.websocket.jsr356.samples.FruitDecoder;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DecoderFactoryTest
+{
+ private DecoderMetadataSet metadatas;
+ private DecoderFactory factory;
+
+ private void assertMetadataFor(Class<?> type, Class<? extends Decoder> expectedDecoderClass, MessageType expectedType)
+ {
+ DecoderMetadata metadata = factory.getMetadataFor(type);
+ Assert.assertEquals("metadata.coderClass",metadata.getCoderClass(),expectedDecoderClass);
+ Assert.assertThat("metadata.messageType",metadata.getMessageType(),is(expectedType));
+ Assert.assertEquals("metadata.objectType",metadata.getObjectType(),type);
+ }
+
+ @Before
+ public void initDecoderFactory()
+ {
+ DecoderFactory primitivesFactory = new DecoderFactory(PrimitiveDecoderMetadataSet.INSTANCE);
+ metadatas = new DecoderMetadataSet();
+ factory = new DecoderFactory(metadatas,primitivesFactory);
+ }
+
+ @Test
+ public void testGetMetadataForByteArray()
+ {
+ assertMetadataFor(byte[].class,ByteArrayDecoder.class,MessageType.BINARY);
+ }
+
+ @Test
+ public void testGetMetadataForByteBuffer()
+ {
+ assertMetadataFor(ByteBuffer.class,ByteBufferDecoder.class,MessageType.BINARY);
+ }
+
+ @Test
+ public void testGetMetadataForDate()
+ {
+ metadatas.add(DateDecoder.class);
+ assertMetadataFor(Date.class,DateDecoder.class,MessageType.TEXT);
+ }
+
+ @Test
+ public void testGetMetadataForFruit()
+ {
+ metadatas.add(FruitDecoder.class);
+ assertMetadataFor(Fruit.class,FruitDecoder.class,MessageType.TEXT);
+ }
+
+ @Test
+ public void testGetMetadataForInteger()
+ {
+ assertMetadataFor(Integer.TYPE,IntegerDecoder.class,MessageType.TEXT);
+ }
+
+ @Test
+ public void testGetMetadataForLong()
+ {
+ assertMetadataFor(Long.TYPE,LongDecoder.class,MessageType.TEXT);
+ }
+
+ @Test
+ public void testGetStringDecoder()
+ {
+ assertMetadataFor(String.class,StringDecoder.class,MessageType.TEXT);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EchoCaptureHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EchoCaptureHandler.java
new file mode 100644
index 0000000..9d414f0
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EchoCaptureHandler.java
@@ -0,0 +1,32 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import javax.websocket.MessageHandler;
+
+public class EchoCaptureHandler implements MessageHandler.Whole<String>
+{
+ public MessageQueue messageQueue = new MessageQueue();
+
+ @Override
+ public void onMessage(String message)
+ {
+ messageQueue.offer(message);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EchoHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EchoHandler.java
new file mode 100644
index 0000000..3afac77
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EchoHandler.java
@@ -0,0 +1,42 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import org.eclipse.jetty.websocket.server.WebSocketHandler;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+public class EchoHandler extends WebSocketHandler implements WebSocketCreator
+{
+ public JettyEchoSocket socket = new JettyEchoSocket();
+
+ @Override
+ public void configure(WebSocketServletFactory factory)
+ {
+ factory.setCreator(this);
+ }
+
+ @Override
+ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+ {
+ return socket;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EncoderFactoryTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EncoderFactoryTest.java
new file mode 100644
index 0000000..858bc94
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EncoderFactoryTest.java
@@ -0,0 +1,86 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.is;
+
+import javax.websocket.Encoder;
+
+import org.eclipse.jetty.websocket.jsr356.encoders.IntegerEncoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.LongEncoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.PrimitiveEncoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.samples.Fruit;
+import org.eclipse.jetty.websocket.jsr356.samples.FruitBinaryEncoder;
+import org.eclipse.jetty.websocket.jsr356.samples.FruitTextEncoder;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests against the Encoders class
+ */
+public class EncoderFactoryTest
+{
+ private EncoderMetadataSet metadatas;
+ private EncoderFactory factory;
+
+ private void assertMetadataFor(Class<?> type, Class<? extends Encoder> expectedEncoderClass, MessageType expectedType)
+ {
+ EncoderMetadata metadata = factory.getMetadataFor(type);
+ Assert.assertEquals("metadata.coderClass",metadata.getCoderClass(),expectedEncoderClass);
+ Assert.assertThat("metadata.messageType",metadata.getMessageType(),is(expectedType));
+ Assert.assertEquals("metadata.objectType",metadata.getObjectType(),type);
+ }
+
+ @Before
+ public void initEncoderFactory()
+ {
+ EncoderFactory primitivesFactory = new EncoderFactory(PrimitiveEncoderMetadataSet.INSTANCE);
+ metadatas = new EncoderMetadataSet();
+ factory = new EncoderFactory(metadatas,primitivesFactory);
+ }
+
+ @Test
+ public void testGetMetadataForFruitBinary()
+ {
+ metadatas.add(FruitBinaryEncoder.class);
+ assertMetadataFor(Fruit.class,FruitBinaryEncoder.class,MessageType.BINARY);
+ }
+
+ @Test
+ public void testGetMetadataForFruitText()
+ {
+ metadatas.add(FruitTextEncoder.class);
+ assertMetadataFor(Fruit.class,FruitTextEncoder.class,MessageType.TEXT);
+ }
+
+ @Test
+ public void testGetMetadataForInteger()
+ {
+ assertMetadataFor(Integer.TYPE,IntegerEncoder.class,MessageType.TEXT);
+ }
+
+ @Test
+ public void testGetMetadataForLong()
+ {
+ assertMetadataFor(Long.TYPE,LongEncoder.class,MessageType.TEXT);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EndpointEchoClient.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EndpointEchoClient.java
new file mode 100644
index 0000000..6b8008e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EndpointEchoClient.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.io.IOException;
+
+import javax.websocket.CloseReason;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Assert;
+
+/**
+ * Basic Echo Client from extended Endpoint
+ */
+public class EndpointEchoClient extends Endpoint
+{
+ private static final Logger LOG = Log.getLogger(EndpointEchoClient.class);
+ private Session session = null;
+ private CloseReason close = null;
+ public EchoCaptureHandler textCapture = new EchoCaptureHandler();
+
+ public CloseReason getClose()
+ {
+ return close;
+ }
+
+ @Override
+ public void onOpen(Session session, EndpointConfig config)
+ {
+ LOG.debug("onOpen({}, {})",session,config);
+ this.session = session;
+ Assert.assertThat("Session is required",session,notNullValue());
+ Assert.assertThat("EndpointConfig is required",config,notNullValue());
+ this.session.addMessageHandler(textCapture);
+ }
+
+ public void sendText(String text) throws IOException
+ {
+ if (session != null)
+ {
+ session.getBasicRemote().sendText(text);
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EndpointEchoTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EndpointEchoTest.java
new file mode 100644
index 0000000..53774ee
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EndpointEchoTest.java
@@ -0,0 +1,140 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.instanceOf;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.samples.EchoStringEndpoint;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class EndpointEchoTest
+{
+ private static final Logger LOG = Log.getLogger(EndpointEchoTest.class);
+ private static Server server;
+ private static EchoHandler handler;
+ private static URI serverUri;
+
+ @BeforeClass
+ public static void startServer() throws Exception
+ {
+ server = new Server();
+ ServerConnector connector = new ServerConnector(server);
+ server.addConnector(connector);
+
+ handler = new EchoHandler();
+
+ ContextHandler context = new ContextHandler();
+ context.setContextPath("/");
+ context.setHandler(handler);
+ server.setHandler(context);
+
+ // Start Server
+ server.start();
+
+ String host = connector.getHost();
+ if (host == null)
+ {
+ host = "localhost";
+ }
+ int port = connector.getLocalPort();
+ serverUri = new URI(String.format("ws://%s:%d/",host,port));
+ }
+
+ @AfterClass
+ public static void stopServer()
+ {
+ try
+ {
+ server.stop();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace(System.err);
+ }
+ }
+
+ @Test
+ public void testBasicEchoInstance() throws Exception
+ {
+ WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+ EndpointEchoClient echoer = new EndpointEchoClient();
+ Assert.assertThat(echoer,instanceOf(javax.websocket.Endpoint.class));
+ // Issue connect using instance of class that extends Endpoint
+ Session session = container.connectToServer(echoer,serverUri);
+ LOG.debug("Client Connected: {}",session);
+ session.getBasicRemote().sendText("Echo");
+ LOG.debug("Client Message Sent");
+ echoer.textCapture.messageQueue.awaitMessages(1,1000,TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ public void testBasicEchoClassref() throws Exception
+ {
+ WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+ // Issue connect using class reference (class extends Endpoint)
+ Session session = container.connectToServer(EndpointEchoClient.class,serverUri);
+ LOG.debug("Client Connected: {}",session);
+ session.getBasicRemote().sendText("Echo");
+ LOG.debug("Client Message Sent");
+ // TODO: figure out echo verification.
+ // echoer.textCapture.messageQueue.awaitMessages(1,1000,TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ public void testAbstractEchoInstance() throws Exception
+ {
+ WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+ EchoStringEndpoint echoer = new EchoStringEndpoint();
+ Assert.assertThat(echoer,instanceOf(javax.websocket.Endpoint.class));
+ // Issue connect using instance of class that extends abstract that extends Endpoint
+ Session session = container.connectToServer(echoer,serverUri);
+ LOG.debug("Client Connected: {}",session);
+ session.getBasicRemote().sendText("Echo");
+ LOG.debug("Client Message Sent");
+ echoer.messageQueue.awaitMessages(1,1000,TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ public void testAbstractEchoClassref() throws Exception
+ {
+ WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+ // Issue connect using class reference (class that extends abstract that extends Endpoint)
+ Session session = container.connectToServer(EchoStringEndpoint.class,serverUri);
+ LOG.debug("Client Connected: {}",session);
+ session.getBasicRemote().sendText("Echo");
+ LOG.debug("Client Message Sent");
+ // TODO: figure out echo verification.
+ // echoer.messageQueue.awaitMessages(1,1000,TimeUnit.MILLISECONDS);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JettyEchoSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JettyEchoSocket.java
new file mode 100644
index 0000000..2eb5247
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JettyEchoSocket.java
@@ -0,0 +1,50 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+
+/**
+ * Jetty Echo Socket. using Jetty techniques.
+ */
+public class JettyEchoSocket extends WebSocketAdapter
+{
+ private static final Logger LOG = Log.getLogger(JettyEchoSocket.class);
+
+ @Override
+ public void onWebSocketBinary(byte[] payload, int offset, int len)
+ {
+ getRemote().sendBytes(BufferUtil.toBuffer(payload,offset,len),null);
+ }
+
+ @Override
+ public void onWebSocketError(Throwable cause)
+ {
+ LOG.warn(cause);
+ }
+
+ @Override
+ public void onWebSocketText(String message)
+ {
+ getRemote().sendString(message,null);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JsrSessionTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JsrSessionTest.java
new file mode 100644
index 0000000..a39e998
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JsrSessionTest.java
@@ -0,0 +1,116 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.instanceOf;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.DeploymentException;
+import javax.websocket.MessageHandler;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
+import org.eclipse.jetty.websocket.jsr356.client.SimpleEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEndpointEventDriver;
+import org.eclipse.jetty.websocket.jsr356.handlers.ByteArrayWholeHandler;
+import org.eclipse.jetty.websocket.jsr356.handlers.ByteBufferPartialHandler;
+import org.eclipse.jetty.websocket.jsr356.handlers.LongMessageHandler;
+import org.eclipse.jetty.websocket.jsr356.handlers.StringWholeHandler;
+import org.eclipse.jetty.websocket.jsr356.samples.DummyConnection;
+import org.eclipse.jetty.websocket.jsr356.samples.DummyEndpoint;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class JsrSessionTest
+{
+ private ClientContainer container;
+ private JsrSession session;
+
+ @Before
+ public void initSession()
+ {
+ container = new ClientContainer();
+ String id = JsrSessionTest.class.getSimpleName();
+ URI requestURI = URI.create("ws://localhost/" + id);
+ WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+ ClientEndpointConfig config = new EmptyClientEndpointConfig();
+ DummyEndpoint websocket = new DummyEndpoint();
+ SimpleEndpointMetadata metadata = new SimpleEndpointMetadata(websocket.getClass());
+ // Executor executor = null;
+
+ EndpointInstance ei = new EndpointInstance(websocket,config,metadata);
+
+ EventDriver driver = new JsrEndpointEventDriver(policy,ei);
+ DummyConnection connection = new DummyConnection();
+ session = new JsrSession(requestURI,driver,connection,container,id);
+ }
+
+ @Test
+ public void testMessageHandlerBinary() throws DeploymentException
+ {
+ session.addMessageHandler(new ByteBufferPartialHandler());
+ MessageHandlerWrapper wrapper = session.getMessageHandlerWrapper(MessageType.BINARY);
+ Assert.assertThat("Binary Handler",wrapper.getHandler(),instanceOf(ByteBufferPartialHandler.class));
+ Assert.assertEquals("Message Class",wrapper.getMetadata().getMessageClass(),ByteBuffer.class);
+ }
+
+ @Test
+ public void testMessageHandlerBoth() throws DeploymentException
+ {
+ session.addMessageHandler(new StringWholeHandler());
+ session.addMessageHandler(new ByteArrayWholeHandler());
+ MessageHandlerWrapper wrapper = session.getMessageHandlerWrapper(MessageType.TEXT);
+ Assert.assertThat("Text Handler",wrapper.getHandler(),instanceOf(StringWholeHandler.class));
+ Assert.assertEquals("Message Class",wrapper.getMetadata().getMessageClass(),String.class);
+ wrapper = session.getMessageHandlerWrapper(MessageType.BINARY);
+ Assert.assertThat("Binary Handler",wrapper.getHandler(),instanceOf(ByteArrayWholeHandler.class));
+ Assert.assertEquals("Message Class",wrapper.getMetadata().getMessageClass(),byte[].class);
+ }
+
+ @Test
+ public void testMessageHandlerReplaceTextHandler() throws DeploymentException
+ {
+ MessageHandler oldText = new StringWholeHandler();
+ session.addMessageHandler(oldText); // add a TEXT handler
+ session.addMessageHandler(new ByteArrayWholeHandler()); // add BINARY handler
+ session.removeMessageHandler(oldText); // remove original TEXT handler
+ session.addMessageHandler(new LongMessageHandler()); // add new TEXT handler
+ MessageHandlerWrapper wrapper = session.getMessageHandlerWrapper(MessageType.BINARY);
+ Assert.assertThat("Binary Handler",wrapper.getHandler(),instanceOf(ByteArrayWholeHandler.class));
+ Assert.assertEquals("Message Class",wrapper.getMetadata().getMessageClass(),byte[].class);
+ wrapper = session.getMessageHandlerWrapper(MessageType.TEXT);
+ Assert.assertThat("Text Handler",wrapper.getHandler(),instanceOf(LongMessageHandler.class));
+ Assert.assertEquals("Message Class",wrapper.getMetadata().getMessageClass(),Long.class);
+ }
+
+ @Test
+ public void testMessageHandlerText() throws DeploymentException
+ {
+ session.addMessageHandler(new StringWholeHandler());
+ MessageHandlerWrapper wrapper = session.getMessageHandlerWrapper(MessageType.TEXT);
+ Assert.assertThat("Text Handler",wrapper.getHandler(),instanceOf(StringWholeHandler.class));
+ Assert.assertEquals("Message Class",wrapper.getMetadata().getMessageClass(),String.class);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactoryTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactoryTest.java
new file mode 100644
index 0000000..7843d69
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactoryTest.java
@@ -0,0 +1,76 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.is;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+import javax.websocket.DeploymentException;
+
+import org.eclipse.jetty.websocket.jsr356.decoders.PrimitiveDecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.handlers.ByteArrayPartialHandler;
+import org.eclipse.jetty.websocket.jsr356.handlers.StringPartialHandler;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.metadata.MessageHandlerMetadata;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MessageHandlerFactoryTest
+{
+ private MessageHandlerFactory factory;
+ private DecoderMetadataSet metadatas;
+ private DecoderFactory decoders;
+
+ @Before
+ public void init() throws DeploymentException
+ {
+ DecoderFactory primitivesFactory = new DecoderFactory(PrimitiveDecoderMetadataSet.INSTANCE);
+ metadatas = new DecoderMetadataSet();
+ decoders = new DecoderFactory(metadatas,primitivesFactory);
+ factory = new MessageHandlerFactory();
+ }
+
+ @Test
+ public void testByteArrayPartial() throws DeploymentException
+ {
+ List<MessageHandlerMetadata> metadatas = factory.getMetadata(ByteArrayPartialHandler.class);
+ Assert.assertThat("Metadata.list.size",metadatas.size(),is(1));
+
+ MessageHandlerMetadata handlerMetadata = metadatas.get(0);
+ DecoderMetadata decoderMetadata = decoders.getMetadataFor(handlerMetadata.getMessageClass());
+ Assert.assertThat("Message Type",decoderMetadata.getMessageType(),is(MessageType.BINARY));
+ Assert.assertThat("Message Class",handlerMetadata.getMessageClass(),is((Type)byte[].class));
+ }
+
+ @Test
+ public void testStringPartial() throws DeploymentException
+ {
+ List<MessageHandlerMetadata> metadatas = factory.getMetadata(StringPartialHandler.class);
+ Assert.assertThat("Metadata.list.size",metadatas.size(),is(1));
+
+ MessageHandlerMetadata handlerMetadata = metadatas.get(0);
+ DecoderMetadata decoderMetadata = decoders.getMetadataFor(handlerMetadata.getMessageClass());
+ Assert.assertThat("Message Type",decoderMetadata.getMessageType(),is(MessageType.TEXT));
+ Assert.assertThat("Message Class",handlerMetadata.getMessageClass(),is((Type)String.class));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/MessageQueue.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/MessageQueue.java
new file mode 100644
index 0000000..b1dd649
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/MessageQueue.java
@@ -0,0 +1,56 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class MessageQueue extends BlockingArrayQueue<String>
+{
+ private static final Logger LOG = Log.getLogger(MessageQueue.class);
+
+ public void awaitMessages(int expectedMessageCount, int timeoutDuration, TimeUnit timeoutUnit) throws TimeoutException
+ {
+ long msDur = TimeUnit.MILLISECONDS.convert(timeoutDuration,timeoutUnit);
+ long now = System.currentTimeMillis();
+ long expireOn = now + msDur;
+ LOG.debug("Await Message.. Now: {} - expireOn: {} ({} ms)",now,expireOn,msDur);
+
+ while (this.size() < expectedMessageCount)
+ {
+ try
+ {
+ TimeUnit.MILLISECONDS.sleep(20);
+ }
+ catch (InterruptedException gnore)
+ {
+ /* ignore */
+ }
+ if (!LOG.isDebugEnabled() && (System.currentTimeMillis() > expireOn))
+ {
+ throw new TimeoutException(String.format("Timeout reading all %d expected messages. (managed to only read %d messages)",expectedMessageCount,
+ this.size()));
+ }
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/annotations/DateTextSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/annotations/DateTextSocket.java
new file mode 100644
index 0000000..a07b2dd
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/annotations/DateTextSocket.java
@@ -0,0 +1,57 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.decoders.DateDecoder;
+
+@ClientEndpoint(decoders =
+{ DateDecoder.class })
+public class DateTextSocket
+{
+ private Session session;
+
+ @OnMessage
+ public void onMessage(Date d) throws IOException
+ {
+ if (d == null)
+ {
+ session.getAsyncRemote().sendText("Error: Date is null");
+ }
+ else
+ {
+ String msg = SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT).format(d);
+ session.getAsyncRemote().sendText(msg);
+ }
+ }
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoderTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoderTest.java
new file mode 100644
index 0000000..de0bf3b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoderTest.java
@@ -0,0 +1,57 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import static org.hamcrest.Matchers.is;
+
+import java.lang.reflect.Method;
+import java.util.Date;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.decoders.DateDecoder;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JsrParamIdDecoderTest
+{
+ private JsrCallable getOnMessageCallableFrom(Class<?> clazz, String methodName)
+ {
+ for (Method method : clazz.getMethods())
+ {
+ if (method.getName().equals(methodName))
+ {
+ return new OnMessageCallable(clazz,method);
+ }
+ }
+ return null;
+ }
+
+ @Test
+ public void testMatchDateDecoder()
+ {
+ DecoderMetadata metadata = new DecoderMetadata(DateDecoder.class,Date.class,MessageType.TEXT,false);
+ JsrParamIdDecoder paramId = new JsrParamIdDecoder(metadata);
+
+ JsrCallable callable = getOnMessageCallableFrom(DateTextSocket.class,"onMessage");
+ Param param = new Param(0,Date.class,null);
+
+ Assert.assertThat("Match for Decoder",paramId.process(param,callable),is(true));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/BadDualDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/BadDualDecoder.java
new file mode 100644
index 0000000..6e3520a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/BadDualDecoder.java
@@ -0,0 +1,122 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.jsr356.samples.Fruit;
+import org.eclipse.jetty.websocket.jsr356.samples.FruitBinaryEncoder;
+
+/**
+ * Intentionally bad example of attempting to decode the same object to different message formats.
+ */
+public class BadDualDecoder implements Decoder.Text<Fruit>, Decoder.Binary<Fruit>
+{
+ @Override
+ public Fruit decode(ByteBuffer bytes) throws DecodeException
+ {
+ try
+ {
+ int id = bytes.get(bytes.position());
+ if (id != FruitBinaryEncoder.FRUIT_ID_BYTE)
+ {
+ // not a binary fruit object
+ throw new DecodeException(bytes,"Not an encoded Binary Fruit object");
+ }
+
+ Fruit fruit = new Fruit();
+ fruit.name = getUTF8String(bytes);
+ fruit.color = getUTF8String(bytes);
+ return fruit;
+ }
+ catch (BufferUnderflowException e)
+ {
+ throw new DecodeException(bytes,"Unable to read Fruit from binary message",e);
+ }
+ }
+
+ @Override
+ public Fruit decode(String s) throws DecodeException
+ {
+ Pattern pat = Pattern.compile("([^|]*)|([^|]*)");
+ Matcher mat = pat.matcher(s);
+ if (!mat.find())
+ {
+ throw new DecodeException(s,"Unable to find Fruit reference encoded in text message");
+ }
+
+ Fruit fruit = new Fruit();
+ fruit.name = mat.group(1);
+ fruit.color = mat.group(2);
+
+ return fruit;
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ private String getUTF8String(ByteBuffer buf)
+ {
+ int strLen = buf.getInt();
+ ByteBuffer slice = buf.slice();
+ slice.limit(slice.position() + strLen);
+ String str = BufferUtil.toUTF8String(slice);
+ buf.position(buf.position() + strLen);
+ return str;
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+
+ @Override
+ public boolean willDecode(ByteBuffer bytes)
+ {
+ if (bytes == null)
+ {
+ return false;
+ }
+ int id = bytes.get(bytes.position());
+ return (id != FruitBinaryEncoder.FRUIT_ID_BYTE);
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ if (s == null)
+ {
+ return false;
+ }
+
+ Pattern pat = Pattern.compile("([^|]*)|([^|]*)");
+ Matcher mat = pat.matcher(s);
+ return (mat.find());
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/DateDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/DateDecoder.java
new file mode 100644
index 0000000..8191aaf
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/DateDecoder.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Decode Date
+ */
+public class DateDecoder implements Decoder.Text<Date>
+{
+ @Override
+ public Date decode(String s) throws DecodeException
+ {
+ try
+ {
+ return new SimpleDateFormat("yyyy.MM.dd").parse(s);
+ }
+ catch (ParseException e)
+ {
+ throw new DecodeException(s,e.getMessage(),e);
+ }
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ return true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/DateTimeDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/DateTimeDecoder.java
new file mode 100644
index 0000000..f901983
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/DateTimeDecoder.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Decode Date and Time
+ */
+public class DateTimeDecoder implements Decoder.Text<Date>
+{
+ @Override
+ public Date decode(String s) throws DecodeException
+ {
+ try
+ {
+ return new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z").parse(s);
+ }
+ catch (ParseException e)
+ {
+ throw new DecodeException(s,e.getMessage(),e);
+ }
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ return true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/IntegerDecoderTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/IntegerDecoderTest.java
new file mode 100644
index 0000000..1db8dd6
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/IntegerDecoderTest.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import static org.hamcrest.Matchers.is;
+
+import javax.websocket.DecodeException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class IntegerDecoderTest
+{
+ @Test
+ public void testDecode() throws DecodeException
+ {
+ IntegerDecoder decoder = new IntegerDecoder();
+ Integer val = decoder.decode("123");
+ Assert.assertThat("Decoded value",val,is(123));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/PrimitiveDecoderMetadataSetTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/PrimitiveDecoderMetadataSetTest.java
new file mode 100644
index 0000000..3b52810
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/PrimitiveDecoderMetadataSetTest.java
@@ -0,0 +1,54 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PrimitiveDecoderMetadataSetTest
+{
+ private void assertClassEquals(String msg, Class<?> actual, Class<?> expected)
+ {
+ Assert.assertThat(msg,actual.getName(),is(expected.getName()));
+ }
+
+ private void assertDecoderType(Class<? extends Decoder> expectedDecoder, MessageType expectedMsgType, Class<?> type)
+ {
+ PrimitiveDecoderMetadataSet primitives = new PrimitiveDecoderMetadataSet();
+ DecoderMetadata metadata = primitives.getMetadataByType(type);
+ String prefix = String.format("Metadata By Type [%s]",type.getName());
+ Assert.assertThat(prefix,metadata,notNullValue());
+
+ assertClassEquals(prefix + ".coderClass",metadata.getCoderClass(),expectedDecoder);
+ Assert.assertThat(prefix + ".messageType",metadata.getMessageType(),is(expectedMsgType));
+ }
+
+ @Test
+ public void testGetByteArray()
+ {
+ assertDecoderType(ByteArrayDecoder.class,MessageType.BINARY,byte[].class);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/TimeDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/TimeDecoder.java
new file mode 100644
index 0000000..1f3a28e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/TimeDecoder.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Decode Time
+ */
+public class TimeDecoder implements Decoder.Text<Date>
+{
+ @Override
+ public Date decode(String s) throws DecodeException
+ {
+ try
+ {
+ return new SimpleDateFormat("HH:mm:ss z").parse(s);
+ }
+ catch (ParseException e)
+ {
+ throw new DecodeException(s,e.getMessage(),e);
+ }
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ return true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/ValidDualDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/ValidDualDecoder.java
new file mode 100644
index 0000000..1454d57
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/ValidDualDecoder.java
@@ -0,0 +1,65 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Example of a valid decoder impl declaring 2 decoders.
+ */
+public class ValidDualDecoder implements Decoder.Text<Integer>, Decoder.Binary<Long>
+{
+ @Override
+ public Long decode(ByteBuffer bytes) throws DecodeException
+ {
+ return bytes.getLong();
+ }
+
+ @Override
+ public Integer decode(String s) throws DecodeException
+ {
+ return Integer.parseInt(s);
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+
+ @Override
+ public boolean willDecode(ByteBuffer bytes)
+ {
+ return true;
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ return true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/demo/ExampleClient.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/demo/ExampleClient.java
new file mode 100644
index 0000000..39f7e2f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/demo/ExampleClient.java
@@ -0,0 +1,57 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.demo;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.DeploymentException;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+public class ExampleClient
+{
+ public static void main(String[] args)
+ {
+ try
+ {
+ new ExampleClient().run();
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace();
+ }
+ }
+
+ private void run() throws DeploymentException, IOException, URISyntaxException, InterruptedException
+ {
+ WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+ ExampleSocket socket = new ExampleSocket();
+ URI uri = new URI("ws://echo.websocket.org/");
+ Session session = container.connectToServer(socket,uri);
+ socket.writeMessage("Hello");
+ socket.messageLatch.await(1,TimeUnit.SECONDS); // give remote 1 second to respond
+ session.close();
+ socket.closeLatch.await(1,TimeUnit.SECONDS); // give remote 1 second to acknowledge response
+ System.exit(0);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/demo/ExampleSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/demo/ExampleSocket.java
new file mode 100644
index 0000000..c4d9cd4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/demo/ExampleSocket.java
@@ -0,0 +1,71 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.demo;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+@ClientEndpoint
+public class ExampleSocket
+{
+ private Session session;
+ public CountDownLatch messageLatch = new CountDownLatch(1);
+ public CountDownLatch closeLatch = new CountDownLatch(1);
+
+ @OnClose
+ public void onClose(CloseReason close)
+ {
+ System.out.printf("Closed: %d, \"%s\"%n",close.getCloseCode().getCode(),close.getReasonPhrase());
+ closeLatch.countDown();
+ }
+
+ @OnMessage
+ public void onMessage(String message)
+ {
+ System.out.printf("Received: \"%s\"%n",message);
+ messageLatch.countDown();
+ }
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ System.out.printf("Opened%n");
+ this.session = session;
+ }
+
+ public void writeMessage(String message)
+ {
+ System.out.printf("Writing: \"%s\"%n",message);
+ try
+ {
+ session.getBasicRemote().sendText(message);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/BadDualEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/BadDualEncoder.java
new file mode 100644
index 0000000..81149ab
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/BadDualEncoder.java
@@ -0,0 +1,54 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Intentionally bad example of attempting to encode the same object for different message types.
+ */
+public class BadDualEncoder implements Encoder.Text<Integer>, Encoder.TextStream<Integer>
+{
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public String encode(Integer object) throws EncodeException
+ {
+ return Integer.toString(object);
+ }
+
+ @Override
+ public void encode(Integer object, Writer writer) throws EncodeException, IOException
+ {
+ writer.write(Integer.toString(object));
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DateEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DateEncoder.java
new file mode 100644
index 0000000..cc7b664
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DateEncoder.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Encode Date
+ */
+public class DateEncoder implements Encoder.Text<Date>
+{
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public String encode(Date object) throws EncodeException
+ {
+ return new SimpleDateFormat("yyyy.MM.dd").format(object);
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DateTimeEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DateTimeEncoder.java
new file mode 100644
index 0000000..4a684a8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DateTimeEncoder.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Encode Date
+ */
+public class DateTimeEncoder implements Encoder.Text<Date>
+{
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public String encode(Date object) throws EncodeException
+ {
+ return new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z").format(object);
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DualEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DualEncoder.java
new file mode 100644
index 0000000..7475020
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DualEncoder.java
@@ -0,0 +1,58 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.samples.Fruit;
+
+/**
+ * Intentionally bad example of attempting to decode the same object to different message formats.
+ */
+public class DualEncoder implements Encoder.Text<Fruit>, Encoder.TextStream<Fruit>
+{
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public String encode(Fruit fruit) throws EncodeException
+ {
+ return String.format("%s|%s",fruit.name,fruit.color);
+ }
+
+ @Override
+ public void encode(Fruit fruit, Writer writer) throws EncodeException, IOException
+ {
+ writer.write(fruit.name);
+ writer.write('|');
+ writer.write(fruit.color);
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/TimeEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/TimeEncoder.java
new file mode 100644
index 0000000..fc316da
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/TimeEncoder.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Encode Time
+ */
+public class TimeEncoder implements Encoder.Text<Date>
+{
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public String encode(Date object) throws EncodeException
+ {
+ return new SimpleDateFormat("HH:mm:ss z").format(object);
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/ValidDualEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/ValidDualEncoder.java
new file mode 100644
index 0000000..7df3678
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/ValidDualEncoder.java
@@ -0,0 +1,64 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Example of a valid encoder impl declaring 2 encoders.
+ */
+public class ValidDualEncoder implements Encoder.Text<Integer>, Encoder.BinaryStream<Long>
+{
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public String encode(Integer object) throws EncodeException
+ {
+ return Integer.toString(object);
+ }
+
+ @Override
+ public void encode(Long object, OutputStream os) throws EncodeException, IOException
+ {
+ byte b[] = new byte[8];
+ long v = object;
+ b[0] = (byte)(v >>> 56);
+ b[1] = (byte)(v >>> 48);
+ b[2] = (byte)(v >>> 40);
+ b[3] = (byte)(v >>> 32);
+ b[4] = (byte)(v >>> 24);
+ b[5] = (byte)(v >>> 16);
+ b[6] = (byte)(v >>> 8);
+ b[7] = (byte)(v >>> 0);
+ os.write(b,0,8);
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_GoodSignaturesTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_GoodSignaturesTest.java
new file mode 100644
index 0000000..e25cf3a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_GoodSignaturesTest.java
@@ -0,0 +1,169 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.CloseReason;
+import javax.websocket.PongMessage;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.ClientContainer;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrCallable;
+import org.eclipse.jetty.websocket.jsr356.client.AnnotatedClientEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicBinaryMessageByteBufferSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorSessionThrowableSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorThrowableSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorThrowableSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicInputStreamSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicInputStreamWithThrowableSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicOpenSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicOpenSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicPongMessageSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicTextMessageStringSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseReasonSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseSessionReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseSocket;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test {@link AnnotatedEndpointScanner} against various valid, simple, 1 method {@link ClientEndpoint} annotated classes with valid signatures.
+ */
+@RunWith(Parameterized.class)
+public class ClientAnnotatedEndpointScanner_GoodSignaturesTest
+{
+ public static class Case
+ {
+ public static void add(List<Case[]> data, Class<?> pojo, Field metadataField, Class<?>... expectedParams)
+ {
+ data.add(new Case[]
+ { new Case(pojo,metadataField,expectedParams) });
+ }
+
+ // The websocket pojo to test against
+ Class<?> pojo;
+ // The JsrAnnotatedMetadata field that should be populated
+ Field metadataField;
+ // The expected parameters for the Callable found by the scanner
+ Class<?> expectedParameters[];
+
+ public Case(Class<?> pojo, Field metadataField, Class<?>... expectedParams)
+ {
+ this.pojo = pojo;
+ this.metadataField = metadataField;
+ this.expectedParameters = expectedParams;
+ }
+ }
+
+ private static ClientContainer container = new ClientContainer();
+
+ @Parameters
+ public static Collection<Case[]> data() throws Exception
+ {
+ List<Case[]> data = new ArrayList<>();
+ Field fOpen = findFieldRef(AnnotatedEndpointMetadata.class,"onOpen");
+ Field fClose = findFieldRef(AnnotatedEndpointMetadata.class,"onClose");
+ Field fError = findFieldRef(AnnotatedEndpointMetadata.class,"onError");
+ Field fText = findFieldRef(AnnotatedEndpointMetadata.class,"onText");
+ Field fBinary = findFieldRef(AnnotatedEndpointMetadata.class,"onBinary");
+ Field fBinaryStream = findFieldRef(AnnotatedEndpointMetadata.class,"onBinaryStream");
+ Field fPong = findFieldRef(AnnotatedEndpointMetadata.class,"onPong");
+
+ // @formatter:off
+ // -- Open Events
+ Case.add(data, BasicOpenSocket.class, fOpen);
+ Case.add(data, BasicOpenSessionSocket.class, fOpen, Session.class);
+ // -- Close Events
+ Case.add(data, CloseSocket.class, fClose);
+ Case.add(data, CloseReasonSocket.class, fClose, CloseReason.class);
+ Case.add(data, CloseReasonSessionSocket.class, fClose, CloseReason.class, Session.class);
+ Case.add(data, CloseSessionReasonSocket.class, fClose, Session.class, CloseReason.class);
+ // -- Error Events
+ Case.add(data, BasicErrorSocket.class, fError);
+ Case.add(data, BasicErrorSessionSocket.class, fError, Session.class);
+ Case.add(data, BasicErrorSessionThrowableSocket.class, fError, Session.class, Throwable.class);
+ Case.add(data, BasicErrorThrowableSocket.class, fError, Throwable.class);
+ Case.add(data, BasicErrorThrowableSessionSocket.class, fError, Throwable.class, Session.class);
+ // -- Text Events
+ Case.add(data, BasicTextMessageStringSocket.class, fText, String.class);
+ // -- Binary Events
+ Case.add(data, BasicBinaryMessageByteBufferSocket.class, fBinary, ByteBuffer.class);
+ // -- Pong Events
+ Case.add(data, BasicPongMessageSocket.class, fPong, PongMessage.class);
+ // -- InputStream Events
+ Case.add(data, BasicInputStreamSocket.class, fBinaryStream, InputStream.class);
+ Case.add(data, BasicInputStreamWithThrowableSocket.class, fBinaryStream, InputStream.class);
+ // @formatter:on
+
+ // TODO: validate return types
+
+ return data;
+ }
+
+ private static Field findFieldRef(Class<?> clazz, String fldName) throws Exception
+ {
+ return clazz.getField(fldName);
+ }
+
+ private Case testcase;
+
+ public ClientAnnotatedEndpointScanner_GoodSignaturesTest(Case testcase)
+ {
+ this.testcase = testcase;
+ }
+
+ @Test
+ public void testScan_Basic() throws Exception
+ {
+ AnnotatedClientEndpointMetadata metadata = new AnnotatedClientEndpointMetadata(container,testcase.pojo);
+ AnnotatedEndpointScanner<ClientEndpoint, ClientEndpointConfig> scanner = new AnnotatedEndpointScanner<>(metadata);
+ scanner.scan();
+
+ Assert.assertThat("Metadata",metadata,notNullValue());
+
+ JsrCallable cm = (JsrCallable)testcase.metadataField.get(metadata);
+ Assert.assertThat(testcase.metadataField.toString(),cm,notNullValue());
+ int len = testcase.expectedParameters.length;
+ for (int i = 0; i < len; i++)
+ {
+ Class<?> expectedParam = testcase.expectedParameters[i];
+ Class<?> actualParam = cm.getParamTypes()[i];
+
+ Assert.assertTrue("Parameter[" + i + "] - expected:[" + expectedParam + "], actual:[" + actualParam + "]",actualParam.equals(expectedParam));
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_InvalidSignaturesTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_InvalidSignaturesTest.java
new file mode 100644
index 0000000..086bf4a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_InvalidSignaturesTest.java
@@ -0,0 +1,113 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import static org.hamcrest.Matchers.containsString;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.DeploymentException;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.ClientContainer;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.client.AnnotatedClientEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidCloseIntSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidErrorErrorSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidErrorExceptionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidErrorIntSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidOpenCloseReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidOpenIntSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidOpenSessionIntSocket;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test {@link AnnotatedEndpointScanner} against various simple, 1 method, {@link ClientEndpoint} annotated classes with invalid signatures.
+ */
+@RunWith(Parameterized.class)
+public class ClientAnnotatedEndpointScanner_InvalidSignaturesTest
+{
+ private static final Logger LOG = Log.getLogger(ClientAnnotatedEndpointScanner_InvalidSignaturesTest.class);
+ private static ClientContainer container = new ClientContainer();
+
+ @Parameters
+ public static Collection<Class<?>[]> data()
+ {
+ List<Class<?>[]> data = new ArrayList<>();
+
+ // @formatter:off
+ data.add(new Class<?>[]{ InvalidCloseIntSocket.class, OnClose.class });
+ data.add(new Class<?>[]{ InvalidErrorErrorSocket.class, OnError.class });
+ data.add(new Class<?>[]{ InvalidErrorExceptionSocket.class, OnError.class });
+ data.add(new Class<?>[]{ InvalidErrorIntSocket.class, OnError.class });
+ data.add(new Class<?>[]{ InvalidOpenCloseReasonSocket.class, OnOpen.class });
+ data.add(new Class<?>[]{ InvalidOpenIntSocket.class, OnOpen.class });
+ data.add(new Class<?>[]{ InvalidOpenSessionIntSocket.class, OnOpen.class });
+ // @formatter:on
+
+ // TODO: invalid return types
+ // TODO: static methods
+ // TODO: private or protected methods
+ // TODO: abstract methods
+
+ return data;
+ }
+
+ // The pojo to test
+ private Class<?> pojo;
+ // The annotation class expected to be mentioned in the error message
+ private Class<? extends Annotation> expectedAnnoClass;
+
+ public ClientAnnotatedEndpointScanner_InvalidSignaturesTest(Class<?> pojo, Class<? extends Annotation> expectedAnnotation)
+ {
+ this.pojo = pojo;
+ this.expectedAnnoClass = expectedAnnotation;
+ }
+
+ @Test
+ public void testScan_InvalidSignature() throws DeploymentException
+ {
+ AnnotatedClientEndpointMetadata metadata = new AnnotatedClientEndpointMetadata(container,pojo);
+ AnnotatedEndpointScanner<ClientEndpoint, ClientEndpointConfig> scanner = new AnnotatedEndpointScanner<>(metadata);
+ try
+ {
+ scanner.scan();
+ Assert.fail("Expected " + InvalidSignatureException.class + " with message that references " + expectedAnnoClass + " annotation");
+ }
+ catch (InvalidSignatureException e)
+ {
+ LOG.debug("{}:{}",e.getClass(),e.getMessage());
+ Assert.assertThat("Message",e.getMessage(),containsString(expectedAnnoClass.getSimpleName()));
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/OnCloseTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/OnCloseTest.java
new file mode 100644
index 0000000..60ee98a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/OnCloseTest.java
@@ -0,0 +1,126 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import static org.hamcrest.Matchers.is;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.jsr356.ClientContainer;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrEvents;
+import org.eclipse.jetty.websocket.jsr356.client.AnnotatedClientEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseEndpointConfigSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseReasonSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseSessionReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseSocket;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class OnCloseTest
+{
+ private static class Case
+ {
+ public static Case add(List<Case[]> data, Class<?> closeClass)
+ {
+ Case tcase = new Case();
+ tcase.closeClass = closeClass;
+ data.add(new Case[]
+ { tcase });
+ return tcase;
+ }
+
+ Class<?> closeClass;
+ String expectedCloseEvent;
+
+ public Case expect(String expectedEvent)
+ {
+ this.expectedCloseEvent = expectedEvent;
+ return this;
+ }
+ }
+
+ private static ClientContainer container = new ClientContainer();
+
+ @Parameters
+ public static Collection<Case[]> data() throws Exception
+ {
+ List<Case[]> data = new ArrayList<>();
+
+ Case.add(data,CloseSocket.class).expect("onClose()");
+ Case.add(data,CloseReasonSocket.class).expect("onClose(CloseReason)");
+ Case.add(data,CloseSessionSocket.class).expect("onClose(Session)");
+ Case.add(data,CloseReasonSessionSocket.class).expect("onClose(CloseReason,Session)");
+ Case.add(data,CloseSessionReasonSocket.class).expect("onClose(Session,CloseReason)");
+ Case.add(data,CloseEndpointConfigSocket.class).expect("onClose(EndpointConfig)");
+
+ return data;
+ }
+
+ private final Case testcase;
+
+ public OnCloseTest(Case testcase)
+ {
+ this.testcase = testcase;
+ System.err.printf("Testing @OnClose for %s%n",testcase.closeClass.getName());
+ }
+
+ @Test
+ public void testOnCloseCall() throws Exception
+ {
+ // Scan annotations
+ AnnotatedClientEndpointMetadata metadata = new AnnotatedClientEndpointMetadata(container,testcase.closeClass);
+ AnnotatedEndpointScanner<ClientEndpoint, ClientEndpointConfig> scanner = new AnnotatedEndpointScanner<>(metadata);
+ scanner.scan();
+
+ // Build up EventDriver
+ WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+ ClientEndpointConfig config = metadata.getConfig();
+ TrackingSocket endpoint = (TrackingSocket)testcase.closeClass.newInstance();
+ EndpointInstance ei = new EndpointInstance(endpoint,config,metadata);
+ JsrEvents<ClientEndpoint, ClientEndpointConfig> jsrevents = new JsrEvents<>(metadata);
+
+ EventDriver driver = new JsrAnnotatedEventDriver(policy,ei,jsrevents);
+
+ // Execute onClose call
+ driver.onClose(new CloseInfo(StatusCode.NORMAL,"normal"));
+
+ // Test captured event
+ EventQueue<String> events = endpoint.eventQueue;
+ Assert.assertThat("Number of Events Captured",events.size(),is(1));
+ String closeEvent = events.poll();
+ Assert.assertThat("Close Event",closeEvent,is(testcase.expectedCloseEvent));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/TrackingSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/TrackingSocket.java
new file mode 100644
index 0000000..e19ba14
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/TrackingSocket.java
@@ -0,0 +1,127 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCode;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Assert;
+
+/**
+ * Abstract base socket used for tracking state and events within the socket for testing reasons.
+ */
+public abstract class TrackingSocket
+{
+ private static final Logger LOG = Log.getLogger(TrackingSocket.class);
+
+ public CloseReason closeReason;
+ public EventQueue<String> eventQueue = new EventQueue<String>();
+ public EventQueue<Throwable> errorQueue = new EventQueue<>();
+ public CountDownLatch openLatch = new CountDownLatch(1);
+ public CountDownLatch closeLatch = new CountDownLatch(1);
+ public CountDownLatch dataLatch = new CountDownLatch(1);
+
+ protected void addError(Throwable t)
+ {
+ LOG.warn(t);
+ errorQueue.add(t);
+ }
+
+ protected void addEvent(String format, Object... args)
+ {
+ eventQueue.add(String.format(format,args));
+ }
+
+ public void assertClose(CloseCode expectedCode, String expectedReason) throws InterruptedException
+ {
+ assertCloseCode(expectedCode);
+ assertCloseReason(expectedReason);
+ }
+
+ public void assertCloseCode(CloseCode expectedCode) throws InterruptedException
+ {
+ Assert.assertThat("Was Closed",closeLatch.await(50,TimeUnit.MILLISECONDS),is(true));
+ Assert.assertThat("CloseReason",closeReason,notNullValue());
+ Assert.assertThat("Close Code",closeReason.getCloseCode(),is(expectedCode));
+ }
+
+ private void assertCloseReason(String expectedReason)
+ {
+ Assert.assertThat("Close Reason",closeReason.getReasonPhrase(),is(expectedReason));
+ }
+
+ public void assertEvent(String expected)
+ {
+ String actual = eventQueue.poll();
+ Assert.assertEquals("Event",expected,actual);
+ }
+
+ public void assertIsOpen() throws InterruptedException
+ {
+ assertWasOpened();
+ assertNotClosed();
+ }
+
+ public void assertNotClosed()
+ {
+ Assert.assertThat("Closed Latch",closeLatch.getCount(),greaterThanOrEqualTo(1L));
+ }
+
+ public void assertNotOpened()
+ {
+ Assert.assertThat("Open Latch",openLatch.getCount(),greaterThanOrEqualTo(1L));
+ }
+
+ public void assertWasOpened() throws InterruptedException
+ {
+ Assert.assertThat("Was Opened",openLatch.await(500,TimeUnit.MILLISECONDS),is(true));
+ }
+
+ public void clear()
+ {
+ eventQueue.clear();
+ errorQueue.clear();
+ }
+
+ public void waitForClose(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+ {
+ Assert.assertThat("Client Socket Closed",closeLatch.await(timeoutDuration,timeoutUnit),is(true));
+ }
+
+ public void waitForConnected(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+ {
+ Assert.assertThat("Client Socket Connected",openLatch.await(timeoutDuration,timeoutUnit),is(true));
+ }
+
+ public void waitForData(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+ {
+ LOG.debug("Waiting for message");
+ Assert.assertThat("Data Received",dataLatch.await(timeoutDuration,timeoutUnit),is(true));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicBinaryMessageByteBufferSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicBinaryMessageByteBufferSocket.java
new file mode 100644
index 0000000..8516b49
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicBinaryMessageByteBufferSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicBinaryMessageByteBufferSocket extends TrackingSocket
+{
+ @OnMessage
+ public void onBinary(ByteBuffer data)
+ {
+ addEvent("onBinary(%s)",data);
+ dataLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSessionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSessionSocket.java
new file mode 100644
index 0000000..5629359
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSessionSocket.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicErrorSessionSocket extends TrackingSocket
+{
+ @OnError
+ public void onError(Session session)
+ {
+ addEvent("onError(%s)",session);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSessionThrowableSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSessionThrowableSocket.java
new file mode 100644
index 0000000..79fa681
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSessionThrowableSocket.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicErrorSessionThrowableSocket extends TrackingSocket
+{
+ @OnError
+ public void onError(Session session, Throwable t)
+ {
+ addEvent("onError(%s,%s)",session,t);
+ addError(t);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSocket.java
new file mode 100644
index 0000000..7aade74
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSocket.java
@@ -0,0 +1,34 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicErrorSocket extends TrackingSocket
+{
+ @OnError
+ public void onError()
+ {
+ addEvent("onError()");
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorThrowableSessionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorThrowableSessionSocket.java
new file mode 100644
index 0000000..4381df5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorThrowableSessionSocket.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicErrorThrowableSessionSocket extends TrackingSocket
+{
+ @OnError
+ public void onError(Throwable t, Session session)
+ {
+ addEvent("onError(%s,%s)",t,session);
+ addError(t);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorThrowableSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorThrowableSocket.java
new file mode 100644
index 0000000..aa71e20
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorThrowableSocket.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicErrorThrowableSocket extends TrackingSocket
+{
+ @OnError
+ public void onError(Throwable t)
+ {
+ addEvent("onError(%s)",t);
+ addError(t);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicInputStreamSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicInputStreamSocket.java
new file mode 100644
index 0000000..28bb854
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicInputStreamSocket.java
@@ -0,0 +1,47 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicInputStreamSocket extends TrackingSocket
+{
+ @OnMessage
+ public void onBinary(InputStream stream)
+ {
+ try
+ {
+ String msg = IO.toString(stream);
+ addEvent("onBinary(%s)",msg);
+ }
+ catch (IOException e)
+ {
+ super.errorQueue.add(e);
+ }
+ dataLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicInputStreamWithThrowableSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicInputStreamWithThrowableSocket.java
new file mode 100644
index 0000000..b030e83
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicInputStreamWithThrowableSocket.java
@@ -0,0 +1,39 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicInputStreamWithThrowableSocket extends TrackingSocket
+{
+ @OnMessage
+ public void onBinary(InputStream stream) throws IOException
+ {
+ String msg = IO.toString(stream);
+ addEvent("onBinary(%s)",msg);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenCloseSessionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenCloseSessionSocket.java
new file mode 100644
index 0000000..63fc766
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenCloseSessionSocket.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicOpenCloseSessionSocket extends TrackingSocket
+{
+ @OnClose
+ public void onClose(CloseReason close, Session session)
+ {
+ addEvent("onClose(%s, %s)",close,session);
+ this.closeReason = close;
+ closeLatch.countDown();
+ }
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ addEvent("onOpen(%s)",session);
+ openLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenCloseSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenCloseSocket.java
new file mode 100644
index 0000000..ffb8208
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenCloseSocket.java
@@ -0,0 +1,41 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicOpenCloseSocket extends TrackingSocket
+{
+ @OnOpen
+ public void onOpen() {
+ openLatch.countDown();
+ }
+
+ @OnClose
+ public void onClose(CloseReason close) {
+ this.closeReason = close;
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenSessionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenSessionSocket.java
new file mode 100644
index 0000000..fb87077
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenSessionSocket.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicOpenSessionSocket extends TrackingSocket
+{
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ openLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenSocket.java
new file mode 100644
index 0000000..6db95f8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenSocket.java
@@ -0,0 +1,34 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicOpenSocket extends TrackingSocket
+{
+ @OnOpen
+ public void onOpen()
+ {
+ openLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicPongMessageSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicPongMessageSocket.java
new file mode 100644
index 0000000..07dd394
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicPongMessageSocket.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnMessage;
+import javax.websocket.PongMessage;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicPongMessageSocket extends TrackingSocket
+{
+ @OnMessage
+ public void onPong(PongMessage pong)
+ {
+ addEvent("onPong(%s)",pong);
+ dataLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicTextMessageStringSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicTextMessageStringSocket.java
new file mode 100644
index 0000000..019aa6d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicTextMessageStringSocket.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class BasicTextMessageStringSocket extends TrackingSocket
+{
+ @OnMessage
+ public void onText(String message)
+ {
+ addEvent("onText(%s)",message);
+ dataLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidCloseIntSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidCloseIntSocket.java
new file mode 100644
index 0000000..01f68ab
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidCloseIntSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnClose;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class InvalidCloseIntSocket extends TrackingSocket
+{
+ /**
+ * Invalid Close Method Declaration (parameter type int)
+ */
+ @OnClose
+ public void onClose(int statusCode)
+ {
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorErrorSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorErrorSocket.java
new file mode 100644
index 0000000..7ed4ad7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorErrorSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class InvalidErrorErrorSocket extends TrackingSocket
+{
+ /**
+ * Invalid Error Method Declaration (parameter type Error)
+ */
+ @OnError
+ public void onError(Error error)
+ {
+ /* no impl */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorExceptionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorExceptionSocket.java
new file mode 100644
index 0000000..a63d334
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorExceptionSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class InvalidErrorExceptionSocket extends TrackingSocket
+{
+ /**
+ * Invalid Error Method Declaration (parameter type Exception)
+ */
+ @OnError
+ public void onError(Exception e)
+ {
+ /* no impl */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorIntSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorIntSocket.java
new file mode 100644
index 0000000..03e5d22
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorIntSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class InvalidErrorIntSocket extends TrackingSocket
+{
+ /**
+ * Invalid Error Method Declaration (parameter type int)
+ */
+ @OnError
+ public void onError(int errorCount)
+ {
+ /* no impl */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenCloseReasonSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenCloseReasonSocket.java
new file mode 100644
index 0000000..9692ecf
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenCloseReasonSocket.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class InvalidOpenCloseReasonSocket extends TrackingSocket
+{
+ /**
+ * Invalid Open Method Declaration (parameter type CloseReason)
+ */
+ @OnOpen
+ public void onOpen(CloseReason reason)
+ {
+ openLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenIntSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenIntSocket.java
new file mode 100644
index 0000000..675f2e7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenIntSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class InvalidOpenIntSocket extends TrackingSocket
+{
+ /**
+ * Invalid Open Method Declaration (parameter type int)
+ */
+ @OnOpen
+ public void onOpen(int value)
+ {
+ openLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenSessionIntSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenSessionIntSocket.java
new file mode 100644
index 0000000..8f21c75
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenSessionIntSocket.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class InvalidOpenSessionIntSocket extends TrackingSocket
+{
+ /**
+ * Invalid Open Method Declaration (parameter of type int)
+ */
+ @OnOpen
+ public void onOpen(Session session, int count)
+ {
+ openLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseEndpointConfigSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseEndpointConfigSocket.java
new file mode 100644
index 0000000..64aba6c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseEndpointConfigSocket.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples.close;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnClose;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class CloseEndpointConfigSocket extends TrackingSocket
+{
+ @OnClose
+ public void onClose(EndpointConfig config)
+ {
+ addEvent("onClose(EndpointConfig)");
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseReasonSessionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseReasonSessionSocket.java
new file mode 100644
index 0000000..caf8fe3
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseReasonSessionSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples.close;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class CloseReasonSessionSocket extends TrackingSocket
+{
+ @OnClose
+ public void onClose(CloseReason reason, Session session)
+ {
+ addEvent("onClose(CloseReason,Session)");
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseReasonSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseReasonSocket.java
new file mode 100644
index 0000000..2b84946
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseReasonSocket.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples.close;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class CloseReasonSocket extends TrackingSocket
+{
+ @OnClose
+ public void onClose(CloseReason reason)
+ {
+ addEvent("onClose(CloseReason)");
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSessionReasonSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSessionReasonSocket.java
new file mode 100644
index 0000000..ee7439f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSessionReasonSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples.close;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class CloseSessionReasonSocket extends TrackingSocket
+{
+ @OnClose
+ public void onClose(Session session, CloseReason reason)
+ {
+ addEvent("onClose(Session,CloseReason)");
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSessionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSessionSocket.java
new file mode 100644
index 0000000..599deaa
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSessionSocket.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples.close;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnClose;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class CloseSessionSocket extends TrackingSocket
+{
+ @OnClose
+ public void onClose(Session session)
+ {
+ addEvent("onClose(Session)");
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSocket.java
new file mode 100644
index 0000000..e184022
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSocket.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples.close;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnClose;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+@ClientEndpoint
+public class CloseSocket extends TrackingSocket
+{
+ @OnClose
+ public void onClose()
+ {
+ addEvent("onClose()");
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/BaseMessageHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/BaseMessageHandler.java
new file mode 100644
index 0000000..a025f64
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/BaseMessageHandler.java
@@ -0,0 +1,30 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import javax.websocket.MessageHandler;
+
+public class BaseMessageHandler implements MessageHandler.Whole<String>
+{
+ @Override
+ public void onMessage(String message)
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteArrayPartialHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteArrayPartialHandler.java
new file mode 100644
index 0000000..ca56ac2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteArrayPartialHandler.java
@@ -0,0 +1,30 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import javax.websocket.MessageHandler;
+
+public class ByteArrayPartialHandler implements MessageHandler.Partial<byte[]>
+{
+ @Override
+ public void onMessage(byte[] partialMessage, boolean last)
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteArrayWholeHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteArrayWholeHandler.java
new file mode 100644
index 0000000..1cd68b8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteArrayWholeHandler.java
@@ -0,0 +1,30 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import javax.websocket.MessageHandler;
+
+public class ByteArrayWholeHandler implements MessageHandler.Whole<byte[]>
+{
+ @Override
+ public void onMessage(byte[] message)
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferPartialHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferPartialHandler.java
new file mode 100644
index 0000000..7d70ed9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferPartialHandler.java
@@ -0,0 +1,32 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.MessageHandler;
+
+public class ByteBufferPartialHandler implements MessageHandler.Partial<ByteBuffer>
+{
+ @Override
+ public void onMessage(ByteBuffer partialMessage, boolean last)
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferWholeHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferWholeHandler.java
new file mode 100644
index 0000000..1fb628f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferWholeHandler.java
@@ -0,0 +1,32 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.MessageHandler;
+
+public class ByteBufferWholeHandler implements MessageHandler.Whole<ByteBuffer>
+{
+ @Override
+ public void onMessage(ByteBuffer message)
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ComboMessageHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ComboMessageHandler.java
new file mode 100644
index 0000000..4dea474
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ComboMessageHandler.java
@@ -0,0 +1,41 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.MessageHandler;
+
+/**
+ * A particularly annoying type of MessageHandler. One defining 2 implementations.
+ */
+public class ComboMessageHandler implements MessageHandler.Whole<String>, MessageHandler.Partial<ByteBuffer>
+{
+ @Override
+ public void onMessage(ByteBuffer partialMessage, boolean last)
+ {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void onMessage(String message)
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ExtendedMessageHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ExtendedMessageHandler.java
new file mode 100644
index 0000000..c10fbee
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ExtendedMessageHandler.java
@@ -0,0 +1,32 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.MessageHandler;
+
+public class ExtendedMessageHandler extends BaseMessageHandler implements MessageHandler.Partial<ByteBuffer>
+{
+ @Override
+ public void onMessage(ByteBuffer partialMessage, boolean last)
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/InputStreamWholeHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/InputStreamWholeHandler.java
new file mode 100644
index 0000000..b2c73d4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/InputStreamWholeHandler.java
@@ -0,0 +1,32 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import java.io.InputStream;
+
+import javax.websocket.MessageHandler;
+
+public class InputStreamWholeHandler implements MessageHandler.Whole<InputStream>
+{
+ @Override
+ public void onMessage(InputStream stream)
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/LongMessageHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/LongMessageHandler.java
new file mode 100644
index 0000000..de9c637
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/LongMessageHandler.java
@@ -0,0 +1,29 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import javax.websocket.MessageHandler;
+
+public class LongMessageHandler implements MessageHandler.Whole<Long>
+{
+ @Override
+ public void onMessage(Long message)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ReaderWholeHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ReaderWholeHandler.java
new file mode 100644
index 0000000..ccb9324
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ReaderWholeHandler.java
@@ -0,0 +1,32 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import java.io.Reader;
+
+import javax.websocket.MessageHandler;
+
+public class ReaderWholeHandler implements MessageHandler.Whole<Reader>
+{
+ @Override
+ public void onMessage(Reader reader)
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/StringPartialHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/StringPartialHandler.java
new file mode 100644
index 0000000..549a65a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/StringPartialHandler.java
@@ -0,0 +1,30 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import javax.websocket.MessageHandler;
+
+public class StringPartialHandler implements MessageHandler.Partial<String>
+{
+ @Override
+ public void onMessage(String partialMessage, boolean last)
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/StringWholeHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/StringWholeHandler.java
new file mode 100644
index 0000000..4a8fd75
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/StringWholeHandler.java
@@ -0,0 +1,30 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import javax.websocket.MessageHandler;
+
+public class StringWholeHandler implements MessageHandler.Whole<String>
+{
+ @Override
+ public void onMessage(String message)
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSetTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSetTest.java
new file mode 100644
index 0000000..306f818
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSetTest.java
@@ -0,0 +1,134 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.util.List;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.decoders.BadDualDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.DateDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.IntegerDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.TimeDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.ValidDualDecoder;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DecoderMetadataSetTest
+{
+ private void assertMetadata(CoderMetadata<?> metadata, Class<?> expectedType, Class<?> expectedCoder, MessageType expectedMessageType)
+ {
+ Assert.assertEquals("metadata.coderClass",expectedCoder,metadata.getCoderClass());
+ Assert.assertThat("metadata.messageType",metadata.getMessageType(),is(expectedMessageType));
+ Assert.assertEquals("metadata.objectType",expectedType,metadata.getObjectType());
+ }
+
+ @Test
+ public void testAddBadDualDecoders()
+ {
+ try
+ {
+ DecoderMetadataSet coders = new DecoderMetadataSet();
+
+ // has duplicated support for the same target Type
+ coders.add(BadDualDecoder.class);
+ Assert.fail("Should have thrown IllegalStateException for attempting to register Decoders with duplicate implementation");
+ }
+ catch (IllegalStateException e)
+ {
+ Assert.assertThat(e.getMessage(),containsString("Duplicate"));
+ }
+ }
+
+ @Test
+ public void testAddDuplicate()
+ {
+ DecoderMetadataSet coders = new DecoderMetadataSet();
+
+ // Add DateDecoder (decodes java.util.Date)
+ coders.add(DateDecoder.class);
+
+ try
+ {
+ // Add TimeDecoder (which also wants to decode java.util.Date)
+ coders.add(TimeDecoder.class);
+ Assert.fail("Should have thrown IllegalStateException for attempting to register Decoders with duplicate implementation");
+ }
+ catch (IllegalStateException e)
+ {
+ Assert.assertThat(e.getMessage(),containsString("Duplicate"));
+ }
+ }
+
+ @Test
+ public void testAddGetCoder()
+ {
+ DecoderMetadataSet coders = new DecoderMetadataSet();
+
+ coders.add(IntegerDecoder.class);
+ Class<? extends Decoder> actualClazz = coders.getCoder(Integer.class);
+ Assert.assertEquals("Coder Class",IntegerDecoder.class,actualClazz);
+ }
+
+ @Test
+ public void testAddGetMetadataByImpl()
+ {
+ DecoderMetadataSet coders = new DecoderMetadataSet();
+
+ coders.add(IntegerDecoder.class);
+ List<DecoderMetadata> metadatas = coders.getMetadataByImplementation(IntegerDecoder.class);
+ Assert.assertThat("Metadatas (by impl) count",metadatas.size(),is(1));
+ DecoderMetadata metadata = metadatas.get(0);
+ assertMetadata(metadata,Integer.class,IntegerDecoder.class,MessageType.TEXT);
+ }
+
+ @Test
+ public void testAddGetMetadataByType()
+ {
+ DecoderMetadataSet coders = new DecoderMetadataSet();
+
+ coders.add(IntegerDecoder.class);
+ DecoderMetadata metadata = coders.getMetadataByType(Integer.class);
+ assertMetadata(metadata,Integer.class,IntegerDecoder.class,MessageType.TEXT);
+ }
+
+ @Test
+ public void testAddValidDualDecoders()
+ {
+ DecoderMetadataSet coders = new DecoderMetadataSet();
+
+ coders.add(ValidDualDecoder.class);
+
+ List<Class<? extends Decoder>> decodersList = coders.getList();
+ Assert.assertThat("Decoder List",decodersList,notNullValue());
+ Assert.assertThat("Decoder List count",decodersList.size(),is(2));
+
+ DecoderMetadata metadata;
+ metadata = coders.getMetadataByType(Integer.class);
+ assertMetadata(metadata,Integer.class,ValidDualDecoder.class,MessageType.TEXT);
+
+ metadata = coders.getMetadataByType(Long.class);
+ assertMetadata(metadata,Long.class,ValidDualDecoder.class,MessageType.BINARY);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSetTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSetTest.java
new file mode 100644
index 0000000..f224653
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSetTest.java
@@ -0,0 +1,134 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.util.List;
+
+import javax.websocket.Encoder;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.encoders.BadDualEncoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.DateEncoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.IntegerEncoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.TimeEncoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.ValidDualEncoder;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class EncoderMetadataSetTest
+{
+ private void assertMetadata(CoderMetadata<?> metadata, Class<?> expectedType, Class<?> expectedCoder, MessageType expectedMessageType)
+ {
+ Assert.assertEquals("metadata.coderClass",expectedCoder,metadata.getCoderClass());
+ Assert.assertThat("metadata.messageType",metadata.getMessageType(),is(expectedMessageType));
+ Assert.assertEquals("metadata.objectType",expectedType,metadata.getObjectType());
+ }
+
+ @Test
+ public void testAddBadDualEncoders()
+ {
+ try
+ {
+ EncoderMetadataSet coders = new EncoderMetadataSet();
+
+ // has duplicated support for the same target Type
+ coders.add(BadDualEncoder.class);
+ Assert.fail("Should have thrown IllegalStateException for attempting to register Encoders with duplicate implementation");
+ }
+ catch (IllegalStateException e)
+ {
+ Assert.assertThat(e.getMessage(),containsString("Duplicate"));
+ }
+ }
+
+ @Test
+ public void testAddDuplicate()
+ {
+ EncoderMetadataSet coders = new EncoderMetadataSet();
+
+ // Add DateEncoder (decodes java.util.Date)
+ coders.add(DateEncoder.class);
+
+ try
+ {
+ // Add TimeEncoder (which also wants to decode java.util.Date)
+ coders.add(TimeEncoder.class);
+ Assert.fail("Should have thrown IllegalStateException for attempting to register Encoders with duplicate implementation");
+ }
+ catch (IllegalStateException e)
+ {
+ Assert.assertThat(e.getMessage(),containsString("Duplicate"));
+ }
+ }
+
+ @Test
+ public void testAddGetCoder()
+ {
+ EncoderMetadataSet coders = new EncoderMetadataSet();
+
+ coders.add(IntegerEncoder.class);
+ Class<? extends Encoder> actualClazz = coders.getCoder(Integer.class);
+ Assert.assertEquals("Coder Class",IntegerEncoder.class,actualClazz);
+ }
+
+ @Test
+ public void testAddGetMetadataByImpl()
+ {
+ EncoderMetadataSet coders = new EncoderMetadataSet();
+
+ coders.add(IntegerEncoder.class);
+ List<EncoderMetadata> metadatas = coders.getMetadataByImplementation(IntegerEncoder.class);
+ Assert.assertThat("Metadatas (by impl) count",metadatas.size(),is(1));
+ EncoderMetadata metadata = metadatas.get(0);
+ assertMetadata(metadata,Integer.class,IntegerEncoder.class,MessageType.TEXT);
+ }
+
+ @Test
+ public void testAddGetMetadataByType()
+ {
+ EncoderMetadataSet coders = new EncoderMetadataSet();
+
+ coders.add(IntegerEncoder.class);
+ EncoderMetadata metadata = coders.getMetadataByType(Integer.class);
+ assertMetadata(metadata,Integer.class,IntegerEncoder.class,MessageType.TEXT);
+ }
+
+ @Test
+ public void testAddValidDualEncoders()
+ {
+ EncoderMetadataSet coders = new EncoderMetadataSet();
+
+ coders.add(ValidDualEncoder.class);
+
+ List<Class<? extends Encoder>> EncodersList = coders.getList();
+ Assert.assertThat("Encoder List",EncodersList,notNullValue());
+ Assert.assertThat("Encoder List count",EncodersList.size(),is(2));
+
+ EncoderMetadata metadata;
+ metadata = coders.getMetadataByType(Integer.class);
+ assertMetadata(metadata,Integer.class,ValidDualEncoder.class,MessageType.TEXT);
+
+ metadata = coders.getMetadataByType(Long.class);
+ assertMetadata(metadata,Long.class,ValidDualEncoder.class,MessageType.BINARY);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/AbstractStringEndpoint.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/AbstractStringEndpoint.java
new file mode 100644
index 0000000..dec7740
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/AbstractStringEndpoint.java
@@ -0,0 +1,58 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Base Abstract Class.
+ */
+public abstract class AbstractStringEndpoint extends Endpoint implements MessageHandler.Whole<String>
+{
+ private static final Logger LOG = Log.getLogger(AbstractStringEndpoint.class);
+ protected Session session;
+ protected EndpointConfig config;
+
+ @Override
+ public void onOpen(Session session, EndpointConfig config)
+ {
+ LOG.debug("onOpen({}, {})",session,config);
+ session.addMessageHandler(this);
+ this.session = session;
+ this.config = config;
+ }
+
+ public void onClose(Session session, CloseReason closeReason)
+ {
+ LOG.debug("onClose({}, {})",session,closeReason);
+ this.session = null;
+ }
+
+ public void onError(Session session, Throwable thr)
+ {
+ LOG.warn("onError()",thr);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/DummyConnection.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/DummyConnection.java
new file mode 100644
index 0000000..3ca26bf
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/DummyConnection.java
@@ -0,0 +1,155 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.websocket.api.SuspendToken;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.io.IOState;
+
+public class DummyConnection implements LogicalConnection
+{
+ private IOState iostate;
+
+ public DummyConnection()
+ {
+ this.iostate = new IOState();
+ }
+
+ @Override
+ public void close()
+ {
+ }
+
+ @Override
+ public void close(int statusCode, String reason)
+ {
+ }
+
+ @Override
+ public void disconnect()
+ {
+ }
+
+ @Override
+ public ByteBufferPool getBufferPool()
+ {
+ return null;
+ }
+
+ @Override
+ public Executor getExecutor()
+ {
+ return null;
+ }
+
+ @Override
+ public long getIdleTimeout()
+ {
+ return 0;
+ }
+
+ @Override
+ public IOState getIOState()
+ {
+ return this.iostate;
+ }
+
+ @Override
+ public InetSocketAddress getLocalAddress()
+ {
+ return null;
+ }
+
+ @Override
+ public long getMaxIdleTimeout()
+ {
+ return 0;
+ }
+
+ @Override
+ public WebSocketPolicy getPolicy()
+ {
+ return null;
+ }
+
+ @Override
+ public InetSocketAddress getRemoteAddress()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public WebSocketSession getSession()
+ {
+ return null;
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isReading()
+ {
+ return false;
+ }
+
+ @Override
+ public void outgoingFrame(Frame frame, WriteCallback callback)
+ {
+ }
+
+ @Override
+ public void resume()
+ {
+ }
+
+ @Override
+ public void setMaxIdleTimeout(long ms)
+ {
+ }
+
+ @Override
+ public void setNextIncomingFrames(IncomingFrames incoming)
+ {
+ }
+
+ @Override
+ public void setSession(WebSocketSession session)
+ {
+ }
+
+ @Override
+ public SuspendToken suspend()
+ {
+ return null;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/DummyEndpoint.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/DummyEndpoint.java
new file mode 100644
index 0000000..558c300
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/DummyEndpoint.java
@@ -0,0 +1,32 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+public class DummyEndpoint extends Endpoint
+{
+ @Override
+ public void onOpen(Session session, EndpointConfig config)
+ {
+ /* do nothing */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/EchoStringEndpoint.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/EchoStringEndpoint.java
new file mode 100644
index 0000000..d52d4a9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/EchoStringEndpoint.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import org.eclipse.jetty.websocket.jsr356.MessageQueue;
+
+/**
+ * Legitimate structure for an Endpoint
+ */
+public class EchoStringEndpoint extends AbstractStringEndpoint
+{
+ public MessageQueue messageQueue = new MessageQueue();
+
+ @Override
+ public void onMessage(String message)
+ {
+ messageQueue.offer(message);
+ session.getAsyncRemote().sendText(message);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/ExtDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/ExtDecoder.java
new file mode 100644
index 0000000..8073105
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/ExtDecoder.java
@@ -0,0 +1,29 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import javax.websocket.Decoder;
+
+/**
+ * Testing scenario of an extended Decoder interface
+ */
+public interface ExtDecoder<T> extends Decoder.Text<T>
+{
+ void setId(String id);
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/Fruit.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/Fruit.java
new file mode 100644
index 0000000..47b59f8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/Fruit.java
@@ -0,0 +1,25 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+public class Fruit
+{
+ public String name;
+ public String color;
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitBinaryEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitBinaryEncoder.java
new file mode 100644
index 0000000..6cd6f79
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitBinaryEncoder.java
@@ -0,0 +1,68 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class FruitBinaryEncoder implements Encoder.Binary<Fruit>
+{
+ public static final byte FRUIT_ID_BYTE = (byte)0xAF;
+ // the number of bytes to store a string (1 int)
+ public static final int STRLEN_STORAGE = 4;
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public ByteBuffer encode(Fruit fruit) throws EncodeException
+ {
+ int len = 1; // id byte
+ len += STRLEN_STORAGE + fruit.name.length();
+ len += STRLEN_STORAGE + fruit.color.length();
+
+ ByteBuffer buf = ByteBuffer.allocate(len + 64);
+ buf.flip();
+ buf.put(FRUIT_ID_BYTE);
+ putString(buf,fruit.name);
+ putString(buf,fruit.color);
+ buf.flip();
+
+ return buf;
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+
+ private void putString(ByteBuffer buf, String str)
+ {
+ buf.putInt(str.length());
+ BufferUtil.toBuffer(str,Charset.forName("UTF-8"));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitDecoder.java
new file mode 100644
index 0000000..b3fbb92
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitDecoder.java
@@ -0,0 +1,82 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.websocket.DecodeException;
+import javax.websocket.EndpointConfig;
+
+public class FruitDecoder implements ExtDecoder<Fruit>
+{
+ private String id;
+
+ @Override
+ public Fruit decode(String s) throws DecodeException
+ {
+ Pattern pat = Pattern.compile("([^|]*)|([^|]*)");
+ Matcher mat = pat.matcher(s);
+ if (!mat.find())
+ {
+ throw new DecodeException(s,"Unable to find Fruit reference encoded in text message");
+ }
+
+ Fruit fruit = new Fruit();
+ fruit.name = mat.group(1);
+ fruit.color = mat.group(2);
+
+ return fruit;
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+
+ @Override
+ public void setId(String id)
+ {
+ this.id = id;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "SecondDecoder[id=" + id + "]";
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ if (s == null)
+ {
+ return false;
+ }
+
+ Pattern pat = Pattern.compile("([^|]*)|([^|]*)");
+ Matcher mat = pat.matcher(s);
+ return (mat.find());
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitTextEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitTextEncoder.java
new file mode 100644
index 0000000..edc69ea
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitTextEncoder.java
@@ -0,0 +1,42 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+public class FruitTextEncoder implements Encoder.Text<Fruit>
+{
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public String encode(Fruit fruit) throws EncodeException
+ {
+ return String.format("%s|%s",fruit.name,fruit.color);
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/IntSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/IntSocket.java
new file mode 100644
index 0000000..5c55ad2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/IntSocket.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import java.io.IOException;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.EncodeException;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.decoders.BadDualDecoder;
+
+@ClientEndpoint(decoders =
+{ BadDualDecoder.class })
+public class IntSocket
+{
+ @OnMessage
+ public void onInt(Session session, int value)
+ {
+ try
+ {
+ session.getBasicRemote().sendObject(value);
+ }
+ catch (IOException | EncodeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/utils/ReflectUtilsTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/utils/ReflectUtilsTest.java
new file mode 100644
index 0000000..05f2056
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/utils/ReflectUtilsTest.java
@@ -0,0 +1,138 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.utils;
+
+import static org.hamcrest.Matchers.nullValue;
+
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ReflectUtilsTest
+{
+ public static interface Fruit<T>
+ {
+ }
+
+ public static interface Color<T>
+ {
+ }
+
+ public static interface Food<T> extends Fruit<T>
+ {
+ }
+
+ public static abstract class Apple<T extends Object> implements Fruit<T>, Color<String>
+ {
+ }
+
+ public static abstract class Cherry<A extends Object, B extends Number> implements Fruit<A>, Color<B>
+ {
+ }
+
+ public static abstract class Banana implements Fruit<String>, Color<String>
+ {
+ }
+
+ public static class Washington<Z extends Number, X extends Object> extends Cherry<X, Z>
+ {
+ }
+
+ public static class Rainier extends Washington<Float, Short>
+ {
+ }
+
+ public static class Pizza implements Food<Integer>
+ {
+ }
+
+ public static class Cavendish extends Banana
+ {
+ }
+
+ public static class GrannySmith extends Apple<Long>
+ {
+ }
+
+ public static class Pear implements Fruit<String>, Color<Double>
+ {
+ }
+
+ public static class Kiwi implements Fruit<Character>
+ {
+ }
+
+ @Test
+ public void testFindGeneric_PearFruit()
+ {
+ assertFindGenericClass(Pear.class,Fruit.class,String.class);
+ }
+
+ @Test
+ public void testFindGeneric_PizzaFruit()
+ {
+ assertFindGenericClass(Pizza.class,Fruit.class,Integer.class);
+ }
+
+ @Test
+ public void testFindGeneric_KiwiFruit()
+ {
+ assertFindGenericClass(Kiwi.class,Fruit.class,Character.class);
+ }
+
+ @Test
+ public void testFindGeneric_PearColor()
+ {
+ assertFindGenericClass(Pear.class,Color.class,Double.class);
+ }
+
+ @Test
+ public void testFindGeneric_GrannySmithFruit()
+ {
+ assertFindGenericClass(GrannySmith.class,Fruit.class,Long.class);
+ }
+
+ @Test
+ public void testFindGeneric_CavendishFruit()
+ {
+ assertFindGenericClass(Cavendish.class,Fruit.class,String.class);
+ }
+
+ @Test
+ public void testFindGeneric_RainierFruit()
+ {
+ assertFindGenericClass(Rainier.class,Fruit.class,Short.class);
+ }
+
+ @Test
+ public void testFindGeneric_WashingtonFruit()
+ {
+ // Washington does not have a concrete implementation
+ // of the Fruit interface, this should return null
+ Class<?> impl = ReflectUtils.findGenericClassFor(Washington.class,Fruit.class);
+ Assert.assertThat("Washington -> Fruit implementation",impl,nullValue());
+ }
+
+ private void assertFindGenericClass(Class<?> baseClass, Class<?> ifaceClass, Class<?> expectedClass)
+ {
+ Class<?> foundClass = ReflectUtils.findGenericClassFor(baseClass,ifaceClass);
+ String msg = String.format("Expecting %s<%s> found on %s",ifaceClass.getName(),expectedClass.getName(),baseClass.getName());
+ Assert.assertEquals(msg,expectedClass,foundClass);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/utils/TypeTree.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/utils/TypeTree.java
new file mode 100644
index 0000000..648ebd4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/utils/TypeTree.java
@@ -0,0 +1,120 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.utils;
+
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+
+public class TypeTree
+{
+ public static void dumpTree(String indent, Type type)
+ {
+ if ((type == null) || (type == Object.class))
+ {
+ return;
+ }
+
+ if (type instanceof Class<?>)
+ {
+ Class<?> ctype = (Class<?>)type;
+ System.out.printf("%s (Class) = %s%n",indent,ctype.getName());
+
+ String name = ctype.getName();
+ if (name.startsWith("java.lang.") || name.startsWith("java.io."))
+ {
+ // filter away standard classes from tree (otherwise it will go on infinitely)
+ return;
+ }
+
+ Type superType = ctype.getGenericSuperclass();
+ dumpTree(indent + ".genericSuperClass()",superType);
+
+ Type[] ifaces = ctype.getGenericInterfaces();
+ if ((ifaces != null) && (ifaces.length > 0))
+ {
+ // System.out.printf("%s.genericInterfaces[].length = %d%n",indent,ifaces.length);
+ for (int i = 0; i < ifaces.length; i++)
+ {
+ Type iface = ifaces[i];
+ dumpTree(indent + ".genericInterfaces[" + i + "]",iface);
+ }
+ }
+
+ TypeVariable<?>[] typeParams = ctype.getTypeParameters();
+ if ((typeParams != null) && (typeParams.length > 0))
+ {
+ // System.out.printf("%s.typeParameters[].length = %d%n",indent,typeParams.length);
+ for (int i = 0; i < typeParams.length; i++)
+ {
+ TypeVariable<?> typeParam = typeParams[i];
+ dumpTree(indent + ".typeParameters[" + i + "]",typeParam);
+ }
+ }
+ return;
+ }
+
+ if (type instanceof ParameterizedType)
+ {
+ ParameterizedType ptype = (ParameterizedType)type;
+ System.out.printf("%s (ParameterizedType) = %s%n",indent,ReflectUtils.toShortName(ptype));
+ // dumpTree(indent + ".ownerType()",ptype.getOwnerType());
+ dumpTree(indent + ".rawType(" + ReflectUtils.toShortName(ptype.getRawType()) + ")",ptype.getRawType());
+ Type args[] = ptype.getActualTypeArguments();
+ if (args != null)
+ {
+ System.out.printf("%s.actualTypeArguments[].length = %d%n",indent,args.length);
+ for (int i = 0; i < args.length; i++)
+ {
+ Type arg = args[i];
+ dumpTree(indent + ".actualTypeArguments[" + i + "]",arg);
+ }
+ }
+ return;
+ }
+
+ if (type instanceof GenericArrayType)
+ {
+ GenericArrayType gtype = (GenericArrayType)type;
+ System.out.printf("%s (GenericArrayType) = %s%n",indent,gtype);
+ return;
+ }
+
+ if (type instanceof TypeVariable<?>)
+ {
+ TypeVariable<?> tvar = (TypeVariable<?>)type;
+ System.out.printf("%s (TypeVariable) = %s%n",indent,tvar);
+ System.out.printf("%s.getName() = %s%n",indent,tvar.getName());
+ System.out.printf("%s.getGenericDeclaration() = %s%n",indent,tvar.getGenericDeclaration());
+ return;
+ }
+
+ if (type instanceof WildcardType)
+ {
+ System.out.printf("%s (WildcardType) = %s%n",indent,type);
+ return;
+ }
+
+ System.out.printf("%s (?) = %s%n",indent,type);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/resources/jetty-logging.properties b/jetty-websocket/javax-websocket-client-impl/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..6c5baae
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/resources/jetty-logging.properties
@@ -0,0 +1,5 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.LEVEL=WARN
+
+# org.eclipse.jetty.websocket.LEVEL=WARN
+# org.eclipse.jetty.websocket.jsr356.LEVEL=DEBUG
diff --git a/jetty-websocket/javax-websocket-server-impl/pom.xml b/jetty-websocket/javax-websocket-server-impl/pom.xml
new file mode 100644
index 0000000..205a04e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/pom.xml
@@ -0,0 +1,71 @@
+<?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.websocket</groupId>
+ <artifactId>websocket-parent</artifactId>
+ <version>9.1.1-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>javax-websocket-server-impl</artifactId>
+ <name>Jetty :: Websocket :: javax.websocket.server :: Server Implementation</name>
+
+ <properties>
+ <bundle-symbolic-name>${project.groupId}.javax.websocket.server</bundle-symbolic-name>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-annotations</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>javax-websocket-client-impl</artifactId>
+ <version>${project.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.websocket</groupId>
+ <artifactId>javax.websocket-client-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>websocket-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.websocket</groupId>
+ <artifactId>javax.websocket-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-test-helper</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <descriptorRef>config</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/config/modules/websocket.mod b/jetty-websocket/javax-websocket-server-impl/src/main/config/modules/websocket.mod
new file mode 100644
index 0000000..e866b17
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/config/modules/websocket.mod
@@ -0,0 +1,12 @@
+#
+# WebSocket Module
+#
+
+[depend]
+# javax.websocket needs annotations
+annotations
+
+[lib]
+lib/websocket/*.jar
+
+
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointConfig.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointConfig.java
new file mode 100644
index 0000000..4652e9d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointConfig.java
@@ -0,0 +1,209 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.Decoder;
+import javax.websocket.DeploymentException;
+import javax.websocket.Encoder;
+import javax.websocket.Extension;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+public class AnnotatedServerEndpointConfig implements ServerEndpointConfig
+{
+ private final Class<?> endpointClass;
+ private final String path;
+ private final List<Class<? extends Decoder>> decoders;
+ private final List<Class<? extends Encoder>> encoders;
+ private final Configurator configurator;
+ private final List<String> subprotocols;
+
+ private Map<String, Object> userProperties;
+ private List<Extension> extensions;
+
+ public AnnotatedServerEndpointConfig(Class<?> endpointClass, ServerEndpoint anno) throws DeploymentException
+ {
+ this(endpointClass,anno,null);
+ }
+
+ public AnnotatedServerEndpointConfig(Class<?> endpointClass, ServerEndpoint anno, ServerEndpointConfig baseConfig) throws DeploymentException
+ {
+ Configurator configr = null;
+
+ // Copy from base config
+ if (baseConfig != null)
+ {
+ configr = baseConfig.getConfigurator();
+ }
+
+ // Decoders (favor provided config over annotation)
+ if (baseConfig != null && baseConfig.getDecoders() != null && baseConfig.getDecoders().size() > 0)
+ {
+ this.decoders = Collections.unmodifiableList(baseConfig.getDecoders());
+ }
+ else
+ {
+ this.decoders = Collections.unmodifiableList(Arrays.asList(anno.decoders()));
+ }
+
+ // Encoders (favor provided config over annotation)
+ if (baseConfig != null && baseConfig.getEncoders() != null && baseConfig.getEncoders().size() > 0)
+ {
+ this.encoders = Collections.unmodifiableList(baseConfig.getEncoders());
+ }
+ else
+ {
+ this.encoders = Collections.unmodifiableList(Arrays.asList(anno.encoders()));
+ }
+
+ // Sub Protocols (favor provided config over annotation)
+ if (baseConfig != null && baseConfig.getSubprotocols() != null && baseConfig.getSubprotocols().size() > 0)
+ {
+ this.subprotocols = Collections.unmodifiableList(baseConfig.getSubprotocols());
+ }
+ else
+ {
+ this.subprotocols = Collections.unmodifiableList(Arrays.asList(anno.subprotocols()));
+ }
+
+ // Path (favor provided config over annotation)
+ if (baseConfig != null && baseConfig.getPath() != null && baseConfig.getPath().length() > 0)
+ {
+ this.path = baseConfig.getPath();
+ }
+ else
+ {
+ this.path = anno.value();
+ }
+
+ // supplied by init lifecycle
+ this.extensions = new ArrayList<>();
+ // always what is passed in
+ this.endpointClass = endpointClass;
+ // UserProperties in annotation
+ this.userProperties = new HashMap<>();
+ if (baseConfig != null && baseConfig.getUserProperties() != null && baseConfig.getUserProperties().size() > 0)
+ {
+ userProperties.putAll(baseConfig.getUserProperties());
+ }
+
+ if (anno.configurator() == ServerEndpointConfig.Configurator.class)
+ {
+ if (configr != null)
+ {
+ this.configurator = configr;
+ }
+ else
+ {
+ this.configurator = BasicServerEndpointConfigurator.INSTANCE;
+ }
+ }
+ else
+ {
+ try
+ {
+ this.configurator = anno.configurator().newInstance();
+ }
+ catch (InstantiationException | IllegalAccessException e)
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Unable to instantiate ClientEndpoint.configurator() of ");
+ err.append(anno.configurator().getName());
+ err.append(" defined as annotation in ");
+ err.append(anno.getClass().getName());
+ throw new DeploymentException(err.toString(),e);
+ }
+ }
+ }
+
+ @Override
+ public Configurator getConfigurator()
+ {
+ return configurator;
+ }
+
+ @Override
+ public List<Class<? extends Decoder>> getDecoders()
+ {
+ return decoders;
+ }
+
+ @Override
+ public List<Class<? extends Encoder>> getEncoders()
+ {
+ return encoders;
+ }
+
+ @Override
+ public Class<?> getEndpointClass()
+ {
+ return endpointClass;
+ }
+
+ @Override
+ public List<Extension> getExtensions()
+ {
+ return extensions;
+ }
+
+ @Override
+ public String getPath()
+ {
+ return path;
+ }
+
+ @Override
+ public List<String> getSubprotocols()
+ {
+ return subprotocols;
+ }
+
+ @Override
+ public Map<String, Object> getUserProperties()
+ {
+ return userProperties;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("AnnotatedServerEndpointConfig[endpointClass=");
+ builder.append(endpointClass);
+ builder.append(",path=");
+ builder.append(path);
+ builder.append(",decoders=");
+ builder.append(decoders);
+ builder.append(",encoders=");
+ builder.append(encoders);
+ builder.append(",subprotocols=");
+ builder.append(subprotocols);
+ builder.append(",extensions=");
+ builder.append(extensions);
+ builder.append("]");
+ return builder.toString();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointMetadata.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointMetadata.java
new file mode 100644
index 0000000..e185074
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointMetadata.java
@@ -0,0 +1,108 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.LinkedList;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.annotations.IJsrParamId;
+
+public class AnnotatedServerEndpointMetadata extends AnnotatedEndpointMetadata<ServerEndpoint,ServerEndpointConfig> implements ServerEndpointMetadata
+{
+ private final ServerEndpoint endpoint;
+ private final AnnotatedServerEndpointConfig config;
+
+ protected AnnotatedServerEndpointMetadata(Class<?> websocket, ServerEndpointConfig baseConfig) throws DeploymentException
+ {
+ super(websocket);
+
+ ServerEndpoint anno = websocket.getAnnotation(ServerEndpoint.class);
+ if (anno == null)
+ {
+ throw new InvalidWebSocketException("Unsupported WebSocket object, missing @" + ServerEndpoint.class + " annotation");
+ }
+
+ this.endpoint = anno;
+ this.config = new AnnotatedServerEndpointConfig(websocket,anno,baseConfig);
+
+ getDecoders().addAll(anno.decoders());
+ getEncoders().addAll(anno.encoders());
+ }
+
+ @Override
+ public void customizeParamsOnClose(LinkedList<IJsrParamId> params)
+ {
+ super.customizeParamsOnClose(params);
+ params.addFirst(JsrPathParamId.INSTANCE);
+ }
+
+ @Override
+ public void customizeParamsOnError(LinkedList<IJsrParamId> params)
+ {
+ super.customizeParamsOnError(params);
+ params.addFirst(JsrPathParamId.INSTANCE);
+ }
+
+ @Override
+ public void customizeParamsOnOpen(LinkedList<IJsrParamId> params)
+ {
+ super.customizeParamsOnOpen(params);
+ params.addFirst(JsrPathParamId.INSTANCE);
+ }
+
+ @Override
+ public void customizeParamsOnMessage(LinkedList<IJsrParamId> params)
+ {
+ super.customizeParamsOnMessage(params);
+ params.addFirst(JsrPathParamId.INSTANCE);
+ }
+
+ @Override
+ public ServerEndpoint getAnnotation()
+ {
+ return endpoint;
+ }
+
+ public AnnotatedServerEndpointConfig getConfig()
+ {
+ return config;
+ }
+
+ public String getPath()
+ {
+ return config.getPath();
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("AnnotatedServerEndpointMetadata[endpoint=");
+ builder.append(endpoint);
+ builder.append(",config=");
+ builder.append(config);
+ builder.append("]");
+ return builder.toString();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/BasicServerEndpointConfig.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/BasicServerEndpointConfig.java
new file mode 100644
index 0000000..c77b01f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/BasicServerEndpointConfig.java
@@ -0,0 +1,122 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+import javax.websocket.Extension;
+import javax.websocket.server.ServerEndpointConfig;
+
+public class BasicServerEndpointConfig implements ServerEndpointConfig
+{
+ private final List<Class<? extends Decoder>> decoders;
+ private final List<Class<? extends Encoder>> encoders;
+ private final List<Extension> extensions;
+ private final List<String> subprotocols;
+ private final Configurator configurator;
+ private final Class<?> endpointClass;
+ private final String path;
+ private Map<String, Object> userProperties;
+
+ public BasicServerEndpointConfig(Class<?> endpointClass, String path)
+ {
+ this.endpointClass = endpointClass;
+ this.path = path;
+
+ this.decoders = new ArrayList<>();
+ this.encoders = new ArrayList<>();
+ this.subprotocols = new ArrayList<>();
+ this.extensions = new ArrayList<>();
+ this.userProperties = new HashMap<>();
+ this.configurator = BasicServerEndpointConfigurator.INSTANCE;
+ }
+
+ public BasicServerEndpointConfig(ServerEndpointConfig copy)
+ {
+ this.endpointClass = copy.getEndpointClass();
+ this.path = copy.getPath();
+
+ this.decoders = new ArrayList<>(copy.getDecoders());
+ this.encoders = new ArrayList<>(copy.getEncoders());
+ this.subprotocols = new ArrayList<>(copy.getSubprotocols());
+ this.extensions = new ArrayList<>(copy.getExtensions());
+ this.userProperties = new HashMap<>(copy.getUserProperties());
+ if (copy.getConfigurator() != null)
+ {
+ this.configurator = copy.getConfigurator();
+ }
+ else
+ {
+ this.configurator = BasicServerEndpointConfigurator.INSTANCE;
+ }
+ }
+
+ @Override
+ public List<Class<? extends Encoder>> getEncoders()
+ {
+ return encoders;
+ }
+
+ @Override
+ public List<Class<? extends Decoder>> getDecoders()
+ {
+ return decoders;
+ }
+
+ @Override
+ public Map<String, Object> getUserProperties()
+ {
+ return userProperties;
+ }
+
+ @Override
+ public Class<?> getEndpointClass()
+ {
+ return endpointClass;
+ }
+
+ @Override
+ public String getPath()
+ {
+ return path;
+ }
+
+ @Override
+ public List<String> getSubprotocols()
+ {
+ return subprotocols;
+ }
+
+ @Override
+ public List<Extension> getExtensions()
+ {
+ return extensions;
+ }
+
+ @Override
+ public Configurator getConfigurator()
+ {
+ return configurator;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/BasicServerEndpointConfigurator.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/BasicServerEndpointConfigurator.java
new file mode 100644
index 0000000..11ad568
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/BasicServerEndpointConfigurator.java
@@ -0,0 +1,99 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.List;
+
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+import javax.websocket.server.ServerEndpointConfig.Configurator;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+
+public class BasicServerEndpointConfigurator extends Configurator
+{
+ private static final Logger LOG = Log.getLogger(BasicServerEndpointConfigurator.class);
+ public static final Configurator INSTANCE = new BasicServerEndpointConfigurator();
+
+ @Override
+ public boolean checkOrigin(String originHeaderValue)
+ {
+ return true;
+ }
+
+ @Override
+ public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException
+ {
+ LOG.debug(".getEndpointInstance({})",endpointClass);
+ try
+ {
+ return endpointClass.newInstance();
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new InstantiationException(String.format("%s: %s",e.getClass().getName(),e.getMessage()));
+ }
+ }
+
+ @Override
+ public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested)
+ {
+ return requested;
+ }
+
+ @Override
+ public String getNegotiatedSubprotocol(List<String> supported, List<String> requested)
+ {
+ if ((requested == null) || (requested.size() == 0))
+ {
+ // nothing requested, don't return anything
+ return null;
+ }
+
+ // Nothing specifically called out as being supported by the endpoint
+ if ((supported == null) || (supported.isEmpty()))
+ {
+ // Just return the first hit in this case
+ LOG.warn("Client requested Subprotocols on endpoint with none supported: {}", QuoteUtil.join(requested,","));
+ return null;
+ }
+
+ // Return the first matching hit from the list of supported protocols.
+ for (String possible : requested)
+ {
+ if (supported.contains(possible))
+ {
+ return possible;
+ }
+ }
+
+ LOG.warn("Client requested subprotocols {} do not match any endpoint supported subprotocols {}", QuoteUtil.join(requested,","), QuoteUtil.join(supported,","));
+ return null;
+ }
+
+ @Override
+ public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+ {
+ /* do nothing */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java
new file mode 100644
index 0000000..69b2d10
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java
@@ -0,0 +1,145 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.Extension;
+import javax.websocket.Extension.Parameter;
+import javax.websocket.server.ServerEndpointConfig;
+import javax.websocket.server.ServerEndpointConfig.Configurator;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+import org.eclipse.jetty.websocket.jsr356.JsrExtension;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+public class JsrCreator implements WebSocketCreator
+{
+ private static final Logger LOG = Log.getLogger(JsrCreator.class);
+ private final ServerEndpointMetadata metadata;
+ private final ExtensionFactory extensionFactory;
+
+ public JsrCreator(ServerEndpointMetadata metadata, ExtensionFactory extensionFactory)
+ {
+ this.metadata = metadata;
+ this.extensionFactory = extensionFactory;
+ }
+
+ @Override
+ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+ {
+ JsrHandshakeRequest hsreq = new JsrHandshakeRequest(req);
+ JsrHandshakeResponse hsresp = new JsrHandshakeResponse(resp);
+
+ ServerEndpointConfig config = metadata.getConfig();
+
+ Configurator configurator = config.getConfigurator();
+
+ // modify handshake
+ configurator.modifyHandshake(config,hsreq,hsresp);
+
+ // check origin
+ if (!configurator.checkOrigin(req.getOrigin()))
+ {
+ try
+ {
+ resp.sendForbidden("Origin mismatch");
+ }
+ catch (IOException e)
+ {
+ LOG.debug("Unable to send error response",e);
+ }
+ return null;
+ }
+
+ // deal with sub protocols
+ List<String> supported = config.getSubprotocols();
+ List<String> requested = req.getSubProtocols();
+ String subprotocol = configurator.getNegotiatedSubprotocol(supported,requested);
+ if (subprotocol != null)
+ {
+ resp.setAcceptedSubProtocol(subprotocol);
+ }
+
+ // deal with extensions
+ List<Extension> installedExts = new ArrayList<>();
+ for (String extName : extensionFactory.getAvailableExtensions().keySet())
+ {
+ installedExts.add(new JsrExtension(extName));
+ }
+ List<Extension> requestedExts = new ArrayList<>();
+ for (ExtensionConfig reqCfg : req.getExtensions())
+ {
+ requestedExts.add(new JsrExtension(reqCfg));
+ }
+ List<Extension> usedExts = configurator.getNegotiatedExtensions(installedExts,requestedExts);
+ List<ExtensionConfig> configs = new ArrayList<>();
+ if (usedExts != null)
+ {
+ for (Extension used : usedExts)
+ {
+ ExtensionConfig ecfg = new ExtensionConfig(used.getName());
+ for (Parameter param : used.getParameters())
+ {
+ ecfg.setParameter(param.getName(),param.getValue());
+ }
+ configs.add(ecfg);
+ }
+ }
+ resp.setExtensions(configs);
+
+ // create endpoint class
+ try
+ {
+ Class<?> endpointClass = config.getEndpointClass();
+ Object endpoint = config.getConfigurator().getEndpointInstance(endpointClass);
+ PathSpec pathSpec = hsreq.getRequestPathSpec();
+ if (pathSpec instanceof WebSocketPathSpec)
+ {
+ // We have a PathParam path spec
+ WebSocketPathSpec wspathSpec = (WebSocketPathSpec)pathSpec;
+ String requestPath = req.getRequestPath();
+ // Wrap the config with the path spec information
+ config = new PathParamServerEndpointConfig(config,wspathSpec,requestPath);
+ }
+ return new EndpointInstance(endpoint,config,metadata);
+ }
+ catch (InstantiationException e)
+ {
+ LOG.debug("Unable to create websocket: " + config.getEndpointClass().getName(),e);
+ return null;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[metadata=%s]",this.getClass().getName(),metadata);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java
new file mode 100644
index 0000000..f004efb
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java
@@ -0,0 +1,86 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.server.HandshakeRequest;
+
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+
+public class JsrHandshakeRequest implements HandshakeRequest
+{
+ private final ServletUpgradeRequest request;
+
+ public JsrHandshakeRequest(ServletUpgradeRequest req)
+ {
+ this.request = req;
+ }
+
+ @Override
+ public Map<String, List<String>> getHeaders()
+ {
+ return request.getHeaders();
+ }
+
+ @Override
+ public Object getHttpSession()
+ {
+ return request.getSession();
+ }
+
+ @Override
+ public Map<String, List<String>> getParameterMap()
+ {
+ return request.getParameterMap();
+ }
+
+ @Override
+ public String getQueryString()
+ {
+ return request.getQueryString();
+ }
+
+ public PathSpec getRequestPathSpec()
+ {
+ return (PathSpec)request.getServletAttribute(PathSpec.class.getName());
+ }
+
+ @Override
+ public URI getRequestURI()
+ {
+ return request.getRequestURI();
+ }
+
+ @Override
+ public Principal getUserPrincipal()
+ {
+ return request.getUserPrincipal();
+ }
+
+ @Override
+ public boolean isUserInRole(String role)
+ {
+ return request.isUserInRole(role);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeResponse.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeResponse.java
new file mode 100644
index 0000000..0eaf62f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeResponse.java
@@ -0,0 +1,42 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.HandshakeResponse;
+
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+
+public class JsrHandshakeResponse implements HandshakeResponse
+{
+ private final UpgradeResponse response;
+
+ public JsrHandshakeResponse(UpgradeResponse resp)
+ {
+ this.response = resp;
+ }
+
+ @Override
+ public Map<String, List<String>> getHeaders()
+ {
+ return response.getHeaders();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrPathParamId.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrPathParamId.java
new file mode 100644
index 0000000..bc7ddac
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrPathParamId.java
@@ -0,0 +1,49 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import javax.websocket.server.PathParam;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.IJsrParamId;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrCallable;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Param handling for static parameters annotated with @{@link PathParam}
+ */
+public class JsrPathParamId implements IJsrParamId
+{
+ public static final IJsrParamId INSTANCE = new JsrPathParamId();
+
+ @Override
+ public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+ {
+ PathParam pathparam = param.getAnnotation(PathParam.class);
+ if(pathparam != null)
+ {
+ param.bind(Role.PATH_PARAM);
+ param.setPathParamName(pathparam.value());
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerEndpointImpl.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerEndpointImpl.java
new file mode 100644
index 0000000..660734e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerEndpointImpl.java
@@ -0,0 +1,79 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverImpl;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrEvents;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrAnnotatedEventDriver;
+
+/**
+ * Event Driver for classes annotated with @{@link ServerEndpoint}
+ */
+public class JsrServerEndpointImpl implements EventDriverImpl
+{
+ @Override
+ public EventDriver create(Object websocket, WebSocketPolicy policy) throws Throwable
+ {
+ if (!(websocket instanceof EndpointInstance))
+ {
+ throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),EndpointInstance.class.getName()));
+ }
+
+ EndpointInstance ei = (EndpointInstance)websocket;
+ AnnotatedServerEndpointMetadata metadata = (AnnotatedServerEndpointMetadata)ei.getMetadata();
+ JsrEvents<ServerEndpoint, ServerEndpointConfig> events = new JsrEvents<>(metadata);
+ JsrAnnotatedEventDriver driver = new JsrAnnotatedEventDriver(policy,ei,events);
+
+ ServerEndpointConfig config = (ServerEndpointConfig)ei.getConfig();
+ if (config instanceof PathParamServerEndpointConfig)
+ {
+ PathParamServerEndpointConfig ppconfig = (PathParamServerEndpointConfig)config;
+ driver.setPathParameters(ppconfig.getPathParamMap());
+ }
+
+ return driver;
+ }
+
+ @Override
+ public String describeRule()
+ {
+ return "class is annotated with @" + ServerEndpoint.class.getName();
+ }
+
+ @Override
+ public boolean supports(Object websocket)
+ {
+ if (!(websocket instanceof EndpointInstance))
+ {
+ return false;
+ }
+
+ EndpointInstance ei = (EndpointInstance)websocket;
+ Object endpoint = ei.getEndpoint();
+
+ ServerEndpoint anno = endpoint.getClass().getAnnotation(ServerEndpoint.class);
+ return (anno != null);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerExtendsEndpointImpl.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerExtendsEndpointImpl.java
new file mode 100644
index 0000000..57a2d7e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerExtendsEndpointImpl.java
@@ -0,0 +1,71 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverImpl;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEndpointEventDriver;
+
+public class JsrServerExtendsEndpointImpl implements EventDriverImpl
+{
+ @Override
+ public EventDriver create(Object websocket, WebSocketPolicy policy)
+ {
+ if (!(websocket instanceof EndpointInstance))
+ {
+ throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),EndpointInstance.class.getName()));
+ }
+
+ EndpointInstance ei = (EndpointInstance)websocket;
+ JsrEndpointEventDriver driver = new JsrEndpointEventDriver(policy, ei);
+
+ ServerEndpointConfig config = (ServerEndpointConfig)ei.getConfig();
+ if (config instanceof PathParamServerEndpointConfig)
+ {
+ PathParamServerEndpointConfig ppconfig = (PathParamServerEndpointConfig)config;
+ driver.setPathParameters(ppconfig.getPathParamMap());
+ }
+
+ return driver;
+ }
+
+ @Override
+ public String describeRule()
+ {
+ return "class extends " + javax.websocket.Endpoint.class.getName();
+ }
+
+ @Override
+ public boolean supports(Object websocket)
+ {
+ if (!(websocket instanceof EndpointInstance))
+ {
+ return false;
+ }
+
+ EndpointInstance ei = (EndpointInstance)websocket;
+ Object endpoint = ei.getEndpoint();
+
+ return (endpoint instanceof javax.websocket.Endpoint);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java
new file mode 100644
index 0000000..758b606
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java
@@ -0,0 +1,51 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec;
+
+/**
+ * Wrapper for a {@link ServerEndpointConfig} where there PathParm information from the incoming request.
+ */
+public class PathParamServerEndpointConfig extends BasicServerEndpointConfig implements ServerEndpointConfig
+{
+ private final Map<String, String> pathParamMap;
+
+ public PathParamServerEndpointConfig(ServerEndpointConfig config, WebSocketPathSpec pathSpec, String requestPath)
+ {
+ super(config);
+
+ Map<String, String> pathMap = pathSpec.getPathParams(requestPath);
+ pathParamMap = new HashMap<String, String>();
+ if (pathMap != null)
+ {
+ pathParamMap.putAll(pathMap);
+ }
+ }
+
+ public Map<String, String> getPathParamMap()
+ {
+ return pathParamMap;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java
new file mode 100644
index 0000000..d30f62e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java
@@ -0,0 +1,190 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.concurrent.Executor;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
+import org.eclipse.jetty.websocket.jsr356.ClientContainer;
+import org.eclipse.jetty.websocket.jsr356.JsrSessionFactory;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec;
+import org.eclipse.jetty.websocket.server.MappedWebSocketCreator;
+import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
+
+public class ServerContainer extends ClientContainer implements javax.websocket.server.ServerContainer
+{
+ private static final Logger LOG = Log.getLogger(ServerContainer.class);
+
+ private final MappedWebSocketCreator mappedCreator;
+ private final WebSocketServerFactory webSocketServerFactory;
+
+ public ServerContainer(MappedWebSocketCreator creator, WebSocketServerFactory factory, Executor executor)
+ {
+ super(executor);
+ this.mappedCreator = creator;
+ this.webSocketServerFactory = factory;
+ EventDriverFactory eventDriverFactory = this.webSocketServerFactory.getEventDriverFactory();
+ eventDriverFactory.addImplementation(new JsrServerEndpointImpl());
+ eventDriverFactory.addImplementation(new JsrServerExtendsEndpointImpl());
+ this.webSocketServerFactory.addSessionFactory(new JsrSessionFactory(this));
+ }
+
+ public EndpointInstance newClientEndpointInstance(Object endpoint, ServerEndpointConfig config, String path)
+ {
+ EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass());
+ ServerEndpointConfig cec = config;
+ if (config == null)
+ {
+ if (metadata instanceof AnnotatedServerEndpointMetadata)
+ {
+ cec = ((AnnotatedServerEndpointMetadata)metadata).getConfig();
+ }
+ else
+ {
+ cec = new BasicServerEndpointConfig(endpoint.getClass(),path);
+ }
+ }
+ return new EndpointInstance(endpoint,cec,metadata);
+ }
+
+ @Override
+ public void addEndpoint(Class<?> endpointClass) throws DeploymentException
+ {
+ ServerEndpointMetadata metadata = getServerEndpointMetadata(endpointClass,null);
+ addEndpoint(metadata);
+ }
+
+ public void addEndpoint(ServerEndpointMetadata metadata) throws DeploymentException
+ {
+ JsrCreator creator = new JsrCreator(metadata,webSocketServerFactory.getExtensionFactory());
+ mappedCreator.addMapping(new WebSocketPathSpec(metadata.getPath()),creator);
+ }
+
+ @Override
+ public void addEndpoint(ServerEndpointConfig config) throws DeploymentException
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("addEndpoint({}) path={} endpoint={}",config,config.getPath(),config.getEndpointClass());
+ }
+ ServerEndpointMetadata metadata = getServerEndpointMetadata(config.getEndpointClass(),config);
+ addEndpoint(metadata);
+ }
+
+ public ServerEndpointMetadata getServerEndpointMetadata(final Class<?> endpoint, final ServerEndpointConfig config) throws DeploymentException
+ {
+ ServerEndpointMetadata metadata = null;
+
+ ServerEndpoint anno = endpoint.getAnnotation(ServerEndpoint.class);
+ if (anno != null)
+ {
+ // Annotated takes precedence here
+ AnnotatedServerEndpointMetadata ametadata = new AnnotatedServerEndpointMetadata(endpoint,config);
+ AnnotatedEndpointScanner<ServerEndpoint, ServerEndpointConfig> scanner = new AnnotatedEndpointScanner<>(ametadata);
+ metadata = ametadata;
+ scanner.scan();
+ }
+ else if (Endpoint.class.isAssignableFrom(endpoint))
+ {
+ // extends Endpoint
+ @SuppressWarnings("unchecked")
+ Class<? extends Endpoint> eendpoint = (Class<? extends Endpoint>)endpoint;
+ metadata = new SimpleServerEndpointMetadata(eendpoint,config);
+ }
+ else
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Not a recognized websocket [");
+ err.append(endpoint.getName());
+ err.append("] does not extend @").append(ServerEndpoint.class.getName());
+ err.append(" or extend from ").append(Endpoint.class.getName());
+ throw new DeploymentException("Unable to identify as valid Endpoint: " + endpoint);
+ }
+
+ return metadata;
+ }
+
+ @Override
+ public long getDefaultAsyncSendTimeout()
+ {
+ return webSocketServerFactory.getPolicy().getAsyncWriteTimeout();
+ }
+
+ @Override
+ public int getDefaultMaxBinaryMessageBufferSize()
+ {
+ return webSocketServerFactory.getPolicy().getMaxBinaryMessageSize();
+ }
+
+ @Override
+ public long getDefaultMaxSessionIdleTimeout()
+ {
+ return webSocketServerFactory.getPolicy().getIdleTimeout();
+ }
+
+ @Override
+ public int getDefaultMaxTextMessageBufferSize()
+ {
+ return webSocketServerFactory.getPolicy().getMaxTextMessageSize();
+ }
+
+ @Override
+ public void setAsyncSendTimeout(long ms)
+ {
+ super.setAsyncSendTimeout(ms);
+ webSocketServerFactory.getPolicy().setAsyncWriteTimeout(ms);
+ }
+
+ @Override
+ public void setDefaultMaxBinaryMessageBufferSize(int max)
+ {
+ super.setDefaultMaxBinaryMessageBufferSize(max);
+ // overall message limit (used in non-streaming)
+ webSocketServerFactory.getPolicy().setMaxBinaryMessageSize(max);
+ // incoming streaming buffer size
+ webSocketServerFactory.getPolicy().setMaxBinaryMessageBufferSize(max);
+ }
+
+ @Override
+ public void setDefaultMaxSessionIdleTimeout(long ms)
+ {
+ super.setDefaultMaxSessionIdleTimeout(ms);
+ webSocketServerFactory.getPolicy().setIdleTimeout(ms);
+ }
+
+ @Override
+ public void setDefaultMaxTextMessageBufferSize(int max)
+ {
+ super.setDefaultMaxTextMessageBufferSize(max);
+ // overall message limit (used in non-streaming)
+ webSocketServerFactory.getPolicy().setMaxTextMessageSize(max);
+ // incoming streaming buffer size
+ webSocketServerFactory.getPolicy().setMaxTextMessageBufferSize(max);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerEndpointMetadata.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerEndpointMetadata.java
new file mode 100644
index 0000000..25e98c5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerEndpointMetadata.java
@@ -0,0 +1,30 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+
+public interface ServerEndpointMetadata extends EndpointMetadata
+{
+ ServerEndpointConfig getConfig();
+
+ public String getPath();
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/SimpleServerEndpointMetadata.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/SimpleServerEndpointMetadata.java
new file mode 100644
index 0000000..ced8246
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/SimpleServerEndpointMetadata.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import javax.websocket.Endpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.client.SimpleEndpointMetadata;
+
+public class SimpleServerEndpointMetadata extends SimpleEndpointMetadata implements ServerEndpointMetadata
+{
+ private final ServerEndpointConfig config;
+
+ public SimpleServerEndpointMetadata(Class<? extends Endpoint> endpointClass, ServerEndpointConfig config)
+ {
+ super(endpointClass);
+ this.config = config;
+ if (this.config != null)
+ {
+ getDecoders().addAll(config.getDecoders());
+ getEncoders().addAll(config.getEncoders());
+ }
+ }
+
+ @Override
+ public ServerEndpointConfig getConfig()
+ {
+ return config;
+ }
+
+ @Override
+ public String getPath()
+ {
+ return config.getPath();
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("SimpleServerEndpointMetadata [");
+ builder.append("config=").append(config.getClass().getName());
+ builder.append(",path=").append(config.getPath());
+ builder.append(",endpoint=").append(config.getEndpointClass());
+ builder.append(",decoders=").append(config.getDecoders());
+ builder.append(",encoders=").append(config.getEncoders());
+ builder.append("]");
+ return builder.toString();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java
new file mode 100644
index 0000000..6b8779c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java
@@ -0,0 +1,211 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.deploy;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.HandlesTypes;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.server.ServerApplicationConfig;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
+import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter;
+
+@HandlesTypes(
+{ ServerApplicationConfig.class, ServerEndpoint.class, Endpoint.class })
+public class WebSocketServerContainerInitializer implements ServletContainerInitializer
+{
+ public static final String ENABLE_KEY = "org.eclipse.jetty.websocket.jsr356";
+ private static final Logger LOG = Log.getLogger(WebSocketServerContainerInitializer.class);
+
+ public static boolean isJSR356EnabledOnContext(ServletContext context)
+ {
+ Object enable = context.getAttribute(ENABLE_KEY);
+ if (enable instanceof Boolean)
+ {
+ return ((Boolean)enable).booleanValue();
+ }
+
+ return true;
+ }
+
+ public static ServerContainer configureContext(ServletContextHandler context)
+ {
+ // Create Filter
+ WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context);
+
+ // Store reference to the WebSocketUpgradeFilter
+ context.setAttribute(WebSocketUpgradeFilter.class.getName(),filter);
+
+ // Create the Jetty ServerContainer implementation
+ ServerContainer jettyContainer = new ServerContainer(filter,filter.getFactory(),context.getServer().getThreadPool());
+ context.addBean(jettyContainer);
+
+ // Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
+ context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
+
+ return jettyContainer;
+ }
+
+ @Override
+ public void onStartup(Set<Class<?>> c, ServletContext context) throws ServletException
+ {
+ if (!isJSR356EnabledOnContext(context))
+ {
+ LOG.info("JSR-356 support disabled via attribute on context {} - {}",context.getContextPath(),context);
+ return;
+ }
+
+ ContextHandler handler = ContextHandler.getContextHandler(context);
+
+ if (handler == null)
+ {
+ throw new ServletException("Not running on Jetty, JSR support disabled");
+ }
+
+ if (!(handler instanceof ServletContextHandler))
+ {
+ throw new ServletException("Not running in Jetty ServletContextHandler, JSR support disabled");
+ }
+
+ ServletContextHandler jettyContext = (ServletContextHandler)handler;
+
+ // Create the Jetty ServerContainer implementation
+ ServerContainer jettyContainer = configureContext(jettyContext);
+
+ // Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
+ context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
+
+ LOG.debug("Found {} classes",c.size());
+
+ // Now process the incoming classes
+ Set<Class<? extends Endpoint>> discoveredExtendedEndpoints = new HashSet<>();
+ Set<Class<?>> discoveredAnnotatedEndpoints = new HashSet<>();
+ Set<Class<? extends ServerApplicationConfig>> serverAppConfigs = new HashSet<>();
+
+ filterClasses(c,discoveredExtendedEndpoints,discoveredAnnotatedEndpoints,serverAppConfigs);
+
+ LOG.debug("Discovered {} extends Endpoint classes",discoveredExtendedEndpoints.size());
+ LOG.debug("Discovered {} @ServerEndpoint classes",discoveredAnnotatedEndpoints.size());
+ LOG.debug("Discovered {} ServerApplicationConfig classes",serverAppConfigs.size());
+
+ // Process the server app configs to determine endpoint filtering
+ boolean wasFiltered = false;
+ Set<ServerEndpointConfig> deployableExtendedEndpointConfigs = new HashSet<>();
+ Set<Class<?>> deployableAnnotatedEndpoints = new HashSet<>();
+
+ for (Class<? extends ServerApplicationConfig> clazz : serverAppConfigs)
+ {
+ LOG.debug("Found ServerApplicationConfig: {}",clazz);
+ try
+ {
+ ServerApplicationConfig config = (ServerApplicationConfig)clazz.newInstance();
+
+ Set<ServerEndpointConfig> seconfigs = config.getEndpointConfigs(discoveredExtendedEndpoints);
+ if (seconfigs != null)
+ {
+ wasFiltered = true;
+ deployableExtendedEndpointConfigs.addAll(seconfigs);
+ }
+
+ Set<Class<?>> annotatedClasses = config.getAnnotatedEndpointClasses(discoveredAnnotatedEndpoints);
+ if (annotatedClasses != null)
+ {
+ wasFiltered = true;
+ deployableAnnotatedEndpoints.addAll(annotatedClasses);
+ }
+ }
+ catch (InstantiationException | IllegalAccessException e)
+ {
+ throw new ServletException("Unable to instantiate: " + clazz.getName(),e);
+ }
+ }
+
+ // Default behavior if nothing filtered
+ if (!wasFiltered)
+ {
+ deployableAnnotatedEndpoints.addAll(discoveredAnnotatedEndpoints);
+ // Note: it is impossible to determine path of "extends Endpoint" discovered classes
+ deployableExtendedEndpointConfigs = new HashSet<>();
+ }
+
+ // Deploy what should be deployed.
+ LOG.debug("Deploying {} ServerEndpointConfig(s)",deployableExtendedEndpointConfigs.size());
+ for (ServerEndpointConfig config : deployableExtendedEndpointConfigs)
+ {
+ try
+ {
+ jettyContainer.addEndpoint(config);
+ }
+ catch (DeploymentException e)
+ {
+ throw new ServletException(e);
+ }
+ }
+
+ LOG.debug("Deploying {} @ServerEndpoint(s)",deployableAnnotatedEndpoints.size());
+ for (Class<?> annotatedClass : deployableAnnotatedEndpoints)
+ {
+ try
+ {
+ jettyContainer.addEndpoint(annotatedClass);
+ }
+ catch (DeploymentException e)
+ {
+ throw new ServletException(e);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void filterClasses(Set<Class<?>> c, Set<Class<? extends Endpoint>> discoveredExtendedEndpoints, Set<Class<?>> discoveredAnnotatedEndpoints,
+ Set<Class<? extends ServerApplicationConfig>> serverAppConfigs)
+ {
+ for (Class<?> clazz : c)
+ {
+ if (ServerApplicationConfig.class.isAssignableFrom(clazz))
+ {
+ serverAppConfigs.add((Class<? extends ServerApplicationConfig>)clazz);
+ }
+
+ if (Endpoint.class.isAssignableFrom(clazz))
+ {
+ discoveredExtendedEndpoints.add((Class<? extends Endpoint>)clazz);
+ }
+
+ ServerEndpoint endpoint = clazz.getAnnotation(ServerEndpoint.class);
+
+ if (endpoint != null)
+ {
+ discoveredAnnotatedEndpoints.add(clazz);
+ }
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpec.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpec.java
new file mode 100644
index 0000000..9442f06
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpec.java
@@ -0,0 +1,347 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.pathmap;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpecGroup;
+import org.eclipse.jetty.websocket.server.pathmap.RegexPathSpec;
+
+/**
+ * PathSpec for WebSocket @{@link ServerEndpoint} declarations with support for URI templates and @{@link PathParam} annotations
+ *
+ * @see javax.websocket spec (JSR-356) Section 3.1.1 URI Mapping
+ * @see <a href="https://tools.ietf.org/html/rfc6570">URI Templates (Level 1)</a>
+ */
+public class WebSocketPathSpec extends RegexPathSpec
+{
+ private static final Logger LOG = Log.getLogger(WebSocketPathSpec.class);
+
+ private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{(.*)\\}");
+ /** Reserved Symbols in URI Template variable */
+ private static final String VARIABLE_RESERVED = ":/?#[]@" + // gen-delims
+ "!$&'()*+,;="; // sub-delims
+ /** Allowed Symboles in a URI Template variable */
+ private static final String VARIABLE_SYMBOLS="-._";
+ private static final Set<String> FORBIDDEN_SEGMENTS;
+
+ static
+ {
+ FORBIDDEN_SEGMENTS = new HashSet<>();
+ FORBIDDEN_SEGMENTS.add("/./");
+ FORBIDDEN_SEGMENTS.add("/../");
+ FORBIDDEN_SEGMENTS.add("//");
+ }
+
+ private String variables[];
+
+ public WebSocketPathSpec(String pathParamSpec)
+ {
+ super();
+ Objects.requireNonNull(pathParamSpec,"Path Param Spec cannot be null");
+
+ if ("".equals(pathParamSpec) || "/".equals(pathParamSpec))
+ {
+ super.pathSpec = "/";
+ super.pattern = Pattern.compile("^/$");
+ super.pathDepth = 1;
+ this.specLength = 1;
+ this.variables = new String[0];
+ this.group = PathSpecGroup.EXACT;
+ return;
+ }
+
+ if (pathParamSpec.charAt(0) != '/')
+ {
+ // path specs must start with '/'
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: path spec \"");
+ err.append(pathParamSpec);
+ err.append("\" must start with '/'");
+ throw new IllegalArgumentException(err.toString());
+ }
+
+ for (String forbidden : FORBIDDEN_SEGMENTS)
+ {
+ if (pathParamSpec.contains(forbidden))
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: segment ");
+ err.append(forbidden);
+ err.append(" is forbidden in path spec: ");
+ err.append(pathParamSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ }
+
+ this.pathSpec = pathParamSpec;
+
+ StringBuilder regex = new StringBuilder();
+ regex.append('^');
+
+ List<String> varNames = new ArrayList<>();
+ // split up into path segments (ignoring the first slash that will always be empty)
+ String segments[] = pathParamSpec.substring(1).split("/");
+ char segmentSignature[] = new char[segments.length];
+ this.pathDepth = segments.length;
+ for (int i = 0; i < segments.length; i++)
+ {
+ String segment = segments[i];
+ Matcher mat = VARIABLE_PATTERN.matcher(segment);
+
+ if (mat.matches())
+ {
+ // entire path segment is a variable.
+ String variable = mat.group(1);
+ if (varNames.contains(variable))
+ {
+ // duplicate variable names
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: variable ");
+ err.append(variable);
+ err.append(" is duplicated in path spec: ");
+ err.append(pathParamSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+
+ assertIsValidVariableLiteral(variable);
+
+ segmentSignature[i] = 'v'; // variable
+ // valid variable name
+ varNames.add(variable);
+ // build regex
+ regex.append("/([^/]+)");
+ }
+ else if (mat.find(0))
+ {
+ // variable exists as partial segment
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: variable ");
+ err.append(mat.group());
+ err.append(" must exist as entire path segment: ");
+ err.append(pathParamSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ else if ((segment.indexOf('{') >= 0) || (segment.indexOf('}') >= 0))
+ {
+ // variable is split with a path separator
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: invalid path segment /");
+ err.append(segment);
+ err.append("/ variable declaration incomplete: ");
+ err.append(pathParamSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ else if (segment.indexOf('*') >= 0)
+ {
+ // glob segment
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: path segment /");
+ err.append(segment);
+ err.append("/ contains a wildcard symbol (not supported by javax.websocket): ");
+ err.append(pathParamSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ else
+ {
+ // valid path segment
+ segmentSignature[i] = 'e'; // exact
+ // build regex
+ regex.append('/');
+ // escape regex special characters
+ for (char c : segment.toCharArray())
+ {
+ if ((c == '.') || (c == '[') || (c == ']') || (c == '\\'))
+ {
+ regex.append('\\');
+ }
+ regex.append(c);
+ }
+ }
+ }
+
+ // Handle trailing slash (which is not picked up during split)
+ if(pathParamSpec.charAt(pathParamSpec.length()-1) == '/')
+ {
+ regex.append('/');
+ }
+
+ regex.append('$');
+
+ this.pattern = Pattern.compile(regex.toString());
+
+ int varcount = varNames.size();
+ this.variables = varNames.toArray(new String[varcount]);
+
+ // Convert signature to group
+ String sig = String.valueOf(segmentSignature);
+
+ if (Pattern.matches("^e*$",sig))
+ {
+ this.group = PathSpecGroup.EXACT;
+ }
+ else if (Pattern.matches("^e*v+",sig))
+ {
+ this.group = PathSpecGroup.PREFIX_GLOB;
+ }
+ else if (Pattern.matches("^v+e+",sig))
+ {
+ this.group = PathSpecGroup.SUFFIX_GLOB;
+ }
+ else
+ {
+ this.group = PathSpecGroup.MIDDLE_GLOB;
+ }
+ }
+
+ /**
+ * Validate variable literal name, per RFC6570, Section 2.1 Literals
+ * @param variable
+ * @param pathParamSpec
+ */
+ private void assertIsValidVariableLiteral(String variable)
+ {
+ int len = variable.length();
+
+ int i = 0;
+ int codepoint;
+ boolean valid = (len > 0); // must not be zero length
+
+ while (valid && i < len)
+ {
+ codepoint = variable.codePointAt(i);
+ i += Character.charCount(codepoint);
+
+ // basic letters, digits, or symbols
+ if (isValidBasicLiteralCodepoint(codepoint))
+ {
+ continue;
+ }
+
+ // The ucschar and iprivate pieces
+ if (Character.isSupplementaryCodePoint(codepoint))
+ {
+ continue;
+ }
+
+ // pct-encoded
+ if (codepoint == '%')
+ {
+ if (i + 2 > len)
+ {
+ // invalid percent encoding, missing extra 2 chars
+ valid = false;
+ continue;
+ }
+ codepoint = TypeUtil.convertHexDigit(variable.codePointAt(i++)) << 4;
+ codepoint |= TypeUtil.convertHexDigit(variable.codePointAt(i++));
+
+ // validate basic literal
+ if (isValidBasicLiteralCodepoint(codepoint))
+ {
+ continue;
+ }
+ }
+
+ valid = false;
+ }
+
+ if (!valid)
+ {
+ // invalid variable name
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: variable {");
+ err.append(variable);
+ err.append("} an invalid variable name: ");
+ err.append(pathSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ }
+
+ private boolean isValidBasicLiteralCodepoint(int codepoint)
+ {
+ // basic letters or digits
+ if((codepoint >= 'a' && codepoint <= 'z') ||
+ (codepoint >= 'A' && codepoint <= 'Z') ||
+ (codepoint >= '0' && codepoint <= '9'))
+ {
+ return true;
+ }
+
+ // basic allowed symbols
+ if(VARIABLE_SYMBOLS.indexOf(codepoint) >= 0)
+ {
+ return true; // valid simple value
+ }
+
+ // basic reserved symbols
+ if(VARIABLE_RESERVED.indexOf(codepoint) >= 0)
+ {
+ LOG.warn("Detected URI Template reserved symbol [{}] in path spec \"{}\"",(char)codepoint,pathSpec);
+ return false; // valid simple value
+ }
+
+ return false;
+ }
+
+ public Map<String, String> getPathParams(String path)
+ {
+ Matcher matcher = getMatcher(path);
+ if (matcher.matches())
+ {
+ if (group == PathSpecGroup.EXACT)
+ {
+ return Collections.emptyMap();
+ }
+ Map<String, String> ret = new HashMap<>();
+ int groupCount = matcher.groupCount();
+ for (int i = 1; i <= groupCount; i++)
+ {
+ ret.put(this.variables[i - 1],matcher.group(i));
+ }
+ return ret;
+ }
+ return null;
+ }
+
+ public int getVariableCount()
+ {
+ return variables.length;
+ }
+
+ public String[] getVariables()
+ {
+ return this.variables;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/jetty-websocket/javax-websocket-server-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
new file mode 100644
index 0000000..0ee5657
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
@@ -0,0 +1 @@
+org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/resources/META-INF/services/javax.websocket.server.ServerEndpointConfig$Configurator b/jetty-websocket/javax-websocket-server-impl/src/main/resources/META-INF/services/javax.websocket.server.ServerEndpointConfig$Configurator
new file mode 100644
index 0000000..57e3ef3
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/resources/META-INF/services/javax.websocket.server.ServerEndpointConfig$Configurator
@@ -0,0 +1 @@
+org.eclipse.jetty.websocket.jsr356.server.BasicServerEndpointConfigurator
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/GetHttpSessionConfigurator.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/GetHttpSessionConfigurator.java
new file mode 100644
index 0000000..1eff040
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/GetHttpSessionConfigurator.java
@@ -0,0 +1,34 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package examples;
+
+import javax.servlet.http.HttpSession;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+
+public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator
+{
+ @Override
+ public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response)
+ {
+ HttpSession httpSession = (HttpSession)request.getHttpSession();
+ config.getUserProperties().put(HttpSession.class.getName(),httpSession);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/GetHttpSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/GetHttpSessionSocket.java
new file mode 100644
index 0000000..9873c9a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/GetHttpSessionSocket.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package examples;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpSession;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+@ServerEndpoint(value = "/example", configurator = GetHttpSessionConfigurator.class)
+public class GetHttpSessionSocket
+{
+ private Session wsSession;
+ private HttpSession httpSession;
+
+ @OnOpen
+ public void open(Session session, EndpointConfig config) {
+ this.wsSession = session;
+ this.httpSession = (HttpSession)config.getUserProperties().get(HttpSession.class.getName());
+ }
+
+ @OnMessage
+ public void echo(String msg) throws IOException {
+ wsSession.getBasicRemote().sendText(msg);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/MyAuthedConfigurator.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/MyAuthedConfigurator.java
new file mode 100644
index 0000000..acd724b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/MyAuthedConfigurator.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package examples;
+
+import java.security.Principal;
+
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+
+public class MyAuthedConfigurator extends ServerEndpointConfig.Configurator
+{
+ @Override
+ public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+ {
+ // Is Authenticated?
+ Principal principal = request.getUserPrincipal();
+ if (principal == null)
+ {
+ throw new RuntimeException("Not authenticated");
+ }
+
+ // Is Authorized?
+ if (!request.isUserInRole("websocket"))
+ {
+ throw new RuntimeException("Not authorized");
+ }
+
+ // normal operation
+ super.modifyHandshake(sec,request,response);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/MyAuthedSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/MyAuthedSocket.java
new file mode 100644
index 0000000..7cb9133
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/MyAuthedSocket.java
@@ -0,0 +1,33 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package examples;
+
+import javax.websocket.OnMessage;
+import javax.websocket.server.ServerEndpoint;
+
+@ServerEndpoint(value = "/secured/socket", configurator = MyAuthedConfigurator.class)
+public class MyAuthedSocket
+{
+ @OnMessage
+ public String onMessage(String msg)
+ {
+ // echo the message back to the remote
+ return msg;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/StreamingEchoSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/StreamingEchoSocket.java
new file mode 100644
index 0000000..df8db6b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/StreamingEchoSocket.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package examples;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.toolchain.test.IO;
+
+@ServerEndpoint("/echo")
+public class StreamingEchoSocket
+{
+ @OnMessage
+ public void onMessage(Session session, Reader reader)
+ {
+ try (Writer writer = session.getBasicRemote().getSendWriter())
+ {
+ IO.copy(reader,writer);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicAnnotatedTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicAnnotatedTest.java
new file mode 100644
index 0000000..1b5f535
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicAnnotatedTest.java
@@ -0,0 +1,81 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.util.Queue;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoSocket;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Example of an annotated echo server discovered via annotation scanning.
+ */
+public class BasicAnnotatedTest
+{
+ @Rule
+ public TestingDir testdir = new TestingDir();
+
+ @Test
+ public void testEcho() throws Exception
+ {
+ WSServer wsb = new WSServer(testdir,"app");
+ wsb.createWebInf();
+ wsb.copyEndpoint(BasicEchoSocket.class);
+
+ try
+ {
+ wsb.start();
+ URI uri = wsb.getServerBaseURI();
+
+ WebAppContext webapp = wsb.createWebAppContext();
+ wsb.deployWebapp(webapp);
+ // wsb.dump();
+
+ WebSocketClient client = new WebSocketClient();
+ try
+ {
+ client.start();
+ JettyEchoSocket clientEcho = new JettyEchoSocket();
+ Future<Session> foo = client.connect(clientEcho,uri.resolve("echo"));
+ // wait for connect
+ foo.get(1,TimeUnit.SECONDS);
+ clientEcho.sendMessage("Hello World");
+ Queue<String> msgs = clientEcho.awaitMessages(1);
+ Assert.assertEquals("Expected message","Hello World",msgs.poll());
+ }
+ finally
+ {
+ client.stop();
+ }
+ }
+ finally
+ {
+ wsb.stop();
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicEndpointTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicEndpointTest.java
new file mode 100644
index 0000000..3487024
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicEndpointTest.java
@@ -0,0 +1,86 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.util.Queue;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpointConfigContextListener;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Example of an {@link Endpoint} extended echo server added programmatically via the
+ * {@link ServerContainer#addEndpoint(javax.websocket.server.ServerEndpointConfig)}
+ */
+public class BasicEndpointTest
+{
+ @Rule
+ public TestingDir testdir = new TestingDir();
+
+ @Test
+ public void testEcho() throws Exception
+ {
+ WSServer wsb = new WSServer(testdir,"app");
+ wsb.copyWebInf("basic-echo-endpoint-config-web.xml");
+ // the endpoint (extends javax.websocket.Endpoint)
+ wsb.copyClass(BasicEchoEndpoint.class);
+ // the configuration (adds the endpoint)
+ wsb.copyClass(BasicEchoEndpointConfigContextListener.class);
+
+ try
+ {
+ wsb.start();
+ URI uri = wsb.getServerBaseURI();
+
+ WebAppContext webapp = wsb.createWebAppContext();
+ wsb.deployWebapp(webapp);
+ // wsb.dump();
+
+ WebSocketClient client = new WebSocketClient();
+ try
+ {
+ client.start();
+ JettyEchoSocket clientEcho = new JettyEchoSocket();
+ Future<Session> future = client.connect(clientEcho,uri.resolve("echo"));
+ // wait for connect
+ future.get(1,TimeUnit.SECONDS);
+ clientEcho.sendMessage("Hello World");
+ Queue<String> msgs = clientEcho.awaitMessages(1);
+ Assert.assertEquals("Expected message","Hello World",msgs.poll());
+ }
+ finally
+ {
+ client.stop();
+ }
+ }
+ finally
+ {
+ wsb.stop();
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java
new file mode 100644
index 0000000..14835ea
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java
@@ -0,0 +1,219 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.jsr356.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.jsr356.server.blockhead.HttpResponse;
+import org.eclipse.jetty.websocket.jsr356.server.blockhead.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ConfiguratorTest
+{
+ private static final Logger LOG = Log.getLogger(ConfiguratorTest.class);
+
+ public static class EmptyConfigurator extends ServerEndpointConfig.Configurator
+ {
+ }
+
+ @ServerEndpoint(value = "/empty", configurator = EmptyConfigurator.class)
+ public static class EmptySocket
+ {
+ @OnMessage
+ public String echo(String message)
+ {
+ return message;
+ }
+ }
+
+ public static class NoExtensionsConfigurator extends ServerEndpointConfig.Configurator
+ {
+ @Override
+ public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested)
+ {
+ return Collections.emptyList();
+ }
+ }
+
+ @ServerEndpoint(value = "/no-extensions", configurator = NoExtensionsConfigurator.class)
+ public static class NoExtensionsSocket
+ {
+ @OnMessage
+ public String echo(String message)
+ {
+ return message;
+ }
+ }
+
+ public static class CaptureHeadersConfigurator extends ServerEndpointConfig.Configurator
+ {
+ @Override
+ public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+ {
+ super.modifyHandshake(sec,request,response);
+ sec.getUserProperties().put("request-headers",request.getHeaders());
+ }
+ }
+
+ @ServerEndpoint(value = "/capture-request-headers", configurator = CaptureHeadersConfigurator.class)
+ public static class CaptureHeadersSocket
+ {
+ @OnMessage
+ public String getHeaders(Session session, String headerKey)
+ {
+ StringBuilder response = new StringBuilder();
+
+ response.append("Request Header [").append(headerKey).append("]: ");
+ @SuppressWarnings("unchecked")
+ Map<String, List<String>> headers = (Map<String, List<String>>)session.getUserProperties().get("request-headers");
+ if (headers == null)
+ {
+ response.append("<no headers found in session.getUserProperties()>");
+ }
+ else
+ {
+ List<String> values = headers.get(headerKey);
+ if (values == null)
+ {
+ response.append("<header not found>");
+ }
+ else
+ {
+ response.append(QuoteUtil.join(values,","));
+ }
+ }
+
+ return response.toString();
+ }
+ }
+
+ private static Server server;
+ private static URI baseServerUri;
+
+ @BeforeClass
+ public static void startServer() throws Exception
+ {
+ server = new Server();
+ ServerConnector connector = new ServerConnector(server);
+ connector.setPort(0);
+ server.addConnector(connector);
+
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+ context.setContextPath("/");
+ server.setHandler(context);
+
+ ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
+ container.addEndpoint(CaptureHeadersSocket.class);
+ container.addEndpoint(EmptySocket.class);
+ container.addEndpoint(NoExtensionsSocket.class);
+
+ server.start();
+ String host = connector.getHost();
+ if (host == null)
+ {
+ host = "localhost";
+ }
+ int port = connector.getLocalPort();
+ baseServerUri = new URI(String.format("ws://%s:%d/",host,port));
+ LOG.debug("Server started on {}",baseServerUri);
+ }
+
+ @AfterClass
+ public static void stopServer() throws Exception
+ {
+ server.stop();
+ }
+
+ @Test
+ public void testEmptyConfigurator() throws Exception
+ {
+ URI uri = baseServerUri.resolve("/empty");
+
+ try (BlockheadClient client = new BlockheadClient(uri))
+ {
+ client.addExtensions("identity");
+ client.connect();
+ client.sendStandardRequest();
+ HttpResponse response = client.readResponseHeader();
+ Assert.assertThat("response.extensions",response.getExtensionsHeader(),is("identity"));
+ }
+ }
+
+ @Test
+ public void testNoExtensionsConfigurator() throws Exception
+ {
+ URI uri = baseServerUri.resolve("/no-extensions");
+
+ try (BlockheadClient client = new BlockheadClient(uri))
+ {
+ client.addExtensions("identity");
+ client.connect();
+ client.sendStandardRequest();
+ HttpResponse response = client.readResponseHeader();
+ Assert.assertThat("response.extensions",response.getExtensionsHeader(),nullValue());
+ }
+ }
+
+ @Test
+ public void testCaptureRequestHeadersConfigurator() throws Exception
+ {
+ URI uri = baseServerUri.resolve("/capture-request-headers");
+
+ try (BlockheadClient client = new BlockheadClient(uri))
+ {
+ client.addHeader("X-Dummy: Bogus\r\n");
+ client.connect();
+ client.sendStandardRequest();
+ client.expectUpgradeResponse();
+
+ client.write(new TextFrame().setPayload("X-Dummy"));
+ IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
+ WebSocketFrame frame = capture.getFrames().poll();
+ Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Request Header [X-Dummy]: \"Bogus\""));
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyConnection.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyConnection.java
new file mode 100644
index 0000000..86034cc
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyConnection.java
@@ -0,0 +1,160 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.SuspendToken;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.io.IOState;
+
+public class DummyConnection implements LogicalConnection
+{
+ private static final Logger LOG = Log.getLogger(DummyConnection.class);
+ private IOState iostate;
+
+ public DummyConnection()
+ {
+ this.iostate = new IOState();
+ }
+
+ @Override
+ public void close()
+ {
+ }
+
+ @Override
+ public void close(int statusCode, String reason)
+ {
+ }
+
+ @Override
+ public void disconnect()
+ {
+ }
+
+ @Override
+ public ByteBufferPool getBufferPool()
+ {
+ return null;
+ }
+
+ @Override
+ public Executor getExecutor()
+ {
+ return null;
+ }
+
+ @Override
+ public long getIdleTimeout()
+ {
+ return 0;
+ }
+
+ @Override
+ public IOState getIOState()
+ {
+ return this.iostate;
+ }
+
+ @Override
+ public InetSocketAddress getLocalAddress()
+ {
+ return null;
+ }
+
+ @Override
+ public long getMaxIdleTimeout()
+ {
+ return 0;
+ }
+
+ @Override
+ public WebSocketPolicy getPolicy()
+ {
+ return null;
+ }
+
+ @Override
+ public InetSocketAddress getRemoteAddress()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public WebSocketSession getSession()
+ {
+ return null;
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isReading()
+ {
+ return false;
+ }
+
+ @Override
+ public void outgoingFrame(Frame frame, WriteCallback callback)
+ {
+ callback.writeSuccess();
+ }
+
+ @Override
+ public void resume()
+ {
+ }
+
+ @Override
+ public void setMaxIdleTimeout(long ms)
+ {
+ }
+
+ @Override
+ public void setNextIncomingFrames(IncomingFrames incoming)
+ {
+ LOG.debug("setNextIncomingFrames({})",incoming);
+ }
+
+ @Override
+ public void setSession(WebSocketSession session)
+ {
+ }
+
+ @Override
+ public SuspendToken suspend()
+ {
+ return null;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java
new file mode 100644
index 0000000..ac56098
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java
@@ -0,0 +1,39 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import org.eclipse.jetty.websocket.server.MappedWebSocketCreator;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+public class DummyCreator implements MappedWebSocketCreator
+{
+ @Override
+ public void addMapping(PathSpec spec, WebSocketCreator creator)
+ {
+ /* do nothing */
+ }
+
+ @Override
+ public PathMappings<WebSocketCreator> getMappings()
+ {
+ return null;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoCase.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoCase.java
new file mode 100644
index 0000000..5f9abef
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoCase.java
@@ -0,0 +1,181 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.server.ServerEndpoint;
+
+public class EchoCase
+{
+ public static class PartialBinary
+ {
+ ByteBuffer part;
+
+ boolean fin;
+ public PartialBinary(ByteBuffer part, boolean fin)
+ {
+ this.part = part;
+ this.fin = fin;
+ }
+ }
+
+ public static class PartialText
+ {
+ String part;
+
+ boolean fin;
+ public PartialText(String part, boolean fin)
+ {
+ this.part = part;
+ this.fin = fin;
+ }
+ }
+
+ public static EchoCase add(List<EchoCase[]> data, Class<?> serverPojo)
+ {
+ EchoCase ecase = new EchoCase();
+ ecase.serverPojo = serverPojo;
+ data.add(new EchoCase[]
+ { ecase });
+ ServerEndpoint endpoint = serverPojo.getAnnotation(ServerEndpoint.class);
+ ecase.path = endpoint.value();
+ return ecase;
+ }
+
+ public static EchoCase add(List<EchoCase[]> data, Class<?> serverPojo, String path)
+ {
+ EchoCase ecase = new EchoCase();
+ ecase.serverPojo = serverPojo;
+ ecase.path = path;
+ data.add(new EchoCase[]
+ { ecase });
+ return ecase;
+ }
+
+ // The websocket server pojo to test against
+ public Class<?> serverPojo;
+ // The (relative) URL path to hit
+ public String path;
+ // The messages to transmit
+ public List<Object> messages = new ArrayList<>();
+ // The expected Strings (that are echoed back)
+ public List<String> expectedStrings = new ArrayList<>();
+
+ public EchoCase addMessage(Object msg)
+ {
+ messages.add(msg);
+ return this;
+ }
+
+ public EchoCase addSplitMessage(ByteBuffer... parts)
+ {
+ int len = parts.length;
+ for (int i = 0; i < len; i++)
+ {
+ addMessage(new PartialBinary(parts[i],(i == (len-1))));
+ }
+ return this;
+ }
+
+ public EchoCase addSplitMessage(String... parts)
+ {
+ int len = parts.length;
+ for (int i = 0; i < len; i++)
+ {
+ addMessage(new PartialText(parts[i],(i == (len-1))));
+ }
+ return this;
+ }
+
+ public EchoCase expect(String message)
+ {
+ expectedStrings.add(message);
+ return this;
+ }
+
+ public EchoCase requestPath(String path)
+ {
+ this.path = path;
+ return this;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder();
+ str.append("EchoCase['");
+ str.append(path);
+ str.append("',").append(serverPojo.getName());
+ str.append(",messages[").append(messages.size());
+ str.append("]=");
+ boolean delim = false;
+ for (Object msg : messages)
+ {
+ if (delim)
+ {
+ str.append(",");
+ }
+ if (msg instanceof String)
+ {
+ str.append("'").append(msg).append("'");
+ }
+ else
+ {
+ str.append("(").append(msg.getClass().getName()).append(")");
+ str.append(msg);
+ }
+ delim = true;
+ }
+ str.append("]");
+ return str.toString();
+ }
+
+ public int getMessageCount()
+ {
+ int messageCount = 0;
+ for (Object msg : messages)
+ {
+ if (msg instanceof PartialText)
+ {
+ PartialText pt = (PartialText)msg;
+ if (pt.fin)
+ {
+ messageCount++;
+ }
+ }
+ else if (msg instanceof PartialBinary)
+ {
+ PartialBinary pb = (PartialBinary)msg;
+ if (pb.fin)
+ {
+ messageCount++;
+ }
+ }
+ else
+ {
+ messageCount++;
+ }
+ }
+
+ return messageCount;
+ }
+}
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoClientSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoClientSocket.java
new file mode 100644
index 0000000..c85f9f7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoClientSocket.java
@@ -0,0 +1,111 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.EncodeException;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.RemoteEndpoint.Basic;
+import javax.websocket.Session;
+
+@ClientEndpoint
+public class EchoClientSocket extends TrackingSocket
+{
+ public final CountDownLatch eventCountLatch;
+ private Session session;
+ private Basic remote;
+
+ public EchoClientSocket(int expectedEventCount)
+ {
+ this.eventCountLatch = new CountDownLatch(expectedEventCount);
+ }
+
+ public void close() throws IOException
+ {
+ if (session != null)
+ {
+ this.session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE,"Test Complete"));
+ }
+ }
+
+ @OnClose
+ public void onClose(CloseReason close)
+ {
+ this.session = null;
+ super.closeReason = close;
+ super.closeLatch.countDown();
+ }
+
+ @OnError
+ public void onError(Throwable t)
+ {
+ if (t == null)
+ {
+ addError(new NullPointerException("Throwable should not be null"));
+ }
+ else
+ {
+ addError(t);
+ }
+ }
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ this.remote = session.getBasicRemote();
+ openLatch.countDown();
+ }
+
+ @OnMessage
+ public void onText(String text)
+ {
+ addEvent(text);
+ eventCountLatch.countDown();
+ }
+
+ public boolean awaitAllEvents(long timeout, TimeUnit unit) throws InterruptedException
+ {
+ return eventCountLatch.await(timeout,unit);
+ }
+
+ public void sendObject(Object obj) throws IOException, EncodeException
+ {
+ remote.sendObject(obj);
+ }
+
+ public void sendPartialBinary(ByteBuffer part, boolean fin) throws IOException
+ {
+ remote.sendBinary(part,fin);
+ }
+
+ public void sendPartialText(String part, boolean fin) throws IOException
+ {
+ remote.sendText(part,fin);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoTest.java
new file mode 100644
index 0000000..b50605d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoTest.java
@@ -0,0 +1,303 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.contains;
+
+import java.io.File;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.jsr356.server.EchoCase.PartialBinary;
+import org.eclipse.jetty.websocket.jsr356.server.EchoCase.PartialText;
+import org.eclipse.jetty.websocket.jsr356.server.samples.binary.ByteBufferSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.partial.PartialTextSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.partial.PartialTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.BooleanObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.BooleanTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ByteObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ByteTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.CharTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.CharacterObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.DoubleObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.DoubleTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.FloatObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.FloatTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.IntParamTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.IntTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.IntegerObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.LongObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.LongTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ShortObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ShortTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.InputStreamSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.ReaderParamSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.ReaderSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.StringReturnReaderParamSocket;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EchoTest
+{
+ private static final List<EchoCase[]> TESTCASES = new ArrayList<>();
+
+ private static WSServer server;
+ private static URI serverUri;
+ private static WebSocketContainer client;
+
+ static
+ {
+ EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage(true).expect("true");
+ EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage(false).expect("false");
+ EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage(Boolean.TRUE).expect("true");
+ EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage(Boolean.FALSE).expect("false");
+ EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage("true").expect("true");
+ EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage("TRUe").expect("true");
+ EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage("Apple").expect("false");
+ EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage("false").expect("false");
+
+ EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage(true).expect("true");
+ EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage(false).expect("false");
+ EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage(Boolean.TRUE).expect("true");
+ EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage(Boolean.FALSE).expect("false");
+ EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage("true").expect("true");
+ EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage("false").expect("false");
+ EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage("FaLsE").expect("false");
+
+ EchoCase.add(TESTCASES,ByteTextSocket.class).addMessage((byte)88).expect("0x58");
+ EchoCase.add(TESTCASES,ByteTextSocket.class).addMessage((byte)101).expect("0x65");
+ EchoCase.add(TESTCASES,ByteTextSocket.class).addMessage((byte)202).expect("0xCA");
+ EchoCase.add(TESTCASES,ByteTextSocket.class).addMessage(Byte.valueOf((byte)33)).expect("0x21");
+ EchoCase.add(TESTCASES,ByteTextSocket.class).addMessage(Byte.valueOf((byte)131)).expect("0x83");
+ EchoCase.add(TESTCASES,ByteTextSocket.class).addMessage(Byte.valueOf((byte)232)).expect("0xE8");
+
+ EchoCase.add(TESTCASES,ByteObjectTextSocket.class).addMessage((byte)88).expect("0x58");
+ EchoCase.add(TESTCASES,ByteObjectTextSocket.class).addMessage((byte)101).expect("0x65");
+ EchoCase.add(TESTCASES,ByteObjectTextSocket.class).addMessage((byte)202).expect("0xCA");
+ EchoCase.add(TESTCASES,ByteObjectTextSocket.class).addMessage(Byte.valueOf((byte)33)).expect("0x21");
+ EchoCase.add(TESTCASES,ByteObjectTextSocket.class).addMessage(Byte.valueOf((byte)131)).expect("0x83");
+ EchoCase.add(TESTCASES,ByteObjectTextSocket.class).addMessage(Byte.valueOf((byte)232)).expect("0xE8");
+
+ EchoCase.add(TESTCASES,CharTextSocket.class).addMessage((char)40).expect("(");
+ EchoCase.add(TESTCASES,CharTextSocket.class).addMessage((char)106).expect("j");
+ EchoCase.add(TESTCASES,CharTextSocket.class).addMessage((char)126).expect("~");
+ EchoCase.add(TESTCASES,CharTextSocket.class).addMessage(Character.valueOf((char)41)).expect(")");
+ EchoCase.add(TESTCASES,CharTextSocket.class).addMessage(Character.valueOf((char)74)).expect("J");
+ EchoCase.add(TESTCASES,CharTextSocket.class).addMessage(Character.valueOf((char)64)).expect("@");
+
+ EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage((char)40).expect("(");
+ EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage((char)106).expect("j");
+ EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage((char)126).expect("~");
+ EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage("E").expect("E");
+ EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage(Character.valueOf((char)41)).expect(")");
+ EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage(Character.valueOf((char)74)).expect("J");
+ EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage(Character.valueOf((char)64)).expect("@");
+
+ EchoCase.add(TESTCASES,DoubleTextSocket.class).addMessage((double)3.1459).expect("3.1459");
+ EchoCase.add(TESTCASES,DoubleTextSocket.class).addMessage((double)123.456).expect("123.4560");
+ EchoCase.add(TESTCASES,DoubleTextSocket.class).addMessage(Double.valueOf(55)).expect("55.0000");
+ EchoCase.add(TESTCASES,DoubleTextSocket.class).addMessage(Double.valueOf(1.0E8)).expect("100000000.0000");
+ EchoCase.add(TESTCASES,DoubleTextSocket.class).addMessage("42").expect("42.0000");
+ EchoCase.add(TESTCASES,DoubleTextSocket.class).addMessage(".123").expect("0.1230");
+
+ EchoCase.add(TESTCASES,DoubleObjectTextSocket.class).addMessage((double)3.1459).expect("3.1459");
+ EchoCase.add(TESTCASES,DoubleObjectTextSocket.class).addMessage((double)123.456).expect("123.4560");
+ EchoCase.add(TESTCASES,DoubleObjectTextSocket.class).addMessage(Double.valueOf(55)).expect("55.0000");
+ EchoCase.add(TESTCASES,DoubleObjectTextSocket.class).addMessage(Double.valueOf(1.0E8)).expect("100000000.0000");
+ EchoCase.add(TESTCASES,DoubleObjectTextSocket.class).addMessage("42").expect("42.0000");
+ EchoCase.add(TESTCASES,DoubleObjectTextSocket.class).addMessage(".123").expect("0.1230");
+
+ EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage((float)3.1459).expect("3.1459");
+ EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage((float)123.456).expect("123.4560");
+ EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage(Float.valueOf(55)).expect("55.0000");
+ EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage(Float.valueOf(1.0E8f)).expect("100000000.0000");
+ EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage("42").expect("42.0000");
+ EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage(".123").expect("0.1230");
+ EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage("50505E-6").expect("0.0505");
+
+ EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage((float)3.1459).expect("3.1459");
+ EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage((float)123.456).expect("123.4560");
+ EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage(Float.valueOf(55)).expect("55.0000");
+ EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage(Float.valueOf(1.0E8f)).expect("100000000.0000");
+ EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage("42").expect("42.0000");
+ EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage(".123").expect("0.1230");
+ EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage("50505E-6").expect("0.0505");
+
+ EchoCase.add(TESTCASES,IntTextSocket.class).addMessage((int)8).expect("8");
+ EchoCase.add(TESTCASES,IntTextSocket.class).addMessage((int)22).expect("22");
+ EchoCase.add(TESTCASES,IntTextSocket.class).addMessage("12345678").expect("12345678");
+
+ EchoCase.add(TESTCASES,IntegerObjectTextSocket.class).addMessage((int)8).expect("8");
+ EchoCase.add(TESTCASES,IntegerObjectTextSocket.class).addMessage((int)22).expect("22");
+ EchoCase.add(TESTCASES,IntegerObjectTextSocket.class).addMessage("12345678").expect("12345678");
+
+ EchoCase.add(TESTCASES,LongTextSocket.class).addMessage((int)789).expect("789");
+ EchoCase.add(TESTCASES,LongTextSocket.class).addMessage((long)123456L).expect("123456");
+ EchoCase.add(TESTCASES,LongTextSocket.class).addMessage(-456).expect("-456");
+
+ EchoCase.add(TESTCASES,LongObjectTextSocket.class).addMessage((int)789).expect("789");
+ EchoCase.add(TESTCASES,LongObjectTextSocket.class).addMessage((long)123456L).expect("123456");
+ EchoCase.add(TESTCASES,LongObjectTextSocket.class).addMessage(-234).expect("-234");
+
+ EchoCase.add(TESTCASES,ShortTextSocket.class).addMessage((int)4).expect("4");
+ EchoCase.add(TESTCASES,ShortTextSocket.class).addMessage((long)987).expect("987");
+ EchoCase.add(TESTCASES,ShortTextSocket.class).addMessage("32001").expect("32001");
+
+ EchoCase.add(TESTCASES,ShortObjectTextSocket.class).addMessage((int)4).expect("4");
+ EchoCase.add(TESTCASES,ShortObjectTextSocket.class).addMessage((int)987).expect("987");
+ EchoCase.add(TESTCASES,ShortObjectTextSocket.class).addMessage(-32001L).expect("-32001");
+
+ // PathParam based
+ EchoCase.add(TESTCASES,IntParamTextSocket.class).requestPath("/echo/primitives/integer/params/5678").addMessage(1234).expect("1234|5678");
+
+ // ByteBuffer based
+ EchoCase.add(TESTCASES,ByteBufferSocket.class).addMessage(BufferUtil.toBuffer("Hello World")).expect("Hello World");
+
+ // InputStream based
+ EchoCase.add(TESTCASES,InputStreamSocket.class).addMessage(BufferUtil.toBuffer("Hello World")).expect("Hello World");
+
+ // Reader based
+ EchoCase.add(TESTCASES,ReaderSocket.class).addMessage("Hello World").expect("Hello World");
+ EchoCase.add(TESTCASES,ReaderParamSocket.class).requestPath("/echo/streaming/readerparam/OhNo").addMessage("Hello World").expect("Hello World|OhNo");
+ EchoCase.add(TESTCASES,StringReturnReaderParamSocket.class).requestPath("/echo/streaming/readerparam2/OhMy").addMessage("Hello World")
+ .expect("Hello World|OhMy");
+
+ // Partial message based
+ EchoCase.add(TESTCASES,PartialTextSocket.class)
+ .addSplitMessage("Saved"," by ","zero")
+ .expect("('Saved',false)(' by ',false)('zero',true)");
+ EchoCase.add(TESTCASES,PartialTextSessionSocket.class)
+ .addSplitMessage("Built"," for"," the"," future")
+ .expect("('Built',false)(' for',false)(' the',false)(' future',true)");
+ }
+
+ @BeforeClass
+ public static void startServer() throws Exception
+ {
+ File testdir = MavenTestingUtils.getTargetTestingDir(EchoTest.class.getName());
+ server = new WSServer(testdir,"app");
+ server.copyWebInf("empty-web.xml");
+
+ for (EchoCase cases[] : TESTCASES)
+ {
+ for (EchoCase ecase : cases)
+ {
+ server.copyClass(ecase.serverPojo);
+ }
+ }
+
+ server.start();
+ serverUri = server.getServerBaseURI();
+
+ WebAppContext webapp = server.createWebAppContext();
+ server.deployWebapp(webapp);
+ server.dump();
+ }
+
+ @BeforeClass
+ public static void startClient() throws Exception
+ {
+ client = ContainerProvider.getWebSocketContainer();
+ }
+
+ @AfterClass
+ public static void stopServer()
+ {
+ server.stop();
+ }
+
+ @Parameters
+ public static Collection<EchoCase[]> data() throws Exception
+ {
+ return TESTCASES;
+ }
+
+ private EchoCase testcase;
+
+ public EchoTest(EchoCase testcase)
+ {
+ this.testcase = testcase;
+ System.err.println(testcase);
+ }
+
+ @Test(timeout=2000)
+ public void testEcho() throws Exception
+ {
+ int messageCount = testcase.getMessageCount();
+ EchoClientSocket socket = new EchoClientSocket(messageCount);
+ URI toUri = serverUri.resolve(testcase.path.substring(1));
+
+ try
+ {
+ // Connect
+ client.connectToServer(socket,toUri);
+ socket.waitForConnected(2,TimeUnit.SECONDS);
+
+ // Send Messages
+ for (Object msg : testcase.messages)
+ {
+ if (msg instanceof PartialText)
+ {
+ PartialText pt = (PartialText)msg;
+ socket.sendPartialText(pt.part,pt.fin);
+ }
+ else if (msg instanceof PartialBinary)
+ {
+ PartialBinary pb = (PartialBinary)msg;
+ socket.sendPartialBinary(pb.part,pb.fin);
+ }
+ else
+ {
+ socket.sendObject(msg);
+ }
+ }
+
+ // Collect Responses
+ socket.awaitAllEvents(1,TimeUnit.SECONDS);
+ EventQueue<String> received = socket.eventQueue;
+
+ // Validate Responses
+ for (String expected : testcase.expectedStrings)
+ {
+ Assert.assertThat("Received Echo Responses",received,contains(expected));
+ }
+ }
+ finally
+ {
+ // Close
+ socket.close();
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java
new file mode 100644
index 0000000..377bdf9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java
@@ -0,0 +1,87 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.Queue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * This is a Jetty API version of a websocket.
+ * <p>
+ * This is used a a client socket during the server tests.
+ */
+@WebSocket
+public class JettyEchoSocket
+{
+ private static final Logger LOG = Log.getLogger(JettyEchoSocket.class);
+ @SuppressWarnings("unused")
+ private Session session;
+ private RemoteEndpoint remote;
+ private EventQueue<String> incomingMessages = new EventQueue<>();
+
+ public Queue<String> awaitMessages(int expected) throws TimeoutException, InterruptedException
+ {
+ incomingMessages.awaitEventCount(expected,2,TimeUnit.SECONDS);
+ return incomingMessages;
+ }
+
+ @OnWebSocketClose
+ public void onClose(int code, String reason)
+ {
+ session = null;
+ remote = null;
+ }
+
+ @OnWebSocketError
+ public void onError(Throwable t)
+ {
+ LOG.warn(t);
+ }
+
+ @OnWebSocketMessage
+ public void onMessage(String msg)
+ {
+ incomingMessages.add(msg);
+ remote.sendString(msg,null);
+ }
+
+ @OnWebSocketConnect
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ this.remote = session.getRemote();
+ }
+
+ public void sendMessage(String msg)
+ {
+ remote.sendStringByFuture(msg);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyServerEndpointConfiguratorTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyServerEndpointConfiguratorTest.java
new file mode 100644
index 0000000..515e7e0
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyServerEndpointConfiguratorTest.java
@@ -0,0 +1,53 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.junit.Test;
+
+/**
+ * Test the JettyServerEndpointConfigurator impl.
+ */
+public class JettyServerEndpointConfiguratorTest
+{
+ @Test
+ public void testServiceLoader()
+ {
+ System.out.printf("Service Name: %s%n",ServerEndpointConfig.Configurator.class.getName());
+
+ ServiceLoader<ServerEndpointConfig.Configurator> loader = ServiceLoader.load(javax.websocket.server.ServerEndpointConfig.Configurator.class);
+ assertThat("loader",loader,notNullValue());
+ Iterator<ServerEndpointConfig.Configurator> iter = loader.iterator();
+ assertThat("loader.iterator",iter,notNullValue());
+ assertThat("loader.iterator.hasNext",iter.hasNext(),is(true));
+
+ ServerEndpointConfig.Configurator configr = iter.next();
+ assertThat("Configurator",configr,notNullValue());
+ assertThat("COnfigurator type",configr,instanceOf(BasicServerEndpointConfigurator.class));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/LargeAnnotatedTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/LargeAnnotatedTest.java
new file mode 100644
index 0000000..8fe0fc2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/LargeAnnotatedTest.java
@@ -0,0 +1,88 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Queue;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoConfiguredSocket;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test Echo of Large messages, targeting the {@link javax.websocket.Session#setMaxTextMessageBufferSize(int)} functionality
+ */
+public class LargeAnnotatedTest
+{
+ @Rule
+ public TestingDir testdir = new TestingDir();
+
+ @Test
+ public void testEcho() throws Exception
+ {
+ WSServer wsb = new WSServer(testdir,"app");
+ wsb.createWebInf();
+ wsb.copyEndpoint(LargeEchoConfiguredSocket.class);
+
+ try
+ {
+ wsb.start();
+ URI uri = wsb.getServerBaseURI();
+
+ WebAppContext webapp = wsb.createWebAppContext();
+ wsb.deployWebapp(webapp);
+ // wsb.dump();
+
+ WebSocketClient client = new WebSocketClient();
+ try
+ {
+ client.getPolicy().setMaxTextMessageSize(128*1024);
+ client.start();
+ JettyEchoSocket clientEcho = new JettyEchoSocket();
+ Future<Session> foo = client.connect(clientEcho,uri.resolve("echo/large"));
+ // wait for connect
+ foo.get(1,TimeUnit.SECONDS);
+ // The message size should be bigger than default, but smaller than the limit that LargeEchoSocket specifies
+ byte txt[] = new byte[100 * 1024];
+ Arrays.fill(txt,(byte)'o');
+ String msg = new String(txt,StringUtil.__UTF8_CHARSET);
+ clientEcho.sendMessage(msg);
+ Queue<String> msgs = clientEcho.awaitMessages(1);
+ Assert.assertEquals("Expected message",msg,msgs.poll());
+ }
+ finally
+ {
+ client.stop();
+ }
+ }
+ finally
+ {
+ wsb.stop();
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/LargeContainerTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/LargeContainerTest.java
new file mode 100644
index 0000000..fb1564d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/LargeContainerTest.java
@@ -0,0 +1,88 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Queue;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoDefaultSocket;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test Echo of Large messages, targeting the {@link javax.websocket.WebSocketContainer#setDefaultMaxTextMessageBufferSize(int)} functionality
+ */
+public class LargeContainerTest
+{
+ @Rule
+ public TestingDir testdir = new TestingDir();
+
+ @Test
+ public void testEcho() throws Exception
+ {
+ WSServer wsb = new WSServer(testdir,"app");
+ wsb.copyWebInf("large-echo-config-web.xml");
+ wsb.copyEndpoint(LargeEchoDefaultSocket.class);
+
+ try
+ {
+ wsb.start();
+ URI uri = wsb.getServerBaseURI();
+
+ WebAppContext webapp = wsb.createWebAppContext();
+ wsb.deployWebapp(webapp);
+ // wsb.dump();
+
+ WebSocketClient client = new WebSocketClient();
+ try
+ {
+ client.getPolicy().setMaxTextMessageSize(128*1024);
+ client.start();
+ JettyEchoSocket clientEcho = new JettyEchoSocket();
+ Future<Session> foo = client.connect(clientEcho,uri.resolve("echo/large"));
+ // wait for connect
+ foo.get(1,TimeUnit.SECONDS);
+ // The message size should be bigger than default, but smaller than the limit that LargeEchoSocket specifies
+ byte txt[] = new byte[100 * 1024];
+ Arrays.fill(txt,(byte)'o');
+ String msg = new String(txt,StringUtil.__UTF8_CHARSET);
+ clientEcho.sendMessage(msg);
+ Queue<String> msgs = clientEcho.awaitMessages(1);
+ Assert.assertEquals("Expected message",msg,msgs.poll());
+ }
+ finally
+ {
+ client.stop();
+ }
+ }
+ finally
+ {
+ wsb.stop();
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnMessageReturnTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnMessageReturnTest.java
new file mode 100644
index 0000000..36f3862
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnMessageReturnTest.java
@@ -0,0 +1,79 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.util.Queue;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.EchoReturnEndpoint;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class OnMessageReturnTest
+{
+ @Rule
+ public TestingDir testdir = new TestingDir();
+
+ @Test
+ public void testEchoReturn() throws Exception
+ {
+ WSServer wsb = new WSServer(testdir,"app");
+ wsb.copyWebInf("empty-web.xml");
+ wsb.copyClass(EchoReturnEndpoint.class);
+
+ try
+ {
+ wsb.start();
+ URI uri = wsb.getServerBaseURI();
+
+ WebAppContext webapp = wsb.createWebAppContext();
+ wsb.deployWebapp(webapp);
+ wsb.dump();
+
+ WebSocketClient client = new WebSocketClient();
+ try
+ {
+ client.start();
+ JettyEchoSocket clientEcho = new JettyEchoSocket();
+ Future<Session> future = client.connect(clientEcho,uri.resolve("echoreturn"));
+ // wait for connect
+ future.get(1,TimeUnit.SECONDS);
+ clientEcho.sendMessage("Hello World");
+ Queue<String> msgs = clientEcho.awaitMessages(1);
+ Assert.assertEquals("Expected message","Hello World",msgs.poll());
+ }
+ finally
+ {
+ client.stop();
+ }
+ }
+ finally
+ {
+ wsb.stop();
+ }
+ }
+
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java
new file mode 100644
index 0000000..56a012a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java
@@ -0,0 +1,112 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
+import org.eclipse.jetty.websocket.common.events.EventDriverImpl;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.jsr356.ClientContainer;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.server.samples.partial.PartialTrackingSocket;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class OnPartialTest
+{
+ @Rule
+ public TestName testname = new TestName();
+
+ public EventDriver toEventDriver(Object websocket) throws Throwable
+ {
+ WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
+ policy.setInputBufferSize(1024);
+ policy.setMaxBinaryMessageBufferSize(1024);
+ policy.setMaxTextMessageBufferSize(1024);
+
+ // Event Driver Factory
+ EventDriverFactory factory = new EventDriverFactory(policy);
+ factory.addImplementation(new JsrServerEndpointImpl());
+
+ // Create EventDriver
+ EventDriverImpl driverImpl = new JsrServerEndpointImpl();
+ Class<?> endpoint = websocket.getClass();
+ ServerEndpoint anno = endpoint.getAnnotation(ServerEndpoint.class);
+ Assert.assertThat("Endpoint: " + endpoint + " should be annotated with @ServerEndpoint",anno,notNullValue());
+ ServerEndpointConfig config = new BasicServerEndpointConfig(endpoint,"/");
+ AnnotatedServerEndpointMetadata metadata = new AnnotatedServerEndpointMetadata(endpoint,config);
+ AnnotatedEndpointScanner<ServerEndpoint, ServerEndpointConfig> scanner = new AnnotatedEndpointScanner<>(metadata);
+ scanner.scan();
+ EndpointInstance ei = new EndpointInstance(websocket,config,metadata);
+ EventDriver driver = driverImpl.create(ei,policy);
+ Assert.assertThat("EventDriver",driver,notNullValue());
+
+ // Create Local JsrSession
+ String id = testname.getMethodName();
+ URI requestURI = URI.create("ws://localhost/" + id);
+ DummyConnection connection = new DummyConnection();
+ ClientContainer container = new ClientContainer();
+ @SuppressWarnings("resource")
+ JsrSession session = new JsrSession(requestURI,driver,connection,container,id);
+ session.setPolicy(policy);
+ session.open();
+ return driver;
+ }
+
+ @Test
+ public void testOnTextPartial() throws Throwable
+ {
+ List<WebSocketFrame> frames = new ArrayList<>();
+ frames.add(new TextFrame().setPayload("Saved").setFin(false));
+ frames.add(new ContinuationFrame().setPayload(" by ").setFin(false));
+ frames.add(new ContinuationFrame().setPayload("zero").setFin(true));
+
+ PartialTrackingSocket socket = new PartialTrackingSocket();
+
+ EventDriver driver = toEventDriver(socket);
+ driver.onConnect();
+
+ for (WebSocketFrame frame : frames)
+ {
+ driver.incomingFrame(frame);
+ }
+
+ Assert.assertThat("Captured Event Queue size",socket.eventQueue.size(),is(3));
+ Assert.assertThat("Event[0]",socket.eventQueue.poll(),is("onPartial(\"Saved\",false)"));
+ Assert.assertThat("Event[1]",socket.eventQueue.poll(),is("onPartial(\" by \",false)"));
+ Assert.assertThat("Event[2]",socket.eventQueue.poll(),is("onPartial(\"zero\",true)"));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_GoodSignaturesTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_GoodSignaturesTest.java
new file mode 100644
index 0000000..e232857
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_GoodSignaturesTest.java
@@ -0,0 +1,202 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.io.Reader;
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+import javax.websocket.CloseReason;
+import javax.websocket.PongMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrCallable;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicBinaryMessageByteBufferSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicCloseReasonSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicCloseReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicCloseSessionReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicCloseSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicErrorSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicErrorSessionThrowableSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicErrorSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicErrorThrowableSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicErrorThrowableSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicOpenSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicOpenSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicPongMessageSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicTextMessageStringSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.StatelessTextMessageStringSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.beans.DateTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.BooleanObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.BooleanTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ByteObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ByteTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.CharTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.CharacterObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.DoubleObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.DoubleTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.FloatObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.FloatTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.IntTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.IntegerObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ShortObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ShortTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.ReaderParamSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.StringReturnReaderParamSocket;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test {@link AnnotatedEndpointScanner} against various simple, 1 method {@link ServerEndpoint} annotated classes with valid signatures.
+ */
+@RunWith(Parameterized.class)
+public class ServerAnnotatedEndpointScanner_GoodSignaturesTest
+{
+ public static class Case
+ {
+ public static void add(List<Case[]> data, Class<?> pojo, Field metadataField, Class<?>... expectedParams)
+ {
+ data.add(new Case[]
+ { new Case(pojo,metadataField,expectedParams) });
+ }
+
+ // The websocket pojo to test against
+ Class<?> pojo;
+ // The JsrAnnotatedMetadata field that should be populated
+ Field metadataField;
+ // The expected parameters for the Callable found by the scanner
+ Class<?> expectedParameters[];
+
+ public Case(Class<?> pojo, Field metadataField, Class<?>... expectedParams)
+ {
+ this.pojo = pojo;
+ this.metadataField = metadataField;
+ this.expectedParameters = expectedParams;
+ }
+ }
+
+ @Parameters
+ public static Collection<Case[]> data() throws Exception
+ {
+ List<Case[]> data = new ArrayList<>();
+ Field fOpen = findFieldRef(AnnotatedServerEndpointMetadata.class,"onOpen");
+ Field fClose = findFieldRef(AnnotatedServerEndpointMetadata.class,"onClose");
+ Field fError = findFieldRef(AnnotatedServerEndpointMetadata.class,"onError");
+ Field fText = findFieldRef(AnnotatedServerEndpointMetadata.class,"onText");
+ Field fTextStream = findFieldRef(AnnotatedServerEndpointMetadata.class,"onTextStream");
+ Field fBinary = findFieldRef(AnnotatedServerEndpointMetadata.class,"onBinary");
+ @SuppressWarnings("unused")
+ Field fBinaryStream = findFieldRef(AnnotatedServerEndpointMetadata.class,"onBinaryStream");
+ Field fPong = findFieldRef(AnnotatedServerEndpointMetadata.class,"onPong");
+
+ // @formatter:off
+ // -- Open Events
+ Case.add(data, BasicOpenSocket.class, fOpen);
+ Case.add(data, BasicOpenSessionSocket.class, fOpen, Session.class);
+ // -- Close Events
+ Case.add(data, BasicCloseSocket.class, fClose);
+ Case.add(data, BasicCloseReasonSocket.class, fClose, CloseReason.class);
+ Case.add(data, BasicCloseReasonSessionSocket.class, fClose, CloseReason.class, Session.class);
+ Case.add(data, BasicCloseSessionReasonSocket.class, fClose, Session.class, CloseReason.class);
+ // -- Error Events
+ Case.add(data, BasicErrorSocket.class, fError);
+ Case.add(data, BasicErrorSessionSocket.class, fError, Session.class);
+ Case.add(data, BasicErrorSessionThrowableSocket.class, fError, Session.class, Throwable.class);
+ Case.add(data, BasicErrorThrowableSocket.class, fError, Throwable.class);
+ Case.add(data, BasicErrorThrowableSessionSocket.class, fError, Throwable.class, Session.class);
+ // -- Text Events
+ Case.add(data, BasicTextMessageStringSocket.class, fText, String.class);
+ Case.add(data, StatelessTextMessageStringSocket.class, fText, Session.class, String.class);
+ // -- Primitives
+ Case.add(data, BooleanTextSocket.class, fText, Boolean.TYPE);
+ Case.add(data, BooleanObjectTextSocket.class, fText, Boolean.class);
+ Case.add(data, ByteTextSocket.class, fText, Byte.TYPE);
+ Case.add(data, ByteObjectTextSocket.class, fText, Byte.class);
+ Case.add(data, CharTextSocket.class, fText, Character.TYPE);
+ Case.add(data, CharacterObjectTextSocket.class, fText, Character.class);
+ Case.add(data, DoubleTextSocket.class, fText, Double.TYPE);
+ Case.add(data, DoubleObjectTextSocket.class, fText, Double.class);
+ Case.add(data, FloatTextSocket.class, fText, Float.TYPE);
+ Case.add(data, FloatObjectTextSocket.class, fText, Float.class);
+ Case.add(data, IntTextSocket.class, fText, Integer.TYPE);
+ Case.add(data, IntegerObjectTextSocket.class, fText, Integer.class);
+ Case.add(data, ShortTextSocket.class, fText, Short.TYPE);
+ Case.add(data, ShortObjectTextSocket.class, fText, Short.class);
+ // -- Beans
+ Case.add(data, DateTextSocket.class, fText, Date.class);
+ // -- Reader Events
+ Case.add(data, ReaderParamSocket.class, fTextStream, Reader.class, String.class);
+ Case.add(data, StringReturnReaderParamSocket.class, fTextStream, Reader.class, String.class);
+ // -- Binary Events
+ Case.add(data, BasicBinaryMessageByteBufferSocket.class, fBinary, ByteBuffer.class);
+ // -- Pong Events
+ Case.add(data, BasicPongMessageSocket.class, fPong, PongMessage.class);
+ // @formatter:on
+
+ // TODO: validate return types
+
+ return data;
+ }
+
+ private static Field findFieldRef(Class<?> clazz, String fldName) throws Exception
+ {
+ return clazz.getField(fldName);
+ }
+
+ private Case testcase;
+
+ public ServerAnnotatedEndpointScanner_GoodSignaturesTest(Case testcase)
+ {
+ this.testcase = testcase;
+ System.err.printf("Testing signature of %s%n",testcase.pojo.getName());
+ }
+
+ @Test
+ public void testScan_Basic() throws Exception
+ {
+ AnnotatedServerEndpointMetadata metadata = new AnnotatedServerEndpointMetadata(testcase.pojo,null);
+ AnnotatedEndpointScanner<ServerEndpoint, ServerEndpointConfig> scanner = new AnnotatedEndpointScanner<>(metadata);
+ scanner.scan();
+
+ Assert.assertThat("Metadata",metadata,notNullValue());
+
+ JsrCallable method = (JsrCallable)testcase.metadataField.get(metadata);
+ Assert.assertThat(testcase.metadataField.toString(),method,notNullValue());
+ int len = testcase.expectedParameters.length;
+ for (int i = 0; i < len; i++)
+ {
+ Class<?> expectedParam = testcase.expectedParameters[i];
+ Class<?> actualParam = method.getParamTypes()[i];
+
+ Assert.assertTrue("Parameter[" + i + "] - expected:[" + expectedParam + "], actual:[" + actualParam + "]",actualParam.equals(expectedParam));
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_InvalidSignaturesTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_InvalidSignaturesTest.java
new file mode 100644
index 0000000..abfb9bd
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_InvalidSignaturesTest.java
@@ -0,0 +1,111 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.containsString;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnOpen;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidCloseIntSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidErrorErrorSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidErrorExceptionSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidErrorIntSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidOpenCloseReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidOpenIntSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidOpenSessionIntSocket;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test {@link AnnotatedEndpointScanner} against various simple, 1 method {@link ServerEndpoint} annotated classes with invalid signatures.
+ */
+@RunWith(Parameterized.class)
+public class ServerAnnotatedEndpointScanner_InvalidSignaturesTest
+{
+ private static final Logger LOG = Log.getLogger(ServerAnnotatedEndpointScanner_InvalidSignaturesTest.class);
+
+ @Parameters
+ public static Collection<Class<?>[]> data()
+ {
+ List<Class<?>[]> data = new ArrayList<>();
+
+ // @formatter:off
+ data.add(new Class<?>[]{ InvalidCloseIntSocket.class, OnClose.class });
+ data.add(new Class<?>[]{ InvalidErrorErrorSocket.class, OnError.class });
+ data.add(new Class<?>[]{ InvalidErrorExceptionSocket.class, OnError.class });
+ data.add(new Class<?>[]{ InvalidErrorIntSocket.class, OnError.class });
+ data.add(new Class<?>[]{ InvalidOpenCloseReasonSocket.class, OnOpen.class });
+ data.add(new Class<?>[]{ InvalidOpenIntSocket.class, OnOpen.class });
+ data.add(new Class<?>[]{ InvalidOpenSessionIntSocket.class, OnOpen.class });
+ // @formatter:on
+
+ // TODO: invalid return types
+ // TODO: static methods
+ // TODO: private or protected methods
+ // TODO: abstract methods
+
+ return data;
+ }
+
+ // The pojo to test
+ private Class<?> pojo;
+ // The annotation class expected to be mentioned in the error message
+ private Class<? extends Annotation> expectedAnnoClass;
+
+ public ServerAnnotatedEndpointScanner_InvalidSignaturesTest(Class<?> pojo, Class<? extends Annotation> expectedAnnotation)
+ {
+ this.pojo = pojo;
+ this.expectedAnnoClass = expectedAnnotation;
+ }
+
+ @Test
+ public void testScan_InvalidSignature() throws DeploymentException
+ {
+ AnnotatedServerEndpointMetadata metadata = new AnnotatedServerEndpointMetadata(pojo,null);
+ AnnotatedEndpointScanner<ServerEndpoint,ServerEndpointConfig> scanner = new AnnotatedEndpointScanner<>(metadata);
+
+ try
+ {
+ scanner.scan();
+ Assert.fail("Expected " + InvalidSignatureException.class + " with message that references " + expectedAnnoClass + " annotation");
+ }
+ catch (InvalidSignatureException e)
+ {
+ LOG.debug("{}:{}",e.getClass(),e.getMessage());
+ Assert.assertThat("Message",e.getMessage(),containsString(expectedAnnoClass.getSimpleName()));
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionAltConfig.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionAltConfig.java
new file mode 100644
index 0000000..feb3c39
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionAltConfig.java
@@ -0,0 +1,55 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.websocket.Endpoint;
+import javax.websocket.server.ServerApplicationConfig;
+import javax.websocket.server.ServerEndpointConfig;
+
+public class SessionAltConfig implements ServerApplicationConfig
+{
+ @Override
+ public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> endpointClasses)
+ {
+ Set<ServerEndpointConfig> configs = new HashSet<>();
+ Class<?> endpointClass = SessionInfoSocket.class;
+ configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/info/{a}/").build());
+ configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/info/{a}/{b}/").build());
+ configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/info/{a}/{b}/{c}/").build());
+ configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/info/{a}/{b}/{c}/{d}/").build());
+ endpointClass = SessionInfoEndpoint.class;
+ configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/").build());
+ configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/{a}/").build());
+ configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/{a}/{b}/").build());
+ configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/{a}/{b}/{c}/").build());
+ configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/{a}/{b}/{c}/{d}/").build());
+ return configs;
+ }
+
+ @Override
+ public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned)
+ {
+ Set<Class<?>> annotated = new HashSet<>();
+ annotated.add(SessionInfoSocket.class);
+ return annotated;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionInfoEndpoint.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionInfoEndpoint.java
new file mode 100644
index 0000000..e68437b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionInfoEndpoint.java
@@ -0,0 +1,102 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+
+public class SessionInfoEndpoint extends Endpoint implements MessageHandler.Whole<String>
+{
+ private Session session;
+
+ @Override
+ public void onOpen(Session session, EndpointConfig config)
+ {
+ this.session = session;
+ this.session.addMessageHandler(this);
+ }
+
+ @Override
+ public void onMessage(String message)
+ {
+ try
+ {
+ if ("pathParams".equalsIgnoreCase(message))
+ {
+ StringBuilder ret = new StringBuilder();
+ ret.append("pathParams");
+ Map<String, String> pathParams = session.getPathParameters();
+ if (pathParams == null)
+ {
+ ret.append("=<null>");
+ }
+ else
+ {
+ ret.append('[').append(pathParams.size()).append(']');
+ List<String> keys = new ArrayList<>();
+ for (String key : pathParams.keySet())
+ {
+ keys.add(key);
+ }
+ Collections.sort(keys);
+ for (String key : keys)
+ {
+ String value = pathParams.get(key);
+ ret.append(": '").append(key).append("'=").append(value);
+ }
+ }
+ session.getBasicRemote().sendText(ret.toString());
+ return;
+ }
+
+ if ("requestUri".equalsIgnoreCase(message))
+ {
+ StringBuilder ret = new StringBuilder();
+ ret.append("requestUri=");
+ URI uri = session.getRequestURI();
+ if (uri == null)
+ {
+ ret.append("=<null>");
+ }
+ else
+ {
+ ret.append(uri.toASCIIString());
+ }
+ session.getBasicRemote().sendText(ret.toString());
+ return;
+ }
+
+ // simple echo
+ session.getBasicRemote().sendText("echo:'" + message + "'");
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace(System.err);
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionInfoSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionInfoSocket.java
new file mode 100644
index 0000000..3ad75a5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionInfoSocket.java
@@ -0,0 +1,83 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+@ServerEndpoint(value = "/info/")
+public class SessionInfoSocket
+{
+ @OnMessage
+ public String onMessage(Session session, String message)
+ {
+ if ("pathParams".equalsIgnoreCase(message))
+ {
+ StringBuilder ret = new StringBuilder();
+ ret.append("pathParams");
+ Map<String, String> pathParams = session.getPathParameters();
+ if (pathParams == null)
+ {
+ ret.append("=<null>");
+ }
+ else
+ {
+ ret.append('[').append(pathParams.size()).append(']');
+ List<String> keys = new ArrayList<>();
+ for (String key : pathParams.keySet())
+ {
+ keys.add(key);
+ }
+ Collections.sort(keys);
+ for (String key : keys)
+ {
+ String value = pathParams.get(key);
+ ret.append(": '").append(key).append("'=").append(value);
+ }
+ }
+ return ret.toString();
+ }
+
+ if ("requestUri".equalsIgnoreCase(message))
+ {
+ StringBuilder ret = new StringBuilder();
+ ret.append("requestUri=");
+ URI uri = session.getRequestURI();
+ if (uri == null)
+ {
+ ret.append("=<null>");
+ }
+ else
+ {
+ ret.append(uri.toASCIIString());
+ }
+ return ret.toString();
+ }
+
+ // simple echo
+ return "echo:'" + message + "'";
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTest.java
new file mode 100644
index 0000000..dbda210
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTest.java
@@ -0,0 +1,237 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.is;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SessionTest
+{
+ private static interface Case
+ {
+ public void customize(WebAppContext context);
+ }
+
+ @Parameters
+ public static Collection<Case[]> data()
+ {
+ List<Case[]> cases = new ArrayList<>();
+ cases.add(new Case[]
+ { new Case()
+ {
+ @Override
+ public void customize(WebAppContext context)
+ {
+ // no customization
+ }
+ } });
+ cases.add(new Case[]
+ { new Case()
+ {
+ @Override
+ public void customize(WebAppContext context)
+ {
+ // Test with DefaultServlet only
+ context.addServlet(DefaultServlet.class,"/");
+ }
+ } });
+ cases.add(new Case[]
+ { new Case()
+ {
+ @Override
+ public void customize(WebAppContext context)
+ {
+ // Test with Servlet mapped to "/*"
+ context.addServlet(DefaultServlet.class,"/*");
+ }
+ } });
+ cases.add(new Case[]
+ { new Case()
+ {
+ @Override
+ public void customize(WebAppContext context)
+ {
+ // Test with Servlet mapped to "/info/*"
+ context.addServlet(DefaultServlet.class,"/info/*");
+ }
+ } });
+ return cases;
+ }
+
+ private final Case testcase;
+ private WSServer server;
+ private URI serverUri;
+
+ public SessionTest(Case testcase)
+ {
+ this.testcase = testcase;
+ }
+
+ @Before
+ public void startServer() throws Exception
+ {
+ server = new WSServer(MavenTestingUtils.getTargetTestingDir(SessionTest.class.getSimpleName()),"app");
+ server.copyWebInf("empty-web.xml");
+ server.copyClass(SessionInfoSocket.class);
+ server.copyClass(SessionAltConfig.class);
+ server.start();
+ serverUri = server.getServerBaseURI();
+
+ WebAppContext webapp = server.createWebAppContext();
+ testcase.customize(webapp);
+ server.deployWebapp(webapp);
+ }
+
+ @After
+ public void stopServer()
+ {
+ server.stop();
+ }
+
+ private void assertResponse(String requestPath, String requestMessage, String expectedResponse) throws Exception
+ {
+ WebSocketClient client = new WebSocketClient();
+ try
+ {
+ client.start();
+ JettyEchoSocket clientEcho = new JettyEchoSocket();
+ Future<Session> future = client.connect(clientEcho,serverUri.resolve(requestPath));
+ // wait for connect
+ future.get(1,TimeUnit.SECONDS);
+ clientEcho.sendMessage(requestMessage);
+ Queue<String> msgs = clientEcho.awaitMessages(1);
+ Assert.assertThat("Expected message",msgs.poll(),is(expectedResponse));
+ }
+ finally
+ {
+ client.stop();
+ }
+ }
+
+ @Test
+ public void testPathParams_Annotated_Empty() throws Exception
+ {
+ assertResponse("info/","pathParams","pathParams[0]");
+ }
+
+ @Test
+ public void testPathParams_Annotated_Single() throws Exception
+ {
+ assertResponse("info/apple/","pathParams","pathParams[1]: 'a'=apple");
+ }
+
+ @Test
+ public void testPathParams_Annotated_Double() throws Exception
+ {
+ assertResponse("info/apple/pear/","pathParams","pathParams[2]: 'a'=apple: 'b'=pear");
+ }
+
+ @Test
+ public void testPathParams_Annotated_Triple() throws Exception
+ {
+ assertResponse("info/apple/pear/cherry/","pathParams","pathParams[3]: 'a'=apple: 'b'=pear: 'c'=cherry");
+ }
+
+ @Test
+ public void testPathParams_Endpoint_Empty() throws Exception
+ {
+ assertResponse("einfo/","pathParams","pathParams[0]");
+ }
+
+ @Test
+ public void testPathParams_Endpoint_Single() throws Exception
+ {
+ assertResponse("einfo/apple/","pathParams","pathParams[1]: 'a'=apple");
+ }
+
+ @Test
+ public void testPathParams_Endpoint_Double() throws Exception
+ {
+ assertResponse("einfo/apple/pear/","pathParams","pathParams[2]: 'a'=apple: 'b'=pear");
+ }
+
+ @Test
+ public void testPathParams_Endpoint_Triple() throws Exception
+ {
+ assertResponse("einfo/apple/pear/cherry/","pathParams","pathParams[3]: 'a'=apple: 'b'=pear: 'c'=cherry");
+ }
+
+ @Test
+ public void testRequestUri_Annotated_Basic() throws Exception
+ {
+ URI expectedUri = serverUri.resolve("info/");
+ assertResponse("info/","requestUri","requestUri=" + expectedUri.toASCIIString());
+ }
+
+ @Test
+ public void testRequestUri_Annotated_WithPathParam() throws Exception
+ {
+ URI expectedUri = serverUri.resolve("info/apple/banana/");
+ assertResponse("info/apple/banana/","requestUri","requestUri=" + expectedUri.toASCIIString());
+ }
+
+ @Test
+ public void testRequestUri_Annotated_WithPathParam_WithQuery() throws Exception
+ {
+ URI expectedUri = serverUri.resolve("info/apple/banana/?fruit=fresh&store=grandmasfarm");
+ assertResponse("info/apple/banana/?fruit=fresh&store=grandmasfarm","requestUri","requestUri=" + expectedUri.toASCIIString());
+ }
+
+ @Test
+ public void testRequestUri_Endpoint_Basic() throws Exception
+ {
+ URI expectedUri = serverUri.resolve("einfo/");
+ assertResponse("einfo/","requestUri","requestUri=" + expectedUri.toASCIIString());
+ }
+
+ @Test
+ public void testRequestUri_Endpoint_WithPathParam() throws Exception
+ {
+ URI expectedUri = serverUri.resolve("einfo/apple/banana/");
+ assertResponse("einfo/apple/banana/","requestUri","requestUri=" + expectedUri.toASCIIString());
+ }
+
+ @Test
+ public void testRequestUri_Endpoint_WithPathParam_WithQuery() throws Exception
+ {
+ URI expectedUri = serverUri.resolve("einfo/apple/banana/?fruit=fresh&store=grandmasfarm");
+ assertResponse("einfo/apple/banana/?fruit=fresh&store=grandmasfarm","requestUri","requestUri=" + expectedUri.toASCIIString());
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/StackUtil.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/StackUtil.java
new file mode 100644
index 0000000..296c947
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/StackUtil.java
@@ -0,0 +1,42 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+public final class StackUtil
+{
+ public static String toString(Throwable t)
+ {
+ try (StringWriter w = new StringWriter())
+ {
+ try (PrintWriter out = new PrintWriter(w))
+ {
+ t.printStackTrace(out);
+ return w.toString();
+ }
+ }
+ catch (IOException e)
+ {
+ return "Unable to get stacktrace for: " + t;
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/TrackingSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/TrackingSocket.java
new file mode 100644
index 0000000..b6d542c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/TrackingSocket.java
@@ -0,0 +1,127 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCode;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Assert;
+
+/**
+ * Abstract base socket used for tracking state and events within the socket for testing reasons.
+ */
+public abstract class TrackingSocket
+{
+ private static final Logger LOG = Log.getLogger(TrackingSocket.class);
+
+ public CloseReason closeReason;
+ public EventQueue<String> eventQueue = new EventQueue<String>();
+ public EventQueue<Throwable> errorQueue = new EventQueue<>();
+ public CountDownLatch openLatch = new CountDownLatch(1);
+ public CountDownLatch closeLatch = new CountDownLatch(1);
+ public CountDownLatch dataLatch = new CountDownLatch(1);
+
+ protected void addError(Throwable t)
+ {
+ LOG.warn(t);
+ errorQueue.add(t);
+ }
+
+ protected void addEvent(String format, Object... args)
+ {
+ eventQueue.add(String.format(format,args));
+ }
+
+ public void assertClose(CloseCode expectedCode, String expectedReason) throws InterruptedException
+ {
+ assertCloseCode(expectedCode);
+ assertCloseReason(expectedReason);
+ }
+
+ public void assertCloseCode(CloseCode expectedCode) throws InterruptedException
+ {
+ Assert.assertThat("Was Closed",closeLatch.await(50,TimeUnit.MILLISECONDS),is(true));
+ Assert.assertThat("CloseReason",closeReason,notNullValue());
+ Assert.assertThat("Close Code",closeReason.getCloseCode(),is(expectedCode));
+ }
+
+ private void assertCloseReason(String expectedReason)
+ {
+ Assert.assertThat("Close Reason",closeReason.getReasonPhrase(),is(expectedReason));
+ }
+
+ public void assertEvent(String expected)
+ {
+ String actual = eventQueue.poll();
+ Assert.assertEquals("Event",expected,actual);
+ }
+
+ public void assertIsOpen() throws InterruptedException
+ {
+ assertWasOpened();
+ assertNotClosed();
+ }
+
+ public void assertNotClosed()
+ {
+ Assert.assertThat("Closed Latch",closeLatch.getCount(),greaterThanOrEqualTo(1L));
+ }
+
+ public void assertNotOpened()
+ {
+ Assert.assertThat("Open Latch",openLatch.getCount(),greaterThanOrEqualTo(1L));
+ }
+
+ public void assertWasOpened() throws InterruptedException
+ {
+ Assert.assertThat("Was Opened",openLatch.await(500,TimeUnit.MILLISECONDS),is(true));
+ }
+
+ public void clear()
+ {
+ eventQueue.clear();
+ errorQueue.clear();
+ }
+
+ public void waitForClose(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+ {
+ Assert.assertThat("Client Socket Closed",closeLatch.await(timeoutDuration,timeoutUnit),is(true));
+ }
+
+ public void waitForConnected(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+ {
+ Assert.assertThat("Client Socket Connected",openLatch.await(timeoutDuration,timeoutUnit),is(true));
+ }
+
+ public void waitForData(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+ {
+ LOG.debug("Waiting for message");
+ Assert.assertThat("Data Received",dataLatch.await(timeoutDuration,timeoutUnit),is(true));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/WSServer.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/WSServer.java
new file mode 100644
index 0000000..acfff23
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/WSServer.java
@@ -0,0 +1,197 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.plus.webapp.EnvConfiguration;
+import org.eclipse.jetty.plus.webapp.PlusConfiguration;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.OS;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.FragmentConfiguration;
+import org.eclipse.jetty.webapp.MetaInfConfiguration;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebInfConfiguration;
+import org.eclipse.jetty.webapp.WebXmlConfiguration;
+import org.junit.Assert;
+
+/**
+ * Utility to build out exploded directory WebApps, in the /target/tests/ directory, for testing out servers that use javax.websocket endpoints.
+ * <p>
+ * This is particularly useful when the WebSocket endpoints are discovered via the javax.websocket annotation scanning.
+ */
+public class WSServer
+{
+ private static final Logger LOG = Log.getLogger(WSServer.class);
+ private final File contextDir;
+ private final String contextPath;
+ private Server server;
+ private URI serverUri;
+ private ContextHandlerCollection contexts;
+ private File webinf;
+ private File classesDir;
+
+ public WSServer(TestingDir testdir, String contextName)
+ {
+ this(testdir.getDir(),contextName);
+ }
+
+ public WSServer(File testdir, String contextName)
+ {
+ this.contextDir = new File(testdir,contextName);
+ this.contextPath = "/" + contextName;
+ FS.ensureEmpty(contextDir);
+ }
+
+ public void copyClass(Class<?> clazz) throws Exception
+ {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ String endpointPath = clazz.getName().replace('.','/') + ".class";
+ URL classUrl = cl.getResource(endpointPath);
+ Assert.assertThat("Class URL for: " + clazz,classUrl,notNullValue());
+ File destFile = new File(classesDir,OS.separators(endpointPath));
+ FS.ensureDirExists(destFile.getParentFile());
+ File srcFile = new File(classUrl.toURI());
+ IO.copy(srcFile,destFile);
+ }
+
+ public void copyEndpoint(Class<?> endpointClass) throws Exception
+ {
+ copyClass(endpointClass);
+ }
+
+ public void copyWebInf(String testResourceName) throws IOException
+ {
+ webinf = new File(contextDir,"WEB-INF");
+ FS.ensureDirExists(webinf);
+ classesDir = new File(webinf,"classes");
+ FS.ensureDirExists(classesDir);
+ File webxml = new File(webinf,"web.xml");
+ File testWebXml = MavenTestingUtils.getTestResourceFile(testResourceName);
+ IO.copy(testWebXml,webxml);
+ }
+
+ public WebAppContext createWebAppContext() throws MalformedURLException, IOException
+ {
+ WebAppContext context = new WebAppContext();
+ context.setContextPath(this.contextPath);
+ context.setBaseResource(Resource.newResource(this.contextDir));
+
+ // @formatter:off
+ context.setConfigurations(new Configuration[] {
+ new AnnotationConfiguration(),
+ new WebXmlConfiguration(),
+ new WebInfConfiguration(),
+ new PlusConfiguration(),
+ new MetaInfConfiguration(),
+ new FragmentConfiguration(),
+ new EnvConfiguration()});
+ // @formatter:on
+
+ return context;
+ }
+
+ public void createWebInf() throws IOException
+ {
+ copyWebInf("empty-web.xml");
+ }
+
+ public void deployWebapp(WebAppContext webapp) throws Exception
+ {
+ contexts.addHandler(webapp);
+ webapp.start();
+ if (LOG.isDebugEnabled())
+ {
+ webapp.dump(System.err);
+ }
+ }
+
+ public void dump()
+ {
+ server.dumpStdErr();
+ }
+
+ public URI getServerBaseURI()
+ {
+ return serverUri;
+ }
+
+ public File getWebAppDir()
+ {
+ return this.contextDir;
+ }
+
+ public void start() throws Exception
+ {
+ server = new Server();
+ ServerConnector connector = new ServerConnector(server);
+ connector.setPort(0);
+ server.addConnector(connector);
+
+ HandlerCollection handlers = new HandlerCollection();
+ contexts = new ContextHandlerCollection();
+ handlers.addHandler(contexts);
+ server.setHandler(handlers);
+
+ server.start();
+
+ String host = connector.getHost();
+ if (host == null)
+ {
+ host = "localhost";
+ }
+ int port = connector.getLocalPort();
+ serverUri = new URI(String.format("ws://%s:%d%s/",host,port,contextPath));
+ LOG.debug("Server started on {}",serverUri);
+
+ }
+
+ public void stop()
+ {
+ if (server != null)
+ {
+ try
+ {
+ server.stop();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace(System.err);
+ }
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/blockhead/BlockheadClient.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/blockhead/BlockheadClient.java
new file mode 100644
index 0000000..7f376db
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/blockhead/BlockheadClient.java
@@ -0,0 +1,746 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.blockhead;
+
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.HttpsURLConnection;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.api.util.WSURI;
+import org.eclipse.jetty.websocket.common.AcceptHash;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.ConnectionState;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
+import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
+import org.eclipse.jetty.websocket.common.io.IOState;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
+import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser;
+import org.junit.Assert;
+
+/**
+ * A simple websocket client for performing unit tests with.
+ * <p>
+ * This client will use {@link HttpURLConnection} and {@link HttpsURLConnection} with standard blocking calls to perform websocket requests.
+ * <p>
+ * This client is <u>NOT</u> intended to be performant or follow the websocket spec religiously. In fact, being able to deviate from the websocket spec at will
+ * is desired for this client to operate properly for the unit testing within this module.
+ * <p>
+ * The BlockheadClient should never validate frames or bytes being sent for validity, against any sort of spec, or even sanity. It should, however be honest
+ * with regards to basic IO behavior, a write should work as expected, a read should work as expected, but <u>what</u> byte it sends or reads is not within its
+ * scope.
+ */
+public class BlockheadClient implements IncomingFrames, OutgoingFrames, ConnectionStateListener, Closeable
+{
+ private static final String REQUEST_HASH_KEY = "dGhlIHNhbXBsZSBub25jZQ==";
+ private static final int BUFFER_SIZE = 8192;
+ private static final Logger LOG = Log.getLogger(BlockheadClient.class);
+ /** Set to true to disable timeouts (for debugging reasons) */
+ private boolean debug = false;
+ private final URI destHttpURI;
+ private final URI destWebsocketURI;
+ private final ByteBufferPool bufferPool;
+ private final Generator generator;
+ private final Parser parser;
+ private final IncomingFramesCapture incomingFrames;
+ private final WebSocketExtensionFactory extensionFactory;
+
+ private Socket socket;
+ private OutputStream out;
+ private InputStream in;
+ private int version = 13; // default to RFC-6455
+ private String protocols;
+ private List<String> extensions = new ArrayList<>();
+ private List<String> headers = new ArrayList<>();
+ private byte[] clientmask = new byte[]
+ { (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF };
+ private int timeout = 1000;
+ private AtomicInteger parseCount;
+ private OutgoingFrames outgoing = this;
+ private boolean eof = false;
+ private ExtensionStack extensionStack;
+ private IOState ioState;
+ private CountDownLatch disconnectedLatch = new CountDownLatch(1);
+ private ByteBuffer remainingBuffer;
+ private String connectionValue = "Upgrade";
+
+ public BlockheadClient(URI destWebsocketURI) throws URISyntaxException
+ {
+ this(WebSocketPolicy.newClientPolicy(),destWebsocketURI);
+ }
+
+ public BlockheadClient(WebSocketPolicy policy, URI destWebsocketURI) throws URISyntaxException
+ {
+ Assert.assertThat("Websocket URI scheme",destWebsocketURI.getScheme(),anyOf(is("ws"),is("wss")));
+ this.destWebsocketURI = destWebsocketURI;
+ if (destWebsocketURI.getScheme().equals("wss"))
+ {
+ throw new RuntimeException("Sorry, BlockheadClient does not support SSL");
+ }
+ this.destHttpURI = WSURI.toHttp(destWebsocketURI);
+
+ LOG.debug("WebSocket URI: {}",destWebsocketURI);
+ LOG.debug(" HTTP URI: {}",destHttpURI);
+
+ this.bufferPool = new MappedByteBufferPool(8192);
+ this.generator = new Generator(policy,bufferPool);
+ this.parser = new Parser(policy,bufferPool);
+ this.parseCount = new AtomicInteger(0);
+
+ this.incomingFrames = new IncomingFramesCapture();
+
+ this.extensionFactory = new WebSocketExtensionFactory(policy,bufferPool);
+ this.ioState = new IOState();
+ this.ioState.addListener(this);
+ }
+
+ public void addExtensions(String xtension)
+ {
+ this.extensions.add(xtension);
+ }
+
+ public void addHeader(String header)
+ {
+ this.headers.add(header);
+ }
+
+ public boolean awaitDisconnect(long timeout, TimeUnit unit) throws InterruptedException
+ {
+ return disconnectedLatch.await(timeout,unit);
+ }
+
+ public void clearCaptured()
+ {
+ this.incomingFrames.clear();
+ }
+
+ public void clearExtensions()
+ {
+ extensions.clear();
+ }
+
+ public void close()
+ {
+ LOG.debug("close()");
+ close(-1,null);
+ }
+
+ public void close(int statusCode, String message)
+ {
+ CloseInfo close = new CloseInfo(statusCode,message);
+
+ ioState.onCloseLocal(close);
+
+ if (!ioState.isClosed())
+ {
+ WebSocketFrame frame = close.asFrame();
+ LOG.debug("Issuing: {}",frame);
+ try
+ {
+ write(frame);
+ }
+ catch (IOException e)
+ {
+ LOG.debug(e);
+ }
+ }
+ }
+
+ public void connect() throws IOException
+ {
+ InetAddress destAddr = InetAddress.getByName(destHttpURI.getHost());
+ int port = destHttpURI.getPort();
+
+ SocketAddress endpoint = new InetSocketAddress(destAddr,port);
+
+ socket = new Socket();
+ socket.setSoTimeout(timeout);
+ socket.connect(endpoint);
+
+ out = socket.getOutputStream();
+ in = socket.getInputStream();
+ }
+
+ public void disconnect()
+ {
+ LOG.debug("disconnect");
+ IO.close(in);
+ IO.close(out);
+ disconnectedLatch.countDown();
+ if (socket != null)
+ {
+ try
+ {
+ socket.close();
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ }
+ }
+
+ public void expectServerDisconnect()
+ {
+ if (eof)
+ {
+ return;
+ }
+
+ try
+ {
+ int len = in.read();
+ if (len == (-1))
+ {
+ // we are disconnected
+ eof = true;
+ return;
+ }
+
+ Assert.assertThat("Expecting no data and proper socket disconnect (issued from server)",len,is(-1));
+ }
+ catch (SocketTimeoutException e)
+ {
+ LOG.warn(e);
+ Assert.fail("Expected a server initiated disconnect, instead the read timed out");
+ }
+ catch (IOException e)
+ {
+ // acceptable path
+ }
+ }
+
+ public HttpResponse expectUpgradeResponse() throws IOException
+ {
+ HttpResponse response = readResponseHeader();
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("Response Header: {}{}",'\n',response);
+ }
+
+ Assert.assertThat("Response Status Code",response.getStatusCode(),is(101));
+ Assert.assertThat("Response Status Reason",response.getStatusReason(),is("Switching Protocols"));
+ Assert.assertThat("Response Header[Upgrade]",response.getHeader("Upgrade"),is("WebSocket"));
+ Assert.assertThat("Response Header[Connection]",response.getHeader("Connection"),is("Upgrade"));
+
+ // Validate the Sec-WebSocket-Accept
+ String acceptKey = response.getHeader("Sec-WebSocket-Accept");
+ Assert.assertThat("Response Header[Sec-WebSocket-Accept Exists]",acceptKey,notNullValue());
+
+ String reqKey = REQUEST_HASH_KEY;
+ String expectedHash = AcceptHash.hashKey(reqKey);
+
+ Assert.assertThat("Valid Sec-WebSocket-Accept Hash?",acceptKey,is(expectedHash));
+
+ // collect extensions configured in response header
+ List<ExtensionConfig> configs = getExtensionConfigs(response);
+ extensionStack = new ExtensionStack(this.extensionFactory);
+ extensionStack.negotiate(configs);
+
+ // Start with default routing
+ extensionStack.setNextIncoming(this); // the websocket layer
+ extensionStack.setNextOutgoing(outgoing); // the network layer
+
+ // Configure Parser / Generator
+ extensionStack.configure(parser);
+ extensionStack.configure(generator);
+
+ // Start Stack
+ try
+ {
+ extensionStack.start();
+ }
+ catch (Exception e)
+ {
+ throw new IOException("Unable to start Extension Stack");
+ }
+
+ // configure parser
+ parser.setIncomingFramesHandler(extensionStack);
+ ioState.onOpened();
+
+ LOG.debug("outgoing = {}",outgoing);
+ LOG.debug("incoming = {}",extensionStack);
+
+ return response;
+ }
+
+ public void flush() throws IOException
+ {
+ out.flush();
+ }
+
+ public String getConnectionValue()
+ {
+ return connectionValue;
+ }
+
+ private List<ExtensionConfig> getExtensionConfigs(HttpResponse response)
+ {
+ List<ExtensionConfig> configs = new ArrayList<>();
+
+ String econf = response.getHeader("Sec-WebSocket-Extensions");
+ if (econf != null)
+ {
+ LOG.debug("Found Extension Response: {}",econf);
+ ExtensionConfig config = ExtensionConfig.parse(econf);
+ configs.add(config);
+ }
+ return configs;
+ }
+
+ public List<String> getExtensions()
+ {
+ return extensions;
+ }
+
+ public URI getHttpURI()
+ {
+ return destHttpURI;
+ }
+
+ public IOState getIOState()
+ {
+ return ioState;
+ }
+
+ public String getProtocols()
+ {
+ return protocols;
+ }
+
+ public String getRequestHost()
+ {
+ if (destHttpURI.getPort() > 0)
+ {
+ return String.format("%s:%d",destHttpURI.getHost(),destHttpURI.getPort());
+ }
+ else
+ {
+ return destHttpURI.getHost();
+ }
+ }
+
+ public String getRequestPath()
+ {
+ StringBuilder path = new StringBuilder();
+ path.append(destHttpURI.getPath());
+ if (StringUtil.isNotBlank(destHttpURI.getQuery()))
+ {
+ path.append('?').append(destHttpURI.getQuery());
+ }
+ return path.toString();
+ }
+
+ public String getRequestWebSocketKey()
+ {
+ return REQUEST_HASH_KEY;
+ }
+
+ public String getRequestWebSocketOrigin()
+ {
+ return destWebsocketURI.toASCIIString();
+ }
+
+ public int getVersion()
+ {
+ return version;
+ }
+
+ public URI getWebsocketURI()
+ {
+ return destWebsocketURI;
+ }
+
+ /**
+ * Errors received (after extensions)
+ */
+ @Override
+ public void incomingError(Throwable e)
+ {
+ incomingFrames.incomingError(e);
+ }
+
+ /**
+ * Frames received (after extensions)
+ */
+ @Override
+ public void incomingFrame(Frame frame)
+ {
+ LOG.debug("incoming({})",frame);
+ int count = parseCount.incrementAndGet();
+ if ((count % 10) == 0)
+ {
+ LOG.info("Client parsed {} frames",count);
+ }
+
+ if (frame.getOpCode() == OpCode.CLOSE)
+ {
+ CloseInfo close = new CloseInfo(frame);
+ ioState.onCloseRemote(close);
+ }
+
+ WebSocketFrame copy = WebSocketFrame.copy(frame);
+ incomingFrames.incomingFrame(copy);
+ }
+
+ public boolean isConnected()
+ {
+ return (socket != null) && (socket.isConnected());
+ }
+
+ @Override
+ public void onConnectionStateChange(ConnectionState state)
+ {
+ switch (state)
+ {
+ case CLOSED:
+ // Per Spec, client should not initiate disconnect on its own
+ // this.disconnect();
+ break;
+ case CLOSING:
+ if (ioState.wasRemoteCloseInitiated())
+ {
+ CloseInfo close = ioState.getCloseInfo();
+ close(close.getStatusCode(),close.getReason());
+ }
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+
+ @Override
+ public void outgoingFrame(Frame frame, WriteCallback callback)
+ {
+ ByteBuffer headerBuf = generator.generateHeaderBytes(frame);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("writing out: {}",BufferUtil.toDetailString(headerBuf));
+ }
+ try
+ {
+ BufferUtil.writeTo(headerBuf,out);
+ BufferUtil.writeTo(frame.getPayload(),out);
+ out.flush();
+ if (callback != null)
+ {
+ callback.writeSuccess();
+ }
+ }
+ catch (IOException e)
+ {
+ if (callback != null)
+ {
+ callback.writeFailed(e);
+ }
+ }
+ finally
+ {
+ bufferPool.release(headerBuf);
+ }
+
+ if (frame.getOpCode() == OpCode.CLOSE)
+ {
+ disconnect();
+ }
+ }
+
+ public int read(ByteBuffer buf) throws IOException
+ {
+ if (eof)
+ {
+ throw new EOFException("Hit EOF");
+ }
+
+ if ((remainingBuffer != null) && (remainingBuffer.remaining() > 0))
+ {
+ return BufferUtil.put(remainingBuffer,buf);
+ }
+
+ int len = -1;
+ int b;
+ while ((in.available() > 0) && (buf.remaining() > 0))
+ {
+ b = in.read();
+ if (b == (-1))
+ {
+ eof = true;
+ break;
+ }
+ buf.put((byte)b);
+ len++;
+ }
+
+ return len;
+ }
+
+ public IncomingFramesCapture readFrames(int expectedCount, TimeUnit timeoutUnit, int timeoutDuration) throws IOException, TimeoutException
+ {
+ LOG.debug("Read: waiting for {} frame(s) from server",expectedCount);
+
+ ByteBuffer buf = bufferPool.acquire(BUFFER_SIZE,false);
+ BufferUtil.clearToFill(buf);
+ try
+ {
+ long msDur = TimeUnit.MILLISECONDS.convert(timeoutDuration,timeoutUnit);
+ long now = System.currentTimeMillis();
+ long expireOn = now + msDur;
+ LOG.debug("Now: {} - expireOn: {} ({} ms)",now,expireOn,msDur);
+
+ long iter = 0;
+
+ int len = 0;
+ while (incomingFrames.size() < expectedCount)
+ {
+ BufferUtil.clearToFill(buf);
+ len = read(buf);
+ if (len > 0)
+ {
+ BufferUtil.flipToFlush(buf,0);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("Read {} bytes: {}",len,BufferUtil.toDetailString(buf));
+ }
+ parser.parse(buf);
+ }
+ else
+ {
+ if (LOG.isDebugEnabled())
+ {
+ iter++;
+ if ((iter % 10000000) == 0)
+ {
+ LOG.debug("10,000,000 reads of zero length");
+ iter = 0;
+ }
+ }
+ }
+
+ if (!debug && (System.currentTimeMillis() > expireOn))
+ {
+ incomingFrames.dump();
+ throw new TimeoutException(String.format("Timeout reading all %d expected frames. (managed to only read %d frame(s))",expectedCount,
+ incomingFrames.size()));
+ }
+ }
+ }
+ finally
+ {
+ bufferPool.release(buf);
+ }
+
+ return incomingFrames;
+ }
+
+ public HttpResponse readResponseHeader() throws IOException
+ {
+ HttpResponse response = new HttpResponse();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(response);
+
+ ByteBuffer buf = BufferUtil.allocate(512);
+
+ do
+ {
+ BufferUtil.flipToFill(buf);
+ read(buf);
+ BufferUtil.flipToFlush(buf,0);
+ }
+ while (parser.parse(buf) == null);
+
+ remainingBuffer = response.getRemainingBuffer();
+
+ return response;
+ }
+
+ public void sendStandardRequest() throws IOException
+ {
+ StringBuilder req = new StringBuilder();
+ req.append("GET ").append(getRequestPath()).append(" HTTP/1.1\r\n");
+ req.append("Host: ").append(getRequestHost()).append("\r\n");
+ req.append("Upgrade: websocket\r\n");
+ req.append("Connection: ").append(connectionValue).append("\r\n");
+ for (String header : headers)
+ {
+ req.append(header);
+ }
+ req.append("Sec-WebSocket-Key: ").append(getRequestWebSocketKey()).append("\r\n");
+ req.append("Sec-WebSocket-Origin: ").append(getRequestWebSocketOrigin()).append("\r\n");
+ if (StringUtil.isNotBlank(protocols))
+ {
+ req.append("Sec-WebSocket-Protocol: ").append(protocols).append("\r\n");
+ }
+
+ for (String xtension : extensions)
+ {
+ req.append("Sec-WebSocket-Extensions: ").append(xtension).append("\r\n");
+ }
+ req.append("Sec-WebSocket-Version: ").append(version).append("\r\n");
+ req.append("\r\n");
+ writeRaw(req.toString());
+ }
+
+ public void setConnectionValue(String connectionValue)
+ {
+ this.connectionValue = connectionValue;
+ }
+
+ public void setDebug(boolean flag)
+ {
+ this.debug = flag;
+ }
+
+ public void setProtocols(String protocols)
+ {
+ this.protocols = protocols;
+ }
+
+ public void setTimeout(TimeUnit unit, int duration)
+ {
+ this.timeout = (int)TimeUnit.MILLISECONDS.convert(duration,unit);
+ }
+
+ public void setVersion(int version)
+ {
+ this.version = version;
+ }
+
+ public void skipTo(String string) throws IOException
+ {
+ int state = 0;
+
+ while (true)
+ {
+ int b = in.read();
+ if (b < 0)
+ {
+ throw new EOFException();
+ }
+
+ if (b == string.charAt(state))
+ {
+ state++;
+ if (state == string.length())
+ {
+ break;
+ }
+ }
+ else
+ {
+ state = 0;
+ }
+ }
+ }
+
+ public void sleep(TimeUnit unit, int duration) throws InterruptedException
+ {
+ LOG.info("Sleeping for {} {}",duration,unit);
+ unit.sleep(duration);
+ LOG.info("Waking up from sleep");
+ }
+
+ public void write(WebSocketFrame frame) throws IOException
+ {
+ if (!ioState.isOpen())
+ {
+ return;
+ }
+ LOG.debug("write(Frame->{}) to {}",frame,outgoing);
+ if (LOG.isDebugEnabled())
+ {
+ frame.setMask(new byte[]
+ { 0x00, 0x00, 0x00, 0x00 });
+ }
+ else
+ {
+ frame.setMask(clientmask);
+ }
+ extensionStack.outgoingFrame(frame,null);
+ }
+
+ public void writeRaw(ByteBuffer buf) throws IOException
+ {
+ LOG.debug("write(ByteBuffer) {}",BufferUtil.toDetailString(buf));
+ BufferUtil.writeTo(buf,out);
+ }
+
+ public void writeRaw(ByteBuffer buf, int numBytes) throws IOException
+ {
+ int len = Math.min(numBytes,buf.remaining());
+ byte arr[] = new byte[len];
+ buf.get(arr,0,len);
+ out.write(arr);
+ }
+
+ public void writeRaw(String str) throws IOException
+ {
+ LOG.debug("write((String)[{}]){}{})",str.length(),'\n',str);
+ out.write(StringUtil.getBytes(str,StringUtil.__ISO_8859_1));
+ }
+
+ public void writeRawSlowly(ByteBuffer buf, int segmentSize) throws IOException
+ {
+ while (buf.remaining() > 0)
+ {
+ writeRaw(buf,segmentSize);
+ flush();
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/blockhead/BlockheadClientConstructionTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/blockhead/BlockheadClientConstructionTest.java
new file mode 100644
index 0000000..88f1c99
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/blockhead/BlockheadClientConstructionTest.java
@@ -0,0 +1,72 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.blockhead;
+
+import static org.hamcrest.Matchers.is;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Gotta test some basic constructors of the BlockheadClient.
+ */
+@RunWith(value = Parameterized.class)
+public class BlockheadClientConstructionTest
+{
+ @Parameters
+ public static Collection<Object[]> data()
+ {
+ List<Object[]> data = new ArrayList<>();
+ // @formatter:off
+ data.add(new Object[] { "ws://localhost/", "http://localhost/" });
+ data.add(new Object[] { "ws://localhost:8080/", "http://localhost:8080/" });
+ data.add(new Object[] { "ws://webtide.com/", "http://webtide.com/" });
+ data.add(new Object[] { "ws://www.webtide.com/sockets/chat", "http://www.webtide.com/sockets/chat" });
+ // @formatter:on
+ return data;
+ }
+
+ private URI expectedWsUri;
+ private URI expectedHttpUri;
+
+ public BlockheadClientConstructionTest(String wsuri, String httpuri)
+ {
+ this.expectedWsUri = URI.create(wsuri);
+ this.expectedHttpUri = URI.create(httpuri);
+ }
+
+ @SuppressWarnings("resource")
+ @Test
+ public void testURIs() throws URISyntaxException
+ {
+ BlockheadClient client = new BlockheadClient(expectedWsUri);
+ Assert.assertThat("Websocket URI",client.getWebsocketURI(),is(expectedWsUri));
+ Assert.assertThat("Websocket URI",client.getHttpURI(),is(expectedHttpUri));
+ }
+
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/blockhead/HttpResponse.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/blockhead/HttpResponse.java
new file mode 100644
index 0000000..88f52eb
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/blockhead/HttpResponse.java
@@ -0,0 +1,94 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.blockhead;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener;
+
+public class HttpResponse implements HttpResponseHeaderParseListener
+{
+ private int statusCode;
+ private String statusReason;
+ private Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ private ByteBuffer remainingBuffer;
+
+ @Override
+ public void addHeader(String name, String value)
+ {
+ headers.put(name,value);
+ }
+
+ public String getExtensionsHeader()
+ {
+ return getHeader("Sec-WebSocket-Extensions");
+ }
+
+ public String getHeader(String name)
+ {
+ return headers.get(name);
+ }
+
+ public ByteBuffer getRemainingBuffer()
+ {
+ return remainingBuffer;
+ }
+
+ public int getStatusCode()
+ {
+ return statusCode;
+ }
+
+ public String getStatusReason()
+ {
+ return statusReason;
+ }
+
+ @Override
+ public void setRemainingBuffer(ByteBuffer copy)
+ {
+ this.remainingBuffer = copy;
+ }
+
+ @Override
+ public void setStatusCode(int code)
+ {
+ this.statusCode = code;
+ }
+
+ @Override
+ public void setStatusReason(String reason)
+ {
+ this.statusReason = reason;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder();
+ str.append("HTTP/1.1 ").append(statusCode).append(' ').append(statusReason);
+ for (Map.Entry<String, String> entry : headers.entrySet())
+ {
+ str.append('\n').append(entry.getKey()).append(": ").append(entry.getValue());
+ }
+ return str.toString();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/blockhead/IncomingFramesCapture.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/blockhead/IncomingFramesCapture.java
new file mode 100644
index 0000000..e482d84
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/blockhead/IncomingFramesCapture.java
@@ -0,0 +1,148 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.blockhead;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+
+import java.util.Queue;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.junit.Assert;
+
+public class IncomingFramesCapture implements IncomingFrames
+{
+ private static final Logger LOG = Log.getLogger(IncomingFramesCapture.class);
+ private EventQueue<WebSocketFrame> frames = new EventQueue<>();
+ private EventQueue<Throwable> errors = new EventQueue<>();
+
+ public void assertErrorCount(int expectedCount)
+ {
+ Assert.assertThat("Captured error count",errors.size(),is(expectedCount));
+ }
+
+ public void assertFrameCount(int expectedCount)
+ {
+ Assert.assertThat("Captured frame count",frames.size(),is(expectedCount));
+ }
+
+ public void assertHasErrors(Class<? extends WebSocketException> errorType, int expectedCount)
+ {
+ Assert.assertThat(errorType.getSimpleName(),getErrorCount(errorType),is(expectedCount));
+ }
+
+ public void assertHasFrame(byte op)
+ {
+ Assert.assertThat(OpCode.name(op),getFrameCount(op),greaterThanOrEqualTo(1));
+ }
+
+ public void assertHasFrame(byte op, int expectedCount)
+ {
+ Assert.assertThat(OpCode.name(op),getFrameCount(op),is(expectedCount));
+ }
+
+ public void assertHasNoFrames()
+ {
+ Assert.assertThat("Has no frames",frames.size(),is(0));
+ }
+
+ public void assertNoErrors()
+ {
+ Assert.assertThat("Has no errors",errors.size(),is(0));
+ }
+
+ public void clear()
+ {
+ frames.clear();
+ }
+
+ public void dump()
+ {
+ System.err.printf("Captured %d incoming frames%n",frames.size());
+ int i = 0;
+ for (Frame frame : frames)
+ {
+ System.err.printf("[%3d] %s%n",i++,frame);
+ System.err.printf(" payload: %s%n",BufferUtil.toDetailString(frame.getPayload()));
+ }
+ }
+
+ public int getErrorCount(Class<? extends Throwable> errorType)
+ {
+ int count = 0;
+ for (Throwable error : errors)
+ {
+ if (errorType.isInstance(error))
+ {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ public Queue<Throwable> getErrors()
+ {
+ return errors;
+ }
+
+ public int getFrameCount(byte op)
+ {
+ int count = 0;
+ for (WebSocketFrame frame : frames)
+ {
+ if (frame.getOpCode() == op)
+ {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ public Queue<WebSocketFrame> getFrames()
+ {
+ return frames;
+ }
+
+ @Override
+ public void incomingError(Throwable e)
+ {
+ LOG.debug(e);
+ errors.add(e);
+ }
+
+ @Override
+ public void incomingFrame(Frame frame)
+ {
+ WebSocketFrame copy = WebSocketFrame.copy(frame);
+ frames.add(copy);
+ }
+
+ public int size()
+ {
+ return frames.size();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserConfigurator.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserConfigurator.java
new file mode 100644
index 0000000..56892b6
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserConfigurator.java
@@ -0,0 +1,52 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.browser;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+
+public class JsrBrowserConfigurator extends ServerEndpointConfig.Configurator
+{
+ @Override
+ public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+ {
+ super.modifyHandshake(sec,request,response);
+ sec.getUserProperties().put("userAgent",getHeaderValue(request,"User-Agent"));
+ sec.getUserProperties().put("requestedExtensions",getHeaderValue(request,"Sec-WebSocket-Extensions"));
+ }
+
+ private String getHeaderValue(HandshakeRequest request, String key)
+ {
+ List<String> value = request.getHeaders().get(key);
+ return QuoteUtil.join(value,",");
+ }
+
+ @Override
+ public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested)
+ {
+ return Collections.emptyList();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java
new file mode 100644
index 0000000..4ecd289
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java
@@ -0,0 +1,97 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.browser;
+
+import javax.websocket.DeploymentException;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+
+/**
+ * Tool to help debug JSR based websocket circumstances reported around browsers.
+ * <p>
+ * Provides a server, with a few simple websocket's that can be twiddled from a browser. This helps with setting up breakpoints and whatnot to help debug our
+ * websocket implementation from the context of a browser client.
+ */
+public class JsrBrowserDebugTool
+{
+ private static final Logger LOG = Log.getLogger(JsrBrowserDebugTool.class);
+
+ public static void main(String[] args)
+ {
+ int port = 8080;
+
+ for (int i = 0; i < args.length; i++)
+ {
+ String a = args[i];
+ if ("-p".equals(a) || "--port".equals(a))
+ {
+ port = Integer.parseInt(args[++i]);
+ }
+ }
+
+ try
+ {
+ JsrBrowserDebugTool tool = new JsrBrowserDebugTool();
+ tool.setupServer(port);
+ tool.runForever();
+ }
+ catch (Throwable t)
+ {
+ LOG.warn(t);
+ }
+ }
+
+ private Server server;
+
+ private void runForever() throws Exception
+ {
+ server.start();
+ server.dumpStdErr();
+ LOG.info("Server available.");
+ server.join();
+ }
+
+ private void setupServer(int port) throws DeploymentException
+ {
+ server = new Server();
+ ServerConnector connector = new ServerConnector(server);
+ connector.setPort(port);
+ server.addConnector(connector);
+
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+ context.setContextPath("/");
+ ServletHolder holder = context.addServlet(DefaultServlet.class,"/");
+ holder.setInitParameter("resourceBase","src/test/resources/jsr-browser-debug-tool");
+ holder.setInitParameter("dirAllowed","true");
+ server.setHandler(context);
+
+ ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
+ container.addEndpoint(JsrBrowserSocket.class);
+
+ LOG.info("{} setup on port {}",this.getClass().getName(),port);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserSocket.java
new file mode 100644
index 0000000..611a247
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserSocket.java
@@ -0,0 +1,227 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.browser;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.Random;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.RemoteEndpoint.Async;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+@ServerEndpoint(value = "/", subprotocols = { "tool" }, configurator = JsrBrowserConfigurator.class)
+public class JsrBrowserSocket
+{
+ private static class WriteMany implements Runnable
+ {
+ private Async remote;
+ private int size;
+ private int count;
+
+ public WriteMany(Async remote, int size, int count)
+ {
+ this.remote = remote;
+ this.size = size;
+ this.count = count;
+ }
+
+ @Override
+ public void run()
+ {
+ char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-|{}[]():".toCharArray();
+ int lettersLen = letters.length;
+ char randomText[] = new char[size];
+ Random rand = new Random(42);
+ String msg;
+
+ for (int n = 0; n < count; n++)
+ {
+ // create random text
+ for (int i = 0; i < size; i++)
+ {
+ randomText[i] = letters[rand.nextInt(lettersLen)];
+ }
+ msg = String.format("ManyThreads [%s]",String.valueOf(randomText));
+ remote.sendText(msg);
+ }
+ }
+ }
+
+ private static final Logger LOG = Log.getLogger(JsrBrowserSocket.class);
+ private Session session;
+ private Async remote;
+ private String userAgent;
+ private String requestedExtensions;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ LOG.info("Open: {}",session);
+ this.session = session;
+ this.remote = session.getAsyncRemote();
+ this.userAgent = (String)session.getUserProperties().get("userAgent");
+ this.requestedExtensions = (String)session.getUserProperties().get("requestedExtensions");
+ }
+
+ @OnClose
+ public void onClose(CloseReason close)
+ {
+ LOG.info("Close: {}: {}",close.getCloseCode(),close.getReasonPhrase());
+ this.session = null;
+ }
+
+ @OnMessage
+ public void onMessage(String message)
+ {
+ LOG.info("onTextMessage({})",message);
+
+ int idx = message.indexOf(':');
+ if (idx > 0)
+ {
+ String key = message.substring(0,idx).toLowerCase(Locale.ENGLISH);
+ String val = message.substring(idx + 1);
+ switch (key)
+ {
+ case "info":
+ {
+ writeMessage("Using javax.websocket");
+ if (StringUtil.isBlank(userAgent))
+ {
+ writeMessage("Client has no User-Agent");
+ }
+ else
+ {
+ writeMessage("Client User-Agent: " + this.userAgent);
+ }
+
+ if (StringUtil.isBlank(requestedExtensions))
+ {
+ writeMessage("Client requested no Sec-WebSocket-Extensions");
+ }
+ else
+ {
+ writeMessage("Client Sec-WebSocket-Extensions: " + this.requestedExtensions);
+ }
+ break;
+ }
+ case "many":
+ {
+ String parts[] = val.split(",");
+ int size = Integer.parseInt(parts[0]);
+ int count = Integer.parseInt(parts[1]);
+
+ writeManyAsync(size,count);
+ break;
+ }
+ case "manythreads":
+ {
+ String parts[] = val.split(",");
+ int threadCount = Integer.parseInt(parts[0]);
+ int size = Integer.parseInt(parts[1]);
+ int count = Integer.parseInt(parts[2]);
+
+ Thread threads[] = new Thread[threadCount];
+
+ // Setup threads
+ for (int n = 0; n < threadCount; n++)
+ {
+ threads[n] = new Thread(new WriteMany(remote,size,count),"WriteMany[" + n + "]");
+ }
+
+ // Execute threads
+ for (Thread thread : threads)
+ {
+ thread.start();
+ }
+
+ // Drop out of this thread
+ break;
+ }
+ case "time":
+ {
+ Calendar now = Calendar.getInstance();
+ DateFormat sdf = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.FULL,SimpleDateFormat.FULL);
+ writeMessage("Server time: %s",sdf.format(now.getTime()));
+ break;
+ }
+ default:
+ {
+ writeMessage("key[%s] val[%s]",key,val);
+ }
+ }
+ }
+ else
+ {
+ // Not parameterized, echo it back
+ writeMessage(message);
+ }
+ }
+
+ private void writeManyAsync(int size, int count)
+ {
+ char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-|{}[]():".toCharArray();
+ int lettersLen = letters.length;
+ char randomText[] = new char[size];
+ Random rand = new Random(42);
+
+ for (int n = 0; n < count; n++)
+ {
+ // create random text
+ for (int i = 0; i < size; i++)
+ {
+ randomText[i] = letters[rand.nextInt(lettersLen)];
+ }
+ writeMessage("Many [%s]",String.valueOf(randomText));
+ }
+ }
+
+ private void writeMessage(String message)
+ {
+ if (this.session == null)
+ {
+ LOG.debug("Not connected");
+ return;
+ }
+
+ if (session.isOpen() == false)
+ {
+ LOG.debug("Not open");
+ return;
+ }
+
+ // Async write
+ remote.sendText(message);
+ }
+
+ private void writeMessage(String format, Object... args)
+ {
+ writeMessage(String.format(format,args));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/PathMappingsTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/PathMappingsTest.java
new file mode 100644
index 0000000..54b3ec7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/PathMappingsTest.java
@@ -0,0 +1,110 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.pathmap;
+
+import static org.hamcrest.Matchers.notNullValue;
+
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
+import org.eclipse.jetty.websocket.server.pathmap.ServletPathSpec;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PathMappingsTest
+{
+ private void assertMatch(PathMappings<String> pathmap, String path, String expectedValue)
+ {
+ String msg = String.format(".getMatch(\"%s\")",path);
+ MappedResource<String> match = pathmap.getMatch(path);
+ Assert.assertThat(msg,match,notNullValue());
+ String actualMatch = match.getResource();
+ Assert.assertEquals(msg,expectedValue,actualMatch);
+ }
+
+ public void dumpMappings(PathMappings<String> p)
+ {
+ for (MappedResource<String> res : p)
+ {
+ System.out.printf(" %s%n",res);
+ }
+ }
+
+ /**
+ * Test the match order rules with a mixed Servlet and WebSocket path specs
+ * <p>
+ * <ul>
+ * <li>Exact match</li>
+ * <li>Longest prefix match</li>
+ * <li>Longest suffix match</li>
+ * </ul>
+ */
+ @Test
+ public void testMixedMatchOrder()
+ {
+ PathMappings<String> p = new PathMappings<>();
+
+ p.put(new ServletPathSpec("/"),"default");
+ p.put(new ServletPathSpec("/animal/bird/*"),"birds");
+ p.put(new ServletPathSpec("/animal/fish/*"),"fishes");
+ p.put(new ServletPathSpec("/animal/*"),"animals");
+ p.put(new WebSocketPathSpec("/animal/{type}/{name}/chat"),"animalChat");
+ p.put(new WebSocketPathSpec("/animal/{type}/{name}/cam"),"animalCam");
+ p.put(new WebSocketPathSpec("/entrance/cam"),"entranceCam");
+
+ // dumpMappings(p);
+
+ assertMatch(p,"/animal/bird/eagle","birds");
+ assertMatch(p,"/animal/fish/bass/sea","fishes");
+ assertMatch(p,"/animal/peccary/javalina/evolution","animals");
+ assertMatch(p,"/","default");
+ assertMatch(p,"/animal/bird/eagle/chat","animalChat");
+ assertMatch(p,"/animal/bird/penguin/chat","animalChat");
+ assertMatch(p,"/animal/fish/trout/cam","animalCam");
+ assertMatch(p,"/entrance/cam","entranceCam");
+ }
+
+ /**
+ * Test the match order rules imposed by the WebSocket API (JSR-356)
+ * <p>
+ * <ul>
+ * <li>Exact match</li>
+ * <li>Longest prefix match</li>
+ * <li>Longest suffix match</li>
+ * </ul>
+ */
+ @Test
+ public void testWebsocketMatchOrder()
+ {
+ PathMappings<String> p = new PathMappings<>();
+
+ p.put(new WebSocketPathSpec("/a/{var}/c"),"endpointA");
+ p.put(new WebSocketPathSpec("/a/b/c"),"endpointB");
+ p.put(new WebSocketPathSpec("/a/{var1}/{var2}"),"endpointC");
+ p.put(new WebSocketPathSpec("/{var1}/d"),"endpointD");
+ p.put(new WebSocketPathSpec("/b/{var2}"),"endpointE");
+
+ // dumpMappings(p);
+
+ assertMatch(p,"/a/b/c","endpointB");
+ assertMatch(p,"/a/d/c","endpointA");
+ assertMatch(p,"/a/x/y","endpointC");
+
+ assertMatch(p,"/b/d","endpointE");
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecBadSpecsTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecBadSpecsTest.java
new file mode 100644
index 0000000..ebf5107
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecBadSpecsTest.java
@@ -0,0 +1,87 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.pathmap;
+
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests for bad path specs on ServerEndpoint Path Param / URI Template
+ */
+@RunWith(Parameterized.class)
+public class WebSocketPathSpecBadSpecsTest
+{
+ private static void bad(List<String[]> data, String str)
+ {
+ data.add(new String[]
+ { str });
+ }
+
+ @Parameters
+ public static Collection<String[]> data()
+ {
+ List<String[]> data = new ArrayList<>();
+ bad(data,"/a/b{var}"); // bad syntax - variable does not encompass whole path segment
+ bad(data,"a/{var}"); // bad syntax - no start slash
+ bad(data,"/a/{var/b}"); // path segment separator in variable name
+ bad(data,"/{var}/*"); // bad syntax - no globs allowed
+ bad(data,"/{var}.do"); // bad syntax - variable does not encompass whole path segment
+ bad(data,"/a/{var*}"); // use of glob character not allowed in variable name
+ bad(data,"/a/{}"); // bad syntax - no variable name
+ // MIGHT BE ALLOWED bad(data,"/a/{---}"); // no alpha in variable name
+ bad(data,"{var}"); // bad syntax - no start slash
+ bad(data,"/a/{my special variable}"); // bad syntax - space in variable name
+ bad(data,"/a/{var}/{var}"); // variable name duplicate
+ // MIGHT BE ALLOWED bad(data,"/a/{var}/{Var}/{vAR}"); // variable name duplicated (diff case)
+ bad(data,"/a/../../../{var}"); // path navigation not allowed
+ bad(data,"/a/./{var}"); // path navigation not allowed
+ bad(data,"/a//{var}"); // bad syntax - double path slash (no path segment)
+ return data;
+ }
+
+ private String pathSpec;
+
+ public WebSocketPathSpecBadSpecsTest(String pathSpec)
+ {
+ this.pathSpec = pathSpec;
+ }
+
+ @Test
+ public void testBadPathSpec()
+ {
+ try
+ {
+ new WebSocketPathSpec(this.pathSpec);
+ fail("Expected IllegalArgumentException for a bad PathParam pathspec on: " + pathSpec);
+ }
+ catch (IllegalArgumentException e)
+ {
+ // expected path
+ System.out.println(e.getMessage());
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecTest.java
new file mode 100644
index 0000000..15fb8ad
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecTest.java
@@ -0,0 +1,286 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.pathmap;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.util.Map;
+
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpecGroup;
+import org.junit.Test;
+
+/**
+ * Tests for ServerEndpoint Path Param / URI Template Path Specs
+ */
+public class WebSocketPathSpecTest
+{
+ private void assertDetectedVars(WebSocketPathSpec spec, String... expectedVars)
+ {
+ String prefix = String.format("Spec(\"%s\")",spec.getPathSpec());
+ assertEquals(prefix + ".variableCount",expectedVars.length,spec.getVariableCount());
+ assertEquals(prefix + ".variable.length",expectedVars.length,spec.getVariables().length);
+ for (int i = 0; i < expectedVars.length; i++)
+ {
+ assertEquals(String.format("%s.variable[%d]",prefix,i),expectedVars[i],spec.getVariables()[i]);
+ }
+ }
+
+ private void assertMatches(PathSpec spec, String path)
+ {
+ String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+ assertThat(msg,spec.matches(path),is(true));
+ }
+
+ private void assertNotMatches(PathSpec spec, String path)
+ {
+ String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+ assertThat(msg,spec.matches(path),is(false));
+ }
+
+ @Test
+ public void testDefaultPathSpec()
+ {
+ WebSocketPathSpec spec = new WebSocketPathSpec("/");
+ assertEquals("Spec.pathSpec","/",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
+
+ assertEquals("Spec.variableCount",0,spec.getVariableCount());
+ assertEquals("Spec.variable.length",0,spec.getVariables().length);
+ }
+
+ @Test
+ public void testExactOnePathSpec()
+ {
+ WebSocketPathSpec spec = new WebSocketPathSpec("/a");
+ assertEquals("Spec.pathSpec","/a",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
+
+ assertMatches(spec,"/a");
+ assertMatches(spec,"/a?type=other");
+ assertNotMatches(spec,"/a/b");
+ assertNotMatches(spec,"/a/");
+
+ assertEquals("Spec.variableCount",0,spec.getVariableCount());
+ assertEquals("Spec.variable.length",0,spec.getVariables().length);
+ }
+
+ @Test
+ public void testExactPathSpec_TestWebapp()
+ {
+ WebSocketPathSpec spec = new WebSocketPathSpec("/javax.websocket/");
+ assertEquals("Spec.pathSpec","/javax.websocket/",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/javax\\.websocket/$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
+
+ assertMatches(spec,"/javax.websocket/");
+ assertNotMatches(spec,"/javax.websocket");
+
+ assertEquals("Spec.variableCount",0,spec.getVariableCount());
+ assertEquals("Spec.variable.length",0,spec.getVariables().length);
+ }
+
+ @Test
+ public void testExactTwoPathSpec()
+ {
+ WebSocketPathSpec spec = new WebSocketPathSpec("/a/b");
+ assertEquals("Spec.pathSpec","/a/b",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a/b$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
+
+ assertEquals("Spec.variableCount",0,spec.getVariableCount());
+ assertEquals("Spec.variable.length",0,spec.getVariables().length);
+
+ assertMatches(spec,"/a/b");
+
+ assertNotMatches(spec,"/a/b/");
+ assertNotMatches(spec,"/a/");
+ assertNotMatches(spec,"/a/bb");
+ }
+
+ @Test
+ public void testMiddleVarPathSpec()
+ {
+ WebSocketPathSpec spec = new WebSocketPathSpec("/a/{var}/c");
+ assertEquals("Spec.pathSpec","/a/{var}/c",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a/([^/]+)/c$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"var");
+
+ assertMatches(spec,"/a/b/c");
+ assertMatches(spec,"/a/zz/c");
+ assertMatches(spec,"/a/hello+world/c");
+ assertNotMatches(spec,"/a/bc");
+ assertNotMatches(spec,"/a/b/");
+ assertNotMatches(spec,"/a/b");
+
+ Map<String, String> mapped = spec.getPathParams("/a/b/c");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(1));
+ assertEquals("Spec.pathParams[var]","b",mapped.get("var"));
+ }
+
+ @Test
+ public void testOneVarPathSpec()
+ {
+ WebSocketPathSpec spec = new WebSocketPathSpec("/a/{foo}");
+ assertEquals("Spec.pathSpec","/a/{foo}",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a/([^/]+)$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"foo");
+
+ assertMatches(spec,"/a/b");
+ assertNotMatches(spec,"/a/");
+ assertNotMatches(spec,"/a");
+
+ Map<String, String> mapped = spec.getPathParams("/a/b");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(1));
+ assertEquals("Spec.pathParams[foo]","b",mapped.get("foo"));
+ }
+
+ @Test
+ public void testOneVarSuffixPathSpec()
+ {
+ WebSocketPathSpec spec = new WebSocketPathSpec("/{var}/b/c");
+ assertEquals("Spec.pathSpec","/{var}/b/c",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/([^/]+)/b/c$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"var");
+
+ assertMatches(spec,"/a/b/c");
+ assertMatches(spec,"/az/b/c");
+ assertMatches(spec,"/hello+world/b/c");
+ assertNotMatches(spec,"/a/bc");
+ assertNotMatches(spec,"/a/b/");
+ assertNotMatches(spec,"/a/b");
+
+ Map<String, String> mapped = spec.getPathParams("/a/b/c");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(1));
+ assertEquals("Spec.pathParams[var]","a",mapped.get("var"));
+ }
+
+ @Test
+ public void testTwoVarComplexInnerPathSpec()
+ {
+ WebSocketPathSpec spec = new WebSocketPathSpec("/a/{var1}/c/{var2}/e");
+ assertEquals("Spec.pathSpec","/a/{var1}/c/{var2}/e",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a/([^/]+)/c/([^/]+)/e$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",5,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"var1","var2");
+
+ assertMatches(spec,"/a/b/c/d/e");
+ assertNotMatches(spec,"/a/bc/d/e");
+ assertNotMatches(spec,"/a/b/d/e");
+ assertNotMatches(spec,"/a/b//d/e");
+
+ Map<String, String> mapped = spec.getPathParams("/a/b/c/d/e");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(2));
+ assertEquals("Spec.pathParams[var1]","b",mapped.get("var1"));
+ assertEquals("Spec.pathParams[var2]","d",mapped.get("var2"));
+ }
+
+ @Test
+ public void testTwoVarComplexOuterPathSpec()
+ {
+ WebSocketPathSpec spec = new WebSocketPathSpec("/{var1}/b/{var2}/{var3}");
+ assertEquals("Spec.pathSpec","/{var1}/b/{var2}/{var3}",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/([^/]+)/b/([^/]+)/([^/]+)$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",4,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"var1","var2","var3");
+
+ assertMatches(spec,"/a/b/c/d");
+ assertNotMatches(spec,"/a/bc/d/e");
+ assertNotMatches(spec,"/a/c/d/e");
+ assertNotMatches(spec,"/a//d/e");
+
+ Map<String, String> mapped = spec.getPathParams("/a/b/c/d");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(3));
+ assertEquals("Spec.pathParams[var1]","a",mapped.get("var1"));
+ assertEquals("Spec.pathParams[var2]","c",mapped.get("var2"));
+ assertEquals("Spec.pathParams[var3]","d",mapped.get("var3"));
+ }
+
+ @Test
+ public void testTwoVarPrefixPathSpec()
+ {
+ WebSocketPathSpec spec = new WebSocketPathSpec("/a/{var1}/{var2}");
+ assertEquals("Spec.pathSpec","/a/{var1}/{var2}",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a/([^/]+)/([^/]+)$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"var1","var2");
+
+ assertMatches(spec,"/a/b/c");
+ assertNotMatches(spec,"/a/bc");
+ assertNotMatches(spec,"/a/b/");
+ assertNotMatches(spec,"/a/b");
+
+ Map<String, String> mapped = spec.getPathParams("/a/b/c");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(2));
+ assertEquals("Spec.pathParams[var1]","b",mapped.get("var1"));
+ assertEquals("Spec.pathParams[var2]","c",mapped.get("var2"));
+ }
+
+ @Test
+ public void testVarOnlyPathSpec()
+ {
+ WebSocketPathSpec spec = new WebSocketPathSpec("/{var1}");
+ assertEquals("Spec.pathSpec","/{var1}",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/([^/]+)$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"var1");
+
+ assertMatches(spec,"/a");
+ assertNotMatches(spec,"/");
+ assertNotMatches(spec,"/a/b");
+ assertNotMatches(spec,"/a/b/c");
+
+ Map<String, String> mapped = spec.getPathParams("/a");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(1));
+ assertEquals("Spec.pathParams[var1]","a",mapped.get("var1"));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicBinaryMessageByteBufferSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicBinaryMessageByteBufferSocket.java
new file mode 100644
index 0000000..c654e35
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicBinaryMessageByteBufferSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.OnMessage;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicBinaryMessageByteBufferSocket extends TrackingSocket
+{
+ @OnMessage
+ public void onBinary(ByteBuffer data)
+ {
+ addEvent("onBinary(%s)",data);
+ dataLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseReasonSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseReasonSessionSocket.java
new file mode 100644
index 0000000..bf26e84
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseReasonSessionSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicCloseReasonSessionSocket extends TrackingSocket
+{
+ @OnClose
+ public void onClose(CloseReason reason, Session session)
+ {
+ addEvent("onClose(%s,%s)",reason,session);
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseReasonSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseReasonSocket.java
new file mode 100644
index 0000000..33b8125
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseReasonSocket.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicCloseReasonSocket extends TrackingSocket
+{
+ @OnClose
+ public void onClose(CloseReason reason)
+ {
+ addEvent("onClose(%s)", reason);
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseSessionReasonSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseSessionReasonSocket.java
new file mode 100644
index 0000000..ec7b630
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseSessionReasonSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicCloseSessionReasonSocket extends TrackingSocket
+{
+ @OnClose
+ public void onClose(Session session, CloseReason reason)
+ {
+ addEvent("onClose(%s,%s)",session,reason);
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseSocket.java
new file mode 100644
index 0000000..c8408aa
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseSocket.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnClose;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value = "/basic")
+public class BasicCloseSocket extends TrackingSocket
+{
+ @OnClose
+ public void onClose()
+ {
+ addEvent("onClose()");
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSessionSocket.java
new file mode 100644
index 0000000..36c99b1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSessionSocket.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicErrorSessionSocket extends TrackingSocket
+{
+ @OnError
+ public void onError(Session session)
+ {
+ addEvent("onError(%s)",session);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSessionThrowableSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSessionThrowableSocket.java
new file mode 100644
index 0000000..5f7d6f9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSessionThrowableSocket.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicErrorSessionThrowableSocket extends TrackingSocket
+{
+ @OnError
+ public void onError(Session session, Throwable t)
+ {
+ addEvent("onError(%s,%s)",session,t);
+ addError(t);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSocket.java
new file mode 100644
index 0000000..86f42d8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSocket.java
@@ -0,0 +1,34 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicErrorSocket extends TrackingSocket
+{
+ @OnError
+ public void onError()
+ {
+ addEvent("onError()");
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorThrowableSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorThrowableSessionSocket.java
new file mode 100644
index 0000000..172cd18
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorThrowableSessionSocket.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicErrorThrowableSessionSocket extends TrackingSocket
+{
+ @OnError
+ public void onError(Throwable t, Session session)
+ {
+ addEvent("onError(%s,%s)",t,session);
+ addError(t);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorThrowableSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorThrowableSocket.java
new file mode 100644
index 0000000..6b3b8ac
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorThrowableSocket.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicErrorThrowableSocket extends TrackingSocket
+{
+ @OnError
+ public void onError(Throwable t)
+ {
+ addEvent("onError(%s)",t);
+ addError(t);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenCloseSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenCloseSessionSocket.java
new file mode 100644
index 0000000..62ad1b7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenCloseSessionSocket.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicOpenCloseSessionSocket extends TrackingSocket
+{
+ @OnClose
+ public void onClose(CloseReason close, Session session)
+ {
+ addEvent("onClose(%s, %s)",close,session);
+ this.closeReason = close;
+ closeLatch.countDown();
+ }
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ addEvent("onOpen(%s)",session);
+ openLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenCloseSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenCloseSocket.java
new file mode 100644
index 0000000..3a3c69c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenCloseSocket.java
@@ -0,0 +1,41 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnOpen;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicOpenCloseSocket extends TrackingSocket
+{
+ @OnOpen
+ public void onOpen() {
+ openLatch.countDown();
+ }
+
+ @OnClose
+ public void onClose(CloseReason close) {
+ this.closeReason = close;
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenSessionSocket.java
new file mode 100644
index 0000000..014a41d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenSessionSocket.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicOpenSessionSocket extends TrackingSocket
+{
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ openLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenSocket.java
new file mode 100644
index 0000000..a62f1d9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenSocket.java
@@ -0,0 +1,34 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnOpen;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicOpenSocket extends TrackingSocket
+{
+ @OnOpen
+ public void onOpen()
+ {
+ openLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicPongMessageSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicPongMessageSocket.java
new file mode 100644
index 0000000..979d212
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicPongMessageSocket.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnMessage;
+import javax.websocket.PongMessage;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicPongMessageSocket extends TrackingSocket
+{
+ @OnMessage
+ public void onPong(PongMessage pong)
+ {
+ addEvent("onPong(%s)",pong);
+ dataLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicTextMessageStringSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicTextMessageStringSocket.java
new file mode 100644
index 0000000..a4a0a52
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicTextMessageStringSocket.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnMessage;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/basic")
+public class BasicTextMessageStringSocket extends TrackingSocket
+{
+ @OnMessage
+ public void onText(String message)
+ {
+ addEvent("onText(%s)",message);
+ dataLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidCloseIntSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidCloseIntSocket.java
new file mode 100644
index 0000000..5c16ced
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidCloseIntSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnClose;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/invalid")
+public class InvalidCloseIntSocket extends TrackingSocket
+{
+ /**
+ * Invalid Close Method Declaration (parameter type int)
+ */
+ @OnClose
+ public void onClose(int statusCode)
+ {
+ closeLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorErrorSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorErrorSocket.java
new file mode 100644
index 0000000..12b5b6f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorErrorSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/invalid")
+public class InvalidErrorErrorSocket extends TrackingSocket
+{
+ /**
+ * Invalid Error Method Declaration (parameter type Error)
+ */
+ @OnError
+ public void onError(Error error)
+ {
+ /* no impl */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorExceptionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorExceptionSocket.java
new file mode 100644
index 0000000..206578b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorExceptionSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/invalid")
+public class InvalidErrorExceptionSocket extends TrackingSocket
+{
+ /**
+ * Invalid Error Method Declaration (parameter type Exception)
+ */
+ @OnError
+ public void onError(Exception e)
+ {
+ /* no impl */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorIntSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorIntSocket.java
new file mode 100644
index 0000000..2c8fb3d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorIntSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/invalid")
+public class InvalidErrorIntSocket extends TrackingSocket
+{
+ /**
+ * Invalid Error Method Declaration (parameter type int)
+ */
+ @OnError
+ public void onError(int errorCount)
+ {
+ /* no impl */
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenCloseReasonSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenCloseReasonSocket.java
new file mode 100644
index 0000000..72593c7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenCloseReasonSocket.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnOpen;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/invalid")
+public class InvalidOpenCloseReasonSocket extends TrackingSocket
+{
+ /**
+ * Invalid Open Method Declaration (parameter type CloseReason)
+ */
+ @OnOpen
+ public void onOpen(CloseReason reason)
+ {
+ openLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenIntSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenIntSocket.java
new file mode 100644
index 0000000..b703d5d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenIntSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnOpen;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/invalid")
+public class InvalidOpenIntSocket extends TrackingSocket
+{
+ /**
+ * Invalid Open Method Declaration (parameter type int)
+ */
+ @OnOpen
+ public void onOpen(int value)
+ {
+ openLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenSessionIntSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenSessionIntSocket.java
new file mode 100644
index 0000000..dc5290e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenSessionIntSocket.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value="/invalid")
+public class InvalidOpenSessionIntSocket extends TrackingSocket
+{
+ /**
+ * Invalid Open Method Declaration (parameter of type int)
+ */
+ @OnOpen
+ public void onOpen(Session session, int count)
+ {
+ openLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/StatelessTextMessageStringSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/StatelessTextMessageStringSocket.java
new file mode 100644
index 0000000..6afdfd9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/StatelessTextMessageStringSocket.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint(value = "/stateless")
+public class StatelessTextMessageStringSocket extends TrackingSocket
+{
+ @OnMessage
+ public void onText(Session session, String message)
+ {
+ addEvent("onText(%s,%s)",session,message);
+ dataLatch.countDown();
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateDecoder.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateDecoder.java
new file mode 100644
index 0000000..b2ab7e5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateDecoder.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Decode Date
+ */
+public class DateDecoder implements Decoder.Text<Date>
+{
+ @Override
+ public Date decode(String s) throws DecodeException
+ {
+ try
+ {
+ return new SimpleDateFormat("yyyy.MM.dd").parse(s);
+ }
+ catch (ParseException e)
+ {
+ throw new DecodeException(s,e.getMessage(),e);
+ }
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ return true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateEncoder.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateEncoder.java
new file mode 100644
index 0000000..4171d3e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateEncoder.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Encode Date
+ */
+public class DateEncoder implements Encoder.Text<Date>
+{
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public String encode(Date object) throws EncodeException
+ {
+ return new SimpleDateFormat("yyyy.MM.dd").format(object);
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTextSocket.java
new file mode 100644
index 0000000..68674b1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTextSocket.java
@@ -0,0 +1,70 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint(value = "/echo/beans/date", decoders = { DateDecoder.class }, encoders = { DateEncoder.class })
+public class DateTextSocket
+{
+ private static final Logger LOG = Log.getLogger(DateTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ // The decoder declared in the @ServerEndpoint will be used
+ @OnMessage
+ public void onMessage(Date d) throws IOException
+ {
+ if (d == null)
+ {
+ session.getAsyncRemote().sendText("Error: Date is null");
+ }
+ else
+ {
+ String msg = SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT).format(d);
+ // The encoder declared in the @ServerEndpoint will be used
+ session.getAsyncRemote().sendText(msg);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTimeDecoder.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTimeDecoder.java
new file mode 100644
index 0000000..56892dc
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTimeDecoder.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Decode Date and Time
+ */
+public class DateTimeDecoder implements Decoder.Text<Date>
+{
+ @Override
+ public Date decode(String s) throws DecodeException
+ {
+ try
+ {
+ return new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z").parse(s);
+ }
+ catch (ParseException e)
+ {
+ throw new DecodeException(s,e.getMessage(),e);
+ }
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ return true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTimeEncoder.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTimeEncoder.java
new file mode 100644
index 0000000..67dcf22
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTimeEncoder.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Encode Date
+ */
+public class DateTimeEncoder implements Encoder.Text<Date>
+{
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public String encode(Date object) throws EncodeException
+ {
+ return new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z").format(object);
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/TimeDecoder.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/TimeDecoder.java
new file mode 100644
index 0000000..326804c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/TimeDecoder.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Decode Time
+ */
+public class TimeDecoder implements Decoder.Text<Date>
+{
+ @Override
+ public Date decode(String s) throws DecodeException
+ {
+ try
+ {
+ return new SimpleDateFormat("HH:mm:ss z").parse(s);
+ }
+ catch (ParseException e)
+ {
+ throw new DecodeException(s,e.getMessage(),e);
+ }
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+
+ @Override
+ public boolean willDecode(String s)
+ {
+ return true;
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/TimeEncoder.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/TimeEncoder.java
new file mode 100644
index 0000000..87e49ba
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/TimeEncoder.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Encode Time
+ */
+public class TimeEncoder implements Encoder.Text<Date>
+{
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public String encode(Date object) throws EncodeException
+ {
+ return new SimpleDateFormat("HH:mm:ss z").format(object);
+ }
+
+ @Override
+ public void init(EndpointConfig config)
+ {
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/binary/ByteBufferSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/binary/ByteBufferSocket.java
new file mode 100644
index 0000000..2da6421
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/binary/ByteBufferSocket.java
@@ -0,0 +1,51 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.binary;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/binary/bytebuffer")
+public class ByteBufferSocket
+{
+ private static final Logger LOG = Log.getLogger(ByteBufferSocket.class);
+
+ @OnMessage
+ public String onByteBuffer(ByteBuffer bbuf)
+ {
+ return BufferUtil.toUTF8String(bbuf);
+ }
+
+ @OnError
+ public void onError(Session session, Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpoint.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpoint.java
new file mode 100644
index 0000000..761fc0c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpoint.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+
+/**
+ * Example of websocket endpoint based on extending {@link Endpoint}
+ */
+public class BasicEchoEndpoint extends Endpoint implements MessageHandler.Whole<String>
+{
+ private Session session;
+
+ @Override
+ public void onMessage(String msg)
+ {
+ // reply with echo
+ session.getAsyncRemote().sendText(msg);
+ }
+
+ @Override
+ public void onOpen(Session session, EndpointConfig config)
+ {
+ this.session = session;
+ this.session.addMessageHandler(this);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointConfigContextListener.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointConfigContextListener.java
new file mode 100644
index 0000000..ae9ee3f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointConfigContextListener.java
@@ -0,0 +1,54 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+/**
+ * Example of adding a server WebSocket (extending {@link Endpoint}) programmatically via config
+ */
+public class BasicEchoEndpointConfigContextListener implements ServletContextListener
+{
+ @Override
+ public void contextDestroyed(ServletContextEvent sce)
+ {
+ /* do nothing */
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce)
+ {
+ ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName());
+ // Build up a configuration with a specific path
+ String path = "/echo";
+ ServerEndpointConfig.Builder builder = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class,path);
+ try
+ {
+ container.addEndpoint(builder.build());
+ }
+ catch (DeploymentException e)
+ {
+ throw new RuntimeException("Unable to add endpoint via config file",e);
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointContextListener.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointContextListener.java
new file mode 100644
index 0000000..1a8b5a2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointContextListener.java
@@ -0,0 +1,53 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+/**
+ * Example of adding a server WebSocket (extending {@link Endpoint}) programmatically directly.
+ * <p>
+ * NOTE: this shouldn't work as the endpoint has no path associated with it.
+ */
+public class BasicEchoEndpointContextListener implements ServletContextListener
+{
+ @Override
+ public void contextDestroyed(ServletContextEvent sce)
+ {
+ /* do nothing */
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce)
+ {
+ ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName());
+ try
+ {
+ // Should fail as there is no path associated with this endpoint
+ container.addEndpoint(BasicEchoEndpoint.class);
+ }
+ catch (DeploymentException e)
+ {
+ throw new RuntimeException("Unable to add endpoint directly",e);
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocket.java
new file mode 100644
index 0000000..49a7e2e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocket.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+/**
+ * Annotated echo socket
+ */
+@ServerEndpoint("/echo")
+public class BasicEchoSocket
+{
+ @OnMessage
+ public void echo(Session session, String msg)
+ {
+ // reply with echo
+ session.getAsyncRemote().sendText(msg);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocketConfigContextListener.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocketConfigContextListener.java
new file mode 100644
index 0000000..0210a33
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocketConfigContextListener.java
@@ -0,0 +1,55 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+/**
+ * Example of adding a server socket (which extends {@link Endpoint}) programmatically via the {@link ServerContainer#addEndpoint(ServerEndpointConfig)}
+ */
+public class BasicEchoSocketConfigContextListener implements ServletContextListener
+{
+ @Override
+ public void contextDestroyed(ServletContextEvent sce)
+ {
+ /* do nothing */
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce)
+ {
+ ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName());
+ // Build up a configuration with a specific path
+ // Intentionally using alternate path in config (which differs from @ServerEndpoint declaration)
+ String path = "/echo-alt";
+ ServerEndpointConfig.Builder builder = ServerEndpointConfig.Builder.create(BasicEchoSocket.class,path);
+ try
+ {
+ container.addEndpoint(builder.build());
+ }
+ catch (DeploymentException e)
+ {
+ throw new RuntimeException("Unable to add endpoint via config file",e);
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocketContextListener.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocketContextListener.java
new file mode 100644
index 0000000..e278c72
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocketContextListener.java
@@ -0,0 +1,50 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+/**
+ * Example of adding a server socket (annotated) programmatically directly with no config
+ */
+public class BasicEchoSocketContextListener implements ServletContextListener
+{
+ @Override
+ public void contextDestroyed(ServletContextEvent sce)
+ {
+ /* do nothing */
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce)
+ {
+ ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName());
+ try
+ {
+ container.addEndpoint(BasicEchoSocket.class);
+ }
+ catch (DeploymentException e)
+ {
+ throw new RuntimeException("Unable to add endpoint directly",e);
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/EchoReturnEndpoint.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/EchoReturnEndpoint.java
new file mode 100644
index 0000000..c9f0165
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/EchoReturnEndpoint.java
@@ -0,0 +1,64 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import java.io.IOException;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+
+@ServerEndpoint(value = "/echoreturn")
+public class EchoReturnEndpoint
+{
+ private Session session = null;
+ public CloseReason close = null;
+ public EventQueue<String> messageQueue = new EventQueue<>();
+
+ public void onClose(CloseReason close)
+ {
+ this.close = close;
+ }
+
+ @OnMessage
+ public String onMessage(String message)
+ {
+ this.messageQueue.offer(message);
+ // Return the message
+ return message;
+ }
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ public void sendText(String text) throws IOException
+ {
+ if (session != null)
+ {
+ session.getBasicRemote().sendText(text);
+ }
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoConfiguredSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoConfiguredSocket.java
new file mode 100644
index 0000000..f0acfb4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoConfiguredSocket.java
@@ -0,0 +1,47 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+/**
+ * Annotated echo socket
+ */
+@ServerEndpoint(value = "/echo/large")
+public class LargeEchoConfiguredSocket
+{
+ private Session session;
+
+ @OnOpen
+ public void open(Session session)
+ {
+ this.session = session;
+ this.session.setMaxTextMessageBufferSize(128 * 1024);
+ }
+
+ @OnMessage
+ public void echo(String msg)
+ {
+ // reply with echo
+ session.getAsyncRemote().sendText(msg);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoContextListener.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoContextListener.java
new file mode 100644
index 0000000..9a73823
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoContextListener.java
@@ -0,0 +1,42 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.websocket.server.ServerContainer;
+
+/**
+ * Configure the Large Text Message Size via the Container
+ */
+public class LargeEchoContextListener implements ServletContextListener
+{
+ @Override
+ public void contextDestroyed(ServletContextEvent sce)
+ {
+ /* do nothing */
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce)
+ {
+ ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName());
+ container.setDefaultMaxTextMessageBufferSize(128 * 1024);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoDefaultSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoDefaultSocket.java
new file mode 100644
index 0000000..851a1ac
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoDefaultSocket.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.ServerEndpoint;
+
+/**
+ * Annotated echo socket (default behavior as defined from {@link WebSocketContainer#setDefaultMaxTextMessageBufferSize(int)})
+ */
+@ServerEndpoint(value = "/echo/large")
+public class LargeEchoDefaultSocket
+{
+ @OnMessage
+ public void echo(Session session, String msg)
+ {
+ // reply with echo
+ session.getAsyncRemote().sendText(msg);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTextSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTextSessionSocket.java
new file mode 100644
index 0000000..fac9bab
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTextSessionSocket.java
@@ -0,0 +1,55 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.partial;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/partial/textsession")
+public class PartialTextSessionSocket
+{
+ private static final Logger LOG = Log.getLogger(PartialTextSessionSocket.class);
+ private StringBuilder buf = new StringBuilder();
+
+ @OnMessage
+ public void onPartial(String msg, boolean fin, Session session) throws IOException
+ {
+ buf.append("('").append(msg).append("',").append(fin).append(')');
+ if (fin)
+ {
+ session.getBasicRemote().sendText(buf.toString());
+ buf.setLength(0);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable cause, Session session) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTextSocket.java
new file mode 100644
index 0000000..009c3fa
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTextSocket.java
@@ -0,0 +1,63 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.partial;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/partial/text")
+public class PartialTextSocket
+{
+ private static final Logger LOG = Log.getLogger(PartialTextSocket.class);
+ private Session session;
+ private StringBuilder buf = new StringBuilder();
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onPartial(String msg, boolean fin) throws IOException
+ {
+ buf.append("('").append(msg).append("',").append(fin).append(')');
+ if (fin)
+ {
+ session.getBasicRemote().sendText(buf.toString());
+ buf.setLength(0);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTrackingSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTrackingSocket.java
new file mode 100644
index 0000000..475b59d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTrackingSocket.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.partial;
+
+import java.io.IOException;
+
+import javax.websocket.OnMessage;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+@ServerEndpoint("/echo/partial/tracking")
+public class PartialTrackingSocket extends TrackingSocket
+{
+ @OnMessage
+ public void onPartial(String msg, boolean fin) throws IOException
+ {
+ addEvent("onPartial(\"%s\",%b)",msg,fin);
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanObjectTextParamSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanObjectTextParamSocket.java
new file mode 100644
index 0000000..441a2f1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanObjectTextParamSocket.java
@@ -0,0 +1,67 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/booleanobject/params/{a}")
+public class BooleanObjectTextParamSocket
+{
+ private static final Logger LOG = Log.getLogger(BooleanObjectTextParamSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(Boolean b, @PathParam("a") Boolean param) throws IOException
+ {
+ if (b == null)
+ {
+ session.getAsyncRemote().sendText("Error: Boolean is null");
+ }
+ else
+ {
+ String msg = String.format("%b|%b", b, param);
+ session.getAsyncRemote().sendText(msg);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanObjectTextSocket.java
new file mode 100644
index 0000000..0a53134
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/booleanobject")
+public class BooleanObjectTextSocket
+{
+ private static final Logger LOG = Log.getLogger(BooleanObjectTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(Boolean b) throws IOException
+ {
+ if (b == null)
+ {
+ session.getAsyncRemote().sendText("Error: Boolean is null");
+ }
+ else
+ {
+ String msg = b.toString();
+ session.getAsyncRemote().sendText(msg);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanTextParamSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanTextParamSocket.java
new file mode 100644
index 0000000..d9d0824
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanTextParamSocket.java
@@ -0,0 +1,60 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/boolean/params/{a}")
+public class BooleanTextParamSocket
+{
+ private static final Logger LOG = Log.getLogger(BooleanTextParamSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(boolean b, @PathParam("a") boolean param) throws IOException
+ {
+ String msg = String.format("%b|%b", b, param);
+ session.getAsyncRemote().sendText(msg);
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanTextSocket.java
new file mode 100644
index 0000000..d90e941
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanTextSocket.java
@@ -0,0 +1,59 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/boolean")
+public class BooleanTextSocket
+{
+ private static final Logger LOG = Log.getLogger(BooleanTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(boolean b) throws IOException
+ {
+ String msg = Boolean.toString(b);
+ session.getAsyncRemote().sendText(msg);
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ByteObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ByteObjectTextSocket.java
new file mode 100644
index 0000000..1d56470
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ByteObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/byteobject")
+public class ByteObjectTextSocket
+{
+ private static final Logger LOG = Log.getLogger(ByteObjectTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(Byte b) throws IOException
+ {
+ if (b == null)
+ {
+ session.getAsyncRemote().sendText("Error: Byte is null");
+ }
+ else
+ {
+ String msg = String.format("0x%02X",b);
+ session.getAsyncRemote().sendText(msg);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ByteTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ByteTextSocket.java
new file mode 100644
index 0000000..d2f9971
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ByteTextSocket.java
@@ -0,0 +1,59 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/byte")
+public class ByteTextSocket
+{
+ private static final Logger LOG = Log.getLogger(ByteTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(byte b) throws IOException
+ {
+ String msg = String.format("0x%02X",b);
+ session.getAsyncRemote().sendText(msg);
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/CharTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/CharTextSocket.java
new file mode 100644
index 0000000..d7fee3c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/CharTextSocket.java
@@ -0,0 +1,59 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/char")
+public class CharTextSocket
+{
+ private static final Logger LOG = Log.getLogger(CharTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(char c) throws IOException
+ {
+ String msg = Character.toString(c);
+ session.getAsyncRemote().sendText(msg);
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/CharacterObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/CharacterObjectTextSocket.java
new file mode 100644
index 0000000..0b9f347
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/CharacterObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/characterobject")
+public class CharacterObjectTextSocket
+{
+ private static final Logger LOG = Log.getLogger(CharacterObjectTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(Character c) throws IOException
+ {
+ if (c == null)
+ {
+ session.getAsyncRemote().sendText("Error: Character is null");
+ }
+ else
+ {
+ String msg = c.toString();
+ session.getAsyncRemote().sendText(msg);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/DoubleObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/DoubleObjectTextSocket.java
new file mode 100644
index 0000000..9abc572
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/DoubleObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/doubleobject")
+public class DoubleObjectTextSocket
+{
+ private static final Logger LOG = Log.getLogger(DoubleObjectTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(Double d) throws IOException
+ {
+ if (d == null)
+ {
+ session.getAsyncRemote().sendText("Error: Double is null");
+ }
+ else
+ {
+ String msg = String.format("%.4f",d);
+ session.getAsyncRemote().sendText(msg);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/DoubleTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/DoubleTextSocket.java
new file mode 100644
index 0000000..bd3669e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/DoubleTextSocket.java
@@ -0,0 +1,59 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/double")
+public class DoubleTextSocket
+{
+ private static final Logger LOG = Log.getLogger(DoubleTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(double d) throws IOException
+ {
+ String msg = String.format("%.4f",d);
+ session.getAsyncRemote().sendText(msg);
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/FloatObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/FloatObjectTextSocket.java
new file mode 100644
index 0000000..c6e44f2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/FloatObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/floatobject")
+public class FloatObjectTextSocket
+{
+ private static final Logger LOG = Log.getLogger(FloatObjectTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(Float f) throws IOException
+ {
+ if (f == null)
+ {
+ session.getAsyncRemote().sendText("Error: Float is null");
+ }
+ else
+ {
+ String msg = String.format("%.4f",f);
+ session.getAsyncRemote().sendText(msg);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/FloatTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/FloatTextSocket.java
new file mode 100644
index 0000000..0c86cab
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/FloatTextSocket.java
@@ -0,0 +1,59 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/float")
+public class FloatTextSocket
+{
+ private static final Logger LOG = Log.getLogger(FloatTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(float f) throws IOException
+ {
+ String msg = String.format("%.4f",f);
+ session.getAsyncRemote().sendText(msg);
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntParamTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntParamTextSocket.java
new file mode 100644
index 0000000..14f25be
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntParamTextSocket.java
@@ -0,0 +1,60 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/integer/params/{a}")
+public class IntParamTextSocket
+{
+ private static final Logger LOG = Log.getLogger(IntParamTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(int i, @PathParam("a") int param) throws IOException
+ {
+ String msg = String.format("%d|%d",i,param);
+ session.getAsyncRemote().sendText(msg);
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntTextSocket.java
new file mode 100644
index 0000000..0e35130
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntTextSocket.java
@@ -0,0 +1,59 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/integer")
+public class IntTextSocket
+{
+ private static final Logger LOG = Log.getLogger(IntTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(int i) throws IOException
+ {
+ String msg = Integer.toString(i);
+ session.getAsyncRemote().sendText(msg);
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntegerObjectParamTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntegerObjectParamTextSocket.java
new file mode 100644
index 0000000..ebee7b8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntegerObjectParamTextSocket.java
@@ -0,0 +1,67 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/integerobject/params/{a}")
+public class IntegerObjectParamTextSocket
+{
+ private static final Logger LOG = Log.getLogger(IntegerObjectParamTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(Integer i, @PathParam("a") int param) throws IOException
+ {
+ if (i == null)
+ {
+ session.getAsyncRemote().sendText("Error: Integer is null");
+ }
+ else
+ {
+ String msg = String.format("%d|%d",i,param);
+ session.getAsyncRemote().sendText(msg);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntegerObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntegerObjectTextSocket.java
new file mode 100644
index 0000000..fa8fce5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntegerObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/integerobject")
+public class IntegerObjectTextSocket
+{
+ private static final Logger LOG = Log.getLogger(IntegerObjectTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(Integer i) throws IOException
+ {
+ if (i == null)
+ {
+ session.getAsyncRemote().sendText("Error: Integer is null");
+ }
+ else
+ {
+ String msg = i.toString();
+ session.getAsyncRemote().sendText(msg);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/LongObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/LongObjectTextSocket.java
new file mode 100644
index 0000000..96e74fa
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/LongObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/longobject")
+public class LongObjectTextSocket
+{
+ private static final Logger LOG = Log.getLogger(LongObjectTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(Long l) throws IOException
+ {
+ if (l == null)
+ {
+ session.getAsyncRemote().sendText("Error: Long is null");
+ }
+ else
+ {
+ String msg = l.toString();
+ session.getAsyncRemote().sendText(msg);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/LongTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/LongTextSocket.java
new file mode 100644
index 0000000..cac5856
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/LongTextSocket.java
@@ -0,0 +1,59 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/long")
+public class LongTextSocket
+{
+ private static final Logger LOG = Log.getLogger(LongTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(long l) throws IOException
+ {
+ String msg = Long.toString(l);
+ session.getAsyncRemote().sendText(msg);
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ShortObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ShortObjectTextSocket.java
new file mode 100644
index 0000000..8f83284
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ShortObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/shortobject")
+public class ShortObjectTextSocket
+{
+ private static final Logger LOG = Log.getLogger(ShortObjectTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(Short s) throws IOException
+ {
+ if (s == null)
+ {
+ session.getAsyncRemote().sendText("Error: Short is null");
+ }
+ else
+ {
+ String msg = s.toString();
+ session.getAsyncRemote().sendText(msg);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ShortTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ShortTextSocket.java
new file mode 100644
index 0000000..fc4d528
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ShortTextSocket.java
@@ -0,0 +1,59 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/primitives/short")
+public class ShortTextSocket
+{
+ private static final Logger LOG = Log.getLogger(ShortTextSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onMessage(short s) throws IOException
+ {
+ String msg = Short.toString(s);
+ session.getAsyncRemote().sendText(msg);
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/InputStreamSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/InputStreamSocket.java
new file mode 100644
index 0000000..f981438
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/InputStreamSocket.java
@@ -0,0 +1,52 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.streaming;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/streaming/inputstream")
+public class InputStreamSocket
+{
+ private static final Logger LOG = Log.getLogger(InputStreamSocket.class);
+
+ @OnMessage
+ public String onInputStream(InputStream stream) throws IOException
+ {
+ return IO.toString(stream, StringUtil.__UTF8);
+ }
+
+ @OnError
+ public void onError(Session session, Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/ReaderParamSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/ReaderParamSocket.java
new file mode 100644
index 0000000..a15ca84
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/ReaderParamSocket.java
@@ -0,0 +1,65 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.streaming;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/streaming/readerparam/{param}")
+public class ReaderParamSocket
+{
+ private static final Logger LOG = Log.getLogger(ReaderParamSocket.class);
+
+ private Session session;
+
+ @OnOpen
+ public void onOpen(Session session)
+ {
+ this.session = session;
+ }
+
+ @OnMessage
+ public void onReader(Reader reader, @PathParam("param") String param) throws IOException
+ {
+ StringBuilder msg = new StringBuilder();
+ msg.append(IO.toString(reader));
+ msg.append('|');
+ msg.append(param);
+ session.getAsyncRemote().sendText(msg.toString());
+ }
+
+ @OnError
+ public void onError(Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/ReaderSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/ReaderSocket.java
new file mode 100644
index 0000000..0bf4372
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/ReaderSocket.java
@@ -0,0 +1,51 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.streaming;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/streaming/reader")
+public class ReaderSocket
+{
+ private static final Logger LOG = Log.getLogger(ReaderSocket.class);
+
+ @OnMessage
+ public String onReader(Reader reader) throws IOException
+ {
+ return IO.toString(reader);
+ }
+
+ @OnError
+ public void onError(Session session, Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/StringReturnReaderParamSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/StringReturnReaderParamSocket.java
new file mode 100644
index 0000000..81d7a50
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/StringReturnReaderParamSocket.java
@@ -0,0 +1,56 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.streaming;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.StackUtil;
+
+@ServerEndpoint("/echo/streaming/readerparam2/{param}")
+public class StringReturnReaderParamSocket
+{
+ private static final Logger LOG = Log.getLogger(StringReturnReaderParamSocket.class);
+
+ @OnMessage
+ public String onReader(Reader reader, @PathParam("param") String param) throws IOException
+ {
+ StringBuilder msg = new StringBuilder();
+ msg.append(IO.toString(reader));
+ msg.append('|');
+ msg.append(param);
+ return msg.toString();
+ }
+
+ @OnError
+ public void onError(Session session, Throwable cause) throws IOException
+ {
+ LOG.warn("Error",cause);
+ session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+ }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/basic-echo-endpoint-config-web.xml b/jetty-websocket/javax-websocket-server-impl/src/test/resources/basic-echo-endpoint-config-web.xml
new file mode 100644
index 0000000..afd9cef
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/basic-echo-endpoint-config-web.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="http://java.sun.com/xml/ns/javaee"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ metadata-complete="false"
+ version="3.0">
+
+ <listener>
+ <listener-class>org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpointConfigContextListener</listener-class>
+ </listener>
+</web-app>
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/empty-web.xml b/jetty-websocket/javax-websocket-server-impl/src/test/resources/empty-web.xml
new file mode 100644
index 0000000..18aafa5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/empty-web.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="http://java.sun.com/xml/ns/javaee"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ metadata-complete="false"
+ version="3.0">
+</web-app>
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/jetty-logging.properties b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..c5a50f6
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jetty-logging.properties
@@ -0,0 +1,11 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.LEVEL=WARN
+
+# org.eclipse.jetty.websocket.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.LEVEL=INFO
+# org.eclipse.jetty.websocket.LEVEL=WARN
+# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG
+
+### Show state changes on BrowserDebugTool
+# -- LEAVE THIS AT DEBUG LEVEL --
+org.eclipse.jetty.websocket.jsr356.server.browser.LEVEL=DEBUG
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/index.html b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/index.html
new file mode 100644
index 0000000..ee9ef00
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/index.html
@@ -0,0 +1,37 @@
+<html>
+ <head>
+ <title>Jetty WebSocket Browser -> Server Debug Tool</title>
+ <script type="text/javascript" src="websocket.js"></script>
+ <link rel="stylesheet" type="text/css" href="main.css" media="all" >
+ </head>
+ <body>
+ jetty websocket/browser/javascript -> server debug tool #console
+ <div id="console"></div>
+ <div id="buttons">
+ <input id="connect" class="button" type="submit" name="connect" value="connect"/>
+ <input id="close" class="button" type="submit" name="close" value="close" disabled="disabled"/>
+ <input id="info" class="button" type="submit" name="info" value="info" disabled="disabled"/>
+ <input id="time" class="button" type="submit" name="time" value="time" disabled="disabled"/>
+ <input id="many" class="button" type="submit" name="many" value="many" disabled="disabled"/>
+ <input id="manythreads" class="button" type="submit" name="many" value="manythreads" disabled="disabled"/>
+ <input id="hello" class="button" type="submit" name="hello" value="hello" disabled="disabled"/>
+ <input id="there" class="button" type="submit" name="there" value="there" disabled="disabled"/>
+ <input id="json" class="button" type="submit" name="json" value="json" disabled="disabled"/>
+ </div>
+ <script type="text/javascript">
+ $("connect").onclick = function(event) { wstool.connect(); return false; }
+ $("close").onclick = function(event) {wstool.close(); return false; }
+ $("info").onclick = function(event) {wstool.write("info:"); return false; }
+ $("time").onclick = function(event) {wstool.write("time:"); return false; }
+ $("many").onclick = function(event) {wstool.write("many:15,300"); return false; }
+ $("manythreads").onclick = function(event) {wstool.write("manythreads:20,25,60"); return false; }
+ $("hello").onclick = function(event) {wstool.write("Hello"); return false; }
+ $("there").onclick = function(event) {wstool.write("There"); return false; }
+ $("json").onclick = function(event) {wstool.write("[{\"channel\":\"/meta/subscribe\",\"subscription\":\"/chat/demo\",\"id\":\"2\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+ + " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/meta/subscribe\",\"subscription\":\"/members/demo\",\"id\":\"3\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+ + " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/chat/demo\",\"data\":{\"user\":\"ch\",\"membership\":\"join\",\"chat\":\"ch"
+ + " has joined\"},\"id\":\"4\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+ + " 12 Sep 2013 19:42:30 GMT\"}]"); return false; }
+ </script>
+ </body>
+</html>
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/main.css b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/main.css
new file mode 100644
index 0000000..7a808dc
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/main.css
@@ -0,0 +1,33 @@
+body {
+ font-family: sans-serif;
+}
+
+div {
+ border: 0px solid black;
+}
+
+div#console {
+ clear: both;
+ width: 40em;
+ height: 20em;
+ overflow: auto;
+ background-color: #f0f0f0;
+ padding: 4px;
+ border: 1px solid black;
+}
+
+div#console .info {
+ color: black;
+}
+
+div#console .error {
+ color: red;
+}
+
+div#console .client {
+ color: blue;
+}
+
+div#console .server {
+ color: magenta;
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/websocket.js b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/websocket.js
new file mode 100644
index 0000000..3e1d5a9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/websocket.js
@@ -0,0 +1,139 @@
+if (!window.WebSocket && window.MozWebSocket) {
+ window.WebSocket = window.MozWebSocket;
+}
+
+if (!window.WebSocket) {
+ alert("WebSocket not supported by this browser");
+}
+
+function $() {
+ return document.getElementById(arguments[0]);
+}
+function $F() {
+ return document.getElementById(arguments[0]).value;
+}
+
+function getKeyCode(ev) {
+ if (window.event)
+ return window.event.keyCode;
+ return ev.keyCode;
+}
+
+var wstool = {
+ connect : function() {
+ var location = document.location.toString().replace('http://', 'ws://')
+ .replace('https://', 'wss://');
+
+ wstool.info("Document URI: " + document.location);
+ wstool.info("WS URI: " + location);
+
+ this._scount = 0;
+
+ try {
+ this._ws = new WebSocket(location, "tool");
+ this._ws.onopen = this._onopen;
+ this._ws.onmessage = this._onmessage;
+ this._ws.onclose = this._onclose;
+ this._ws.onerror = this._onerror;
+ } catch (exception) {
+ wstool.info("Connect Error: " + exception);
+ }
+ },
+
+ close : function() {
+ this._ws.close();
+ },
+
+ _out : function(css, message) {
+ var console = $('console');
+ var spanText = document.createElement('span');
+ spanText.className = 'text ' + css;
+ spanText.innerHTML = message;
+ var lineBreak = document.createElement('br');
+ console.appendChild(spanText);
+ console.appendChild(lineBreak);
+ console.scrollTop = console.scrollHeight - console.clientHeight;
+ },
+
+ info : function(message) {
+ wstool._out("info", message);
+ },
+
+ error : function(message) {
+ wstool._out("error", message);
+ },
+
+ infoc : function(message) {
+ wstool._out("client", "[c] " + message);
+ },
+
+ infos : function(message) {
+ this._scount++;
+ wstool._out("server", "[s" + this._scount + "] " + message);
+ },
+
+ setState : function(enabled) {
+ $('connect').disabled = enabled;
+ $('close').disabled = !enabled;
+ $('info').disabled = !enabled;
+ $('time').disabled = !enabled;
+ $('many').disabled = !enabled;
+ $('manythreads').disabled = !enabled;
+ $('hello').disabled = !enabled;
+ $('there').disabled = !enabled;
+ $('json').disabled = !enabled;
+ },
+
+ _onopen : function() {
+ wstool.setState(true);
+ wstool.info("Websocket Connected");
+ },
+
+ _onerror : function(evt) {
+ wstool.setState(false);
+ wstool.error("Websocket Error: " + evt.data);
+ wstool.error("See Javascript Console for possible detailed error message");
+ },
+
+ _send : function(message) {
+ if (this._ws) {
+ this._ws.send(message);
+ wstool.infoc(message);
+ }
+ },
+
+ write : function(text) {
+ wstool._send(text);
+ },
+
+ _onmessage : function(m) {
+ if (m.data) {
+ wstool.infos(m.data);
+ }
+ },
+
+ _onclose : function(closeEvent) {
+ this._ws = null;
+ wstool.setState(false);
+ wstool.info("Websocket Closed");
+ wstool.info(" .wasClean = " + closeEvent.wasClean);
+
+ var codeMap = {};
+ codeMap[1000] = "(NORMAL)";
+ codeMap[1001] = "(ENDPOINT_GOING_AWAY)";
+ codeMap[1002] = "(PROTOCOL_ERROR)";
+ codeMap[1003] = "(UNSUPPORTED_DATA)";
+ codeMap[1004] = "(UNUSED/RESERVED)";
+ codeMap[1005] = "(INTERNAL/NO_CODE_PRESENT)";
+ codeMap[1006] = "(INTERNAL/ABNORMAL_CLOSE)";
+ codeMap[1007] = "(BAD_DATA)";
+ codeMap[1008] = "(POLICY_VIOLATION)";
+ codeMap[1009] = "(MESSAGE_TOO_BIG)";
+ codeMap[1010] = "(HANDSHAKE/EXT_FAILURE)";
+ codeMap[1011] = "(SERVER/UNEXPECTED_CONDITION)";
+ codeMap[1015] = "(INTERNAL/TLS_ERROR)";
+ var codeStr = codeMap[closeEvent.code];
+ wstool.info(" .code = " + closeEvent.code + " " + codeStr);
+ wstool.info(" .reason = " + closeEvent.reason);
+ }
+};
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/large-echo-config-web.xml b/jetty-websocket/javax-websocket-server-impl/src/test/resources/large-echo-config-web.xml
new file mode 100644
index 0000000..08e696f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/large-echo-config-web.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="http://java.sun.com/xml/ns/javaee"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ metadata-complete="false"
+ version="3.0">
+
+ <listener>
+ <listener-class>org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoContextListener</listener-class>
+ </listener>
+</web-app>
\ No newline at end of file
diff --git a/jetty-websocket/pom.xml b/jetty-websocket/pom.xml
index 87f4220..6cac433 100644
--- a/jetty-websocket/pom.xml
+++ b/jetty-websocket/pom.xml
@@ -3,7 +3,7 @@
<parent>
<artifactId>jetty-project</artifactId>
<groupId>org.eclipse.jetty</groupId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -19,6 +19,9 @@
<module>websocket-client</module>
<module>websocket-server</module>
<module>websocket-servlet</module>
+ <module>websocket-mux-extension</module>
+ <module>javax-websocket-client-impl</module>
+ <module>javax-websocket-server-impl</module>
</modules>
<build>
@@ -41,8 +44,8 @@
</goals>
<configuration>
<instructions>
- <Export-Package>${bundle-symbolic-name}.*;version="9.0"</Export-Package>
- <Import-Package>javax.servlet.*;version="[2.6.0,3.0)",org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
+ <Export-Package>${bundle-symbolic-name}.*;version="9.1"</Export-Package>
+ <Import-Package>javax.servlet.*;version="[3.0,4.0)",org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
<_nouses>true</_nouses>
</instructions>
</configuration>
@@ -58,6 +61,24 @@
</archive>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>clirr-maven-plugin</artifactId>
+ <version>2.5</version>
+ <executions>
+ <execution>
+ <id>compare-api</id>
+ <phase>package</phase>
+ <goals>
+ <goal>clirr</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <minSeverity>info</minSeverity>
+ <comparisonVersion>9.0.3.v20130506</comparisonVersion>
+ </configuration>
+ </plugin>
</plugins>
</build>
</project>
diff --git a/jetty-websocket/websocket-api/pom.xml b/jetty-websocket/websocket-api/pom.xml
index f45d0ba..f7d5b58 100644
--- a/jetty-websocket/websocket-api/pom.xml
+++ b/jetty-websocket/websocket-api/pom.xml
@@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/BadPayloadException.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/BadPayloadException.java
index 70f57af..262150b 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/BadPayloadException.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/BadPayloadException.java
@@ -20,7 +20,7 @@
/**
* Exception to terminate the connection because it has received data within a frame payload that was not consistent with the requirements of that frame
- * payload. (eg: not UTF-8 in a text frame, or a bad data seen in the {@link PerMessageCompressionExtension})
+ * payload. (eg: not UTF-8 in a text frame, or a unexpected data seen by an extension)
*
* @see StatusCode#BAD_PAYLOAD
*/
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/InvalidWebSocketException.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/InvalidWebSocketException.java
index 3ffe7f1..b3de699 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/InvalidWebSocketException.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/InvalidWebSocketException.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.websocket.api;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+
/**
* Indicating that the provided Class is not a valid WebSocket as defined by the API.
* <p>
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java
index dfdefec..c1d000c 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java
@@ -35,19 +35,27 @@
void sendBytes(ByteBuffer data) throws IOException;
/**
- * Initiates the asynchronous transmission of a binary message. This method returns before the message is transmitted. Developers may provide a callback to
- * be notified when the message has been transmitted, or may use the returned Future object to track progress of the transmission. Errors in transmission
- * are given to the developer in the WriteResult object in either case.
+ * Initiates the asynchronous transmission of a binary message. This method returns before the message is transmitted. Developers may use the returned
+ * Future object to track progress of the transmission.
*
* @param data
* the data being sent
- * @param completion
- * handler that will be notified of progress
* @return the Future object representing the send operation.
*/
Future<Void> sendBytesByFuture(ByteBuffer data);
/**
+ * Initiates the asynchronous transmission of a binary message. This method returns before the message is transmitted. Developers may provide a callback to
+ * be notified when the message has been transmitted or resulted in an error.
+ *
+ * @param data
+ * the data being sent
+ * @param callback
+ * callback to notify of success or failure of the write operation
+ */
+ void sendBytes(ByteBuffer data, WriteCallback callback);
+
+ /**
* Send a binary message in pieces, blocking until all of the message has been transmitted. The runtime reads the message in order. Non-final pieces are
* sent with isLast set to false. The final piece must be sent with isLast set to true.
*
@@ -94,15 +102,23 @@
void sendString(String text) throws IOException;
/**
- * Initiates the asynchronous transmission of a text message. This method returns before the message is transmitted. Developers may provide a callback to be
- * notified when the message has been transmitted, or may use the returned Future object to track progress of the transmission. Errors in transmission are
- * given to the developer in the WriteResult object in either case.
+ * Initiates the asynchronous transmission of a text message. This method may return before the message is transmitted. Developers may use the returned
+ * Future object to track progress of the transmission.
*
* @param text
* the text being sent
- * @param completion
- * the handler which will be notified of progress
* @return the Future object representing the send operation.
*/
Future<Void> sendStringByFuture(String text);
+
+ /**
+ * Initiates the asynchronous transmission of a text message. This method may return before the message is transmitted. Developers may provide a callback to
+ * be notified when the message has been transmitted or resulted in an error.
+ *
+ * @param text
+ * the text being sent
+ * @param callback
+ * callback to notify of success or failure of the write operation
+ */
+ void sendString(String text, WriteCallback callback);
}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java
index 7748dd0..28b3b77 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java
@@ -40,7 +40,7 @@
* @see #disconnect()
*/
@Override
- void close() throws IOException;
+ void close();
/**
* Request Close the current conversation, giving a reason for the closure. Note the websocket spec defines the acceptable uses of status codes and reason
@@ -55,7 +55,7 @@
* @see #close(int, String)
* @see #disconnect()
*/
- void close(CloseStatus closeStatus) throws IOException;
+ void close(CloseStatus closeStatus);
/**
* Send a websocket Close frame, with status code.
@@ -72,7 +72,7 @@
* @see #close(CloseStatus)
* @see #disconnect()
*/
- void close(int statusCode, String reason) throws IOException;
+ void close(int statusCode, String reason);
/**
* Issue a harsh disconnect of the underlying connection.
@@ -107,13 +107,6 @@
public InetSocketAddress getLocalAddress();
/**
- * The maximum total length of messages, text or binary, that this Session can handle.
- *
- * @return the message size
- */
- long getMaximumMessageSize();
-
- /**
* Access the (now read-only) {@link WebSocketPolicy} in use for this connection.
*
* @return the policy in use
@@ -179,11 +172,6 @@
void setIdleTimeout(long ms);
/**
- * Sets the maximum total length of messages, text or binary, that this Session can handle.
- */
- void setMaximumMessageSize(long length);
-
- /**
* Suspend a the incoming read events on the connection.
*
* @return the suspend token suitable for resuming the reading of data on the connection.
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/StatusCode.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/StatusCode.java
index f286e7d..ee3a13c 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/StatusCode.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/StatusCode.java
@@ -75,6 +75,12 @@
* See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
*/
public final static int NO_CLOSE = 1006;
+
+ /**
+ * Abnormal Close is a synonym for {@link #NO_CLOSE}, used to indicate a close
+ * condition where no close frame was processed from the remote side.
+ */
+ public final static int ABNORMAL = NO_CLOSE;
/**
* 1007 indicates that an endpoint is terminating the connection because it has received data within a message that was not consistent with the type of the
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
index 2840512..8729131 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
@@ -20,12 +20,13 @@
import java.net.HttpCookie;
import java.net.URI;
+import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
+import java.util.TreeMap;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
@@ -36,8 +37,8 @@
private List<String> subProtocols = new ArrayList<>();
private List<ExtensionConfig> extensions = new ArrayList<>();
private List<HttpCookie> cookies = new ArrayList<>();
- private Map<String, List<String>> headers = new HashMap<>();
- private Map<String, String[]> parameters = new HashMap<>();
+ private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ private Map<String, List<String>> parameters = new HashMap<>();
private Object session;
private String httpVersion;
private String method;
@@ -76,6 +77,11 @@
}
}
+ public void clearHeaders()
+ {
+ headers.clear();
+ }
+
public List<HttpCookie> getCookies()
{
return cookies;
@@ -88,7 +94,7 @@
public String getHeader(String name)
{
- List<String> values = headers.get(name.toLowerCase(Locale.ENGLISH));
+ List<String> values = headers.get(name);
// no value list
if (values == null)
{
@@ -122,7 +128,7 @@
public int getHeaderInt(String name)
{
- List<String> values = headers.get(name.toLowerCase(Locale.ENGLISH));
+ List<String> values = headers.get(name);
// no value list
if (values == null)
{
@@ -177,7 +183,7 @@
*
* @return a unmodifiable map of query parameters of the request.
*/
- public Map<String, String[]> getParameterMap()
+ public Map<String, List<String>> getParameterMap()
{
return Collections.unmodifiableMap(parameters);
}
@@ -219,6 +225,19 @@
return subProtocols;
}
+ /**
+ * Get the User Principal for this request.
+ * <p>
+ * Only applicable when using UpgradeRequest from server side.
+ *
+ * @return the user principal
+ */
+ public Principal getUserPrincipal()
+ {
+ // Server side should override to implement
+ return null;
+ }
+
public boolean hasSubProtocol(String test)
{
for (String protocol : subProtocols)
@@ -248,14 +267,26 @@
public void setHeader(String name, List<String> values)
{
- headers.put(name.toLowerCase(Locale.ENGLISH),values);
+ headers.put(name,values);
}
public void setHeader(String name, String value)
{
List<String> values = new ArrayList<>();
values.add(value);
- setHeader(name.toLowerCase(Locale.ENGLISH),values);
+ setHeader(name,values);
+ }
+
+ public void setHeaders(Map<String, List<String>> headers)
+ {
+ clearHeaders();
+
+ for (Map.Entry<String, List<String>> entry : headers.entrySet())
+ {
+ String name = entry.getKey();
+ List<String> values = entry.getValue();
+ setHeader(name,values);
+ }
}
public void setHttpVersion(String httpVersion)
@@ -268,7 +299,7 @@
this.method = method;
}
- protected void setParameterMap(Map<String, String[]> parameters)
+ protected void setParameterMap(Map<String, List<String>> parameters)
{
this.parameters.clear();
this.parameters.putAll(parameters);
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java
index cf4424a..147c5a0 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java
@@ -20,10 +20,10 @@
import java.io.IOException;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
@@ -33,13 +33,13 @@
public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
private int statusCode;
private String statusReason;
- private Map<String, List<String>> headers = new HashMap<>();
+ private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private List<ExtensionConfig> extensions = new ArrayList<>();
private boolean success = false;
public void addHeader(String name, String value)
{
- String key = name.toLowerCase();
+ String key = name;
List<String> values = headers.get(key);
if (values == null)
{
@@ -115,7 +115,7 @@
public List<String> getHeaders(String name)
{
- return headers.get(name.toLowerCase());
+ return headers.get(name);
}
public int getStatusCode()
@@ -163,8 +163,6 @@
/**
* Set the list of extensions that are approved for use with this websocket.
* <p>
- * This is Advanced usage of the WebSocketCreator to allow for a custom set of negotiated extensions.
- * <p>
* Notes:
* <ul>
* <li>Per the spec you cannot add extensions that have not been seen in the {@link UpgradeRequest}, just remove entries you don't want to use</li>
@@ -188,7 +186,7 @@
{
List<String> values = new ArrayList<>();
values.add(value);
- headers.put(name.toLowerCase(),values);
+ headers.put(name,values);
}
public void setStatusCode(int statusCode)
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java
index 89c7127..8f632a8 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java
@@ -38,7 +38,7 @@
/**
* A Close Event was received.
* <p>
- * The underlying {@link WebSocketConnection} will be considered closed at this point.
+ * The underlying Connection will be considered closed at this point.
*
* @param statusCode
* the close status code. (See {@link StatusCode})
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java
index 348a0e0..ce20b2c 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java
@@ -38,9 +38,45 @@
/**
* The maximum size of a text message during parsing/generating.
* <p>
+ * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
+ * <p>
* Default: 65536 (64 K)
*/
- private long maxMessageSize = 64 * KB;
+ private int maxTextMessageSize = 64 * KB;
+
+ /**
+ * The maximum size of a text message buffer.
+ * <p>
+ * Used ONLY for stream based message writing.
+ * <p>
+ * Default: 32768 (32 K)
+ */
+ private int maxTextMessageBufferSize = 32 * KB;
+
+ /**
+ * The maximum size of a binary message during parsing/generating.
+ * <p>
+ * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
+ * <p>
+ * Default: 65536 (64 K)
+ */
+ private int maxBinaryMessageSize = 64 * KB;
+
+ /**
+ * The maximum size of a binary message buffer
+ * <p>
+ * Used ONLY for for stream based message writing
+ * <p>
+ * Default: 32768 (32 K)
+ */
+ private int maxBinaryMessageBufferSize = 32 * KB;
+
+ /**
+ * The timeout in ms (milliseconds) for async write operations.
+ * <p>
+ * Negative values indicate a disabled timeout.
+ */
+ private long asyncWriteTimeout = 60000;
/**
* The time in ms (milliseconds) that a websocket may be idle before closing.
@@ -66,14 +102,42 @@
this.behavior = behavior;
}
- public void assertValidMessageSize(int requestedSize)
+ private void assertLessThan(String name, long size, String otherName, long otherSize)
{
- if (maxMessageSize > 0)
+ if (size > otherSize)
+ {
+ throw new IllegalArgumentException(String.format("%s [%d] must be less than %s [%d]",name,size,otherName,otherSize));
+ }
+ }
+
+ private void assertPositive(String name, long size)
+ {
+ if (size < 1)
+ {
+ throw new IllegalArgumentException(String.format("%s [%d] must be a postive value larger than 0",name,size));
+ }
+ }
+
+ public void assertValidBinaryMessageSize(int requestedSize)
+ {
+ if (maxBinaryMessageSize > 0)
{
// validate it
- if (requestedSize > maxMessageSize)
+ if (requestedSize > maxBinaryMessageSize)
{
- throw new MessageTooLargeException("Requested message size [" + requestedSize + "] exceeds maximum size [" + maxMessageSize + "]");
+ throw new MessageTooLargeException("Binary message size [" + requestedSize + "] exceeds maximum size [" + maxBinaryMessageSize + "]");
+ }
+ }
+ }
+
+ public void assertValidTextMessageSize(int requestedSize)
+ {
+ if (maxTextMessageSize > 0)
+ {
+ // validate it
+ if (requestedSize > maxTextMessageSize)
+ {
+ throw new MessageTooLargeException("Text message size [" + requestedSize + "] exceeds maximum size [" + maxTextMessageSize + "]");
}
}
}
@@ -82,43 +146,213 @@
{
WebSocketPolicy clone = new WebSocketPolicy(this.behavior);
clone.idleTimeout = this.idleTimeout;
- clone.maxMessageSize = this.maxMessageSize;
+ clone.maxTextMessageSize = this.maxTextMessageSize;
+ clone.maxTextMessageBufferSize = this.maxTextMessageBufferSize;
+ clone.maxBinaryMessageSize = this.maxBinaryMessageSize;
+ clone.maxBinaryMessageBufferSize = this.maxBinaryMessageBufferSize;
clone.inputBufferSize = this.inputBufferSize;
+ clone.asyncWriteTimeout = this.asyncWriteTimeout;
return clone;
}
+ /**
+ * The timeout in ms (milliseconds) for async write operations.
+ * <p>
+ * Negative values indicate a disabled timeout.
+ *
+ * @return the timeout for async write operations. negative values indicate disabled timeout.
+ */
+ public long getAsyncWriteTimeout()
+ {
+ return asyncWriteTimeout;
+ }
+
public WebSocketBehavior getBehavior()
{
return behavior;
}
+ /**
+ * The time in ms (milliseconds) that a websocket connection mad by idle before being closed automatically.
+ *
+ * @return the timeout in milliseconds for idle timeout.
+ */
public long getIdleTimeout()
{
return idleTimeout;
}
+ /**
+ * The size of the input (read from network layer) buffer size.
+ * <p>
+ * This is the raw read operation buffer size, before the parsing of the websocket frames.
+ *
+ * @return the raw network bytes read operation buffer size.
+ */
public int getInputBufferSize()
{
return inputBufferSize;
}
- public long getMaxMessageSize()
+ /**
+ * Get the maximum size of a binary message buffer (for streaming writing)
+ *
+ * @return the maximum size of a binary message buffer
+ */
+ public int getMaxBinaryMessageBufferSize()
{
- return maxMessageSize;
+ return maxBinaryMessageBufferSize;
}
- public void setIdleTimeout(long idleTimeout)
+ /**
+ * Get the maximum size of a binary message during parsing/generating.
+ * <p>
+ * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
+ *
+ * @return the maximum size of a binary message
+ */
+ public int getMaxBinaryMessageSize()
{
- this.idleTimeout = idleTimeout;
+ return maxBinaryMessageSize;
}
- public void setInputBufferSize(int inputBufferSize)
+ /**
+ * Get the maximum size of a text message buffer (for streaming writing)
+ *
+ * @return the maximum size of a text message buffer
+ */
+ public int getMaxTextMessageBufferSize()
{
- this.inputBufferSize = inputBufferSize;
+ return maxTextMessageBufferSize;
}
- public void setMaxMessageSize(long maxMessageSize)
+ /**
+ * Get the maximum size of a text message during parsing/generating.
+ * <p>
+ * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
+ *
+ * @return the maximum size of a text message.
+ */
+ public int getMaxTextMessageSize()
{
- this.maxMessageSize = maxMessageSize;
+ return maxTextMessageSize;
+ }
+
+ /**
+ * The timeout in ms (milliseconds) for async write operations.
+ * <p>
+ * Negative values indicate a disabled timeout.
+ *
+ * @param ms
+ * the timeout in milliseconds
+ */
+ public void setAsyncWriteTimeout(long ms)
+ {
+ assertLessThan("AsyncWriteTimeout",ms,"IdleTimeout",idleTimeout);
+ this.asyncWriteTimeout = ms;
+ }
+
+ /**
+ * The time in ms (milliseconds) that a websocket may be idle before closing.
+ *
+ * @param ms
+ * the timeout in milliseconds
+ */
+ public void setIdleTimeout(long ms)
+ {
+ assertPositive("IdleTimeout",ms);
+ this.idleTimeout = ms;
+ }
+
+ /**
+ * The size of the input (read from network layer) buffer size.
+ *
+ * @param size
+ * the size in bytes
+ */
+ public void setInputBufferSize(int size)
+ {
+ assertPositive("InputBufferSize",size);
+ assertLessThan("InputBufferSize",size,"MaxTextMessageBufferSize",maxTextMessageBufferSize);
+ assertLessThan("InputBufferSize",size,"MaxBinaryMessageBufferSize",maxBinaryMessageBufferSize);
+
+ this.inputBufferSize = size;
+ }
+
+ /**
+ * The maximum size of a binary message buffer.
+ * <p>
+ * Used ONLY for stream based message writing.
+ *
+ * @param size
+ * the maximum size of the binary message buffer
+ */
+ public void setMaxBinaryMessageBufferSize(int size)
+ {
+ assertPositive("MaxBinaryMessageBufferSize",size);
+
+ this.maxBinaryMessageBufferSize = size;
+ }
+
+ /**
+ * The maximum size of a binary message during parsing/generating.
+ * <p>
+ * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
+ *
+ * @param size
+ * the maximum allowed size of a binary message.
+ */
+ public void setMaxBinaryMessageSize(int size)
+ {
+ assertPositive("MaxBinaryMessageSize",size);
+
+ this.maxBinaryMessageSize = size;
+ }
+
+ /**
+ * The maximum size of a text message buffer.
+ * <p>
+ * Used ONLY for stream based message writing.
+ *
+ * @param size
+ * the maximum size of the text message buffer
+ */
+ public void setMaxTextMessageBufferSize(int size)
+ {
+ assertPositive("MaxTextMessageBufferSize",size);
+
+ this.maxTextMessageBufferSize = size;
+ }
+
+ /**
+ * The maximum size of a text message during parsing/generating.
+ * <p>
+ * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
+ *
+ * @param size
+ * the maximum allowed size of a text message.
+ */
+ public void setMaxTextMessageSize(int size)
+ {
+ assertPositive("MaxTextMessageSize",size);
+
+ this.maxTextMessageSize = size;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("WebSocketPolicy@").append(Integer.toHexString(hashCode()));
+ builder.append("[behavior=").append(behavior);
+ builder.append(",maxTextMessageSize=").append(maxTextMessageSize);
+ builder.append(",maxTextMessageBufferSize=").append(maxTextMessageBufferSize);
+ builder.append(",maxBinaryMessageSize=").append(maxBinaryMessageSize);
+ builder.append(",maxBinaryMessageBufferSize=").append(maxBinaryMessageBufferSize);
+ builder.append(",asyncWriteTimeout=").append(asyncWriteTimeout);
+ builder.append(",idleTimeout=").append(idleTimeout);
+ builder.append(",inputBufferSize=").append(inputBufferSize);
+ builder.append("]");
+ return builder.toString();
}
}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketFrame.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketFrame.java
index 13f8309..959cebd 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketFrame.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketFrame.java
@@ -25,7 +25,6 @@
import java.lang.annotation.Target;
import org.eclipse.jetty.websocket.api.Session;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
/**
* (ADVANCED) Annotation for tagging methods to receive frame events.
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java
index a95378b..a62c36b 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java
@@ -35,7 +35,9 @@
{
int inputBufferSize() default -2;
+ int maxBinaryMessageSize() default -2;
+
int maxIdleTime() default -2;
- int maxMessageSize() default -2;
+ int maxTextMessageSize() default -2;
}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Extension.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Extension.java
index 1db2187..09064ff 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Extension.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Extension.java
@@ -21,7 +21,7 @@
/**
* Interface for WebSocket Extensions.
* <p>
- * That work is performed by the two {@link FrameHandler} implementations for incoming and outgoing frame handling.
+ * That {@link Frame}s are passed through the Extension via the {@link IncomingFrames} and {@link OutgoingFrames} interfaces
*/
public interface Extension extends IncomingFrames, OutgoingFrames
{
@@ -67,19 +67,6 @@
public abstract boolean isRsv3User();
/**
- * Used to indicate that the extension works as a decoder of TEXT Data Frames.
- * <p>
- * This is used to adjust validation during parsing/generating, as per spec TEXT Data Frames can only contain UTF8 encoded String data.
- * <p>
- * Example: a compression extension will process a compressed set of text data, the parser/generator should no longer be concerned about the validity of the
- * TEXT Data Frames as this is now the responsibility of the extension.
- *
- * @return true if extension will process TEXT Data Frames, false if extension makes no modifications of TEXT Data Frames. If false, the parser/generator is
- * now free to validate the conformance to spec of TEXT Data Frames.
- */
- public abstract boolean isTextDataDecoder();
-
- /**
* Set the next {@link IncomingFrames} to call in the chain.
*
* @param nextIncoming
@@ -94,4 +81,9 @@
* the next outgoing extension
*/
public void setNextOutgoingFrames(OutgoingFrames nextOutgoing);
+
+ // TODO: Extension should indicate if it requires boundary of fragments to be preserved
+
+ // TODO: Extension should indicate if it uses the Extension data field of frame for its own reasons.
+
}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java
index b03f0cb..04c0121 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java
@@ -96,8 +96,12 @@
{
str.append(';');
str.append(param);
- str.append('=');
- QuoteUtil.quoteIfNeeded(str,parameters.get(param),";=");
+ String value = parameters.get(param);
+ if (value != null)
+ {
+ str.append('=');
+ QuoteUtil.quoteIfNeeded(str,value,";=");
+ }
}
return str.toString();
}
@@ -108,7 +112,7 @@
}
/**
- * Return parameters in way similar to how {@link javax.net.websocket.extensions.Extension#getParameters()} works.
+ * Return parameters found in request URI.
*
* @return the parameter map
*/
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionFactory.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionFactory.java
index b95d10d..7edb2db 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionFactory.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionFactory.java
@@ -34,7 +34,10 @@
availableExtensions = new HashMap<>();
for (Extension ext : extensionLoader)
{
- availableExtensions.put(ext.getName(),ext.getClass());
+ if (ext != null)
+ {
+ availableExtensions.put(ext.getName(),ext.getClass());
+ }
}
}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Frame.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Frame.java
index c4cf84b..7a201f8 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Frame.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Frame.java
@@ -81,16 +81,17 @@
public ByteBuffer getPayload();
+ /**
+ * The original payload length ({@link ByteBuffer#remaining()})
+ *
+ * @return the original payload length ({@link ByteBuffer#remaining()})
+ */
public int getPayloadLength();
- public int getPayloadStart();
-
public Type getType();
public boolean hasPayload();
- public boolean isContinuation();
-
public boolean isFin();
/**
@@ -98,6 +99,7 @@
*
* @return true if final frame.
*/
+ // FIXME: remove
public boolean isLast();
public boolean isMasked();
@@ -108,5 +110,10 @@
public boolean isRsv3();
+ /**
+ * The current number of bytes left to read from the payload ByteBuffer.
+ *
+ * @return the current number of bytes left to read from the payload ByteBuffer
+ */
public int remaining();
}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/IncomingFrames.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/IncomingFrames.java
index 2ea7e09..a4b5780 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/IncomingFrames.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/IncomingFrames.java
@@ -18,15 +18,12 @@
package org.eclipse.jetty.websocket.api.extensions;
-import org.eclipse.jetty.websocket.api.WebSocketException;
-
/**
* Interface for dealing with Incoming Frames.
*/
public interface IncomingFrames
{
- // TODO: JSR-356 change to Throwable
- public void incomingError(WebSocketException e);
+ public void incomingError(Throwable t);
public void incomingFrame(Frame frame);
}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/util/QuoteUtil.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/util/QuoteUtil.java
index 42a63fc..eed6f20 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/util/QuoteUtil.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/util/QuoteUtil.java
@@ -18,8 +18,8 @@
package org.eclipse.jetty.websocket.api.util;
-import java.io.IOException;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
@@ -342,7 +342,6 @@
* the string to possibly quote
* @param delim
* the delimiter characters that will trigger automatic quoting
- * @throws IOException
*/
public static void quoteIfNeeded(StringBuilder buf, String str, String delim)
{
@@ -448,4 +447,57 @@
}
return ret.toString();
}
+
+ public static String join(Object[] objs, String delim)
+ {
+ if (objs == null)
+ {
+ return "";
+ }
+ StringBuilder ret = new StringBuilder();
+ int len = objs.length;
+ for (int i = 0; i < len; i++)
+ {
+ if (i > 0)
+ {
+ ret.append(delim);
+ }
+ if (objs[i] instanceof String)
+ {
+ ret.append('"').append(objs[i]).append('"');
+ }
+ else
+ {
+ ret.append(objs[i]);
+ }
+ }
+ return ret.toString();
+ }
+
+ public static String join(Collection<?> objs, String delim)
+ {
+ if (objs == null)
+ {
+ return "";
+ }
+ StringBuilder ret = new StringBuilder();
+ boolean needDelim = false;
+ for (Object obj : objs)
+ {
+ if (needDelim)
+ {
+ ret.append(delim);
+ }
+ if (obj instanceof String)
+ {
+ ret.append('"').append(obj).append('"');
+ }
+ else
+ {
+ ret.append(obj);
+ }
+ needDelim = true;
+ }
+ return ret.toString();
+ }
}
diff --git a/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfigTest.java b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfigTest.java
index 25d36fe..5cd323b 100644
--- a/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfigTest.java
+++ b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfigTest.java
@@ -18,7 +18,8 @@
package org.eclipse.jetty.websocket.api.extensions;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
import java.util.HashMap;
import java.util.Map;
diff --git a/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtilTest.java b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtilTest.java
index 3eb9a55..d0f1f64 100644
--- a/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtilTest.java
+++ b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtilTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.api.util;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.util.Iterator;
import java.util.NoSuchElementException;
diff --git a/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtil_QuoteTest.java b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtil_QuoteTest.java
index 8d0ad63..4519bc4 100644
--- a/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtil_QuoteTest.java
+++ b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtil_QuoteTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.api.util;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.util.ArrayList;
import java.util.Collection;
diff --git a/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/WSURITest.java b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/WSURITest.java
index a4aac8e..8f4ccdd 100644
--- a/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/WSURITest.java
+++ b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/WSURITest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.api.util;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.net.URI;
import java.net.URISyntaxException;
diff --git a/jetty-websocket/websocket-client/pom.xml b/jetty-websocket/websocket-client/pom.xml
index afa9bbe..0be0853 100644
--- a/jetty-websocket/websocket-client/pom.xml
+++ b/jetty-websocket/websocket-client/pom.xml
@@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java
index 94b9a16..5885586 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java
@@ -22,6 +22,7 @@
import java.net.HttpCookie;
import java.net.URI;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -173,7 +174,7 @@
// Other headers
for (String key : getHeaders().keySet())
{
- if (FORBIDDEN_HEADERS.contains(key.toLowerCase()))
+ if (FORBIDDEN_HEADERS.contains(key))
{
LOG.warn("Skipping forbidden header - {}",key);
continue; // skip
@@ -216,7 +217,7 @@
super.setRequestURI(uri);
// parse parameter map
- Map<String, String[]> pmap = new HashMap<>();
+ Map<String, List<String>> pmap = new HashMap<>();
String query = uri.getQuery();
@@ -230,12 +231,14 @@
List<String> values = params.getValues(key);
if (values == null)
{
- pmap.put(key,new String[0]);
+ pmap.put(key,new ArrayList<String>());
}
else
{
- int len = values.size();
- pmap.put(key,values.toArray(new String[len]));
+ // break link to original
+ List<String> copy = new ArrayList<>();
+ copy.addAll(values);
+ pmap.put(key,copy);
}
}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
index e875612..b69627e 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
@@ -47,8 +47,11 @@
import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
import org.eclipse.jetty.websocket.client.io.ConnectPromise;
import org.eclipse.jetty.websocket.client.io.ConnectionManager;
+import org.eclipse.jetty.websocket.client.io.UpgradeListener;
import org.eclipse.jetty.websocket.client.masks.Masker;
import org.eclipse.jetty.websocket.client.masks.RandomMasker;
+import org.eclipse.jetty.websocket.common.SessionFactory;
+import org.eclipse.jetty.websocket.common.WebSocketSessionFactory;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
@@ -63,7 +66,8 @@
private final WebSocketPolicy policy;
private final SslContextFactory sslContextFactory;
private final WebSocketExtensionFactory extensionRegistry;
- private final EventDriverFactory eventDriverFactory;
+ private EventDriverFactory eventDriverFactory;
+ private SessionFactory sessionFactory;
private ByteBufferPool bufferPool;
private Executor executor;
private Scheduler scheduler;
@@ -75,16 +79,29 @@
public WebSocketClient()
{
- this(null);
+ this(null,null);
+ }
+
+ public WebSocketClient(Executor executor)
+ {
+ this(null,executor);
}
public WebSocketClient(SslContextFactory sslContextFactory)
{
+ this(sslContextFactory,null);
+ }
+
+ public WebSocketClient(SslContextFactory sslContextFactory, Executor executor)
+ {
+ this.executor = executor;
this.sslContextFactory = sslContextFactory;
this.policy = WebSocketPolicy.newClientPolicy();
+ this.bufferPool = new MappedByteBufferPool();
this.extensionRegistry = new WebSocketExtensionFactory(policy,bufferPool);
this.masker = new RandomMasker();
this.eventDriverFactory = new EventDriverFactory(policy);
+ this.sessionFactory = new WebSocketSessionFactory();
}
public Future<Session> connect(Object websocket, URI toUri) throws IOException
@@ -98,6 +115,11 @@
public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request) throws IOException
{
+ return connect(websocket,toUri,request,null);
+ }
+
+ public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request, UpgradeListener upgradeListener) throws IOException
+ {
if (!isStarted())
{
throw new IllegalStateException(WebSocketClient.class.getSimpleName() + "@" + this.hashCode() + " is not started");
@@ -133,17 +155,40 @@
}
// Validate websocket URI
- LOG.debug("connect websocket:{} to:{}",websocket,toUri);
+ LOG.debug("connect websocket {} to {}",websocket,toUri);
// Grab Connection Manager
+ initialiseClient();
ConnectionManager manager = getConnectionManager();
// Setup Driver for user provided websocket
- EventDriver driver = eventDriverFactory.wrap(websocket);
+ EventDriver driver = null;
+ if (websocket instanceof EventDriver)
+ {
+ // Use the EventDriver as-is
+ driver = (EventDriver)websocket;
+ }
+ else
+ {
+ // Wrap websocket with appropriate EventDriver
+ driver = eventDriverFactory.wrap(websocket);
+ }
+
+ if (driver == null)
+ {
+ throw new IllegalStateException("Unable to identify as websocket object: " + websocket.getClass().getName());
+ }
// Create the appropriate (physical vs virtual) connection task
ConnectPromise promise = manager.connect(this,driver,request);
+ if (upgradeListener != null)
+ {
+ promise.setUpgradeListener(upgradeListener);
+ }
+
+ LOG.debug("Connect Promise: {}",promise);
+
// Execute the connection on the executor thread
executor.execute(promise);
@@ -151,6 +196,41 @@
return promise;
}
+ private synchronized void initialiseClient() throws IOException
+ {
+ if (executor == null)
+ {
+ QueuedThreadPool threadPool = new QueuedThreadPool();
+ String name = WebSocketClient.class.getSimpleName() + "@" + hashCode();
+ threadPool.setName(name);
+ executor = threadPool;
+ addBean(executor,true);
+ }
+ else
+ {
+ addBean(executor,false);
+ }
+
+ if (connectionManager != null)
+ {
+ return;
+ }
+ try
+ {
+ connectionManager = newConnectionManager();
+ addBean(connectionManager);
+ connectionManager.start();
+ }
+ catch (IOException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new IOException(e);
+ }
+ }
+
@Override
protected void doStart() throws Exception
{
@@ -163,14 +243,6 @@
String name = WebSocketClient.class.getSimpleName() + "@" + hashCode();
- if (executor == null)
- {
- QueuedThreadPool threadPool = new QueuedThreadPool();
- threadPool.setName(name);
- executor = threadPool;
- }
- addBean(executor);
-
if (bufferPool == null)
{
bufferPool = new MappedByteBufferPool();
@@ -188,12 +260,9 @@
cookieStore = new HttpCookieStore.Empty();
}
- this.connectionManager = newConnectionManager();
- addBean(this.connectionManager);
-
super.doStart();
- LOG.info("Started {}",this);
+ LOG.debug("Started {}",this);
}
@Override
@@ -211,6 +280,16 @@
LOG.info("Stopped {}",this);
}
+ /**
+ * Return the number of milliseconds for a timeout of an attempted write operation.
+ *
+ * @return number of milliseconds for timeout of an attempted write operation
+ */
+ public long getAsyncWriteTimeout()
+ {
+ return this.policy.getAsyncWriteTimeout();
+ }
+
public SocketAddress getBindAddress()
{
return bindAddress;
@@ -236,6 +315,11 @@
return cookieStore;
}
+ public EventDriverFactory getEventDriverFactory()
+ {
+ return eventDriverFactory;
+ }
+
public Executor getExecutor()
{
return executor;
@@ -252,6 +336,26 @@
}
/**
+ * Get the maximum size for buffering of a binary message.
+ *
+ * @return the maximum size of a binary message buffer.
+ */
+ public int getMaxBinaryMessageBufferSize()
+ {
+ return this.policy.getMaxBinaryMessageBufferSize();
+ }
+
+ /**
+ * Get the maximum size for a binary message.
+ *
+ * @return the maximum size of a binary message.
+ */
+ public long getMaxBinaryMessageSize()
+ {
+ return this.policy.getMaxBinaryMessageSize();
+ }
+
+ /**
* Get the max idle timeout for new connections.
*
* @return the max idle timeout in milliseconds for new connections.
@@ -261,6 +365,26 @@
return this.policy.getIdleTimeout();
}
+ /**
+ * Get the maximum size for buffering of a text message.
+ *
+ * @return the maximum size of a text message buffer.
+ */
+ public int getMaxTextMessageBufferSize()
+ {
+ return this.policy.getMaxTextMessageBufferSize();
+ }
+
+ /**
+ * Get the maximum size for a text message.
+ *
+ * @return the maximum size of a text message.
+ */
+ public long getMaxTextMessageSize()
+ {
+ return this.policy.getMaxTextMessageSize();
+ }
+
public WebSocketPolicy getPolicy()
{
return this.policy;
@@ -271,9 +395,14 @@
return scheduler;
}
+ public SessionFactory getSessionFactory()
+ {
+ return sessionFactory;
+ }
+
/**
* @return the {@link SslContextFactory} that manages TLS encryption
- * @see WebSocketClient(SslContextFactory)
+ * @see #WebSocketClient(SslContextFactory)
*/
public SslContextFactory getSslContextFactory()
{
@@ -310,6 +439,11 @@
return new ConnectionManager(this);
}
+ public void setAsyncWriteTimeout(long ms)
+ {
+ this.policy.setAsyncWriteTimeout(ms);
+ }
+
public void setBindAdddress(SocketAddress bindAddress)
{
this.bindAddress = bindAddress;
@@ -323,16 +457,16 @@
/**
* Set the timeout for connecting to the remote server.
*
- * @param timeoutMilliseconds
+ * @param ms
* the timeout in milliseconds
*/
- public void setConnectTimeout(long timeoutMilliseconds)
+ public void setConnectTimeout(long ms)
{
- if (timeoutMilliseconds < 0)
+ if (ms < 0)
{
throw new IllegalStateException("Connect Timeout cannot be negative");
}
- this.connectTimeout = timeoutMilliseconds;
+ this.connectTimeout = ms;
}
public void setCookieStore(CookieStore cookieStore)
@@ -340,8 +474,14 @@
this.cookieStore = cookieStore;
}
+ public void setEventDriverFactory(EventDriverFactory factory)
+ {
+ this.eventDriverFactory = factory;
+ }
+
public void setExecutor(Executor executor)
{
+ updateBean(this.executor,executor);
this.executor = executor;
}
@@ -350,16 +490,31 @@
this.masker = masker;
}
+ public void setMaxBinaryMessageBufferSize(int max)
+ {
+ this.policy.setMaxBinaryMessageBufferSize(max);
+ }
+
/**
* Set the max idle timeout for new connections.
* <p>
* Existing connections will not have their max idle timeout adjusted.
*
- * @param milliseconds
+ * @param ms
* the timeout in milliseconds
*/
- public void setMaxIdleTimeout(long milliseconds)
+ public void setMaxIdleTimeout(long ms)
{
- this.policy.setIdleTimeout(milliseconds);
+ this.policy.setIdleTimeout(ms);
+ }
+
+ public void setMaxTextMessageBufferSize(int max)
+ {
+ this.policy.setMaxTextMessageBufferSize(max);
+ }
+
+ public void setSessionFactory(SessionFactory sessionFactory)
+ {
+ this.sessionFactory = sessionFactory;
}
}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectPromise.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectPromise.java
index f918abf..f52e1d3 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectPromise.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectPromise.java
@@ -36,6 +36,7 @@
private final EventDriver driver;
private final ClientUpgradeRequest request;
private final Masker masker;
+ private UpgradeListener upgradeListener;
private ClientUpgradeResponse response;
public ConnectPromise(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request)
@@ -81,11 +82,21 @@
return response;
}
+ public UpgradeListener getUpgradeListener()
+ {
+ return upgradeListener;
+ }
+
public void setResponse(ClientUpgradeResponse response)
{
this.response = response;
}
+ public void setUpgradeListener(UpgradeListener upgradeListener)
+ {
+ this.upgradeListener = upgradeListener;
+ }
+
public void succeeded(WebSocketSession session)
{
session.setUpgradeRequest(request);
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java
index d228144..0eefe65 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java
@@ -203,7 +203,7 @@
return Collections.unmodifiableCollection(sessions);
}
- private boolean isVirtualConnectionPossibleTo(String hostname)
+ public boolean isVirtualConnectionPossibleTo(String hostname)
{
// TODO Auto-generated method stub
return false;
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java
index 8305e7f..5366567 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java
@@ -32,7 +32,6 @@
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.QuotedStringTokenizer;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.UpgradeException;
@@ -41,6 +40,7 @@
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
import org.eclipse.jetty.websocket.common.AcceptHash;
+import org.eclipse.jetty.websocket.common.SessionFactory;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
@@ -61,6 +61,13 @@
{
URI uri = connectPromise.getRequest().getRequestURI();
request.setRequestURI(uri);
+
+ UpgradeListener handshakeListener = connectPromise.getUpgradeListener();
+ if (handshakeListener != null)
+ {
+ handshakeListener.onHandshakeRequest(request);
+ }
+
String rawRequest = request.generate();
ByteBuffer buf = BufferUtil.toBuffer(rawRequest, StandardCharsets.UTF_8);
@@ -75,6 +82,14 @@
// start the interest in fill
fillInterested();
}
+
+ @Override
+ public void failed(Throwable cause)
+ {
+ super.failed(cause);
+ // Fail the connect promise when a fundamental exception during connect occurs.
+ connectPromise.failed(cause);
+ }
}
/** HTTP Response Code: 101 Switching Protocols */
@@ -114,6 +129,12 @@
private void notifyConnect(ClientUpgradeResponse response)
{
connectPromise.setResponse(response);
+
+ UpgradeListener handshakeListener = connectPromise.getUpgradeListener();
+ if (handshakeListener != null)
+ {
+ handshakeListener.onHandshakeResponse(response);
+ }
}
@Override
@@ -216,7 +237,8 @@
WebSocketClientConnection connection = new WebSocketClientConnection(endp,executor,connectPromise,policy);
- WebSocketSession session = new WebSocketSession(request.getRequestURI(),websocket,connection);
+ SessionFactory sessionFactory = connectPromise.getClient().getSessionFactory();
+ WebSocketSession session = sessionFactory.createSession(request.getRequestURI(),websocket,connection);
session.setPolicy(policy);
session.setUpgradeResponse(response);
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeListener.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeListener.java
new file mode 100644
index 0000000..3edfe51
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeListener.java
@@ -0,0 +1,32 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client.io;
+
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+
+/**
+ * Listener for Handshake/Upgrade events.
+ */
+public interface UpgradeListener
+{
+ public void onHandshakeRequest(UpgradeRequest request);
+
+ public void onHandshakeResponse(UpgradeResponse response);
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/MuxClientAddHandler.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/MuxClientAddHandler.java
deleted file mode 100644
index bd9b562..0000000
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/MuxClientAddHandler.java
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.client.mux;
-
-import org.eclipse.jetty.websocket.common.WebSocketSession;
-import org.eclipse.jetty.websocket.common.extensions.mux.add.MuxAddClient;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelResponse;
-
-public class MuxClientAddHandler implements MuxAddClient
-{
- @Override
- public WebSocketSession createSession(MuxAddChannelResponse response)
- {
- // TODO Auto-generated method stub
- return null;
- }
-}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/MuxClientExtension.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/MuxClientExtension.java
deleted file mode 100644
index 58cec30..0000000
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/MuxClientExtension.java
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.client.mux;
-
-import org.eclipse.jetty.websocket.common.extensions.mux.AbstractMuxExtension;
-import org.eclipse.jetty.websocket.common.extensions.mux.Muxer;
-
-public class MuxClientExtension extends AbstractMuxExtension
-{
- @Override
- public void configureMuxer(Muxer muxer)
- {
- muxer.setAddClient(new MuxClientAddHandler());
- }
-}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/package-info.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/package-info.java
deleted file mode 100644
index 6df80bb..0000000
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-/**
- * Jetty WebSocket Client : MUX Extension [<em>Unstable Early Draft</em>]
- */
-package org.eclipse.jetty.websocket.client.mux;
-
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/package-info.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/package-info.java
index 1f5df7d..1087907 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/package-info.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/package-info.java
@@ -19,15 +19,15 @@
/**
* Jetty WebSocket Client API
* <p>
- * The core class is {@link WebSocketClient}, which acts as a central configuration object (for example
- * for {@link WebSocketClient#setConnectTimeout(int) connect timeouts}, {@link WebSocketClient#setCookieStore(CookieStore)
+ * The core class is {@link org.eclipse.jetty.websocket.client.WebSocketClient}, which acts as a central configuration object (for example
+ * for {@link org.eclipse.jetty.websocket.client.WebSocketClient#setConnectTimeout(int) connect timeouts}, {@link WebSocketClient#setCookieStore(CookieStore)
* request cookie store}, etc.) and as a factory for WebSocket {@link org.eclipse.jetty.websocket.api.Session} objects.
* <p>
* The <a href="https://tools.ietf.org/html/rfc6455">WebSocket protocol</a> is based on a framing protocol built
* around an upgraded HTTP connection. It is primarily focused on the sending of messages (text or binary), with an
* occasional control frame (close, ping, pong) that this implementation uses.
* <p />
- * {@link WebSocketClient} holds a number of {@link org.eclipse.jetty.websocket.api.Session Sessions}, which in turn
+ * {@link org.eclipse.jetty.websocket.client.WebSocketClient} holds a number of {@link org.eclipse.jetty.websocket.api.Session Sessions}, which in turn
* is used to manage physical vs virtual connection handling (mux extension).
*/
package org.eclipse.jetty.websocket.client;
diff --git a/jetty-websocket/websocket-client/src/test/java/examples/SimpleEchoSocket.java b/jetty-websocket/websocket-client/src/test/java/examples/SimpleEchoSocket.java
index dca965e..801eddd 100644
--- a/jetty-websocket/websocket-client/src/test/java/examples/SimpleEchoSocket.java
+++ b/jetty-websocket/websocket-client/src/test/java/examples/SimpleEchoSocket.java
@@ -32,7 +32,7 @@
/**
* Basic Echo Client Socket
*/
-@WebSocket(maxMessageSize = 64 * 1024)
+@WebSocket(maxTextMessageSize = 64 * 1024)
public class SimpleEchoSocket
{
private final CountDownLatch closeLatch;
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/BadNetworkTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/BadNetworkTest.java
index bab9416..c7df302 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/BadNetworkTest.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/BadNetworkTest.java
@@ -74,7 +74,7 @@
@Test
public void testAbruptClientClose() throws Exception
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
@@ -103,7 +103,7 @@
@Test
public void testAbruptServerClose() throws Exception
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientConnectTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientConnectTest.java
index 92a5888..06fae5f 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientConnectTest.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientConnectTest.java
@@ -18,10 +18,14 @@
package org.eclipse.jetty.websocket.client;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
import java.io.IOException;
import java.net.ConnectException;
+import java.net.SocketTimeoutException;
import java.net.URI;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -29,6 +33,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.UpgradeException;
@@ -55,11 +60,15 @@
private WebSocketClient client;
@SuppressWarnings("unchecked")
- private <E extends Throwable> E assertExpectedError(ExecutionException e, TrackingSocket wsocket, Class<E> errorClass) throws IOException
+ private <E extends Throwable> E assertExpectedError(ExecutionException e, JettyTrackingSocket wsocket, Class<E> errorClass) throws IOException
{
// Validate thrown cause
Throwable cause = e.getCause();
- Assert.assertThat("ExecutionException.cause",cause,instanceOf(errorClass));
+ if(!errorClass.isInstance(cause))
+ {
+ cause.printStackTrace(System.err);
+ Assert.assertThat("ExecutionException.cause",cause,instanceOf(errorClass));
+ }
// Validate websocket captured cause
Assert.assertThat("Error Queue Length",wsocket.errorQueue.size(),greaterThanOrEqualTo(1));
@@ -104,7 +113,7 @@
@Test
public void testBadHandshake() throws Exception
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
@@ -133,7 +142,7 @@
@Test
public void testBadHandshake_GetOK() throws Exception
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
@@ -162,7 +171,7 @@
@Test
public void testBadHandshake_GetOK_WithSecWebSocketAccept() throws Exception
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
@@ -198,7 +207,7 @@
@Test
public void testBadHandshake_SwitchingProtocols_InvalidConnectionHeader() throws Exception
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
@@ -234,7 +243,7 @@
@Test
public void testBadHandshake_SwitchingProtocols_NoConnectionHeader() throws Exception
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
@@ -270,7 +279,7 @@
@Test
public void testBadUpgrade() throws Exception
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
@@ -300,7 +309,7 @@
@Ignore("Opened bug 399525")
public void testConnectionNotAccepted() throws Exception
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
@@ -330,7 +339,7 @@
@Test
public void testConnectionRefused() throws Exception
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
// Intentionally bad port with nothing listening on it
URI wsUri = new URI("ws://127.0.0.1:1");
@@ -351,15 +360,22 @@
}
catch (ExecutionException e)
{
- // Expected path - java.net.ConnectException
- assertExpectedError(e,wsocket,ConnectException.class);
+ if(OS.IS_WINDOWS)
+ {
+ // On windows, this is a SocketTimeoutException
+ assertExpectedError(e, wsocket, SocketTimeoutException.class);
+ } else
+ {
+ // Expected path - java.net.ConnectException
+ assertExpectedError(e,wsocket,ConnectException.class);
+ }
}
}
@Test(expected = TimeoutException.class)
public void testConnectionTimeout_Concurrent() throws Exception
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/JettyTrackingSocket.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/JettyTrackingSocket.java
new file mode 100644
index 0000000..edb8951
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/JettyTrackingSocket.java
@@ -0,0 +1,172 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Exchanger;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.junit.Assert;
+
+/**
+ * Testing Socket used on client side WebSocket testing.
+ */
+public class JettyTrackingSocket extends WebSocketAdapter
+{
+ private static final Logger LOG = Log.getLogger(JettyTrackingSocket.class);
+
+ public int closeCode = -1;
+ public Exchanger<String> messageExchanger;
+ public StringBuilder closeMessage = new StringBuilder();
+ public CountDownLatch openLatch = new CountDownLatch(1);
+ public CountDownLatch closeLatch = new CountDownLatch(1);
+ public CountDownLatch dataLatch = new CountDownLatch(1);
+ public EventQueue<String> messageQueue = new EventQueue<>();
+ public EventQueue<Throwable> errorQueue = new EventQueue<>();
+
+ public void assertClose(int expectedStatusCode, String expectedReason) throws InterruptedException
+ {
+ assertCloseCode(expectedStatusCode);
+ assertCloseReason(expectedReason);
+ }
+
+ public void assertCloseCode(int expectedCode) throws InterruptedException
+ {
+ Assert.assertThat("Was Closed",closeLatch.await(50,TimeUnit.MILLISECONDS),is(true));
+ Assert.assertThat("Close Code / Received [" + closeMessage + "]",closeCode,is(expectedCode));
+ }
+
+ private void assertCloseReason(String expectedReason)
+ {
+ Assert.assertThat("Close Reason",closeMessage.toString(),is(expectedReason));
+ }
+
+ public void assertIsOpen() throws InterruptedException
+ {
+ assertWasOpened();
+ assertNotClosed();
+ }
+
+ public void assertMessage(String expected)
+ {
+ String actual = messageQueue.poll();
+ Assert.assertEquals("Message",expected,actual);
+ }
+
+ public void assertNotClosed()
+ {
+ Assert.assertThat("Closed Latch",closeLatch.getCount(),greaterThanOrEqualTo(1L));
+ }
+
+ public void assertNotOpened()
+ {
+ Assert.assertThat("Open Latch",openLatch.getCount(),greaterThanOrEqualTo(1L));
+ }
+
+ public void assertWasOpened() throws InterruptedException
+ {
+ Assert.assertThat("Was Opened",openLatch.await(500,TimeUnit.MILLISECONDS),is(true));
+ }
+
+ public void awaitMessage(int expectedMessageCount, TimeUnit timeoutUnit, int timeoutDuration) throws TimeoutException, InterruptedException
+ {
+ messageQueue.awaitEventCount(expectedMessageCount,timeoutDuration,timeoutUnit);
+ }
+
+ public void clear()
+ {
+ messageQueue.clear();
+ }
+
+ @Override
+ public void onWebSocketBinary(byte[] payload, int offset, int len)
+ {
+ LOG.debug("onWebSocketBinary()");
+ dataLatch.countDown();
+ }
+
+ @Override
+ public void onWebSocketClose(int statusCode, String reason)
+ {
+ LOG.debug("onWebSocketClose({},{})",statusCode,reason);
+ super.onWebSocketClose(statusCode,reason);
+ closeCode = statusCode;
+ closeMessage.append(reason);
+ closeLatch.countDown();
+ }
+
+ @Override
+ public void onWebSocketConnect(Session session)
+ {
+ super.onWebSocketConnect(session);
+ openLatch.countDown();
+ }
+
+ @Override
+ public void onWebSocketError(Throwable cause)
+ {
+ LOG.debug("onWebSocketError",cause);
+ Assert.assertThat("Error capture",errorQueue.offer(cause),is(true));
+ }
+
+ @Override
+ public void onWebSocketText(String message)
+ {
+ LOG.debug("onWebSocketText({})",message);
+ messageQueue.offer(message);
+ dataLatch.countDown();
+
+ if (messageExchanger != null)
+ {
+ try
+ {
+ messageExchanger.exchange(message);
+ }
+ catch (InterruptedException e)
+ {
+ LOG.debug(e);
+ }
+ }
+ }
+
+ public void waitForClose(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+ {
+ Assert.assertThat("Client Socket Closed",closeLatch.await(timeoutDuration,timeoutUnit),is(true));
+ }
+
+ public void waitForConnected(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+ {
+ Assert.assertThat("Client Socket Connected",openLatch.await(timeoutDuration,timeoutUnit),is(true));
+ }
+
+ public void waitForMessage(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+ {
+ LOG.debug("Waiting for message");
+ Assert.assertThat("Message Received",dataLatch.await(timeoutDuration,timeoutUnit),is(true));
+ }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/MaxMessageSocket.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/MaxMessageSocket.java
index 1a1f7d0..da8b1a7 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/MaxMessageSocket.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/MaxMessageSocket.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.client;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -34,7 +34,7 @@
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.junit.Assert;
-@WebSocket(maxMessageSize = 100*1024)
+@WebSocket(maxTextMessageSize = 100*1024)
public class MaxMessageSocket
{
private static final Logger LOG = Log.getLogger(MaxMessageSocket.class);
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerReadThread.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerReadThread.java
index 5d4e173..4c53180 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerReadThread.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerReadThread.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.client;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.io.IOException;
import java.nio.ByteBuffer;
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerWriteThread.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerWriteThread.java
index d3a705c..2c297a5 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerWriteThread.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerWriteThread.java
@@ -26,7 +26,7 @@
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.client.blockhead.BlockheadServer.ServerConnection;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
public class ServerWriteThread extends Thread
{
@@ -71,7 +71,7 @@
{
while (m.get() < messageCount)
{
- conn.write(WebSocketFrame.text(message));
+ conn.write(new TextFrame().setPayload(message));
if (exchanger != null)
{
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowClientTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowClientTest.java
index 49ca8c1..0bbace1 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowClientTest.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowClientTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.client;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.net.URI;
import java.util.concurrent.Future;
@@ -75,7 +75,7 @@
@Slow
public void testClientSlowToSend() throws Exception
{
- TrackingSocket tsocket = new TrackingSocket();
+ JettyTrackingSocket tsocket = new JettyTrackingSocket();
client.getPolicy().setIdleTimeout(60000);
URI wsUri = server.getWsUri();
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowServerTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowServerTest.java
index 741d8fa..7521603 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowServerTest.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowServerTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.client;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.net.URI;
import java.util.concurrent.Future;
@@ -76,7 +76,7 @@
@Slow
public void testServerSlowToRead() throws Exception
{
- TrackingSocket tsocket = new TrackingSocket();
+ JettyTrackingSocket tsocket = new JettyTrackingSocket();
client.setMasker(new ZeroMasker());
client.getPolicy().setIdleTimeout(60000);
@@ -125,7 +125,7 @@
public void testServerSlowToSend() throws Exception
{
// final Exchanger<String> exchanger = new Exchanger<String>();
- TrackingSocket tsocket = new TrackingSocket();
+ JettyTrackingSocket tsocket = new JettyTrackingSocket();
// tsocket.messageExchanger = exchanger;
client.setMasker(new ZeroMasker());
client.getPolicy().setIdleTimeout(60000);
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java
index ecfd50c..fcb535f 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.client;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
import java.net.URI;
import java.util.concurrent.Future;
@@ -80,7 +80,7 @@
@Test
public void testIdleDetectedByClient() throws Exception
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
client.setMaxIdleTimeout(1000);
@@ -98,14 +98,14 @@
// Wait for inactivity idle timeout.
long start = System.currentTimeMillis();
- wsocket.waitForClose(10,TimeUnit.SECONDS);
+ wsocket.waitForClose(2,TimeUnit.SECONDS);
long end = System.currentTimeMillis();
long dur = (end - start);
// Make sure idle timeout takes less than 5 total seconds
- Assert.assertThat("Idle Timeout",dur,lessThanOrEqualTo(5000L));
+ Assert.assertThat("Idle Timeout",dur,lessThanOrEqualTo(3000L));
- // Client should see a close event, with status SHUTDOWN
- wsocket.assertCloseCode(StatusCode.SHUTDOWN);
+ // Client should see a close event, with abnormal status NO_CLOSE
+ wsocket.assertCloseCode(StatusCode.ABNORMAL);
}
finally
{
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java
deleted file mode 100644
index eb1c4ae..0000000
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java
+++ /dev/null
@@ -1,171 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.client;
-
-import static org.hamcrest.Matchers.*;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Exchanger;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.eclipse.jetty.toolchain.test.EventQueue;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.Session;
-import org.eclipse.jetty.websocket.api.WebSocketAdapter;
-import org.junit.Assert;
-
-/**
- * Testing Socket used on client side WebSocket testing.
- */
-public class TrackingSocket extends WebSocketAdapter
-{
- private static final Logger LOG = Log.getLogger(TrackingSocket.class);
-
- public int closeCode = -1;
- public Exchanger<String> messageExchanger;
- public StringBuilder closeMessage = new StringBuilder();
- public CountDownLatch openLatch = new CountDownLatch(1);
- public CountDownLatch closeLatch = new CountDownLatch(1);
- public CountDownLatch dataLatch = new CountDownLatch(1);
- public EventQueue<String> messageQueue = new EventQueue<>();
- public EventQueue<Throwable> errorQueue = new EventQueue<>();
-
- public void assertClose(int expectedStatusCode, String expectedReason) throws InterruptedException
- {
- assertCloseCode(expectedStatusCode);
- assertCloseReason(expectedReason);
- }
-
- public void assertCloseCode(int expectedCode) throws InterruptedException
- {
- Assert.assertThat("Was Closed",closeLatch.await(50,TimeUnit.MILLISECONDS),is(true));
- Assert.assertThat("Close Code",closeCode,is(expectedCode));
- }
-
- private void assertCloseReason(String expectedReason)
- {
- Assert.assertThat("Close Reason",closeMessage.toString(),is(expectedReason));
- }
-
- public void assertIsOpen() throws InterruptedException
- {
- assertWasOpened();
- assertNotClosed();
- }
-
- public void assertMessage(String expected)
- {
- String actual = messageQueue.poll();
- Assert.assertEquals("Message",expected,actual);
- }
-
- public void assertNotClosed()
- {
- Assert.assertThat("Closed Latch",closeLatch.getCount(),greaterThanOrEqualTo(1L));
- }
-
- public void assertNotOpened()
- {
- Assert.assertThat("Open Latch",openLatch.getCount(),greaterThanOrEqualTo(1L));
- }
-
- public void assertWasOpened() throws InterruptedException
- {
- Assert.assertThat("Was Opened",openLatch.await(500,TimeUnit.MILLISECONDS),is(true));
- }
-
- public void awaitMessage(int expectedMessageCount, TimeUnit timeoutUnit, int timeoutDuration) throws TimeoutException, InterruptedException
- {
- messageQueue.awaitEventCount(expectedMessageCount,timeoutDuration,timeoutUnit);
- }
-
- public void clear()
- {
- messageQueue.clear();
- }
-
- @Override
- public void onWebSocketBinary(byte[] payload, int offset, int len)
- {
- LOG.debug("onWebSocketBinary()");
- dataLatch.countDown();
- }
-
- @Override
- public void onWebSocketClose(int statusCode, String reason)
- {
- LOG.debug("onWebSocketClose({},{})",statusCode,reason);
- super.onWebSocketClose(statusCode,reason);
- closeCode = statusCode;
- closeMessage.append(reason);
- closeLatch.countDown();
- }
-
- @Override
- public void onWebSocketConnect(Session session)
- {
- super.onWebSocketConnect(session);
- openLatch.countDown();
- }
-
- @Override
- public void onWebSocketError(Throwable cause)
- {
- LOG.debug("onWebSocketError",cause);
- Assert.assertThat("Error capture",errorQueue.offer(cause),is(true));
- }
-
- @Override
- public void onWebSocketText(String message)
- {
- LOG.debug("onWebSocketText({})",message);
- messageQueue.offer(message);
- dataLatch.countDown();
-
- if (messageExchanger != null)
- {
- try
- {
- messageExchanger.exchange(message);
- }
- catch (InterruptedException e)
- {
- LOG.debug(e);
- }
- }
- }
-
- public void waitForClose(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
- {
- Assert.assertThat("Client Socket Closed",closeLatch.await(timeoutDuration,timeoutUnit),is(true));
- }
-
- public void waitForConnected(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
- {
- Assert.assertThat("Client Socket Connected",openLatch.await(timeoutDuration,timeoutUnit),is(true));
- }
-
- public void waitForMessage(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
- {
- LOG.debug("Waiting for message");
- Assert.assertThat("Message Received",dataLatch.await(timeoutDuration,timeoutUnit),is(true));
- }
-}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientBadUriTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientBadUriTest.java
index a25b6dd..7258c2a 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientBadUriTest.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientBadUriTest.java
@@ -85,7 +85,7 @@
@Test
public void testBadURI() throws Exception
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
try
{
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java
index f130450..6646f28 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java
@@ -18,11 +18,15 @@
package org.eclipse.jetty.websocket.client;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@@ -33,7 +37,8 @@
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.client.blockhead.BlockheadServer;
import org.eclipse.jetty.websocket.client.blockhead.BlockheadServer.ServerConnection;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -65,7 +70,7 @@
client.start();
try
{
- TrackingSocket cliSock = new TrackingSocket();
+ JettyTrackingSocket cliSock = new JettyTrackingSocket();
client.getPolicy().setIdleTimeout(10000);
@@ -90,7 +95,7 @@
client.start();
try
{
- TrackingSocket cliSock = new TrackingSocket();
+ JettyTrackingSocket cliSock = new JettyTrackingSocket();
client.getPolicy().setIdleTimeout(10000);
@@ -127,13 +132,54 @@
}
@Test
+ public void testBasicEcho_UsingCallback() throws Exception
+ {
+ WebSocketClient client = new WebSocketClient();
+ client.start();
+ try
+ {
+ JettyTrackingSocket cliSock = new JettyTrackingSocket();
+
+ client.getPolicy().setIdleTimeout(10000);
+
+ URI wsUri = server.getWsUri();
+ ClientUpgradeRequest request = new ClientUpgradeRequest();
+ request.setSubProtocols("echo");
+ Future<Session> future = client.connect(cliSock,wsUri,request);
+
+ final ServerConnection srvSock = server.accept();
+ srvSock.upgrade();
+
+ Session sess = future.get(500,TimeUnit.MILLISECONDS);
+ Assert.assertThat("Session",sess,notNullValue());
+ Assert.assertThat("Session.open",sess.isOpen(),is(true));
+ Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
+ Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
+
+ cliSock.assertWasOpened();
+ cliSock.assertNotClosed();
+
+ Assert.assertThat("client.connectionManager.sessions.size",client.getConnectionManager().getSessions().size(),is(1));
+
+ FutureWriteCallback callback = new FutureWriteCallback();
+
+ cliSock.getSession().getRemote().sendString("Hello World!",callback);
+ callback.get(1,TimeUnit.SECONDS);
+ }
+ finally
+ {
+ client.stop();
+ }
+ }
+
+ @Test
public void testBasicEcho_FromServer() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.start();
try
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
Future<Session> future = client.connect(wsocket,server.getWsUri());
// Server
@@ -148,7 +194,7 @@
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
// Have server send initial message
- srvSock.write(WebSocketFrame.text("Hello World"));
+ srvSock.write(new TextFrame().setPayload("Hello World"));
// Verify connect
future.get(500,TimeUnit.MILLISECONDS);
@@ -170,7 +216,7 @@
fact.start();
try
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = fact.connect(wsocket,wsUri);
@@ -210,7 +256,7 @@
{
int bufferSize = 512;
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = factSmall.connect(wsocket,wsUri);
@@ -261,10 +307,10 @@
Session sess = future.get(500,TimeUnit.MILLISECONDS);
Assert.assertThat("Session",sess,notNullValue());
Assert.assertThat("Session.open",sess.isOpen(),is(true));
-
+
// Create string that is larger than default size of 64k
// but smaller than maxMessageSize of 100k
- byte buf[] = new byte[80*1024];
+ byte buf[] = new byte[80 * 1024];
Arrays.fill(buf,(byte)'x');
String msg = StringUtil.toUTF8String(buf,0,buf.length);
@@ -290,7 +336,7 @@
fact.start();
try
{
- TrackingSocket wsocket = new TrackingSocket();
+ JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri().resolve("/test?snack=cashews&amount=handful&brand=off");
Future<Session> future = fact.connect(wsocket,wsUri);
@@ -306,15 +352,15 @@
UpgradeRequest req = session.getUpgradeRequest();
Assert.assertThat("Upgrade Request",req,notNullValue());
- Map<String, String[]> parameterMap = req.getParameterMap();
+ Map<String, List<String>> parameterMap = req.getParameterMap();
Assert.assertThat("Parameter Map",parameterMap,notNullValue());
- Assert.assertThat("Parameter[snack]",parameterMap.get("snack"),is(new String[]
- { "cashews" }));
- Assert.assertThat("Parameter[amount]",parameterMap.get("amount"),is(new String[]
- { "handful" }));
- Assert.assertThat("Parameter[brand]",parameterMap.get("brand"),is(new String[]
- { "off" }));
+ Assert.assertThat("Parameter[snack]",parameterMap.get("snack"),is(Arrays.asList(new String[]
+ { "cashews" })));
+ Assert.assertThat("Parameter[amount]",parameterMap.get("amount"),is(Arrays.asList(new String[]
+ { "handful" })));
+ Assert.assertThat("Parameter[brand]",parameterMap.get("brand"),is(Arrays.asList(new String[]
+ { "off" })));
Assert.assertThat("Parameter[cost]",parameterMap.get("cost"),nullValue());
}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java
index 1cf8c94..f496a82 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java
@@ -18,7 +18,8 @@
package org.eclipse.jetty.websocket.client.blockhead;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
import java.io.BufferedReader;
import java.io.IOException;
@@ -49,12 +50,10 @@
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.api.extensions.Frame.Type;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
import org.eclipse.jetty.websocket.common.AcceptHash;
@@ -65,6 +64,7 @@
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
import org.junit.Assert;
/**
@@ -101,7 +101,8 @@
this.socket = socket;
this.incomingFrames = new IncomingFramesCapture();
this.policy = WebSocketPolicy.newServerPolicy();
- this.policy.setMaxMessageSize(100000);
+ this.policy.setMaxBinaryMessageSize(100000);
+ this.policy.setMaxTextMessageSize(100000);
this.bufferPool = new MappedByteBufferPool(BUFFER_SIZE);
this.parser = new Parser(policy,bufferPool);
this.parseCount = new AtomicInteger(0);
@@ -119,7 +120,7 @@
public void close() throws IOException
{
- write(new WebSocketFrame(OpCode.CLOSE));
+ write(new CloseFrame());
flush();
disconnect();
}
@@ -205,7 +206,7 @@
}
@Override
- public void incomingError(WebSocketException e)
+ public void incomingError(Throwable e)
{
incomingFrames.incomingError(e);
}
@@ -219,10 +220,9 @@
{
LOG.info("Server parsed {} frames",count);
}
- WebSocketFrame copy = new WebSocketFrame(frame);
- incomingFrames.incomingFrame(copy);
+ incomingFrames.incomingFrame(WebSocketFrame.copy(frame));
- if (frame.getType() == Type.CLOSE)
+ if (frame.getOpCode() == OpCode.CLOSE)
{
CloseInfo close = new CloseInfo(frame);
LOG.debug("Close frame: {}",close);
@@ -232,22 +232,23 @@
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
{
- ByteBuffer buf = generator.generate(frame);
+ ByteBuffer headerBuf = generator.generateHeaderBytes(frame);
if (LOG.isDebugEnabled())
{
- LOG.debug("writing out: {}",BufferUtil.toDetailString(buf));
+ LOG.debug("writing out: {}",BufferUtil.toDetailString(headerBuf));
}
try
{
- BufferUtil.writeTo(buf,out);
+ BufferUtil.writeTo(headerBuf,out);
+ BufferUtil.writeTo(generator.getPayloadWindow(frame.getPayloadLength(),frame),out);
out.flush();
if (callback != null)
{
callback.writeSuccess();
}
- if (frame.getType().getOpCode() == OpCode.CLOSE)
+ if (frame.getOpCode() == OpCode.CLOSE)
{
disconnect();
}
@@ -512,7 +513,7 @@
resp.append("Connection: upgrade\r\n");
resp.append("Sec-WebSocket-Accept: ");
resp.append(AcceptHash.hashKey(key)).append("\r\n");
- if (!extensionStack.hasNegotiatedExtensions())
+ if (extensionStack.hasNegotiatedExtensions())
{
// Respond to used extensions
resp.append("Sec-WebSocket-Extensions: ");
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/IncomingFramesCapture.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/IncomingFramesCapture.java
index 5c0312c..a033085 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/IncomingFramesCapture.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/IncomingFramesCapture.java
@@ -18,7 +18,8 @@
package org.eclipse.jetty.websocket.client.blockhead;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
import java.util.LinkedList;
@@ -36,7 +37,7 @@
{
private static final Logger LOG = Log.getLogger(IncomingFramesCapture.class);
private LinkedList<WebSocketFrame> frames = new LinkedList<>();
- private LinkedList<WebSocketException> errors = new LinkedList<>();
+ private LinkedList<Throwable> errors = new LinkedList<>();
public void assertErrorCount(int expectedCount)
{
@@ -87,7 +88,7 @@
public int getErrorCount(Class<? extends WebSocketException> errorType)
{
int count = 0;
- for (WebSocketException error : errors)
+ for (Throwable error : errors)
{
if (errorType.isInstance(error))
{
@@ -97,7 +98,7 @@
return count;
}
- public LinkedList<WebSocketException> getErrors()
+ public LinkedList<Throwable> getErrors()
{
return errors;
}
@@ -121,7 +122,7 @@
}
@Override
- public void incomingError(WebSocketException e)
+ public void incomingError(Throwable e)
{
LOG.debug(e);
errors.add(e);
@@ -130,7 +131,7 @@
@Override
public void incomingFrame(Frame frame)
{
- WebSocketFrame copy = new WebSocketFrame(frame);
+ WebSocketFrame copy = WebSocketFrame.copy(frame);
Assert.assertThat("frame.masking must be set",frame.isMasked(),is(true));
frames.add(copy);
}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/examples/TestClient.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/examples/TestClient.java
index 2ab4898..58806a8 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/examples/TestClient.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/examples/TestClient.java
@@ -29,7 +29,6 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/ConnectionManagerTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/ConnectionManagerTest.java
index 481bf9d..042dae7 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/ConnectionManagerTest.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/ConnectionManagerTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.client.internal;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.net.InetSocketAddress;
import java.net.URI;
diff --git a/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties
index f5e58f4..7c9bd36 100644
--- a/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties
+++ b/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties
@@ -1,10 +1,15 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.LEVEL=WARN
+# org.eclipse.jetty.LEVEL=DEBUG
# org.eclipse.jetty.io.ChannelEndPoint.LEVEL=INFO
# org.eclipse.jetty.websocket.LEVEL=WARN
# org.eclipse.jetty.websocket.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.client.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.common.io.WriteBytesProvider.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.common.Generator.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG
# org.eclipse.jetty.websocket.client.TrackingSocket.LEVEL=DEBUG
-# Hide the stacktraces during testing
+
+### Hide the stacktraces during testing
org.eclipse.jetty.websocket.client.internal.io.UpgradeConnection.STACKS=false
-# org.eclipse.jetty.io.SelectorManager.LEVEL=INFO
-# org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection$DataFrameBytes.LEVEL=WARN
diff --git a/jetty-websocket/websocket-common/pom.xml b/jetty-websocket/websocket-common/pom.xml
index 5dfcb01..1bcb667 100644
--- a/jetty-websocket/websocket-common/pom.xml
+++ b/jetty-websocket/websocket-common/pom.xml
@@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/AcceptHash.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/AcceptHash.java
index 83027c0..75130c7 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/AcceptHash.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/AcceptHash.java
@@ -18,12 +18,10 @@
package org.eclipse.jetty.websocket.common;
-import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.StringUtil;
/**
* Logic for working with the <code>Sec-WebSocket-Key</code> and <code>Sec-WebSocket-Accept</code> headers.
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/BlockingWriteCallback.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/BlockingWriteCallback.java
new file mode 100644
index 0000000..b16d350
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/BlockingWriteCallback.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import org.eclipse.jetty.util.BlockingCallback;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+
+public class BlockingWriteCallback extends BlockingCallback implements WriteCallback
+{
+ @Override
+ public void writeFailed(Throwable x)
+ {
+ failed(x);
+ }
+
+ @Override
+ public void writeSuccess()
+ {
+ succeeded();
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/CloseInfo.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/CloseInfo.java
index a56308e..49e85a9 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/CloseInfo.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/CloseInfo.java
@@ -29,6 +29,7 @@
import org.eclipse.jetty.websocket.api.ProtocolException;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
public class CloseInfo
{
@@ -128,7 +129,7 @@
this.reason = reason;
}
- private byte[] asByteBuffer()
+ private ByteBuffer asByteBuffer()
{
if ((statusCode == StatusCode.NO_CLOSE) || (statusCode == StatusCode.NO_CODE) || (statusCode == (-1)))
{
@@ -143,22 +144,23 @@
utf = StringUtil.getUtf8Bytes(reason);
len += utf.length;
}
-
- byte buf[] = new byte[len];
- buf[0] = (byte)((statusCode >>> 8) & 0xFF);
- buf[1] = (byte)((statusCode >>> 0) & 0xFF);
+
+ ByteBuffer buf = ByteBuffer.allocate(len);
+ buf.put((byte)((statusCode >>> 8) & 0xFF));
+ buf.put((byte)((statusCode >>> 0) & 0xFF));
if (utf != null)
{
- System.arraycopy(utf,0,buf,2,utf.length);
+ buf.put(utf,0,utf.length);
}
+ buf.flip();
return buf;
}
- public WebSocketFrame asFrame()
+ public CloseFrame asFrame()
{
- WebSocketFrame frame = new WebSocketFrame(OpCode.CLOSE);
+ CloseFrame frame = new CloseFrame();
frame.setFin(true);
frame.setPayload(asByteBuffer());
return frame;
@@ -178,6 +180,11 @@
{
return !((statusCode == StatusCode.NORMAL) || (statusCode == StatusCode.NO_CODE));
}
+
+ public boolean isAbnormal()
+ {
+ return (statusCode == StatusCode.ABNORMAL);
+ }
@Override
public String toString()
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java
index aefc719..4aee18c 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java
@@ -19,6 +19,8 @@
package org.eclipse.jetty.websocket.common;
import org.eclipse.jetty.websocket.common.io.IOState;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
+
/**
* Connection states as outlined in <a href="https://tools.ietf.org/html/rfc6455">RFC6455</a>.
@@ -47,7 +49,7 @@
* <p>
* This can be considered a half-closed state.
* <p>
- * When receiving this as an event on {@link IOState.ConnectionStateListener#onConnectionStateChange(ConnectionState)} a close frame should be sent using
+ * When receiving this as an event on {@link ConnectionStateListener#onConnectionStateChange(ConnectionState)} a close frame should be sent using
* the {@link CloseInfo} available from {@link IOState#getCloseInfo()}
*/
CLOSING,
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Generator.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Generator.java
index f16e7de..b431dbb 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Generator.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Generator.java
@@ -23,8 +23,6 @@
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.ProtocolException;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
@@ -57,7 +55,6 @@
*/
public class Generator
{
- private static final Logger LOG = Log.getLogger(Generator.class);
/**
* The overhead (maximum) for a framing header. Assuming a maximum sized payload with masking key.
*/
@@ -65,7 +62,7 @@
private final WebSocketBehavior behavior;
private final ByteBufferPool bufferPool;
- private boolean validating;
+ private final boolean validating;
/** Is there an extension using RSV1 */
private boolean rsv1InUse = false;
@@ -132,7 +129,7 @@
throw new ProtocolException("RSV3 not allowed to be set");
}
- if (frame.getType().isControl())
+ if (OpCode.isControlFrame(frame.getOpCode()))
{
/*
* RFC 6455 Section 5.5
@@ -154,7 +151,7 @@
*
* close frame payload is specially formatted which is checked in CloseInfo
*/
- if (frame.getType().getOpCode() == OpCode.CLOSE)
+ if (frame.getOpCode() == OpCode.CLOSE)
{
ByteBuffer payload = frame.getPayload();
@@ -175,7 +172,7 @@
this.rsv3InUse = false;
// configure from list of extensions in use
- for(Extension ext: exts)
+ for (Extension ext : exts)
{
if (ext.isRsv1User())
{
@@ -192,193 +189,183 @@
}
}
- /**
- * generate a byte buffer based on the frame being passed in
- *
- * bufferSize is determined by the length of the payload + 28 for frame overhead
- *
- * @param frame
- * @return
- */
- public synchronized ByteBuffer generate(Frame frame)
+ public ByteBuffer generateHeaderBytes(Frame frame)
{
- int bufferSize = frame.getPayloadLength() + OVERHEAD;
- return generate(bufferSize,frame);
- }
+ // we need a framing header
+ assertFrameValid(frame);
- /**
- * Generate, into a ByteBuffer, no more than bufferSize of contents from the frame. If the frame exceeds the bufferSize, then multiple calls to
- * {@link #generate(int, WebSocketFrame)} are required to obtain each window of ByteBuffer to complete the frame.
- */
- public synchronized ByteBuffer generate(int windowSize, Frame frame)
- {
- if (windowSize < OVERHEAD)
- {
- throw new IllegalArgumentException("Cannot have windowSize less than " + OVERHEAD);
- }
-
- LOG.debug("{} Generate: {} (windowSize {})",behavior,frame,windowSize);
+ ByteBuffer buffer = bufferPool.acquire(OVERHEAD,true);
+ BufferUtil.clearToFill(buffer);
/*
- * prepare the byte buffer to put frame into
+ * start the generation process
*/
- ByteBuffer buffer = bufferPool.acquire(windowSize,false);
- BufferUtil.clearToFill(buffer);
- if (LOG.isDebugEnabled())
+ byte b;
+
+ // Setup fin thru opcode
+ b = 0x00;
+ if (frame.isFin())
{
- LOG.debug("Acquired Buffer (windowSize={}): {}",windowSize,BufferUtil.toDetailString(buffer));
+ b |= 0x80; // 1000_0000
}
- // since the buffer from the pool can exceed the window size, artificially
- // limit the buffer to the window size.
- int newlimit = Math.min(buffer.position() + windowSize,buffer.limit());
- buffer.limit(newlimit);
- LOG.debug("Buffer limited: {}",buffer);
-
- if (frame.remaining() == frame.getPayloadLength())
+ if (frame.isRsv1())
{
- // we need a framing header
- assertFrameValid(frame);
+ b |= 0x40; // 0100_0000
+ }
+ if (frame.isRsv2())
+ {
+ b |= 0x20; // 0010_0000
+ }
+ if (frame.isRsv3())
+ {
+ b |= 0x10; // 0001_0000
+ }
- /*
- * start the generation process
- */
- byte b;
+ // NOTE: using .getOpCode() here, not .getType().getOpCode() for testing reasons
+ byte opcode = frame.getOpCode();
- // Setup fin thru opcode
- b = 0x00;
- if (frame.isFin())
- {
- b |= 0x80; // 1000_0000
- }
- if (frame.isRsv1())
- {
- b |= 0x40; // 0100_0000
- }
- if (frame.isRsv2())
- {
- b |= 0x20; // 0010_0000
- }
- if (frame.isRsv3())
- {
- b |= 0x10;
- }
+ if (frame.getOpCode() == OpCode.CONTINUATION)
+ {
+ // Continuations are not the same OPCODE
+ opcode = OpCode.CONTINUATION;
+ }
- // NOTE: using .getOpCode() here, not .getType().getOpCode() for testing reasons
- byte opcode = frame.getOpCode();
+ b |= opcode & 0x0F;
- if (frame.isContinuation())
- {
- // Continuations are not the same OPCODE
- opcode = OpCode.CONTINUATION;
- }
+ buffer.put(b);
- b |= opcode & 0x0F;
+ // is masked
+ b = 0x00;
+ b |= (frame.isMasked()?0x80:0x00);
+ // payload lengths
+ int payloadLength = frame.getPayloadLength();
+
+ /*
+ * if length is over 65535 then its a 7 + 64 bit length
+ */
+ if (payloadLength > 0xFF_FF)
+ {
+ // we have a 64 bit length
+ b |= 0x7F;
+ buffer.put(b); // indicate 8 byte length
+ buffer.put((byte)0); //
+ buffer.put((byte)0); // anything over an
+ buffer.put((byte)0); // int is just
+ buffer.put((byte)0); // intsane!
+ buffer.put((byte)((payloadLength >> 24) & 0xFF));
+ buffer.put((byte)((payloadLength >> 16) & 0xFF));
+ buffer.put((byte)((payloadLength >> 8) & 0xFF));
+ buffer.put((byte)(payloadLength & 0xFF));
+ }
+ /*
+ * if payload is greater 126 we have a 7 + 16 bit length
+ */
+ else if (payloadLength >= 0x7E)
+ {
+ b |= 0x7E;
+ buffer.put(b); // indicate 2 byte length
+ buffer.put((byte)(payloadLength >> 8));
+ buffer.put((byte)(payloadLength & 0xFF));
+ }
+ /*
+ * we have a 7 bit length
+ */
+ else
+ {
+ b |= (payloadLength & 0x7F);
buffer.put(b);
-
- // is masked
- b = 0x00;
- b |= (frame.isMasked()?0x80:0x00);
-
- // payload lengths
- int payloadLength = frame.getPayloadLength();
-
- /*
- * if length is over 65535 then its a 7 + 64 bit length
- */
- if (payloadLength > 0xFF_FF)
- {
- // we have a 64 bit length
- b |= 0x7F;
- buffer.put(b); // indicate 8 byte length
- buffer.put((byte)0); //
- buffer.put((byte)0); // anything over an
- buffer.put((byte)0); // int is just
- buffer.put((byte)0); // intsane!
- buffer.put((byte)((payloadLength >> 24) & 0xFF));
- buffer.put((byte)((payloadLength >> 16) & 0xFF));
- buffer.put((byte)((payloadLength >> 8) & 0xFF));
- buffer.put((byte)(payloadLength & 0xFF));
- }
- /*
- * if payload is ge 126 we have a 7 + 16 bit length
- */
- else if (payloadLength >= 0x7E)
- {
- b |= 0x7E;
- buffer.put(b); // indicate 2 byte length
- buffer.put((byte)(payloadLength >> 8));
- buffer.put((byte)(payloadLength & 0xFF));
- }
- /*
- * we have a 7 bit length
- */
- else
- {
- b |= (payloadLength & 0x7F);
- buffer.put(b);
- }
-
- // masking key
- if (frame.isMasked())
- {
- buffer.put(frame.getMask());
- }
}
- // copy payload
- if (frame.hasPayload())
+ // masking key
+ if (frame.isMasked())
{
- // remember the position
- int maskingStartPosition = buffer.position();
+ byte[] mask = frame.getMask();
+ buffer.put(mask);
+ int maskInt = ByteBuffer.wrap(mask).getInt();
- // remember the offset within the frame payload (for working with
- // windowed frames that don't split on 4 byte barriers)
- int payloadOffset = frame.getPayload().position();
- int payloadStart = frame.getPayloadStart();
-
- // put as much as possible into the buffer
- BufferUtil.put(frame.getPayload(),buffer);
-
- // mask it if needed
- if (frame.isMasked())
+ // perform data masking here
+ ByteBuffer payload = frame.getPayload();
+ if ((payload != null) && (payload.remaining() > 0))
{
- // move back to remembered position.
- int size = buffer.position() - maskingStartPosition;
- byte[] mask = frame.getMask();
- byte b;
- int posBuf;
- int posFrame;
- for (int i = 0; i < size; i++)
+ int maskOffset = 0;
+ int start = payload.position();
+ int end = payload.limit();
+ int remaining;
+ while ((remaining = end - start) > 0)
{
- posBuf = i + maskingStartPosition;
- posFrame = i + (payloadOffset - payloadStart);
-
- // get raw byte from buffer.
- b = buffer.get(posBuf);
-
- // mask, using offset information from frame windowing.
- b ^= mask[posFrame % 4];
-
- // Mask each byte by its absolute position in the bytebuffer
- buffer.put(posBuf,b);
+ if (remaining >= 4)
+ {
+ payload.putInt(start, payload.getInt(start) ^ maskInt);
+ start += 4;
+ }
+ else
+ {
+ payload.put(start, (byte)(payload.get(start) ^ mask[maskOffset & 3]));
+ ++start;
+ ++maskOffset;
+ }
}
}
}
BufferUtil.flipToFlush(buffer,0);
- if (LOG.isDebugEnabled())
- {
- LOG.debug("Generated Buffer: {}",BufferUtil.toDetailString(buffer));
- }
return buffer;
}
+ /**
+ * Generate the whole frame (header + payload copy) into a single ByteBuffer.
+ * <p>
+ * Note: THIS IS SLOW. Only use this if you must.
+ *
+ * @param frame
+ * the frame to generate
+ */
+ public void generateWholeFrame(Frame frame, ByteBuffer buf)
+ {
+ buf.put(generateHeaderBytes(frame));
+ if (frame.hasPayload())
+ {
+ buf.put(getPayloadWindow(frame.getPayloadLength(),frame));
+ }
+ }
+
public ByteBufferPool getBufferPool()
{
return bufferPool;
}
+ public ByteBuffer getPayloadWindow(int windowSize, Frame frame)
+ {
+ if (!frame.hasPayload())
+ {
+ return BufferUtil.EMPTY_BUFFER;
+ }
+
+ ByteBuffer buffer;
+
+ // We will create a slice representing the windowSize of this payload
+ if (frame.getPayload().remaining() <= windowSize)
+ {
+ // remaining will fit within window
+ buffer = frame.getPayload().slice();
+ // adjust the frame payload position (mark as read)
+ frame.getPayload().position(frame.getPayload().limit());
+ }
+ else
+ {
+ // remaining is over the window size limit, slice it
+ buffer = frame.getPayload().slice();
+ buffer.limit(windowSize);
+ int offset = frame.getPayload().position(); // offset within frame payload
+ // adjust the frame payload position
+ int newpos = Math.min(offset + windowSize,frame.getPayload().limit());
+ frame.getPayload().position(newpos);
+ }
+
+ return buffer;
+ }
+
public boolean isRsv1InUse()
{
return rsv1InUse;
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/LogicalConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/LogicalConnection.java
index dd6431d..d825e28 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/LogicalConnection.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/LogicalConnection.java
@@ -19,7 +19,9 @@
package org.eclipse.jetty.websocket.common;
import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
@@ -58,6 +60,23 @@
void disconnect();
/**
+ * Get the ByteBufferPool in use by the connection
+ */
+ ByteBufferPool getBufferPool();
+
+ /**
+ * Get the Executor used by this connection.
+ */
+ Executor getExecutor();
+
+ /**
+ * Get the read/write idle timeout.
+ *
+ * @return the idle timeout in milliseconds
+ */
+ public long getIdleTimeout();
+
+ /**
* Get the IOState of the connection.
*
* @return the IOState of the connection.
@@ -117,6 +136,9 @@
/**
* Set the maximum number of milliseconds of idleness before the connection is closed/disconnected, (ie no frames are either sent or received)
+ * <p>
+ * This idle timeout cannot be garunteed to take immediate effect for any active read/write actions.
+ * New read/write actions will have this new idle timeout.
*
* @param ms
* the number of milliseconds of idle timeout
@@ -143,8 +165,6 @@
/**
* Suspend a the incoming read events on the connection.
- *
- * @return
*/
SuspendToken suspend();
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Parser.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Parser.java
index bca7e30..46c4f7c 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Parser.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Parser.java
@@ -33,11 +33,15 @@
import org.eclipse.jetty.websocket.api.extensions.Extension;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
-import org.eclipse.jetty.websocket.common.io.payload.CloseReasonValidator;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.ControlFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.common.io.payload.DeMaskProcessor;
-import org.eclipse.jetty.websocket.common.io.payload.NoOpValidator;
import org.eclipse.jetty.websocket.common.io.payload.PayloadProcessor;
-import org.eclipse.jetty.websocket.common.io.payload.UTF8Validator;
/**
* Parsing of a frames in WebSocket land.
@@ -70,7 +74,7 @@
private ByteBuffer payload;
private int payloadLength;
private PayloadProcessor maskProcessor = new DeMaskProcessor();
- private PayloadProcessor strictnessProcessor;
+ // private PayloadProcessor strictnessProcessor;
/** Is there an extension using RSV1 */
private boolean rsv1InUse = false;
@@ -78,8 +82,6 @@
private boolean rsv2InUse = false;
/** Is there an extension using RSV3 */
private boolean rsv3InUse = false;
- /** Is there an extension that processes invalid UTF8 text messages (such as compressed content) */
- private boolean isTextFrameValidated = true;
private IncomingFrames incomingFramesHandler;
@@ -91,14 +93,17 @@
private void assertSanePayloadLength(long len)
{
- LOG.debug("Payload Length: " + len);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("Payload Length: {} - {}",len,this);
+ }
+
// Since we use ByteBuffer so often, having lengths over Integer.MAX_VALUE is really impossible.
if (len > Integer.MAX_VALUE)
{
// OMG! Sanity Check! DO NOT WANT! Won't anyone think of the memory!
throw new MessageTooLargeException("[int-sane!] cannot handle payload lengths larger than " + Integer.MAX_VALUE);
}
- policy.assertValidMessageSize((int)len);
switch (frame.getOpCode())
{
@@ -110,12 +115,18 @@
// fall thru
case OpCode.PING:
case OpCode.PONG:
- if (len > WebSocketFrame.MAX_CONTROL_PAYLOAD)
+ if (len > ControlFrame.MAX_CONTROL_PAYLOAD)
{
throw new ProtocolException("Invalid control frame payload length, [" + payloadLength + "] cannot exceed ["
- + WebSocketFrame.MAX_CONTROL_PAYLOAD + "]");
+ + ControlFrame.MAX_CONTROL_PAYLOAD + "]");
}
break;
+ case OpCode.TEXT:
+ policy.assertValidTextMessageSize((int)len);
+ break;
+ case OpCode.BINARY:
+ policy.assertValidBinaryMessageSize((int)len);
+ break;
}
}
@@ -125,7 +136,6 @@
this.rsv1InUse = false;
this.rsv2InUse = false;
this.rsv3InUse = false;
- this.isTextFrameValidated = true;
// configure from list of extensions in use
for (Extension ext : exts)
@@ -142,10 +152,6 @@
{
this.rsv3InUse = true;
}
- if (ext.isTextDataDecoder())
- {
- this.isTextFrameValidated = false;
- }
}
}
@@ -334,63 +340,64 @@
throw new ProtocolException("RSV3 not allowed to be set");
}
- boolean isContinuation = false;
-
- switch (opcode)
- {
+ // base framing flags
+ switch(opcode) {
case OpCode.TEXT:
- if (isTextFrameValidated)
+ frame = new TextFrame();
+ // data validation
+ if ((priorDataFrame != null) && (!priorDataFrame.isFin()))
{
- strictnessProcessor = new UTF8Validator();
+ throw new ProtocolException("Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION");
}
- else
+ break;
+ case OpCode.BINARY:
+ frame = new BinaryFrame();
+ // data validation
+ if ((priorDataFrame != null) && (!priorDataFrame.isFin()))
{
- strictnessProcessor = NoOpValidator.INSTANCE;
+ throw new ProtocolException("Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION");
}
break;
+ case OpCode.CONTINUATION:
+ frame = new ContinuationFrame();
+ // continuation validation
+ if (priorDataFrame == null)
+ {
+ throw new ProtocolException("CONTINUATION frame without prior !FIN");
+ }
+ // Be careful to use the original opcode
+ opcode = lastDataOpcode;
+ break;
case OpCode.CLOSE:
- strictnessProcessor = new CloseReasonValidator();
+ frame = new CloseFrame();
+ // control frame validation
+ if (!fin)
+ {
+ throw new ProtocolException("Fragmented Close Frame [" + OpCode.name(opcode) + "]");
+ }
break;
- default:
- strictnessProcessor = NoOpValidator.INSTANCE;
+ case OpCode.PING:
+ frame = new PingFrame();
+ // control frame validation
+ if (!fin)
+ {
+ throw new ProtocolException("Fragmented Ping Frame [" + OpCode.name(opcode) + "]");
+ }
+ break;
+ case OpCode.PONG:
+ frame = new PongFrame();
+ // control frame validation
+ if (!fin)
+ {
+ throw new ProtocolException("Fragmented Pong Frame [" + OpCode.name(opcode) + "]");
+ }
break;
}
-
- if (OpCode.isControlFrame(opcode))
- {
- // control frame validation
- if (!fin)
- {
- throw new ProtocolException("Fragmented Control Frame [" + OpCode.name(opcode) + "]");
- }
- }
- else if (opcode == OpCode.CONTINUATION)
- {
- isContinuation = true;
- // continuation validation
- if (priorDataFrame == null)
- {
- throw new ProtocolException("CONTINUATION frame without prior !FIN");
- }
- // Be careful to use the original opcode
- opcode = lastDataOpcode;
- }
- else if (OpCode.isDataFrame(opcode))
- {
- // data validation
- if ((priorDataFrame != null) && (!priorDataFrame.isFin()))
- {
- throw new ProtocolException("Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION");
- }
- }
-
- // base framing flags
- frame = new WebSocketFrame(opcode);
+
frame.setFin(fin);
frame.setRsv1(rsv1);
frame.setRsv2(rsv2);
frame.setRsv3(rsv3);
- frame.setContinuation(isContinuation);
if (frame.isDataFrame())
{
@@ -572,7 +579,6 @@
}
maskProcessor.process(window);
- strictnessProcessor.process(window);
int len = BufferUtil.put(window,payload);
buffer.position(buffer.position() + len); // update incoming buffer position
@@ -597,7 +603,8 @@
public String toString()
{
StringBuilder builder = new StringBuilder();
- builder.append("Parser[");
+ builder.append("Parser@").append(Integer.toHexString(hashCode()));
+ builder.append("[");
if (incomingFramesHandler == null)
{
builder.append("NO_HANDLER");
@@ -606,14 +613,11 @@
{
builder.append(incomingFramesHandler.getClass().getSimpleName());
}
- builder.append(",s=");
- builder.append(state);
- builder.append(",c=");
- builder.append(cursor);
- builder.append(",len=");
- builder.append(payloadLength);
- builder.append(",f=");
- builder.append(frame);
+ builder.append(",s=").append(state);
+ builder.append(",c=").append(cursor);
+ builder.append(",len=").append(payloadLength);
+ builder.append(",f=").append(frame);
+ builder.append(",p=").append(policy);
builder.append("]");
return builder.toString();
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionFactory.java
new file mode 100644
index 0000000..9119f3f
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionFactory.java
@@ -0,0 +1,33 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.net.URI;
+
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+
+/**
+ * Interface for creating jetty {@link WebSocketSession} objects.
+ */
+public interface SessionFactory
+{
+ public boolean supports(EventDriver websocket);
+
+ public WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection);
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketFrame.java
index 869a122..ddbe0b0 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketFrame.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketFrame.java
@@ -19,13 +19,16 @@
package org.eclipse.jetty.websocket.common;
import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.websocket.api.ProtocolException;
import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
/**
* A Base Frame as seen in <a href="https://tools.ietf.org/html/rfc6455#section-5.2">RFC 6455. Sec 5.2</a>
@@ -51,187 +54,106 @@
* +---------------------------------------------------------------+
* </pre>
*/
-public class WebSocketFrame implements Frame
+public abstract class WebSocketFrame implements Frame
{
- /** Maximum size of Control frame, per RFC 6455 */
- public static final int MAX_CONTROL_PAYLOAD = 125;
-
- public static WebSocketFrame binary()
+ public static WebSocketFrame copy(Frame copy)
{
- return new WebSocketFrame(OpCode.BINARY);
+ WebSocketFrame frame = null;
+ switch (copy.getOpCode())
+ {
+ case OpCode.BINARY:
+ frame = new BinaryFrame();
+ break;
+ case OpCode.TEXT:
+ frame = new TextFrame();
+ break;
+ case OpCode.CLOSE:
+ frame = new CloseFrame();
+ break;
+ case OpCode.CONTINUATION:
+ frame = new ContinuationFrame();
+ break;
+ case OpCode.PING:
+ frame = new PingFrame();
+ break;
+ case OpCode.PONG:
+ frame = new PongFrame();
+ break;
+ default:
+ throw new IllegalArgumentException("Cannot copy frame with opcode " + copy.getOpCode() + " - " + copy);
+ }
+
+ frame.copyHeaders(copy);
+ frame.setPayload(copy.getPayload());
+
+ return frame;
}
- public static WebSocketFrame binary(byte buf[])
- {
- return new WebSocketFrame(OpCode.BINARY).setPayload(buf);
- }
+ /**
+ * Combined FIN + RSV1 + RSV2 + RSV3 + OpCode byte.
+ * <p>
+ *
+ * <pre>
+ * 1000_0000 (0x80) = fin
+ * 0100_0000 (0x40) = rsv1
+ * 0010_0000 (0x20) = rsv2
+ * 0001_0000 (0x10) = rsv3
+ * 0000_1111 (0x0F) = opcode
+ * </pre>
+ */
+ protected byte finRsvOp;
+ protected boolean masked = false;
- public static WebSocketFrame ping()
- {
- return new WebSocketFrame(OpCode.PING);
- }
-
- public static WebSocketFrame pong()
- {
- return new WebSocketFrame(OpCode.PONG);
- }
-
- public static WebSocketFrame text()
- {
- return new WebSocketFrame(OpCode.TEXT);
- }
-
- public static WebSocketFrame text(String msg)
- {
- return new WebSocketFrame(OpCode.TEXT).setPayload(msg);
- }
-
- private boolean fin = true;
- private boolean rsv1 = false;
- private boolean rsv2 = false;
- private boolean rsv3 = false;
- protected byte opcode = OpCode.UNDEFINED;
- private boolean masked = false;
- private byte mask[];
+ protected byte mask[];
/**
* The payload data.
* <p>
* It is assumed to always be in FLUSH mode (ready to read) in this object.
*/
- private ByteBuffer data;
- private int payloadLength = 0;
- /** position of start of data within a fresh payload */
- private int payloadStart = -1;
+ protected ByteBuffer data;
- private Type type;
- private boolean continuation = false;
- private int continuationIndex = 0;
-
- /**
- * Default constructor
- */
- public WebSocketFrame()
- {
- this(OpCode.UNDEFINED);
- }
+ protected int payloadLength = 0;
/**
* Construct form opcode
*/
- public WebSocketFrame(byte opcode)
+ protected WebSocketFrame(byte opcode)
{
reset();
setOpCode(opcode);
}
- /**
- * Copy constructor for the websocket frame.
- *
- * @param copy
- * the websocket to copy.
- */
- public WebSocketFrame(Frame frame)
+ public abstract void assertValid();
+
+ protected void copy(WebSocketFrame copy, ByteBuffer payload)
{
- if (frame instanceof WebSocketFrame)
+ copyHeaders(copy);
+ setPayload(payload);
+ }
+
+ protected void copyHeaders(Frame frame)
+ {
+ finRsvOp = 0x00;
+ finRsvOp |= frame.isFin()?0x80:0x00;
+ finRsvOp |= frame.isRsv1()?0x40:0x00;
+ finRsvOp |= frame.isRsv2()?0x20:0x00;
+ finRsvOp |= frame.isRsv3()?0x10:0x00;
+ finRsvOp |= frame.getOpCode() & 0x0F;
+
+ masked = frame.isMasked();
+ if (masked)
{
- WebSocketFrame wsf = (WebSocketFrame)frame;
- copy(wsf,wsf.data);
+ mask = frame.getMask();
}
else
{
- // Copy manually
- fin = frame.isFin();
- rsv1 = frame.isRsv1();
- rsv2 = frame.isRsv2();
- rsv3 = frame.isRsv3();
- opcode = frame.getType().getOpCode();
- type = frame.getType();
- masked = frame.isMasked();
mask = null;
- byte maskCopy[] = frame.getMask();
- if (maskCopy != null)
- {
- mask = new byte[maskCopy.length];
- System.arraycopy(maskCopy,0,mask,0,mask.length);
- }
-
- setPayload(frame.getPayload());
}
}
- /**
- * Copy constructor for the websocket frame.
- * <p>
- * Note: the underlying payload is merely a {@link ByteBuffer#slice()} of the input frame.
- *
- * @param copy
- * the websocket to copy.
- */
- public WebSocketFrame(WebSocketFrame copy)
+ protected void copyHeaders(WebSocketFrame copy)
{
- copy(copy,copy.data);
- }
-
- /**
- * Copy constructor for the websocket frame, with an alternate payload.
- * <p>
- * This is especially useful for Extensions to utilize when mutating the payload.
- *
- * @param copy
- * the websocket to copy.
- * @param altPayload
- * the alternate payload to use for this frame.
- */
- public WebSocketFrame(WebSocketFrame copy, ByteBuffer altPayload)
- {
- copy(copy,altPayload);
- }
-
- public void assertValid()
- {
- if (OpCode.isControlFrame(opcode))
- {
- if (getPayloadLength() > WebSocketFrame.MAX_CONTROL_PAYLOAD)
- {
- throw new ProtocolException("Desired payload length [" + getPayloadLength() + "] exceeds maximum control payload length ["
- + MAX_CONTROL_PAYLOAD + "]");
- }
-
- if (fin == false)
- {
- throw new ProtocolException("Cannot have FIN==false on Control frames");
- }
-
- if (rsv1 == true)
- {
- throw new ProtocolException("Cannot have RSV1==true on Control frames");
- }
-
- if (rsv2 == true)
- {
- throw new ProtocolException("Cannot have RSV2==true on Control frames");
- }
-
- if (rsv3 == true)
- {
- throw new ProtocolException("Cannot have RSV3==true on Control frames");
- }
-
- if (isContinuation())
- {
- throw new ProtocolException("Control frames cannot be Continuations");
- }
- }
- }
-
- private final void copy(WebSocketFrame copy, ByteBuffer payload)
- {
- fin = copy.fin;
- rsv1 = copy.rsv1;
- rsv2 = copy.rsv2;
- rsv3 = copy.rsv3;
- opcode = copy.opcode;
- type = copy.type;
+ finRsvOp = copy.finRsvOp;
masked = copy.masked;
mask = null;
if (copy.mask != null)
@@ -239,10 +161,6 @@
mask = new byte[copy.mask.length];
System.arraycopy(copy.mask,0,mask,0,mask.length);
}
- continuationIndex = copy.continuationIndex;
- continuation = copy.continuation;
-
- setPayload(payload);
}
@Override
@@ -261,14 +179,6 @@
return false;
}
WebSocketFrame other = (WebSocketFrame)obj;
- if (continuation != other.continuation)
- {
- return false;
- }
- if (continuationIndex != other.continuationIndex)
- {
- return false;
- }
if (data == null)
{
if (other.data != null)
@@ -280,7 +190,7 @@
{
return false;
}
- if (fin != other.fin)
+ if (finRsvOp != other.finRsvOp)
{
return false;
}
@@ -292,53 +202,19 @@
{
return false;
}
- if (opcode != other.opcode)
- {
- return false;
- }
- if (rsv1 != other.rsv1)
- {
- return false;
- }
- if (rsv2 != other.rsv2)
- {
- return false;
- }
- if (rsv3 != other.rsv3)
- {
- return false;
- }
return true;
}
- /**
- * The number of fragments this frame consists of.
- * <p>
- * For every {@link OpCode#CONTINUATION} opcode encountered, this increments by one.
- * <p>
- * Note: Not part of the Base Framing Protocol / header information.
- *
- * @return the number of continuation fragments encountered.
- */
- public int getContinuationIndex()
- {
- return continuationIndex;
- }
-
@Override
public byte[] getMask()
{
- if (!masked)
- {
- throw new IllegalStateException("Frame is not masked");
- }
return mask;
}
@Override
public final byte getOpCode()
{
- return opcode;
+ return (byte)(finRsvOp & 0x0F);
}
/**
@@ -352,23 +228,12 @@
@Override
public ByteBuffer getPayload()
{
- if (data != null)
- {
- return data;
- }
- else
- {
- return null;
- }
+ return data;
}
public String getPayloadAsUTF8()
{
- if (data == null)
- {
- return null;
- }
- return BufferUtil.toUTF8String(data);
+ return BufferUtil.toUTF8String(getPayload());
}
@Override
@@ -382,19 +247,9 @@
}
@Override
- public int getPayloadStart()
- {
- if (data == null)
- {
- return -1;
- }
- return payloadStart;
- }
-
- @Override
public Type getType()
{
- return type;
+ return Type.from(getOpCode());
}
@Override
@@ -402,16 +257,9 @@
{
final int prime = 31;
int result = 1;
- result = (prime * result) + (continuation?1231:1237);
- result = (prime * result) + continuationIndex;
result = (prime * result) + ((data == null)?0:data.hashCode());
- result = (prime * result) + (fin?1231:1237);
+ result = (prime * result) + finRsvOp;
result = (prime * result) + Arrays.hashCode(mask);
- result = (prime * result) + (masked?1231:1237);
- result = (prime * result) + opcode;
- result = (prime * result) + (rsv1?1231:1237);
- result = (prime * result) + (rsv2?1231:1237);
- result = (prime * result) + (rsv3?1231:1237);
return result;
}
@@ -421,37 +269,20 @@
return ((data != null) && (payloadLength > 0));
}
- @Override
- public boolean isContinuation()
- {
- return continuation;
- }
+ public abstract boolean isControlFrame();
- public boolean isControlFrame()
- {
- return OpCode.isControlFrame(opcode);
- }
-
- public boolean isDataFrame()
- {
- return OpCode.isDataFrame(opcode);
- }
+ public abstract boolean isDataFrame();
@Override
public boolean isFin()
{
- return fin;
+ return (byte)(finRsvOp & 0x80) != 0;
}
@Override
public boolean isLast()
{
- return fin;
- }
-
- public boolean isLastFrame()
- {
- return fin;
+ return isFin();
}
@Override
@@ -463,19 +294,19 @@
@Override
public boolean isRsv1()
{
- return rsv1;
+ return (byte)(finRsvOp & 0x40) != 0;
}
@Override
public boolean isRsv2()
{
- return rsv2;
+ return (byte)(finRsvOp & 0x20) != 0;
}
@Override
public boolean isRsv3()
{
- return rsv3;
+ return (byte)(finRsvOp & 0x10) != 0;
}
/**
@@ -513,34 +344,17 @@
public void reset()
{
- fin = true;
- rsv1 = false;
- rsv2 = false;
- rsv3 = false;
- opcode = -1;
+ finRsvOp = (byte)0x80; // FIN (!RSV, opcode 0)
masked = false;
data = null;
payloadLength = 0;
mask = null;
- continuationIndex = 0;
- continuation = false;
- }
-
- public Frame setContinuation(boolean continuation)
- {
- this.continuation = continuation;
- return this;
- }
-
- public Frame setContinuationIndex(int continuationIndex)
- {
- this.continuationIndex = continuationIndex;
- return this;
}
public WebSocketFrame setFin(boolean fin)
{
- this.fin = fin;
+ // set bit 1
+ this.finRsvOp = (byte)((finRsvOp & 0x7F) | (fin?0x80:0x00));
return this;
}
@@ -557,74 +371,9 @@
return this;
}
- public WebSocketFrame setOpCode(byte op)
+ protected WebSocketFrame setOpCode(byte op)
{
- this.opcode = op;
-
- if (op == OpCode.UNDEFINED)
- {
- this.type = null;
- }
- else
- {
- this.type = Frame.Type.from(op);
- }
- return this;
- }
-
- /**
- * Set the data and payload length.
- *
- * @param buf
- * the bytebuffer to set
- */
- public WebSocketFrame setPayload(byte buf[])
- {
- if (buf == null)
- {
- data = null;
- return this;
- }
-
- if (OpCode.isControlFrame(opcode))
- {
- if (buf.length > WebSocketFrame.MAX_CONTROL_PAYLOAD)
- {
- throw new ProtocolException("Control Payloads can not exceed 125 bytes in length.");
- }
- }
-
- data = BufferUtil.toBuffer(buf);
- payloadStart = data.position();
- payloadLength = data.limit();
- return this;
- }
-
- /**
- * Set the data and payload length.
- *
- * @param buf
- * the bytebuffer to set
- */
- public WebSocketFrame setPayload(byte buf[], int offset, int len)
- {
- if (buf == null)
- {
- data = null;
- return this;
- }
-
- if (OpCode.isControlFrame(opcode))
- {
- if (len > WebSocketFrame.MAX_CONTROL_PAYLOAD)
- {
- throw new ProtocolException("Control Payloads can not exceed 125 bytes in length.");
- }
- }
-
- data = BufferUtil.toBuffer(buf,offset,len);
- payloadStart = data.position();
- payloadLength = data.limit();
+ this.finRsvOp = (byte)((finRsvOp & 0xF0) | (op & 0x0F));
return this;
}
@@ -646,41 +395,29 @@
return this;
}
- if (OpCode.isControlFrame(opcode))
- {
- if (buf.remaining() > WebSocketFrame.MAX_CONTROL_PAYLOAD)
- {
- throw new ProtocolException("Control Payloads can not exceed 125 bytes in length. (was " + buf.remaining() + " bytes)");
- }
- }
-
data = buf.slice();
- payloadStart = data.position();
payloadLength = data.limit();
return this;
}
- public WebSocketFrame setPayload(String str)
- {
- setPayload(BufferUtil.toBuffer(str, StandardCharsets.UTF_8));
- return this;
- }
-
public WebSocketFrame setRsv1(boolean rsv1)
{
- this.rsv1 = rsv1;
+ // set bit 2
+ this.finRsvOp = (byte)((finRsvOp & 0xBF) | (rsv1?0x40:0x00));
return this;
}
public WebSocketFrame setRsv2(boolean rsv2)
{
- this.rsv2 = rsv2;
+ // set bit 3
+ this.finRsvOp = (byte)((finRsvOp & 0xDF) | (rsv2?0x20:0x00));
return this;
}
public WebSocketFrame setRsv3(boolean rsv3)
{
- this.rsv3 = rsv3;
+ // set bit 4
+ this.finRsvOp = (byte)((finRsvOp & 0xEF) | (rsv3?0x10:0x00));
return this;
}
@@ -688,17 +425,15 @@
public String toString()
{
StringBuilder b = new StringBuilder();
- b.append(OpCode.name(opcode));
+ b.append(OpCode.name((byte)(finRsvOp & 0x0F)));
b.append('[');
b.append("len=").append(payloadLength);
- b.append(",fin=").append(fin);
+ b.append(",fin=").append((finRsvOp & 0x80) != 0);
b.append(",rsv=");
- b.append(rsv1?'1':'.');
- b.append(rsv2?'1':'.');
- b.append(rsv3?'1':'.');
+ b.append(((finRsvOp & 0x40) != 0)?'1':'.');
+ b.append(((finRsvOp & 0x20) != 0)?'1':'.');
+ b.append(((finRsvOp & 0x10) != 0)?'1':'.');
b.append(",masked=").append(masked);
- b.append(",continuation=").append(continuation);
- b.append(",payloadStart=").append(getPayloadStart());
b.append(",remaining=").append(remaining());
b.append(",position=").append(position());
b.append(']');
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketRemoteEndpoint.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketRemoteEndpoint.java
index 4f4d88d..bfa36a3 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketRemoteEndpoint.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketRemoteEndpoint.java
@@ -21,14 +21,22 @@
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
/**
@@ -36,9 +44,31 @@
*/
public class WebSocketRemoteEndpoint implements RemoteEndpoint
{
+ private static final String PRIORMSG_ERROR = "Prior message pending, cannot start new message yet.";
+ /** Type of Message */
+ private static final int NONE = 0;
+ private static final int TEXT = 1;
+ private static final int BINARY = 2;
+ private static final int CONTROL = 3;
+ private static final WriteCallback NOOP_CALLBACK = new WriteCallback()
+ {
+ @Override
+ public void writeSuccess()
+ {
+ }
+
+ @Override
+ public void writeFailed(Throwable x)
+ {
+ }
+ };
+
private static final Logger LOG = Log.getLogger(WebSocketRemoteEndpoint.class);
public final LogicalConnection connection;
public final OutgoingFrames outgoing;
+ private final ReentrantLock msgLock = new ReentrantLock();
+ private final AtomicInteger msgType = new AtomicInteger(NONE);
+ private boolean partialStarted = false;
public WebSocketRemoteEndpoint(LogicalConnection connection, OutgoingFrames outgoing)
{
@@ -52,19 +82,11 @@
private void blockingWrite(WebSocketFrame frame) throws IOException
{
- Future<Void> fut = sendAsyncFrame(frame);
- try
- {
- fut.get(); // block till done
- }
- catch (ExecutionException e)
- {
- throw new IOException("Failed to write bytes",e.getCause());
- }
- catch (InterruptedException e)
- {
- throw new IOException("Failed to write bytes",e);
- }
+ // TODO Blocking callbacks can be recycled, but they do not handle concurrent calls,
+ // so if some mutual exclusion can be applied, then this callback can be reused.
+ BlockingWriteCallback callback = new BlockingWriteCallback();
+ sendFrame(frame,callback);
+ callback.block();
}
public InetSocketAddress getInetSocketAddress()
@@ -82,15 +104,7 @@
private Future<Void> sendAsyncFrame(WebSocketFrame frame)
{
FutureWriteCallback future = new FutureWriteCallback();
- try
- {
- connection.getIOState().assertOutputOpen();
- outgoing.outgoingFrame(frame,future);
- }
- catch (IOException e)
- {
- future.writeFailed(e);
- }
+ sendFrame(frame,future);
return future;
}
@@ -100,89 +114,255 @@
@Override
public void sendBytes(ByteBuffer data) throws IOException
{
- connection.getIOState().assertOutputOpen();
- if (LOG.isDebugEnabled())
+ if (msgLock.tryLock())
{
- LOG.debug("sendBytes with {}",BufferUtil.toDetailString(data));
+ try
+ {
+ msgType.set(BINARY);
+ connection.getIOState().assertOutputOpen();
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendBytes with {}",BufferUtil.toDetailString(data));
+ }
+ blockingWrite(new BinaryFrame().setPayload(data));
+ }
+ finally
+ {
+ msgType.set(NONE);
+ msgLock.unlock();
+ }
}
- WebSocketFrame frame = WebSocketFrame.binary().setPayload(data);
- blockingWrite(frame);
+ else
+ {
+ throw new IllegalStateException(PRIORMSG_ERROR);
+ }
}
@Override
public Future<Void> sendBytesByFuture(ByteBuffer data)
{
+ msgType.set(BINARY);
if (LOG.isDebugEnabled())
{
LOG.debug("sendBytesByFuture with {}",BufferUtil.toDetailString(data));
}
- WebSocketFrame frame = WebSocketFrame.binary().setPayload(data);
- return sendAsyncFrame(frame);
+ return sendAsyncFrame(new BinaryFrame().setPayload(data));
+ }
+
+ @Override
+ public void sendBytes(ByteBuffer data, WriteCallback callback)
+ {
+ msgType.set(BINARY);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendBytes({}, {})",BufferUtil.toDetailString(data),callback);
+ }
+ sendFrame(new BinaryFrame().setPayload(data),callback==null?NOOP_CALLBACK:callback);
+ }
+
+ public void sendFrame(WebSocketFrame frame, WriteCallback callback)
+ {
+ try
+ {
+ connection.getIOState().assertOutputOpen();
+ outgoing.outgoingFrame(frame,callback);
+ }
+ catch (IOException e)
+ {
+ callback.writeFailed(e);
+ }
}
@Override
public void sendPartialBytes(ByteBuffer fragment, boolean isLast) throws IOException
{
- if (LOG.isDebugEnabled())
+ if (msgLock.tryLock())
{
- LOG.debug("sendPartialBytes({}, {})",BufferUtil.toDetailString(fragment),isLast);
+ try
+ {
+ if (msgType.get() == TEXT)
+ {
+ throw new IllegalStateException("Prior TEXT message pending, cannot start new BINARY message yet.");
+ }
+ msgType.set(BINARY);
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendPartialBytes({}, {})",BufferUtil.toDetailString(fragment),isLast);
+ }
+ DataFrame frame = null;
+ if (partialStarted)
+ {
+ frame = new ContinuationFrame().setPayload(fragment);
+ }
+ else
+ {
+ frame = new BinaryFrame().setPayload(fragment);
+ }
+ frame.setFin(isLast);
+ blockingWrite(frame);
+ partialStarted = !isLast;
+ }
+ finally
+ {
+ if (isLast)
+ {
+ msgType.set(NONE);
+ }
+ msgLock.unlock();
+ }
}
- WebSocketFrame frame = WebSocketFrame.binary().setPayload(fragment).setFin(isLast);
- blockingWrite(frame);
+ else
+ {
+ throw new IllegalStateException(PRIORMSG_ERROR);
+ }
}
@Override
public void sendPartialString(String fragment, boolean isLast) throws IOException
{
- if (LOG.isDebugEnabled())
+ if (msgLock.tryLock())
{
- LOG.debug("sendPartialString({}, {})",fragment,isLast);
+ try
+ {
+ if (msgType.get() == BINARY)
+ {
+ throw new IllegalStateException("Prior BINARY message pending, cannot start new TEXT message yet.");
+ }
+ msgType.set(TEXT);
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendPartialString({}, {})",fragment,isLast);
+ }
+ DataFrame frame = null;
+ if (partialStarted)
+ {
+ frame = new ContinuationFrame().setPayload(fragment);
+ }
+ else
+ {
+ frame = new TextFrame().setPayload(fragment);
+ }
+ frame.setFin(isLast);
+ blockingWrite(frame);
+ partialStarted = !isLast;
+ }
+ finally
+ {
+ if (isLast)
+ {
+ msgType.set(NONE);
+ }
+ msgLock.unlock();
+ }
}
- WebSocketFrame frame = WebSocketFrame.text(fragment).setFin(isLast);
- blockingWrite(frame);
+ else
+ {
+ throw new IllegalStateException(PRIORMSG_ERROR);
+ }
}
@Override
public void sendPing(ByteBuffer applicationData) throws IOException
{
- if (LOG.isDebugEnabled())
+ if (msgLock.tryLock())
{
- LOG.debug("sendPing with {}",BufferUtil.toDetailString(applicationData));
+ try
+ {
+ msgType.set(CONTROL);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendPing with {}",BufferUtil.toDetailString(applicationData));
+ }
+ blockingWrite(new PingFrame().setPayload(applicationData));
+ }
+ finally
+ {
+ msgType.set(NONE);
+ msgLock.unlock();
+ }
}
- WebSocketFrame frame = WebSocketFrame.ping().setPayload(applicationData);
- blockingWrite(frame);
+ else
+ {
+ throw new IllegalStateException(PRIORMSG_ERROR);
+ }
}
@Override
public void sendPong(ByteBuffer applicationData) throws IOException
{
- if (LOG.isDebugEnabled())
+ if (msgLock.tryLock())
{
- LOG.debug("sendPong with {}",BufferUtil.toDetailString(applicationData));
+ try
+ {
+ msgType.set(CONTROL);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendPong with {}",BufferUtil.toDetailString(applicationData));
+ }
+ blockingWrite(new PongFrame().setPayload(applicationData));
+ }
+ finally
+ {
+ msgType.set(NONE);
+ msgLock.unlock();
+ }
}
- WebSocketFrame frame = WebSocketFrame.pong().setPayload(applicationData);
- blockingWrite(frame);
+ else
+ {
+ throw new IllegalStateException(PRIORMSG_ERROR);
+ }
}
@Override
public void sendString(String text) throws IOException
{
- WebSocketFrame frame = WebSocketFrame.text(text);
- if (LOG.isDebugEnabled())
+ if (msgLock.tryLock())
{
- LOG.debug("sendString with {}",BufferUtil.toDetailString(frame.getPayload()));
+ try
+ {
+ msgType.set(TEXT);
+ WebSocketFrame frame = new TextFrame().setPayload(text);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendString with {}",BufferUtil.toDetailString(frame.getPayload()));
+ }
+ blockingWrite(frame);
+ }
+ finally
+ {
+ msgType.set(NONE);
+ msgLock.unlock();
+ }
}
- blockingWrite(WebSocketFrame.text(text));
+ else
+ {
+ throw new IllegalStateException(PRIORMSG_ERROR);
+ }
}
@Override
public Future<Void> sendStringByFuture(String text)
{
- WebSocketFrame frame = WebSocketFrame.text(text);
+ msgType.set(TEXT);
+ TextFrame frame = new TextFrame().setPayload(text);
if (LOG.isDebugEnabled())
{
LOG.debug("sendStringByFuture with {}",BufferUtil.toDetailString(frame.getPayload()));
}
return sendAsyncFrame(frame);
}
+
+ @Override
+ public void sendString(String text, WriteCallback callback)
+ {
+ msgType.set(TEXT);
+ TextFrame frame = new TextFrame().setPayload(text);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sendString({},{})",BufferUtil.toDetailString(frame.getPayload()),callback);
+ }
+ sendFrame(frame,callback==null?NOOP_CALLBACK:callback);
+ }
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
index 1b79f3b..6b9c4de 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
@@ -21,14 +21,12 @@
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
-import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
-import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
@@ -59,8 +57,8 @@
private final URI requestURI;
private final EventDriver websocket;
private final LogicalConnection connection;
+ private final Executor executor;
private ExtensionFactory extensionFactory;
- private long maximumMessageSize;
private String protocolVersion;
private Map<String, String[]> parameterMap = new HashMap<>();
private WebSocketRemoteEndpoint remote;
@@ -80,30 +78,15 @@
this.requestURI = requestURI;
this.websocket = websocket;
this.connection = connection;
+ this.executor = connection.getExecutor();
this.outgoingHandler = connection;
this.incomingHandler = websocket;
this.connection.getIOState().addListener(this);
-
- // Get the parameter map (use the jetty MultiMap to do this right)
- MultiMap<String> params = new MultiMap<>();
- String query = requestURI.getQuery();
- if (StringUtil.isNotBlank(query))
- {
- UrlEncoded.decodeTo(query,params, StandardCharsets.UTF_8,-1);
- }
-
- for (String name : params.keySet())
- {
- List<String> valueList = params.getValues(name);
- String valueArr[] = new String[valueList.size()];
- valueArr = valueList.toArray(valueArr);
- parameterMap.put(name,valueArr);
- }
}
@Override
- public void close() throws IOException
+ public void close()
{
this.close(StatusCode.NORMAL,null);
}
@@ -133,6 +116,11 @@
notifyClose(StatusCode.NO_CLOSE,"Harsh disconnect");
}
+ public void dispatch(Runnable runnable)
+ {
+ executor.execute(runnable);
+ }
+
@Override
public void dump(Appendable out, String indent) throws IOException
{
@@ -188,6 +176,11 @@
return true;
}
+ public ByteBufferPool getBufferPool()
+ {
+ return this.connection.getBufferPool();
+ }
+
public LogicalConnection getConnection()
{
return connection;
@@ -219,12 +212,6 @@
return connection.getLocalAddress();
}
- @Override
- public long getMaximumMessageSize()
- {
- return maximumMessageSize;
- }
-
@ManagedAttribute(readonly = true)
public OutgoingFrames getOutgoingHandler()
{
@@ -292,12 +279,12 @@
* Incoming Errors from Parser
*/
@Override
- public void incomingError(WebSocketException e)
+ public void incomingError(Throwable t)
{
if (connection.getIOState().isInputAvailable())
{
// Forward Errors to User WebSocket Object
- websocket.incomingError(e);
+ websocket.incomingError(t);
}
}
@@ -342,6 +329,11 @@
websocket.onClose(new CloseInfo(statusCode,reason));
}
+ public void notifyError(Throwable cause)
+ {
+ incomingError(cause);
+ }
+
@Override
public void onConnectionStateChange(ConnectionState state)
{
@@ -403,12 +395,6 @@
connection.setMaxIdleTimeout(ms);
}
- @Override
- public void setMaximumMessageSize(long length)
- {
- this.maximumMessageSize = length;
- }
-
public void setOutgoingHandler(OutgoingFrames outgoing)
{
this.outgoingHandler = outgoing;
@@ -423,6 +409,22 @@
{
this.upgradeRequest = request;
this.protocolVersion = request.getProtocolVersion();
+ this.parameterMap.clear();
+ if (request.getParameterMap() != null)
+ {
+ for (Map.Entry<String, List<String>> entry : request.getParameterMap().entrySet())
+ {
+ List<String> values = entry.getValue();
+ if (values != null)
+ {
+ this.parameterMap.put(entry.getKey(),values.toArray(new String[values.size()]));
+ }
+ else
+ {
+ this.parameterMap.put(entry.getKey(),new String[0]);
+ }
+ }
+ }
}
public void setUpgradeResponse(UpgradeResponse response)
@@ -433,8 +435,7 @@
@Override
public SuspendToken suspend()
{
- // TODO Auto-generated method stub
- return null;
+ return connection;
}
@Override
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionFactory.java
new file mode 100644
index 0000000..c649369
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionFactory.java
@@ -0,0 +1,43 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.net.URI;
+
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.JettyAnnotatedEventDriver;
+import org.eclipse.jetty.websocket.common.events.JettyListenerEventDriver;
+
+/**
+ * Default Session factory, creating WebSocketSession objects.
+ */
+public class WebSocketSessionFactory implements SessionFactory
+{
+ @Override
+ public boolean supports(EventDriver websocket)
+ {
+ return (websocket instanceof JettyAnnotatedEventDriver) || (websocket instanceof JettyListenerEventDriver);
+ }
+
+ @Override
+ public WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection)
+ {
+ return new WebSocketSession(requestURI,websocket,connection);
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java
new file mode 100644
index 0000000..2e214b8
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java
@@ -0,0 +1,247 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.CloseException;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.message.MessageAppender;
+
+/**
+ * EventDriver is the main interface between the User's WebSocket POJO and the internal jetty implementation of WebSocket.
+ */
+public abstract class AbstractEventDriver implements IncomingFrames, EventDriver
+{
+ private static final Logger LOG = Log.getLogger(AbstractEventDriver.class);
+ protected final WebSocketPolicy policy;
+ protected final Object websocket;
+ protected WebSocketSession session;
+ protected MessageAppender activeMessage;
+
+ public AbstractEventDriver(WebSocketPolicy policy, Object websocket)
+ {
+ this.policy = policy;
+ this.websocket = websocket;
+ }
+
+ protected void appendMessage(ByteBuffer buffer, boolean fin) throws IOException
+ {
+ activeMessage.appendMessage(buffer,fin);
+
+ if (fin)
+ {
+ activeMessage.messageComplete();
+ activeMessage = null;
+ }
+ }
+
+ protected void dispatch(Runnable runnable)
+ {
+ session.dispatch(runnable);
+ }
+
+ @Override
+ public WebSocketPolicy getPolicy()
+ {
+ return policy;
+ }
+
+ @Override
+ public WebSocketSession getSession()
+ {
+ return session;
+ }
+
+ @Override
+ public final void incomingError(Throwable e)
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("incoming(WebSocketException)",e);
+ }
+
+ if (e instanceof CloseException)
+ {
+ CloseException close = (CloseException)e;
+ terminateConnection(close.getStatusCode(),close.getMessage());
+ }
+
+ onError(e);
+ }
+
+ @Override
+ public void incomingFrame(Frame frame)
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("{}.onFrame({})",websocket.getClass().getSimpleName(),frame);
+ }
+
+ try
+ {
+ onFrame(frame);
+
+ byte opcode = frame.getOpCode();
+ switch (opcode)
+ {
+ case OpCode.CLOSE:
+ {
+ boolean validate = true;
+ CloseFrame closeframe = (CloseFrame)frame;
+ CloseInfo close = new CloseInfo(closeframe,validate);
+
+ // notify user websocket pojo
+ onClose(close);
+
+ // process handshake
+ session.getConnection().getIOState().onCloseRemote(close);
+
+ return;
+ }
+ case OpCode.PING:
+ {
+ ByteBuffer pongBuf;
+ if (frame.hasPayload())
+ {
+ pongBuf = ByteBuffer.allocate(frame.getPayload().remaining());
+ BufferUtil.put(frame.getPayload(),pongBuf);
+ BufferUtil.flipToFlush(pongBuf,0);
+ }
+ else
+ {
+ pongBuf = ByteBuffer.allocate(0);
+ }
+ onPing(frame.getPayload());
+ session.getRemote().sendPong(pongBuf);
+ break;
+ }
+ case OpCode.PONG:
+ {
+ onPong(frame.getPayload());
+ break;
+ }
+ case OpCode.BINARY:
+ {
+ onBinaryFrame(frame.getPayload(),frame.isFin());
+ return;
+ }
+ case OpCode.TEXT:
+ {
+ onTextFrame(frame.getPayload(),frame.isFin());
+ return;
+ }
+ case OpCode.CONTINUATION:
+ {
+ onContinuationFrame(frame.getPayload(),frame.isFin());
+ return;
+ }
+ default:
+ {
+ LOG.debug("Unhandled OpCode: {}",opcode);
+ }
+ }
+ }
+ catch (NotUtf8Exception e)
+ {
+ terminateConnection(StatusCode.BAD_PAYLOAD,e.getMessage());
+ }
+ catch (CloseException e)
+ {
+ terminateConnection(e.getStatusCode(),e.getMessage());
+ }
+ catch (Throwable t)
+ {
+ unhandled(t);
+ }
+ }
+
+ @Override
+ public void onContinuationFrame(ByteBuffer buffer, boolean fin) throws IOException
+ {
+ if (activeMessage == null)
+ {
+ throw new IOException("Out of order Continuation frame encountered");
+ }
+
+ appendMessage(buffer,fin);
+ }
+
+ @Override
+ public void onPong(ByteBuffer buffer)
+ {
+ /* TODO: provide annotation in future */
+ }
+
+ @Override
+ public void onPing(ByteBuffer buffer)
+ {
+ /* TODO: provide annotation in future */
+ }
+
+ @Override
+ public void openSession(WebSocketSession session)
+ {
+ LOG.debug("openSession({})",session);
+ this.session = session;
+ try
+ {
+ this.onConnect();
+ }
+ catch (Throwable t)
+ {
+ unhandled(t);
+ }
+ }
+
+ protected void terminateConnection(int statusCode, String rawreason)
+ {
+ LOG.debug("terminateConnection({},{})",statusCode,rawreason);
+ session.close(statusCode,CloseFrame.truncate(rawreason));
+ }
+
+ private void unhandled(Throwable t)
+ {
+ LOG.warn("Unhandled Error (closing connection)",t);
+ onError(t);
+
+ // Unhandled Error, close the connection.
+ switch (policy.getBehavior())
+ {
+ case SERVER:
+ terminateConnection(StatusCode.SERVER_ERROR,t.getClass().getSimpleName());
+ break;
+ case CLIENT:
+ terminateConnection(StatusCode.POLICY_VIOLATION,t.getClass().getSimpleName());
+ break;
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AnnotatedEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AnnotatedEventDriver.java
deleted file mode 100644
index 083904f..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AnnotatedEventDriver.java
+++ /dev/null
@@ -1,207 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.events;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.api.annotations.WebSocket;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.message.MessageAppender;
-import org.eclipse.jetty.websocket.common.message.MessageInputStream;
-import org.eclipse.jetty.websocket.common.message.MessageReader;
-import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage;
-import org.eclipse.jetty.websocket.common.message.SimpleTextMessage;
-
-/**
- * Handler for Annotated User WebSocket objects.
- */
-public class AnnotatedEventDriver extends EventDriver
-{
- private final EventMethods events;
- private MessageAppender activeMessage;
- private boolean hasCloseBeenCalled = false;
-
- public AnnotatedEventDriver(WebSocketPolicy policy, Object websocket, EventMethods events)
- {
- super(policy,websocket);
- this.events = events;
-
- WebSocket anno = websocket.getClass().getAnnotation(WebSocket.class);
- // Setup the policy
- if (anno.maxMessageSize() > 0)
- {
- this.policy.setMaxMessageSize(anno.maxMessageSize());
- }
- if (anno.inputBufferSize() > 0)
- {
- this.policy.setInputBufferSize(anno.inputBufferSize());
- }
- if (anno.maxIdleTime() > 0)
- {
- this.policy.setIdleTimeout(anno.maxIdleTime());
- }
- }
-
- @Override
- public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException
- {
- if (events.onBinary == null)
- {
- // not interested in binary events
- return;
- }
-
- if (activeMessage == null)
- {
- if (events.onBinary.isStreaming())
- {
- activeMessage = new MessageInputStream(this);
- }
- else
- {
- activeMessage = new SimpleBinaryMessage(this);
- }
- }
-
- activeMessage.appendMessage(buffer);
-
- if (fin)
- {
- activeMessage.messageComplete();
- activeMessage = null;
- }
- }
-
- @Override
- public void onBinaryMessage(byte[] data)
- {
- if (events.onBinary != null)
- {
- events.onBinary.call(websocket,session,data,0,data.length);
- }
- }
-
- @Override
- public void onClose(CloseInfo close)
- {
- if (hasCloseBeenCalled)
- {
- // avoid duplicate close events (possible when using harsh Session.disconnect())
- return;
- }
- hasCloseBeenCalled = true;
- if (events.onClose != null)
- {
- events.onClose.call(websocket,session,close.getStatusCode(),close.getReason());
- }
- }
-
- @Override
- public void onConnect()
- {
- if (events.onConnect != null)
- {
- events.onConnect.call(websocket,session);
- }
- }
-
- @Override
- public void onError(Throwable cause)
- {
- if (events.onError != null)
- {
- events.onError.call(websocket,session,cause);
- }
- }
-
- @Override
- public void onFrame(Frame frame)
- {
- if (events.onFrame != null)
- {
- events.onFrame.call(websocket,session,frame);
- }
- }
-
- public void onInputStream(InputStream stream)
- {
- if (events.onBinary != null)
- {
- events.onBinary.call(websocket,session,stream);
- }
- }
-
- public void onReader(Reader reader)
- {
- if (events.onText != null)
- {
- events.onText.call(websocket,session,reader);
- }
- }
-
- @Override
- public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException
- {
- if (events.onText == null)
- {
- // not interested in text events
- return;
- }
-
- if (activeMessage == null)
- {
- if (events.onText.isStreaming())
- {
- activeMessage = new MessageReader(this);
- }
- else
- {
- activeMessage = new SimpleTextMessage(this);
- }
- }
-
- activeMessage.appendMessage(buffer);
-
- if (fin)
- {
- activeMessage.messageComplete();
- activeMessage = null;
- }
- }
-
- @Override
- public void onTextMessage(String message)
- {
- if (events.onText != null)
- {
- events.onText.call(websocket,session,message);
- }
- }
-
- @Override
- public String toString()
- {
- return String.format("%s[%s]", this.getClass().getSimpleName(), websocket);
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java
index 5461df4..ff0d584 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java
@@ -19,182 +19,47 @@
package org.eclipse.jetty.websocket.common.events;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
import java.nio.ByteBuffer;
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.CloseException;
-import org.eclipse.jetty.websocket.api.StatusCode;
-import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.OpCode;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.WebSocketSession;
-/**
- * EventDriver is the main interface between the User's WebSocket POJO and the internal jetty implementation of WebSocket.
- */
-public abstract class EventDriver implements IncomingFrames
+public interface EventDriver extends IncomingFrames
{
- private static final Logger LOG = Log.getLogger(EventDriver.class);
- protected final WebSocketPolicy policy;
- protected final Object websocket;
- protected WebSocketSession session;
+ public WebSocketPolicy getPolicy();
- public EventDriver(WebSocketPolicy policy, Object websocket)
- {
- this.policy = policy;
- this.websocket = websocket;
- }
+ public WebSocketSession getSession();
- public WebSocketPolicy getPolicy()
- {
- return policy;
- }
+ public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException;
- public WebSocketSession getSession()
- {
- return session;
- }
+ public void onBinaryMessage(byte[] data);
- @Override
- public final void incomingError(WebSocketException e)
- {
- if (LOG.isDebugEnabled())
- {
- LOG.debug("incoming(WebSocketException)",e);
- }
+ public void onClose(CloseInfo close);
- if (e instanceof CloseException)
- {
- CloseException close = (CloseException)e;
- terminateConnection(close.getStatusCode(),close.getMessage());
- }
+ public void onConnect();
- onError(e);
- }
+ public void onContinuationFrame(ByteBuffer buffer, boolean fin) throws IOException;
- @Override
- public void incomingFrame(Frame frame)
- {
- if (LOG.isDebugEnabled())
- {
- LOG.debug("{}.onFrame({})",websocket.getClass().getSimpleName(),frame);
- }
+ public void onError(Throwable t);
- onFrame(frame);
+ public void onFrame(Frame frame);
- try
- {
- switch (frame.getType().getOpCode())
- {
- case OpCode.CLOSE:
- {
- boolean validate = true;
- CloseInfo close = new CloseInfo(frame,validate);
+ public void onInputStream(InputStream stream);
- // notify user websocket pojo
- onClose(close);
+ public void onPing(ByteBuffer buffer);
+
+ public void onPong(ByteBuffer buffer);
- // process handshake
- session.getConnection().getIOState().onCloseRemote(close);
+ public void onReader(Reader reader);
- return;
- }
- case OpCode.PING:
- {
- byte pongBuf[] = new byte[0];
- if (frame.hasPayload())
- {
- pongBuf = BufferUtil.toArray(frame.getPayload());
- }
- session.getRemote().sendPong(ByteBuffer.wrap(pongBuf));
- break;
- }
- case OpCode.BINARY:
- {
- onBinaryFrame(frame.getPayload(),frame.isFin());
- return;
- }
- case OpCode.TEXT:
- {
- onTextFrame(frame.getPayload(),frame.isFin());
- return;
- }
- }
- }
- catch (NotUtf8Exception e)
- {
- terminateConnection(StatusCode.BAD_PAYLOAD,e.getMessage());
- }
- catch (CloseException e)
- {
- terminateConnection(e.getStatusCode(),e.getMessage());
- }
- catch (Throwable t)
- {
- unhandled(t);
- }
- }
+ public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException;
- public abstract void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException;
+ public void onTextMessage(String message);
- public abstract void onBinaryMessage(byte[] data);
-
- public abstract void onClose(CloseInfo close);
-
- public abstract void onConnect();
-
- public abstract void onError(Throwable t);
-
- public abstract void onFrame(Frame frame);
-
- public abstract void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException;
-
- public abstract void onTextMessage(String message);
-
- public void openSession(WebSocketSession session)
- {
- LOG.debug("openSession({})",session);
- this.session = session;
- try
- {
- this.onConnect();
- }
- catch (Throwable t)
- {
- unhandled(t);
- }
- }
-
- protected void terminateConnection(int statusCode, String rawreason)
- {
- String reason = rawreason;
- reason = StringUtil.truncate(reason,(WebSocketFrame.MAX_CONTROL_PAYLOAD - 2));
- LOG.debug("terminateConnection({},{})",statusCode,rawreason);
- session.close(statusCode,reason);
- }
-
- private void unhandled(Throwable t)
- {
- LOG.warn("Unhandled Error (closing connection)",t);
- onError(t);
-
- // Unhandled Error, close the connection.
- switch (policy.getBehavior())
- {
- case SERVER:
- terminateConnection(StatusCode.SERVER_ERROR,t.getClass().getSimpleName());
- break;
- case CLIENT:
- terminateConnection(StatusCode.POLICY_VIOLATION,t.getClass().getSimpleName());
- break;
- }
- }
-}
+ public void openSession(WebSocketSession session);
+}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java
index e8e9ea6..05cfc0f 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java
@@ -18,340 +18,83 @@
package org.eclipse.jetty.websocket.common.events;
-import java.io.InputStream;
-import java.io.Reader;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.ArrayList;
+import java.util.List;
-import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
-import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
/**
* Create EventDriver implementations.
*/
public class EventDriverFactory
{
- /**
- * Parameter list for @OnWebSocketMessage (Binary mode)
- */
- private static final ParamList validBinaryParams;
-
- /**
- * Parameter list for @OnWebSocketConnect
- */
- private static final ParamList validConnectParams;
-
- /**
- * Parameter list for @OnWebSocketClose
- */
- private static final ParamList validCloseParams;
-
- /**
- * Parameter list for @OnWebSocketError
- */
- private static final ParamList validErrorParams;
-
- /**
- * Parameter list for @OnWebSocketFrame
- */
- private static final ParamList validFrameParams;
-
- /**
- * Parameter list for @OnWebSocketMessage (Text mode)
- */
- private static final ParamList validTextParams;
-
- static
- {
- validConnectParams = new ParamList();
- validConnectParams.addParams(Session.class);
-
- validCloseParams = new ParamList();
- validCloseParams.addParams(int.class,String.class);
- validCloseParams.addParams(Session.class,int.class,String.class);
-
- validErrorParams = new ParamList();
- validErrorParams.addParams(Throwable.class);
- validErrorParams.addParams(Session.class,Throwable.class);
-
- validTextParams = new ParamList();
- validTextParams.addParams(String.class);
- validTextParams.addParams(Session.class,String.class);
- validTextParams.addParams(Reader.class);
- validTextParams.addParams(Session.class,Reader.class);
-
- validBinaryParams = new ParamList();
- validBinaryParams.addParams(byte[].class,int.class,int.class);
- validBinaryParams.addParams(Session.class,byte[].class,int.class,int.class);
- validBinaryParams.addParams(InputStream.class);
- validBinaryParams.addParams(Session.class,InputStream.class);
-
- validFrameParams = new ParamList();
- validFrameParams.addParams(Frame.class);
- validFrameParams.addParams(Session.class,Frame.class);
- }
-
- private ConcurrentHashMap<Class<?>, EventMethods> cache;
+ private static final Logger LOG = Log.getLogger(EventDriverFactory.class);
private final WebSocketPolicy policy;
+ private final List<EventDriverImpl> implementations;
public EventDriverFactory(WebSocketPolicy policy)
{
this.policy = policy;
- this.cache = new ConcurrentHashMap<>();
+ this.implementations = new ArrayList<>();
+
+ addImplementation(new JettyListenerImpl());
+ addImplementation(new JettyAnnotatedImpl());
}
- private void assertIsPublicNonStatic(Method method)
+ public void addImplementation(EventDriverImpl impl)
{
- int mods = method.getModifiers();
- if (!Modifier.isPublic(mods))
+ if (implementations.contains(impl))
{
- StringBuilder err = new StringBuilder();
- err.append("Invalid declaration of ");
- err.append(method);
- err.append(StringUtil.__LINE_SEPARATOR);
-
- err.append("Method modifier must be public");
-
- throw new InvalidWebSocketException(err.toString());
+ LOG.warn("Ignoring attempt to add duplicate EventDriverImpl: " + impl);
+ return;
}
- if (Modifier.isStatic(mods))
- {
- StringBuilder err = new StringBuilder();
- err.append("Invalid declaration of ");
- err.append(method);
- err.append(StringUtil.__LINE_SEPARATOR);
-
- err.append("Method modifier may not be static");
-
- throw new InvalidWebSocketException(err.toString());
- }
+ implementations.add(impl);
}
- private void assertIsReturn(Method method, Class<?> type)
+ public void clearImplementations()
{
- if (!type.equals(method.getReturnType()))
- {
- StringBuilder err = new StringBuilder();
- err.append("Invalid declaration of ");
- err.append(method);
- err.append(StringUtil.__LINE_SEPARATOR);
-
- err.append("Return type must be ").append(type);
-
- throw new InvalidWebSocketException(err.toString());
- }
+ this.implementations.clear();
}
- private void assertUnset(EventMethod event, Class<? extends Annotation> annoClass, Method method)
+ protected String getClassName(Object websocket)
{
- if (event != null)
- {
- // Attempt to add duplicate frame type (a no-no)
- StringBuilder err = new StringBuilder();
- err.append("Duplicate @").append(annoClass.getSimpleName()).append(" declaration on ");
- err.append(method);
- err.append(StringUtil.__LINE_SEPARATOR);
-
- err.append("@").append(annoClass.getSimpleName()).append(" previously declared at ");
- err.append(event.getMethod());
-
- throw new InvalidWebSocketException(err.toString());
- }
+ return websocket.getClass().getName();
}
- private void assertValidSignature(Method method, Class<? extends Annotation> annoClass, ParamList validParams)
+ public List<EventDriverImpl> getImplementations()
{
- assertIsPublicNonStatic(method);
- assertIsReturn(method,Void.TYPE);
-
- boolean valid = false;
-
- // validate parameters
- Class<?> actual[] = method.getParameterTypes();
- for (Class<?>[] params : validParams)
- {
- if (isSameParameters(actual,params))
- {
- valid = true;
- break;
- }
- }
-
- if (!valid)
- {
- throw InvalidSignatureException.build(method,annoClass,validParams);
- }
+ return implementations;
}
- /**
- * Perform the basic discovery mechanism for WebSocket events from the provided pojo.
- *
- * @param pojo
- * the pojo to scan
- * @return the discovered event methods
- * @throws InvalidWebSocketException
- */
- private EventMethods discoverMethods(Class<?> pojo) throws InvalidWebSocketException
+ public boolean removeImplementation(EventDriverImpl impl)
{
- WebSocket anno = pojo.getAnnotation(WebSocket.class);
- if (anno == null)
- {
- return null;
- }
-
- return scanAnnotatedMethods(pojo);
- }
-
- public EventMethods getMethods(Class<?> pojo) throws InvalidWebSocketException
- {
- if (pojo == null)
- {
- throw new InvalidWebSocketException("Cannot get methods for null class");
- }
- if (cache.containsKey(pojo))
- {
- return cache.get(pojo);
- }
- EventMethods methods = discoverMethods(pojo);
- if (methods == null)
- {
- return null;
- }
- cache.put(pojo,methods);
- return methods;
- }
-
- private boolean isSameParameters(Class<?>[] actual, Class<?>[] params)
- {
- if (actual.length != params.length)
- {
- // skip
- return false;
- }
-
- int len = params.length;
- for (int i = 0; i < len; i++)
- {
- if (!actual[i].equals(params[i]))
- {
- return false; // not valid
- }
- }
-
- return true;
- }
-
- private boolean isSignatureMatch(Method method, ParamList validParams)
- {
- assertIsPublicNonStatic(method);
- assertIsReturn(method,Void.TYPE);
-
- // validate parameters
- Class<?> actual[] = method.getParameterTypes();
- for (Class<?>[] params : validParams)
- {
- if (isSameParameters(actual,params))
- {
- return true;
- }
- }
-
- return false;
- }
-
- private EventMethods scanAnnotatedMethods(Class<?> pojo)
- {
- Class<?> clazz = pojo;
- EventMethods events = new EventMethods(pojo);
-
- clazz = pojo;
- while (clazz.getAnnotation(WebSocket.class) != null)
- {
- for (Method method : clazz.getDeclaredMethods())
- {
- if (method.getAnnotation(OnWebSocketConnect.class) != null)
- {
- assertValidSignature(method,OnWebSocketConnect.class,validConnectParams);
- assertUnset(events.onConnect,OnWebSocketConnect.class,method);
- events.onConnect = new EventMethod(pojo,method);
- continue;
- }
-
- if (method.getAnnotation(OnWebSocketMessage.class) != null)
- {
- if (isSignatureMatch(method,validTextParams))
- {
- // Text mode
- // TODO
-
- assertUnset(events.onText,OnWebSocketMessage.class,method);
- events.onText = new EventMethod(pojo,method);
- continue;
- }
-
- if (isSignatureMatch(method,validBinaryParams))
- {
- // Binary Mode
- // TODO
- assertUnset(events.onBinary,OnWebSocketMessage.class,method);
- events.onBinary = new EventMethod(pojo,method);
- continue;
- }
-
- throw InvalidSignatureException.build(method,OnWebSocketMessage.class,validTextParams,validBinaryParams);
- }
-
- if (method.getAnnotation(OnWebSocketClose.class) != null)
- {
- assertValidSignature(method,OnWebSocketClose.class,validCloseParams);
- assertUnset(events.onClose,OnWebSocketClose.class,method);
- events.onClose = new EventMethod(pojo,method);
- continue;
- }
-
- if (method.getAnnotation(OnWebSocketError.class) != null)
- {
- assertValidSignature(method,OnWebSocketError.class,validErrorParams);
- assertUnset(events.onError,OnWebSocketError.class,method);
- events.onError = new EventMethod(pojo,method);
- continue;
- }
-
- if (method.getAnnotation(OnWebSocketFrame.class) != null)
- {
- assertValidSignature(method,OnWebSocketFrame.class,validFrameParams);
- assertUnset(events.onFrame,OnWebSocketFrame.class,method);
- events.onFrame = new EventMethod(pojo,method);
- continue;
- }
-
- // Not a tagged method we are interested in, ignore
- }
-
- // try superclass now
- clazz = clazz.getSuperclass();
- }
-
- return events;
+ return this.implementations.remove(impl);
}
@Override
public String toString()
{
- return String.format("EventMethodsCache [cache.count=%d]",cache.size());
+ StringBuilder msg = new StringBuilder();
+ msg.append(this.getClass().getSimpleName());
+ msg.append("[implementations=[");
+ boolean delim = false;
+ for (EventDriverImpl impl : implementations)
+ {
+ if (delim)
+ {
+ msg.append(',');
+ }
+ msg.append(impl.toString());
+ delim = true;
+ }
+ msg.append("]");
+ return msg.toString();
}
/**
@@ -368,21 +111,39 @@
throw new InvalidWebSocketException("null websocket object");
}
- if (websocket instanceof WebSocketListener)
+ for (EventDriverImpl impl : implementations)
{
- WebSocketPolicy pojoPolicy = policy.clonePolicy();
- WebSocketListener listener = (WebSocketListener)websocket;
- return new ListenerEventDriver(pojoPolicy,listener);
+ if (impl.supports(websocket))
+ {
+ try
+ {
+ return impl.create(websocket,policy.clonePolicy());
+ }
+ catch (Throwable e)
+ {
+ throw new InvalidWebSocketException("Unable to create websocket",e);
+ }
+ }
}
- EventMethods methods = getMethods(websocket.getClass());
- if (methods != null)
+ // Create a clear error message for the developer
+ StringBuilder err = new StringBuilder();
+ err.append(getClassName(websocket));
+ err.append(" is not a valid WebSocket object.");
+ err.append(" Object must obey one of the following rules: ");
+
+ int len = implementations.size();
+ for (int i = 0; i < len; i++)
{
- WebSocketPolicy pojoPolicy = policy.clonePolicy();
- return new AnnotatedEventDriver(pojoPolicy,websocket,methods);
+ EventDriverImpl impl = implementations.get(i);
+ if (i > 0)
+ {
+ err.append(" or ");
+ }
+ err.append("\n(").append(i + 1).append(") ");
+ err.append(impl.describeRule());
}
- throw new InvalidWebSocketException(websocket.getClass().getName() + " does not implement " + WebSocketListener.class.getName()
- + " or declare @WebSocket");
+ throw new InvalidWebSocketException(err.toString());
}
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverImpl.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverImpl.java
new file mode 100644
index 0000000..1ea9b43
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverImpl.java
@@ -0,0 +1,58 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+
+/**
+ * A specific implementation of a EventDriver.
+ */
+public interface EventDriverImpl
+{
+ /**
+ * Create the EventDriver based on this implementation.
+ *
+ * @param websocket
+ * the websocket to wrap
+ * @param policy
+ * the policy to use
+ * @return the created EventDriver
+ * @throws Throwable
+ * if unable to create the EventDriver
+ */
+ EventDriver create(Object websocket, WebSocketPolicy policy) throws Throwable;
+
+ /**
+ * human readable string describing the rule that would support this EventDriver.
+ * <p>
+ * Used to help developer with possible object annotations, listeners, or base classes.
+ *
+ * @return the human readable description of this event driver rule(s).
+ */
+ String describeRule();
+
+ /**
+ * Test for if this implementation can support the provided websocket.
+ *
+ * @param websocket
+ * the possible websocket to test
+ * @return true if implementation can support it, false if otherwise.
+ */
+ boolean supports(Object websocket);
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventMethod.java
deleted file mode 100644
index e551d26..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventMethod.java
+++ /dev/null
@@ -1,153 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.events;
-
-import java.io.InputStream;
-import java.io.Reader;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.Session;
-import org.eclipse.jetty.websocket.api.WebSocketException;
-
-public class EventMethod
-{
- private static final Logger LOG = Log.getLogger(EventMethod.class);
-
- private static Object[] dropFirstArg(Object[] args)
- {
- if (args.length == 1)
- {
- return new Object[0];
- }
- Object ret[] = new Object[args.length - 1];
- System.arraycopy(args,1,ret,0,ret.length);
- return ret;
- }
-
- protected Class<?> pojo;
- protected Method method;
- private boolean hasSession = false;
- private boolean isStreaming = false;
- private Class<?>[] paramTypes;
-
- public EventMethod(Class<?> pojo, Method method)
- {
- this.pojo = pojo;
- this.paramTypes = method.getParameterTypes();
- this.method = method;
- identifyPresentParamTypes();
- }
-
- public EventMethod(Class<?> pojo, String methodName, Class<?>... paramTypes)
- {
- try
- {
- this.pojo = pojo;
- this.paramTypes = paramTypes;
- this.method = pojo.getMethod(methodName,paramTypes);
- identifyPresentParamTypes();
- }
- catch (NoSuchMethodException | SecurityException e)
- {
- LOG.warn("Cannot use method {}({}): {}",methodName,paramTypes,e.getMessage());
- this.method = null;
- }
- }
-
- public void call(Object obj, Object... args)
- {
- if ((this.pojo == null) || (this.method == null))
- {
- LOG.warn("Cannot execute call: pojo={}, method={}",pojo,method);
- return; // no call event method determined
- }
- if (obj == null)
- {
- LOG.warn("Cannot call {} on null object",this.method);
- return;
- }
- if (args.length > paramTypes.length)
- {
- Object trimArgs[] = dropFirstArg(args);
- call(obj,trimArgs);
- return;
- }
- if (args.length < paramTypes.length)
- {
- throw new IllegalArgumentException("Call arguments length [" + args.length + "] must always be greater than or equal to captured args length ["
- + paramTypes.length + "]");
- }
-
- try
- {
- this.method.invoke(obj,args);
- }
- catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
- {
- String err = String.format("Cannot call method %s on %s with args: %s",method,pojo,args);
- throw new WebSocketException(err,e);
- }
- }
-
- protected Method getMethod()
- {
- return method;
- }
-
- protected Class<?>[] getParamTypes()
- {
- return this.paramTypes;
- }
-
- private void identifyPresentParamTypes()
- {
- this.hasSession = false;
- this.isStreaming = false;
-
- if (paramTypes == null)
- {
- return;
- }
-
- for (Class<?> paramType : paramTypes)
- {
- if (Session.class.isAssignableFrom(paramType))
- {
- this.hasSession = true;
- }
- if (Reader.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType))
- {
- this.isStreaming = true;
- }
- }
- }
-
- public boolean isHasSession()
- {
- return hasSession;
- }
-
- public boolean isStreaming()
- {
- return isStreaming;
- }
-}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventMethods.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventMethods.java
deleted file mode 100644
index 80e2bfe..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventMethods.java
+++ /dev/null
@@ -1,107 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.events;
-
-/**
- * A representation of the methods available to call for a particular class.
- * <p>
- * This class used to cache the method lookups via the {@link EventMethodsCache}
- */
-public class EventMethods
-{
- private Class<?> pojoClass;
- public EventMethod onConnect = null;
- public EventMethod onClose = null;
- public EventMethod onBinary = null;
- public EventMethod onText = null;
- public EventMethod onError = null;
- public EventMethod onFrame = null;
-
- public EventMethods(Class<?> pojoClass)
- {
- this.pojoClass = pojoClass;
- }
-
- @Override
- public boolean equals(Object obj)
- {
- if (this == obj)
- {
- return true;
- }
- if (obj == null)
- {
- return false;
- }
- if (getClass() != obj.getClass())
- {
- return false;
- }
- EventMethods other = (EventMethods)obj;
- if (pojoClass == null)
- {
- if (other.pojoClass != null)
- {
- return false;
- }
- }
- else if (!pojoClass.getName().equals(other.pojoClass.getName()))
- {
- return false;
- }
- return true;
- }
-
- public Class<?> getPojoClass()
- {
- return pojoClass;
- }
-
- @Override
- public int hashCode()
- {
- final int prime = 31;
- int result = 1;
- result = (prime * result) + ((pojoClass == null)?0:pojoClass.getName().hashCode());
- return result;
- }
-
- @Override
- public String toString()
- {
- StringBuilder builder = new StringBuilder();
- builder.append("EventMethods [pojoClass=");
- builder.append(pojoClass);
- builder.append(", onConnect=");
- builder.append(onConnect);
- builder.append(", onClose=");
- builder.append(onClose);
- builder.append(", onBinary=");
- builder.append(onBinary);
- builder.append(", onText=");
- builder.append(onText);
- builder.append(", onException=");
- builder.append(onError);
- builder.append(", onFrame=");
- builder.append(onFrame);
- builder.append("]");
- return builder.toString();
- }
-
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/InvalidSignatureException.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/InvalidSignatureException.java
deleted file mode 100644
index bae7e42..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/InvalidSignatureException.java
+++ /dev/null
@@ -1,73 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.events;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
-
-@SuppressWarnings("serial")
-public class InvalidSignatureException extends InvalidWebSocketException
-{
- public static InvalidSignatureException build(Method method, Class<? extends Annotation> annoClass, ParamList... paramlists)
- {
- // Build big detailed exception to help the developer
- StringBuilder err = new StringBuilder();
- err.append("Invalid declaration of ");
- err.append(method);
- err.append(StringUtil.__LINE_SEPARATOR);
-
- err.append("Acceptable method declarations for @");
- err.append(annoClass.getSimpleName());
- err.append(" are:");
- for (ParamList validParams : paramlists)
- {
- for (Class<?>[] params : validParams)
- {
- err.append(StringUtil.__LINE_SEPARATOR);
- err.append("public void ").append(method.getName());
- err.append('(');
- boolean delim = false;
- for (Class<?> type : params)
- {
- if (delim)
- {
- err.append(',');
- }
- err.append(' ');
- err.append(type.getName());
- if (type.isArray())
- {
- err.append("[]");
- }
- delim = true;
- }
- err.append(')');
- }
- }
- return new InvalidSignatureException(err.toString());
- }
-
- public InvalidSignatureException(String message)
- {
- super(message);
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedEventDriver.java
new file mode 100644
index 0000000..e9b818d
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedEventDriver.java
@@ -0,0 +1,218 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.message.MessageAppender;
+import org.eclipse.jetty.websocket.common.message.MessageInputStream;
+import org.eclipse.jetty.websocket.common.message.MessageReader;
+import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage;
+import org.eclipse.jetty.websocket.common.message.SimpleTextMessage;
+
+/**
+ * Handler for Annotated User WebSocket objects.
+ */
+public class JettyAnnotatedEventDriver extends AbstractEventDriver
+{
+ private final JettyAnnotatedMetadata events;
+ private boolean hasCloseBeenCalled = false;
+
+ public JettyAnnotatedEventDriver(WebSocketPolicy policy, Object websocket, JettyAnnotatedMetadata events)
+ {
+ super(policy,websocket);
+ this.events = events;
+
+ WebSocket anno = websocket.getClass().getAnnotation(WebSocket.class);
+ // Setup the policy
+ if (anno.maxTextMessageSize() > 0)
+ {
+ this.policy.setMaxTextMessageSize(anno.maxTextMessageSize());
+ }
+ if (anno.maxBinaryMessageSize() > 0)
+ {
+ this.policy.setMaxBinaryMessageSize(anno.maxBinaryMessageSize());
+ }
+ if (anno.inputBufferSize() > 0)
+ {
+ this.policy.setInputBufferSize(anno.inputBufferSize());
+ }
+ if (anno.maxIdleTime() > 0)
+ {
+ this.policy.setIdleTimeout(anno.maxIdleTime());
+ }
+ }
+
+ @Override
+ public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException
+ {
+ if (events.onBinary == null)
+ {
+ // not interested in binary events
+ return;
+ }
+
+ if (activeMessage == null)
+ {
+ if (events.onBinary.isStreaming())
+ {
+ activeMessage = new MessageInputStream(session.getConnection());
+ final MessageAppender msg = activeMessage;
+ dispatch(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ events.onBinary.call(websocket,session,msg);
+ }
+ });
+ }
+ else
+ {
+ activeMessage = new SimpleBinaryMessage(this);
+ }
+ }
+
+ appendMessage(buffer,fin);
+ }
+
+ @Override
+ public void onBinaryMessage(byte[] data)
+ {
+ if (events.onBinary != null)
+ {
+ events.onBinary.call(websocket,session,data,0,data.length);
+ }
+ }
+
+ @Override
+ public void onClose(CloseInfo close)
+ {
+ if (hasCloseBeenCalled)
+ {
+ // avoid duplicate close events (possible when using harsh Session.disconnect())
+ return;
+ }
+ hasCloseBeenCalled = true;
+ if (events.onClose != null)
+ {
+ events.onClose.call(websocket,session,close.getStatusCode(),close.getReason());
+ }
+ }
+
+ @Override
+ public void onConnect()
+ {
+ if (events.onConnect != null)
+ {
+ events.onConnect.call(websocket,session);
+ }
+ }
+
+ @Override
+ public void onError(Throwable cause)
+ {
+ if (events.onError != null)
+ {
+ events.onError.call(websocket,session,cause);
+ }
+ }
+
+ @Override
+ public void onFrame(Frame frame)
+ {
+ if (events.onFrame != null)
+ {
+ events.onFrame.call(websocket,session,frame);
+ }
+ }
+
+ @Override
+ public void onInputStream(InputStream stream)
+ {
+ if (events.onBinary != null)
+ {
+ events.onBinary.call(websocket,session,stream);
+ }
+ }
+
+ @Override
+ public void onReader(Reader reader)
+ {
+ if (events.onText != null)
+ {
+ events.onText.call(websocket,session,reader);
+ }
+ }
+
+ @Override
+ public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException
+ {
+ if (events.onText == null)
+ {
+ // not interested in text events
+ return;
+ }
+
+ if (activeMessage == null)
+ {
+ if (events.onText.isStreaming())
+ {
+ activeMessage = new MessageReader(new MessageInputStream(session.getConnection()));
+ final MessageAppender msg = activeMessage;
+ dispatch(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ events.onText.call(websocket,session,msg);
+ }
+ });
+ }
+ else
+ {
+ activeMessage = new SimpleTextMessage(this);
+ }
+ }
+
+ appendMessage(buffer,fin);
+ }
+
+ @Override
+ public void onTextMessage(String message)
+ {
+ if (events.onText != null)
+ {
+ events.onText.call(websocket,session,message);
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[%s]", this.getClass().getSimpleName(), websocket);
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedImpl.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedImpl.java
new file mode 100644
index 0000000..07cf5e5
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedImpl.java
@@ -0,0 +1,65 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+public class JettyAnnotatedImpl implements EventDriverImpl
+{
+ private ConcurrentHashMap<Class<?>, JettyAnnotatedMetadata> cache = new ConcurrentHashMap<>();
+
+ @Override
+ public EventDriver create(Object websocket, WebSocketPolicy policy)
+ {
+ Class<?> websocketClass = websocket.getClass();
+ synchronized (this)
+ {
+ JettyAnnotatedMetadata metadata = cache.get(websocketClass);
+ if (metadata == null)
+ {
+ JettyAnnotatedScanner scanner = new JettyAnnotatedScanner();
+ metadata = scanner.scan(websocketClass);
+ cache.put(websocketClass,metadata);
+ }
+ return new JettyAnnotatedEventDriver(policy,websocket,metadata);
+ }
+ }
+
+ @Override
+ public String describeRule()
+ {
+ return "class is annotated with @" + WebSocket.class.getName();
+ }
+
+ @Override
+ public boolean supports(Object websocket)
+ {
+ WebSocket anno = websocket.getClass().getAnnotation(WebSocket.class);
+ return (anno != null);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s [cache.count=%d]",this.getClass().getSimpleName(),cache.size());
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedMetadata.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedMetadata.java
new file mode 100644
index 0000000..4a5265f
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedMetadata.java
@@ -0,0 +1,53 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod;
+import org.eclipse.jetty.websocket.common.events.annotated.OptionalSessionCallableMethod;
+
+public class JettyAnnotatedMetadata
+{
+ /** @OnWebSocketConnect () */
+ public CallableMethod onConnect;
+ /** @OnWebSocketMessage (byte[], or ByteBuffer, or InputStream) */
+ public OptionalSessionCallableMethod onBinary;
+ /** @OnWebSocketMessage (String, or Reader) */
+ public OptionalSessionCallableMethod onText;
+ /** @OnWebSocketFrame (Frame) */
+ public OptionalSessionCallableMethod onFrame;
+ /** @OnWebSocketError (Throwable) */
+ public OptionalSessionCallableMethod onError;
+ /** @OnWebSocketClose (Frame) */
+ public OptionalSessionCallableMethod onClose;
+
+ @Override
+ public String toString()
+ {
+ StringBuilder s = new StringBuilder();
+ s.append("JettyPojoMetadata[");
+ s.append("onConnect=").append(onConnect);
+ s.append(",onBinary=").append(onBinary);
+ s.append(",onText=").append(onText);
+ s.append(",onFrame=").append(onFrame);
+ s.append(",onError=").append(onError);
+ s.append(",onClose=").append(onClose);
+ s.append("]");
+ return s.toString();
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScanner.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScanner.java
new file mode 100644
index 0000000..eb8c2af
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScanner.java
@@ -0,0 +1,170 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.events.annotated.AbstractMethodAnnotationScanner;
+import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.common.events.annotated.OptionalSessionCallableMethod;
+
+public class JettyAnnotatedScanner extends AbstractMethodAnnotationScanner<JettyAnnotatedMetadata>
+{
+ private static final Logger LOG = Log.getLogger(JettyAnnotatedScanner.class);
+
+ /**
+ * Parameter list for @OnWebSocketMessage (Binary mode)
+ */
+ private static final ParamList validBinaryParams;
+
+ /**
+ * Parameter list for @OnWebSocketConnect
+ */
+ private static final ParamList validConnectParams;
+
+ /**
+ * Parameter list for @OnWebSocketClose
+ */
+ private static final ParamList validCloseParams;
+
+ /**
+ * Parameter list for @OnWebSocketError
+ */
+ private static final ParamList validErrorParams;
+
+ /**
+ * Parameter list for @OnWebSocketFrame
+ */
+ private static final ParamList validFrameParams;
+
+ /**
+ * Parameter list for @OnWebSocketMessage (Text mode)
+ */
+ private static final ParamList validTextParams;
+
+ static
+ {
+ validConnectParams = new ParamList();
+ validConnectParams.addParams(Session.class);
+
+ validCloseParams = new ParamList();
+ validCloseParams.addParams(int.class,String.class);
+ validCloseParams.addParams(Session.class,int.class,String.class);
+
+ validErrorParams = new ParamList();
+ validErrorParams.addParams(Throwable.class);
+ validErrorParams.addParams(Session.class,Throwable.class);
+
+ validTextParams = new ParamList();
+ validTextParams.addParams(String.class);
+ validTextParams.addParams(Session.class,String.class);
+ validTextParams.addParams(Reader.class);
+ validTextParams.addParams(Session.class,Reader.class);
+
+ validBinaryParams = new ParamList();
+ validBinaryParams.addParams(byte[].class,int.class,int.class);
+ validBinaryParams.addParams(Session.class,byte[].class,int.class,int.class);
+ validBinaryParams.addParams(InputStream.class);
+ validBinaryParams.addParams(Session.class,InputStream.class);
+
+ validFrameParams = new ParamList();
+ validFrameParams.addParams(Frame.class);
+ validFrameParams.addParams(Session.class,Frame.class);
+ }
+
+ @Override
+ public void onMethodAnnotation(JettyAnnotatedMetadata metadata, Class<?> pojo, Method method, Annotation annotation)
+ {
+ LOG.debug("onMethodAnnotation({}, {}, {}, {})",metadata,pojo,method,annotation);
+
+ if (isAnnotation(annotation,OnWebSocketConnect.class))
+ {
+ assertValidSignature(method,OnWebSocketConnect.class,validConnectParams);
+ assertUnset(metadata.onConnect,OnWebSocketConnect.class,method);
+ metadata.onConnect = new CallableMethod(pojo,method);
+ return;
+ }
+
+ if (isAnnotation(annotation,OnWebSocketMessage.class))
+ {
+ if (isSignatureMatch(method,validTextParams))
+ {
+ // Text mode
+ assertUnset(metadata.onText,OnWebSocketMessage.class,method);
+ metadata.onText = new OptionalSessionCallableMethod(pojo,method);
+ return;
+ }
+
+ if (isSignatureMatch(method,validBinaryParams))
+ {
+ // Binary Mode
+ // TODO
+ assertUnset(metadata.onBinary,OnWebSocketMessage.class,method);
+ metadata.onBinary = new OptionalSessionCallableMethod(pojo,method);
+ return;
+ }
+
+ throw InvalidSignatureException.build(method,OnWebSocketMessage.class,validTextParams,validBinaryParams);
+ }
+
+ if (isAnnotation(annotation,OnWebSocketClose.class))
+ {
+ assertValidSignature(method,OnWebSocketClose.class,validCloseParams);
+ assertUnset(metadata.onClose,OnWebSocketClose.class,method);
+ metadata.onClose = new OptionalSessionCallableMethod(pojo,method);
+ return;
+ }
+
+ if (isAnnotation(annotation,OnWebSocketError.class))
+ {
+ assertValidSignature(method,OnWebSocketError.class,validErrorParams);
+ assertUnset(metadata.onError,OnWebSocketError.class,method);
+ metadata.onError = new OptionalSessionCallableMethod(pojo,method);
+ return;
+ }
+
+ if (isAnnotation(annotation,OnWebSocketFrame.class))
+ {
+ assertValidSignature(method,OnWebSocketFrame.class,validFrameParams);
+ assertUnset(metadata.onFrame,OnWebSocketFrame.class,method);
+ metadata.onFrame = new OptionalSessionCallableMethod(pojo,method);
+ return;
+ }
+ }
+
+ public JettyAnnotatedMetadata scan(Class<?> pojo)
+ {
+ JettyAnnotatedMetadata metadata = new JettyAnnotatedMetadata();
+ scanMethodAnnotations(metadata,pojo);
+ return metadata;
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerEventDriver.java
new file mode 100644
index 0000000..a122b25
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerEventDriver.java
@@ -0,0 +1,135 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketListener;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage;
+import org.eclipse.jetty.websocket.common.message.SimpleTextMessage;
+
+/**
+ * Handler for {@link WebSocketListener} based User WebSocket implementations.
+ */
+public class JettyListenerEventDriver extends AbstractEventDriver
+{
+ private static final Logger LOG = Log.getLogger(JettyListenerEventDriver.class);
+ private final WebSocketListener listener;
+ private boolean hasCloseBeenCalled = false;
+
+ public JettyListenerEventDriver(WebSocketPolicy policy, WebSocketListener listener)
+ {
+ super(policy,listener);
+ this.listener = listener;
+ }
+
+ @Override
+ public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException
+ {
+ if (activeMessage == null)
+ {
+ activeMessage = new SimpleBinaryMessage(this);
+ }
+
+ appendMessage(buffer,fin);
+ }
+
+ @Override
+ public void onBinaryMessage(byte[] data)
+ {
+ listener.onWebSocketBinary(data,0,data.length);
+ }
+
+ @Override
+ public void onClose(CloseInfo close)
+ {
+ if (hasCloseBeenCalled)
+ {
+ // avoid duplicate close events (possible when using harsh Session.disconnect())
+ return;
+ }
+ hasCloseBeenCalled = true;
+
+ int statusCode = close.getStatusCode();
+ String reason = close.getReason();
+ listener.onWebSocketClose(statusCode,reason);
+ }
+
+ @Override
+ public void onConnect()
+ {
+ LOG.debug("onConnect()");
+ listener.onWebSocketConnect(session);
+ }
+
+ @Override
+ public void onError(Throwable cause)
+ {
+ listener.onWebSocketError(cause);
+ }
+
+ @Override
+ public void onFrame(Frame frame)
+ {
+ /* ignore, not supported by WebSocketListener */
+ }
+
+ @Override
+ public void onInputStream(InputStream stream)
+ {
+ /* not supported in Listener mode (yet) */
+ }
+
+ @Override
+ public void onReader(Reader reader)
+ {
+ /* not supported in Listener mode (yet) */
+ }
+
+ @Override
+ public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException
+ {
+ if (activeMessage == null)
+ {
+ activeMessage = new SimpleTextMessage(this);
+ }
+
+ appendMessage(buffer,fin);
+ }
+
+ @Override
+ public void onTextMessage(String message)
+ {
+ listener.onWebSocketText(message);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[%s]",JettyListenerEventDriver.class.getSimpleName(),listener.getClass().getName());
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerImpl.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerImpl.java
new file mode 100644
index 0000000..d62e9e7
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerImpl.java
@@ -0,0 +1,44 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import org.eclipse.jetty.websocket.api.WebSocketListener;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+
+public class JettyListenerImpl implements EventDriverImpl
+{
+ @Override
+ public EventDriver create(Object websocket, WebSocketPolicy policy)
+ {
+ WebSocketListener listener = (WebSocketListener)websocket;
+ return new JettyListenerEventDriver(policy,listener);
+ }
+
+ @Override
+ public String describeRule()
+ {
+ return "class implements " + WebSocketListener.class.getName();
+ }
+
+ @Override
+ public boolean supports(Object websocket)
+ {
+ return (websocket instanceof WebSocketListener);
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/ListenerEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/ListenerEventDriver.java
deleted file mode 100644
index 07bad1a..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/ListenerEventDriver.java
+++ /dev/null
@@ -1,129 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.events;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.WebSocketListener;
-import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.message.MessageAppender;
-import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage;
-import org.eclipse.jetty.websocket.common.message.SimpleTextMessage;
-
-/**
- * Handler for {@link WebSocketListener} based User WebSocket implementations.
- */
-public class ListenerEventDriver extends EventDriver
-{
- private static final Logger LOG = Log.getLogger(ListenerEventDriver.class);
- private final WebSocketListener listener;
- private MessageAppender activeMessage;
- private boolean hasCloseBeenCalled = false;
-
- public ListenerEventDriver(WebSocketPolicy policy, WebSocketListener listener)
- {
- super(policy,listener);
- this.listener = listener;
- }
-
- @Override
- public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException
- {
- if (activeMessage == null)
- {
- activeMessage = new SimpleBinaryMessage(this);
- }
-
- activeMessage.appendMessage(buffer);
-
- if (fin)
- {
- activeMessage.messageComplete();
- activeMessage = null;
- }
- }
-
- @Override
- public void onBinaryMessage(byte[] data)
- {
- listener.onWebSocketBinary(data,0,data.length);
- }
-
- @Override
- public void onClose(CloseInfo close)
- {
- if (hasCloseBeenCalled)
- {
- // avoid duplicate close events (possible when using harsh Session.disconnect())
- return;
- }
- hasCloseBeenCalled = true;
-
- int statusCode = close.getStatusCode();
- String reason = close.getReason();
- listener.onWebSocketClose(statusCode,reason);
- }
-
- @Override
- public void onConnect()
- {
- LOG.debug("onConnect()");
- listener.onWebSocketConnect(session);
- }
-
- @Override
- public void onError(Throwable cause)
- {
- listener.onWebSocketError(cause);
- }
-
- @Override
- public void onFrame(Frame frame)
- {
- /* ignore, not supported by WebSocketListener */
- }
-
- @Override
- public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException
- {
- if (activeMessage == null)
- {
- activeMessage = new SimpleTextMessage(this);
- }
-
- activeMessage.appendMessage(buffer);
-
- if (fin)
- {
- activeMessage.messageComplete();
- activeMessage = null;
- }
- }
-
- @Override
- public void onTextMessage(String message)
- {
- listener.onWebSocketText(message);
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/AbstractMethodAnnotationScanner.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/AbstractMethodAnnotationScanner.java
new file mode 100644
index 0000000..5abd9d7
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/AbstractMethodAnnotationScanner.java
@@ -0,0 +1,195 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events.annotated;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.common.events.ParamList;
+
+/**
+ * Basic scanner for Annotated Methods
+ */
+public abstract class AbstractMethodAnnotationScanner<T>
+{
+ protected void assertIsPublicNonStatic(Method method)
+ {
+ int mods = method.getModifiers();
+ if (!Modifier.isPublic(mods))
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Invalid declaration of ");
+ err.append(method);
+ err.append(StringUtil.__LINE_SEPARATOR);
+
+ err.append("Method modifier must be public");
+
+ throw new InvalidWebSocketException(err.toString());
+ }
+
+ if (Modifier.isStatic(mods))
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Invalid declaration of ");
+ err.append(method);
+ err.append(StringUtil.__LINE_SEPARATOR);
+
+ err.append("Method modifier may not be static");
+
+ throw new InvalidWebSocketException(err.toString());
+ }
+ }
+
+ protected void assertIsReturn(Method method, Class<?> type)
+ {
+ if (!type.equals(method.getReturnType()))
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Invalid declaration of ");
+ err.append(method);
+ err.append(StringUtil.__LINE_SEPARATOR);
+
+ err.append("Return type must be ").append(type);
+
+ throw new InvalidWebSocketException(err.toString());
+ }
+ }
+
+ protected void assertIsVoidReturn(Method method)
+ {
+ assertIsReturn(method,Void.TYPE);
+ }
+
+ protected void assertUnset(CallableMethod callable, Class<? extends Annotation> annoClass, Method method)
+ {
+ if (callable != null)
+ {
+ // Attempt to add duplicate frame type (a no-no)
+ StringBuilder err = new StringBuilder();
+ err.append("Duplicate @").append(annoClass.getSimpleName()).append(" declaration on ");
+ err.append(method);
+ err.append(StringUtil.__LINE_SEPARATOR);
+
+ err.append("@").append(annoClass.getSimpleName()).append(" previously declared at ");
+ err.append(callable.getMethod());
+
+ throw new InvalidWebSocketException(err.toString());
+ }
+ }
+
+ protected void assertValidSignature(Method method, Class<? extends Annotation> annoClass, ParamList validParams)
+ {
+ assertIsPublicNonStatic(method);
+ assertIsReturn(method,Void.TYPE);
+
+ boolean valid = false;
+
+ // validate parameters
+ Class<?> actual[] = method.getParameterTypes();
+ for (Class<?>[] params : validParams)
+ {
+ if (isSameParameters(actual,params))
+ {
+ valid = true;
+ break;
+ }
+ }
+
+ if (!valid)
+ {
+ throw InvalidSignatureException.build(method,annoClass,validParams);
+ }
+ }
+
+ public boolean isAnnotation(Annotation annotation, Class<? extends Annotation> annotationClass)
+ {
+ return annotation.annotationType().equals(annotationClass);
+ }
+
+ public boolean isSameParameters(Class<?>[] actual, Class<?>[] params)
+ {
+ if (actual.length != params.length)
+ {
+ // skip
+ return false;
+ }
+
+ int len = params.length;
+ for (int i = 0; i < len; i++)
+ {
+ if (!actual[i].equals(params[i]))
+ {
+ return false; // not valid
+ }
+ }
+
+ return true;
+ }
+
+ protected boolean isSignatureMatch(Method method, ParamList validParams)
+ {
+ assertIsPublicNonStatic(method);
+ assertIsReturn(method,Void.TYPE);
+
+ // validate parameters
+ Class<?> actual[] = method.getParameterTypes();
+ for (Class<?>[] params : validParams)
+ {
+ if (isSameParameters(actual,params))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected boolean isTypeAnnotated(Class<?> pojo, Class<? extends Annotation> expectedAnnotation)
+ {
+ return pojo.getAnnotation(expectedAnnotation) != null;
+ }
+
+ public abstract void onMethodAnnotation(T metadata, Class<?> pojo, Method method, Annotation annotation);
+
+ public void scanMethodAnnotations(T metadata, Class<?> pojo)
+ {
+ Class<?> clazz = pojo;
+
+ while ((clazz != null) && Object.class.isAssignableFrom(clazz))
+ {
+ for (Method method : clazz.getDeclaredMethods())
+ {
+ Annotation annotations[] = method.getAnnotations();
+ if ((annotations == null) || (annotations.length <= 0))
+ {
+ continue; // skip
+ }
+ for (Annotation annotation : annotations)
+ {
+ onMethodAnnotation(metadata,clazz,method,annotation);
+ }
+ }
+
+ clazz = clazz.getSuperclass();
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/CallableMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/CallableMethod.java
new file mode 100644
index 0000000..1b40451
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/CallableMethod.java
@@ -0,0 +1,123 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events.annotated;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Objects;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+
+/**
+ * A Callable Method
+ */
+public class CallableMethod
+{
+ private static final Logger LOG = Log.getLogger(CallableMethod.class);
+ protected final Class<?> pojo;
+ protected final Method method;
+ protected Class<?>[] paramTypes;
+
+ public CallableMethod(Class<?> pojo, Method method)
+ {
+ Objects.requireNonNull(pojo, "Pojo cannot be null");
+ Objects.requireNonNull(method, "Method cannot be null");
+ this.pojo = pojo;
+ this.method = method;
+ this.paramTypes = method.getParameterTypes();
+ }
+
+ public Object call(Object obj, Object... args)
+ {
+ if ((this.pojo == null) || (this.method == null))
+ {
+ LOG.warn("Cannot execute call: pojo={}, method={}",pojo,method);
+ return null; // no call event method determined
+ }
+
+ if (obj == null)
+ {
+ LOG.warn("Cannot call {} on null object",this.method);
+ return null;
+ }
+
+ if (args.length < paramTypes.length)
+ {
+ throw new IllegalArgumentException("Call arguments length [" + args.length + "] must always be greater than or equal to captured args length ["
+ + paramTypes.length + "]");
+ }
+
+ try
+ {
+ return this.method.invoke(obj,args);
+ }
+ catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Cannot call method ");
+ err.append(ReflectUtils.toString(pojo,method));
+ err.append(" with args: [");
+
+ boolean delim = false;
+ for (Object arg : args)
+ {
+ if (delim)
+ {
+ err.append(", ");
+ }
+ if (arg == null)
+ {
+ err.append("<null>");
+ }
+ else
+ {
+ err.append(arg.getClass().getName());
+ }
+ delim = true;
+ }
+ err.append("]");
+
+ throw new WebSocketException(err.toString(),e);
+ }
+ }
+
+ public Method getMethod()
+ {
+ return method;
+ }
+
+ public Class<?>[] getParamTypes()
+ {
+ return paramTypes;
+ }
+
+ public Class<?> getPojo()
+ {
+ return pojo;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[%s]",this.getClass().getSimpleName(),method.toGenericString());
+ }
+}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethod.java
new file mode 100644
index 0000000..dcc1e93
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethod.java
@@ -0,0 +1,154 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events.annotated;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+
+public class EventMethod
+{
+ private static final Logger LOG = Log.getLogger(EventMethod.class);
+
+ private static Object[] dropFirstArg(Object[] args)
+ {
+ if (args.length == 1)
+ {
+ return new Object[0];
+ }
+ Object ret[] = new Object[args.length - 1];
+ System.arraycopy(args,1,ret,0,ret.length);
+ return ret;
+ }
+
+ protected Class<?> pojo;
+ protected Method method;
+ private boolean hasSession = false;
+ private boolean isStreaming = false;
+ private Class<?>[] paramTypes;
+
+ public EventMethod(Class<?> pojo, Method method)
+ {
+ this.pojo = pojo;
+ this.paramTypes = method.getParameterTypes();
+ this.method = method;
+ identifyPresentParamTypes();
+ }
+
+ public EventMethod(Class<?> pojo, String methodName, Class<?>... paramTypes)
+ {
+ try
+ {
+ this.pojo = pojo;
+ this.paramTypes = paramTypes;
+ this.method = pojo.getMethod(methodName,paramTypes);
+ identifyPresentParamTypes();
+ }
+ catch (NoSuchMethodException | SecurityException e)
+ {
+ LOG.warn("Cannot use method {}({}): {}",methodName,paramTypes,e.getMessage());
+ this.method = null;
+ }
+ }
+
+ public void call(Object obj, Object... args)
+ {
+ if ((this.pojo == null) || (this.method == null))
+ {
+ LOG.warn("Cannot execute call: pojo={}, method={}",pojo,method);
+ return; // no call event method determined
+ }
+ if (obj == null)
+ {
+ LOG.warn("Cannot call {} on null object",this.method);
+ return;
+ }
+ if (args.length > paramTypes.length)
+ {
+ Object trimArgs[] = dropFirstArg(args);
+ call(obj,trimArgs);
+ return;
+ }
+ if (args.length < paramTypes.length)
+ {
+ throw new IllegalArgumentException("Call arguments length [" + args.length + "] must always be greater than or equal to captured args length ["
+ + paramTypes.length + "]");
+ }
+
+ try
+ {
+ this.method.invoke(obj,args);
+ }
+ catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
+ {
+ String err = String.format("Cannot call method %s on %s with args: %s",method,pojo, QuoteUtil.join(args,","));
+ throw new WebSocketException(err,e);
+ }
+ }
+
+ public Method getMethod()
+ {
+ return method;
+ }
+
+ protected Class<?>[] getParamTypes()
+ {
+ return this.paramTypes;
+ }
+
+ private void identifyPresentParamTypes()
+ {
+ this.hasSession = false;
+ this.isStreaming = false;
+
+ if (paramTypes == null)
+ {
+ return;
+ }
+
+ for (Class<?> paramType : paramTypes)
+ {
+ if (Session.class.isAssignableFrom(paramType))
+ {
+ this.hasSession = true;
+ }
+ if (Reader.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType))
+ {
+ this.isStreaming = true;
+ }
+ }
+ }
+
+ public boolean isHasSession()
+ {
+ return hasSession;
+ }
+
+ public boolean isStreaming()
+ {
+ return isStreaming;
+ }
+}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethods.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethods.java
new file mode 100644
index 0000000..bda2209
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethods.java
@@ -0,0 +1,105 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events.annotated;
+
+/**
+ * A representation of the methods available to call for a particular class.
+ */
+public class EventMethods
+{
+ private Class<?> pojoClass;
+ public EventMethod onConnect = null;
+ public EventMethod onClose = null;
+ public EventMethod onBinary = null;
+ public EventMethod onText = null;
+ public EventMethod onError = null;
+ public EventMethod onFrame = null;
+
+ public EventMethods(Class<?> pojoClass)
+ {
+ this.pojoClass = pojoClass;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ EventMethods other = (EventMethods)obj;
+ if (pojoClass == null)
+ {
+ if (other.pojoClass != null)
+ {
+ return false;
+ }
+ }
+ else if (!pojoClass.getName().equals(other.pojoClass.getName()))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public Class<?> getPojoClass()
+ {
+ return pojoClass;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((pojoClass == null)?0:pojoClass.getName().hashCode());
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("EventMethods [pojoClass=");
+ builder.append(pojoClass);
+ builder.append(", onConnect=");
+ builder.append(onConnect);
+ builder.append(", onClose=");
+ builder.append(onClose);
+ builder.append(", onBinary=");
+ builder.append(onBinary);
+ builder.append(", onText=");
+ builder.append(onText);
+ builder.append(", onException=");
+ builder.append(onError);
+ builder.append(", onFrame=");
+ builder.append(onFrame);
+ builder.append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/InvalidSignatureException.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/InvalidSignatureException.java
new file mode 100644
index 0000000..a953164
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/InvalidSignatureException.java
@@ -0,0 +1,79 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events.annotated;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.common.events.ParamList;
+
+@SuppressWarnings("serial")
+public class InvalidSignatureException extends InvalidWebSocketException
+{
+ public static InvalidSignatureException build(Method method, Class<? extends Annotation> annoClass, ParamList... paramlists)
+ {
+ // Build big detailed exception to help the developer
+ StringBuilder err = new StringBuilder();
+ err.append("Invalid declaration of ");
+ err.append(method);
+ err.append(StringUtil.__LINE_SEPARATOR);
+
+ err.append("Acceptable method declarations for @");
+ err.append(annoClass.getSimpleName());
+ err.append(" are:");
+ for (ParamList validParams : paramlists)
+ {
+ for (Class<?>[] params : validParams)
+ {
+ err.append(StringUtil.__LINE_SEPARATOR);
+ err.append("public void ").append(method.getName());
+ err.append('(');
+ boolean delim = false;
+ for (Class<?> type : params)
+ {
+ if (delim)
+ {
+ err.append(',');
+ }
+ err.append(' ');
+ err.append(type.getName());
+ if (type.isArray())
+ {
+ err.append("[]");
+ }
+ delim = true;
+ }
+ err.append(')');
+ }
+ }
+ return new InvalidSignatureException(err.toString());
+ }
+
+ public InvalidSignatureException(String message)
+ {
+ super(message);
+ }
+
+ public InvalidSignatureException(String message, Throwable cause)
+ {
+ super(message,cause);
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/OptionalSessionCallableMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/OptionalSessionCallableMethod.java
new file mode 100644
index 0000000..b0a17ed
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/OptionalSessionCallableMethod.java
@@ -0,0 +1,91 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events.annotated;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.Method;
+
+import org.eclipse.jetty.websocket.api.Session;
+
+/**
+ * Simple CallableMethod that manages the optional {@link Session} argument
+ */
+public class OptionalSessionCallableMethod extends CallableMethod
+{
+ private final boolean wantsSession;
+ private final boolean streaming;
+
+ public OptionalSessionCallableMethod(Class<?> pojo, Method method)
+ {
+ super(pojo,method);
+
+ boolean foundConnection = false;
+ boolean foundStreaming = false;
+
+ if (paramTypes != null)
+ {
+ for (Class<?> paramType : paramTypes)
+ {
+ if (Session.class.isAssignableFrom(paramType))
+ {
+ foundConnection = true;
+ }
+ if (Reader.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType))
+ {
+ foundStreaming = true;
+ }
+ }
+ }
+
+ this.wantsSession = foundConnection;
+ this.streaming = foundStreaming;
+ }
+
+ public void call(Object obj, Session connection, Object... args)
+ {
+ if (wantsSession)
+ {
+ Object fullArgs[] = new Object[args.length + 1];
+ fullArgs[0] = connection;
+ System.arraycopy(args,0,fullArgs,1,args.length);
+ call(obj,fullArgs);
+ }
+ else
+ {
+ call(obj,args);
+ }
+ }
+
+ public boolean isSessionAware()
+ {
+ return wantsSession;
+ }
+
+ public boolean isStreaming()
+ {
+ return streaming;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[%s]",this.getClass().getSimpleName(),method.toGenericString());
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtension.java
index 5e208ec..84e2f14 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtension.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtension.java
@@ -26,7 +26,6 @@
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.Extension;
@@ -108,7 +107,7 @@
}
@Override
- public void incomingError(WebSocketException e)
+ public void incomingError(Throwable e)
{
nextIncomingError(e);
}
@@ -152,24 +151,7 @@
return false;
}
- /**
- * Used to indicate that the extension works as a decoder of TEXT Data Frames.
- * <p>
- * This is used to adjust validation during parsing/generating, as per spec TEXT Data Frames can only contain UTF8 encoded String data.
- * <p>
- * Example: a compression extension will process a compressed set of text data, the parser/generator should no longer be concerned about the validity of the
- * TEXT Data Frames as this is now the responsibility of the extension.
- *
- * @return true if extension will process TEXT Data Frames, false if extension makes no modifications of TEXT Data Frames. If false, the parser/generator is
- * now free to validate the conformance to spec of TEXT Data Frames.
- */
- @Override
- public boolean isTextDataDecoder()
- {
- return false;
- }
-
- protected void nextIncomingError(WebSocketException e)
+ protected void nextIncomingError(Throwable e)
{
this.nextIncoming.incomingError(e);
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java
index 29450cf..d0c1ae2 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java
@@ -28,7 +28,6 @@
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.Extension;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
@@ -192,7 +191,7 @@
}
@Override
- public void incomingError(WebSocketException e)
+ public void incomingError(Throwable e)
{
nextIncoming.incomingError(e);
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java
index 6ed9224..f56fd1b 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java
@@ -25,10 +25,6 @@
import org.eclipse.jetty.websocket.api.extensions.Extension;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
-import org.eclipse.jetty.websocket.common.extensions.compress.FrameCompressionExtension;
-import org.eclipse.jetty.websocket.common.extensions.compress.MessageCompressionExtension;
-import org.eclipse.jetty.websocket.common.extensions.fragment.FragmentExtension;
-import org.eclipse.jetty.websocket.common.extensions.identity.IdentityExtension;
public class WebSocketExtensionFactory extends ExtensionFactory
{
@@ -40,13 +36,6 @@
super();
this.policy = policy;
this.bufferPool = bufferPool;
-
- register("identity",IdentityExtension.class);
- register("fragment",FragmentExtension.class);
- /* FIXME: Disabled due to bug report - http://bugs.eclipse.org/395444
- * register("x-webkit-deflate-frame",FrameCompressionExtension.class);
- * register("permessage-compress",MessageCompressionExtension.class);
- */
}
@Override
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/ByteAccumulator.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/ByteAccumulator.java
new file mode 100644
index 0000000..bdc0e78
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/ByteAccumulator.java
@@ -0,0 +1,83 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.MessageTooLargeException;
+
+public class ByteAccumulator
+{
+ private static class Buf
+ {
+ public Buf(byte[] buffer, int offset, int length)
+ {
+ this.buffer = buffer;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ byte[] buffer;
+ int offset;
+ int length;
+ }
+
+ private final int maxSize;
+ private int length = 0;
+ private List<Buf> buffers;
+
+ public ByteAccumulator(int maxOverallBufferSize)
+ {
+ this.maxSize = maxOverallBufferSize;
+ this.buffers = new ArrayList<>();
+ }
+
+ public void addBuffer(byte buf[], int offset, int length)
+ {
+ if (this.length + length > maxSize)
+ {
+ throw new MessageTooLargeException("Frame is too large");
+ }
+ buffers.add(new Buf(buf,offset,length));
+ this.length += length;
+ }
+
+ public int getLength()
+ {
+ return length;
+ }
+
+ public ByteBuffer getByteBuffer(ByteBufferPool pool)
+ {
+ ByteBuffer ret = pool.acquire(length,false);
+ BufferUtil.clearToFill(ret);
+
+ for (Buf buf : buffers)
+ {
+ ret.put(buf.buffer, buf.offset, buf.length);
+ }
+
+ BufferUtil.flipToFlush(ret,0);
+ return ret;
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressionMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressionMethod.java
deleted file mode 100644
index 49c415d..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressionMethod.java
+++ /dev/null
@@ -1,44 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.compress;
-
-import java.nio.ByteBuffer;
-
-/**
- * Compression Method
- */
-public interface CompressionMethod
-{
- public interface Process
- {
- public void begin();
-
- public void end();
-
- public void input(ByteBuffer input);
-
- public boolean isDone();
-
- public ByteBuffer process();
- }
-
- public Process compress();
-
- public Process decompress();
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateCompressionMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateCompressionMethod.java
deleted file mode 100644
index a989254..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateCompressionMethod.java
+++ /dev/null
@@ -1,260 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.compress;
-
-import java.nio.ByteBuffer;
-import java.util.zip.DataFormatException;
-import java.util.zip.Deflater;
-import java.util.zip.Inflater;
-
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.BadPayloadException;
-
-/**
- * Deflate Compression Method
- */
-public class DeflateCompressionMethod implements CompressionMethod
-{
- private static class DeflaterProcess implements CompressionMethod.Process
- {
- private static final boolean BFINAL_HACK = Boolean.parseBoolean(System.getProperty("jetty.websocket.bfinal.hack","true"));
-
- private final Deflater deflater;
- private int bufferSize = DEFAULT_BUFFER_SIZE;
-
- public DeflaterProcess(boolean nowrap)
- {
- deflater = new Deflater(Deflater.BEST_COMPRESSION,nowrap);
- deflater.setStrategy(Deflater.DEFAULT_STRATEGY);
- }
-
- @Override
- public void begin()
- {
- deflater.reset();
- }
-
- @Override
- public void end()
- {
- deflater.reset();
- }
-
- @Override
- public void input(ByteBuffer input)
- {
- if (LOG.isDebugEnabled())
- {
- LOG.debug("input: {}",BufferUtil.toDetailString(input));
- }
-
- // Set the data that is uncompressed to the deflater
- byte raw[] = BufferUtil.toArray(input);
- deflater.setInput(raw,0,raw.length);
- deflater.finish();
- }
-
- @Override
- public boolean isDone()
- {
- return deflater.finished();
- }
-
- @Override
- public ByteBuffer process()
- {
- // prepare the output buffer
- ByteBuffer buf = ByteBuffer.allocate(bufferSize);
- BufferUtil.clearToFill(buf);
-
- while (!deflater.finished())
- {
- byte out[] = new byte[bufferSize];
- int len = deflater.deflate(out,0,out.length,Deflater.SYNC_FLUSH);
-
- if (LOG.isDebugEnabled())
- {
- LOG.debug("Deflater: finished={}, needsInput={}, len={}",deflater.finished(),deflater.needsInput(),len);
- }
-
- buf.put(out,0,len);
- }
- BufferUtil.flipToFlush(buf,0);
-
- if (BFINAL_HACK)
- {
- /*
- * Per the spec, it says that BFINAL 1 or 0 are allowed.
- *
- * However, Java always uses BFINAL 1, whereas the browsers Chromium and Safari fail to decompress when it encounters BFINAL 1.
- *
- * This hack will always set BFINAL 0
- */
- byte b0 = buf.get(0);
- if ((b0 & 1) != 0) // if BFINAL 1
- {
- buf.put(0,(b0 ^= 1)); // flip bit to BFINAL 0
- }
- }
- return buf;
- }
-
- public void setBufferSize(int bufferSize)
- {
- this.bufferSize = bufferSize;
- }
- }
-
- private static class InflaterProcess implements CompressionMethod.Process
- {
- /** Tail Bytes per Spec */
- private static final byte[] TAIL = new byte[]
- { 0x00, 0x00, (byte)0xFF, (byte)0xFF };
- private final Inflater inflater;
- private int bufferSize = DEFAULT_BUFFER_SIZE;
-
- public InflaterProcess(boolean nowrap) {
- inflater = new Inflater(nowrap);
- }
-
- @Override
- public void begin()
- {
- inflater.reset();
- }
-
- @Override
- public void end()
- {
- inflater.reset();
- }
-
- @Override
- public void input(ByteBuffer input)
- {
- if (LOG.isDebugEnabled())
- {
- LOG.debug("inflate: {}",BufferUtil.toDetailString(input));
- LOG.debug("Input Data: {}",TypeUtil.toHexString(BufferUtil.toArray(input)));
- }
-
- // Set the data that is compressed (+ TAIL) to the inflater
- int len = input.remaining() + 4;
- byte raw[] = new byte[len];
- int inlen = input.remaining();
- input.slice().get(raw,0,inlen);
- System.arraycopy(TAIL,0,raw,inlen,TAIL.length);
- inflater.setInput(raw,0,raw.length);
- }
-
- @Override
- public boolean isDone()
- {
- return (inflater.getRemaining() <= 0) || inflater.finished();
- }
-
- @Override
- public ByteBuffer process()
- {
- // Establish place for inflated data
- byte buf[] = new byte[bufferSize];
- try
- {
- int inflated = inflater.inflate(buf);
- if (inflated == 0)
- {
- return null;
- }
-
- ByteBuffer ret = BufferUtil.toBuffer(buf,0,inflated);
-
- if (LOG.isDebugEnabled())
- {
- LOG.debug("uncompressed={}",BufferUtil.toDetailString(ret));
- }
-
- return ret;
- }
- catch (DataFormatException e)
- {
- LOG.warn(e);
- throw new BadPayloadException(e);
- }
- }
-
- public void setBufferSize(int bufferSize)
- {
- this.bufferSize = bufferSize;
- }
- }
-
- private static final int DEFAULT_BUFFER_SIZE = 61*1024;
-
- private static final Logger LOG = Log.getLogger(DeflateCompressionMethod.class);
-
- private int bufferSize = 64 * 1024;
- private final DeflaterProcess compress;
- private final InflaterProcess decompress;
-
- public DeflateCompressionMethod()
- {
- /*
- * Specs specify that head/tail of deflate are not to be present.
- *
- * So lets not use the wrapped format of bytes.
- *
- * Setting nowrap to true prevents the Deflater from writing the head/tail bytes and the Inflater from expecting the head/tail bytes.
- */
- boolean nowrap = true;
-
- this.compress = new DeflaterProcess(nowrap);
- this.decompress = new InflaterProcess(nowrap);
- }
-
- @Override
- public Process compress()
- {
- return compress;
- }
-
- @Override
- public Process decompress()
- {
- return decompress;
- }
-
- public int getBufferSize()
- {
- return bufferSize;
- }
-
- public void setBufferSize(int size)
- {
- if (size < 64)
- {
- throw new IllegalArgumentException("Buffer Size [" + size + "] cannot be less than 64 bytes");
- }
- this.bufferSize = size;
- this.compress.setBufferSize(bufferSize);
- this.decompress.setBufferSize(bufferSize);
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtension.java
new file mode 100644
index 0000000..714e9b3
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtension.java
@@ -0,0 +1,236 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import java.nio.ByteBuffer;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BadPayloadException;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
+
+/**
+ * Implementation of the <a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate-05.txt">deflate-frame</a> extension seen out in the
+ * wild.
+ */
+public class DeflateFrameExtension extends AbstractExtension
+{
+ private static final boolean BFINAL_HACK = Boolean.parseBoolean(System.getProperty("jetty.websocket.bfinal.hack","true"));
+ private static final Logger LOG = Log.getLogger(DeflateFrameExtension.class);
+
+ private static final int OVERHEAD = 64;
+ /** Tail Bytes per Spec */
+ private static final byte[] TAIL = new byte[]
+ { 0x00, 0x00, (byte)0xFF, (byte)0xFF };
+ private int bufferSize = 64 * 1024;
+ private Deflater compressor;
+ private Inflater decompressor;
+
+ @Override
+ public String getName()
+ {
+ return "deflate-frame";
+ }
+
+ @Override
+ public synchronized void incomingFrame(Frame frame)
+ {
+ if (OpCode.isControlFrame(frame.getOpCode()) || !frame.isRsv1())
+ {
+ // Cannot modify incoming control frames or ones with RSV1 set.
+ nextIncomingFrame(frame);
+ return;
+ }
+
+ if (!frame.hasPayload())
+ {
+ // no payload? nothing to do.
+ nextIncomingFrame(frame);
+ return;
+ }
+
+ // Prime the decompressor
+ ByteBuffer payload = frame.getPayload();
+ int inlen = payload.remaining();
+ byte compressed[] = new byte[inlen + TAIL.length];
+ payload.get(compressed,0,inlen);
+ System.arraycopy(TAIL,0,compressed,inlen,TAIL.length);
+ decompressor.setInput(compressed,0,compressed.length);
+
+ // Since we don't track text vs binary vs continuation state, just grab whatever is the greater value.
+ int maxSize = Math.max(getPolicy().getMaxTextMessageSize(),getPolicy().getMaxBinaryMessageBufferSize());
+ ByteAccumulator accumulator = new ByteAccumulator(maxSize);
+
+ DataFrame out = new DataFrame(frame);
+ out.setRsv1(false); // Unset RSV1
+
+ // Perform decompression
+ while (decompressor.getRemaining() > 0 && !decompressor.finished())
+ {
+ byte outbuf[] = new byte[Math.min(inlen * 2,bufferSize)];
+ try
+ {
+ int len = decompressor.inflate(outbuf);
+ if (len == 0)
+ {
+ if (decompressor.needsInput())
+ {
+ throw new BadPayloadException("Unable to inflate frame, not enough input on frame");
+ }
+ if (decompressor.needsDictionary())
+ {
+ throw new BadPayloadException("Unable to inflate frame, frame erroneously says it needs a dictionary");
+ }
+ }
+ if (len > 0)
+ {
+ accumulator.addBuffer(outbuf,0,len);
+ }
+ }
+ catch (DataFormatException e)
+ {
+ LOG.warn(e);
+ throw new BadPayloadException(e);
+ }
+ }
+
+ // Forward on the frame
+ out.setPayload(accumulator.getByteBuffer(getBufferPool()));
+ nextIncomingFrame(out);
+ }
+
+ /**
+ * Indicates use of RSV1 flag for indicating deflation is in use.
+ * <p>
+ * Also known as the "COMP" framing header bit
+ */
+ @Override
+ public boolean isRsv1User()
+ {
+ return true;
+ }
+
+ @Override
+ public synchronized void outgoingFrame(Frame frame, WriteCallback callback)
+ {
+ if (OpCode.isControlFrame(frame.getOpCode()))
+ {
+ // skip, cannot compress control frames.
+ nextOutgoingFrame(frame,callback);
+ return;
+ }
+
+ if (!frame.hasPayload())
+ {
+ // pass through, nothing to do
+ nextOutgoingFrame(frame,callback);
+ return;
+ }
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("outgoingFrame({}, {}) - {}",OpCode.name(frame.getOpCode()),callback != null?callback.getClass().getSimpleName():"<null>",
+ BufferUtil.toDetailString(frame.getPayload()));
+ }
+
+ // Prime the compressor
+ byte uncompressed[] = BufferUtil.toArray(frame.getPayload());
+
+ // Perform the compression
+ if (!compressor.finished())
+ {
+ compressor.setInput(uncompressed,0,uncompressed.length);
+ byte compressed[] = new byte[uncompressed.length + OVERHEAD];
+
+ while (!compressor.needsInput())
+ {
+ int len = compressor.deflate(compressed,0,compressed.length,Deflater.SYNC_FLUSH);
+ ByteBuffer outbuf = getBufferPool().acquire(len,true);
+ BufferUtil.clearToFill(outbuf);
+
+ if (len > 0)
+ {
+ outbuf.put(compressed,0,len - 4);
+ }
+
+ BufferUtil.flipToFlush(outbuf,0);
+
+ if (len > 0 && BFINAL_HACK)
+ {
+ /*
+ * Per the spec, it says that BFINAL 1 or 0 are allowed.
+ *
+ * However, Java always uses BFINAL 1, whereas the browsers Chromium and Safari fail to decompress when it encounters BFINAL 1.
+ *
+ * This hack will always set BFINAL 0
+ */
+ byte b0 = outbuf.get(0);
+ if ((b0 & 1) != 0) // if BFINAL 1
+ {
+ outbuf.put(0,(b0 ^= 1)); // flip bit to BFINAL 0
+ }
+ }
+
+ DataFrame out = new DataFrame(frame);
+ out.setRsv1(true);
+ out.setPooledBuffer(true);
+ out.setPayload(outbuf);
+
+ if (!compressor.needsInput())
+ {
+ // this is fragmented
+ out.setFin(false);
+ nextOutgoingFrame(out,null); // non final frames have no callback
+ }
+ else
+ {
+ // pass through the callback
+ nextOutgoingFrame(out,callback);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setConfig(ExtensionConfig config)
+ {
+ super.setConfig(config);
+
+ boolean nowrap = true;
+ compressor = new Deflater(Deflater.BEST_COMPRESSION,nowrap);
+ compressor.setStrategy(Deflater.DEFAULT_STRATEGY);
+
+ decompressor = new Inflater(nowrap);
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.getClass().getSimpleName() + "[]";
+ }
+}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/FrameCompressionExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/FrameCompressionExtension.java
deleted file mode 100644
index a548f20..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/FrameCompressionExtension.java
+++ /dev/null
@@ -1,131 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.compress;
-
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.websocket.api.WriteCallback;
-import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
-
-/**
- * Implementation of the <a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate-05.txt">x-webkit-deflate-frame</a> extension seen out
- * in the wild.
- */
-public class FrameCompressionExtension extends AbstractExtension
-{
- private CompressionMethod method = new DeflateCompressionMethod();
-
- @Override
- public synchronized void incomingFrame(Frame frame)
- {
- if (frame.getType().isControl() || !frame.isRsv1())
- {
- // Cannot modify incoming control frames or ones with RSV1 set.
- nextIncomingFrame(frame);
- return;
- }
-
- ByteBuffer data = frame.getPayload();
- method.decompress().input(data);
- while (!method.decompress().isDone())
- {
- ByteBuffer uncompressed = method.decompress().process();
- WebSocketFrame out = new WebSocketFrame(frame).setPayload(uncompressed);
- if (!method.decompress().isDone())
- {
- out.setFin(false);
- }
- out.setRsv1(false); // Unset RSV1 on decompressed frame
- nextIncomingFrame(out);
- }
-
- // reset on every frame.
- // method.decompress().end();
- }
-
- /**
- * Indicates use of RSV1 flag for indicating deflation is in use.
- * <p>
- * Also known as the "COMP" framing header bit
- */
- @Override
- public boolean isRsv1User()
- {
- return true;
- }
-
- /**
- * Indicate that this extensions is now responsible for TEXT Data Frame compliance to the WebSocket spec.
- */
- @Override
- public boolean isTextDataDecoder()
- {
- return true;
- }
-
- @Override
- public synchronized void outgoingFrame(Frame frame, WriteCallback callback)
- {
- if (frame.getType().isControl())
- {
- // skip, cannot compress control frames.
- nextOutgoingFrame(frame,callback);
- return;
- }
-
- ByteBuffer data = frame.getPayload();
-
- // deflate data
- method.compress().input(data);
- while (!method.compress().isDone())
- {
- ByteBuffer buf = method.compress().process();
- WebSocketFrame out = new WebSocketFrame(frame).setPayload(buf);
- out.setRsv1(true);
- if (!method.compress().isDone())
- {
- out.setFin(false);
- nextOutgoingFrame(frame,null); // no callback for start/end frames
- }
- else
- {
- nextOutgoingFrame(out,callback); // pass thru callback
- }
- }
-
- // reset on every frame.
- method.compress().end();
- }
-
- @Override
- public void setConfig(ExtensionConfig config)
- {
- super.setConfig(config);
- }
-
- @Override
- public String toString()
- {
- return this.getClass().getSimpleName() + "[]";
- }
-}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/MessageCompressionExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/MessageCompressionExtension.java
deleted file mode 100644
index 75d7817..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/MessageCompressionExtension.java
+++ /dev/null
@@ -1,148 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.compress;
-
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.WriteCallback;
-import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
-
-/**
- * Per Message Compression extension for WebSocket.
- * <p>
- * Attempts to follow <a href="https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-01">draft-ietf-hybi-permessage-compression-01</a>
- */
-public class MessageCompressionExtension extends AbstractExtension
-{
- private static final Logger LOG = Log.getLogger(MessageCompressionExtension.class);
-
- private CompressionMethod method;
-
- @Override
- public void incomingFrame(Frame frame)
- {
- if (frame.getType().isControl() || !frame.isRsv1())
- {
- // Cannot modify incoming control frames or ones with RSV1 set.
- nextIncomingFrame(frame);
- return;
- }
-
- ByteBuffer data = frame.getPayload();
- method.decompress().input(data);
- while (!method.decompress().isDone())
- {
- ByteBuffer uncompressed = method.decompress().process();
- if (uncompressed == null)
- {
- continue;
- }
- WebSocketFrame out = new WebSocketFrame(frame).setPayload(uncompressed);
- if (!method.decompress().isDone())
- {
- out.setFin(false);
- }
- out.setRsv1(false); // Unset RSV1 on decompressed frame
- nextIncomingFrame(out);
- }
-
- // reset only at the end of a message.
- if (frame.isFin())
- {
- method.decompress().end();
- }
- }
-
- /**
- * Indicates use of RSV1 flag for indicating deflation is in use.
- */
- @Override
- public boolean isRsv1User()
- {
- return true;
- }
-
- @Override
- public boolean isTextDataDecoder()
- {
- // this extension is responsible for text data frames
- return true;
- }
-
- @Override
- public void outgoingFrame(Frame frame, WriteCallback callback)
- {
- if (frame.getType().isControl())
- {
- // skip, cannot compress control frames.
- nextOutgoingFrame(frame,callback);
- return;
- }
-
- ByteBuffer data = frame.getPayload();
- // deflate data
- method.compress().input(data);
- while (!method.compress().isDone())
- {
- ByteBuffer buf = method.compress().process();
- WebSocketFrame out = new WebSocketFrame(frame).setPayload(buf);
- out.setRsv1(true);
- if (!method.compress().isDone())
- {
- out.setFin(false);
- // no callback for start/middle frames
- nextOutgoingFrame(out,null);
- }
- else
- {
- // pass through callback to last frame
- nextOutgoingFrame(out,callback);
- }
- }
-
- // reset only at end of message
- if (frame.isFin())
- {
- method.compress().end();
- }
- }
-
- @Override
- public void setConfig(ExtensionConfig config)
- {
- super.setConfig(config);
-
- String methodOptions = config.getParameter("method","deflate");
- LOG.debug("Method requested: {}",methodOptions);
-
- method = new DeflateCompressionMethod();
- }
-
- @Override
- public String toString()
- {
- return String.format("%s[method=%s]",this.getClass().getSimpleName(),method);
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtension.java
new file mode 100644
index 0000000..38fcf14
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtension.java
@@ -0,0 +1,262 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import java.nio.ByteBuffer;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BadPayloadException;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
+
+/**
+ * Per Message Deflate Compression extension for WebSocket.
+ * <p>
+ * Attempts to follow <a href="https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-12">draft-ietf-hybi-permessage-compression-12</a>
+ */
+public class PerMessageDeflateExtension extends AbstractExtension
+{
+ private static final boolean BFINAL_HACK = Boolean.parseBoolean(System.getProperty("jetty.websocket.bfinal.hack","true"));
+ private static final Logger LOG = Log.getLogger(PerMessageDeflateExtension.class);
+
+ private static final int OVERHEAD = 64;
+ /** Tail Bytes per Spec */
+ private static final byte[] TAIL = new byte[]
+ { 0x00, 0x00, (byte)0xFF, (byte)0xFF };
+ private int bufferSize = 64 * 1024;
+ private Deflater compressor;
+ private Inflater decompressor;
+
+ @Override
+ public String getName()
+ {
+ return "permessage-deflate";
+ }
+
+ @Override
+ public synchronized void incomingFrame(Frame frame)
+ {
+ if (OpCode.isControlFrame(frame.getOpCode()) || !frame.isRsv1())
+ {
+ // Cannot modify incoming control frames or ones with RSV1 set.
+ nextIncomingFrame(frame);
+ return;
+ }
+
+ if (!frame.hasPayload())
+ {
+ // no payload? nothing to do.
+ nextIncomingFrame(frame);
+ return;
+ }
+
+ // Prime the decompressor
+ ByteBuffer payload = frame.getPayload();
+ int inlen = payload.remaining();
+ byte compressed[] = new byte[inlen + TAIL.length];
+ payload.get(compressed,0,inlen);
+ System.arraycopy(TAIL,0,compressed,inlen,TAIL.length);
+ decompressor.setInput(compressed,0,compressed.length);
+
+ // Since we don't track text vs binary vs continuation state, just grab whatever is the greater value.
+ int maxSize = Math.max(getPolicy().getMaxTextMessageSize(),getPolicy().getMaxBinaryMessageBufferSize());
+ ByteAccumulator accumulator = new ByteAccumulator(maxSize);
+
+ DataFrame out = new DataFrame(frame);
+ out.setRsv1(false); // Unset RSV1
+
+ // Perform decompression
+ while (decompressor.getRemaining() > 0 && !decompressor.finished())
+ {
+ byte outbuf[] = new byte[Math.min(inlen * 2,bufferSize)];
+ try
+ {
+ int len = decompressor.inflate(outbuf);
+ if (len == 0)
+ {
+ if (decompressor.needsInput())
+ {
+ throw new BadPayloadException("Unable to inflate frame, not enough input on frame");
+ }
+ if (decompressor.needsDictionary())
+ {
+ throw new BadPayloadException("Unable to inflate frame, frame erroneously says it needs a dictionary");
+ }
+ }
+ if (len > 0)
+ {
+ accumulator.addBuffer(outbuf,0,len);
+ }
+ }
+ catch (DataFormatException e)
+ {
+ LOG.warn(e);
+ throw new BadPayloadException(e);
+ }
+ }
+
+ // Forward on the frame
+ out.setPayload(accumulator.getByteBuffer(getBufferPool()));
+ nextIncomingFrame(out);
+ }
+
+ /**
+ * Indicates use of RSV1 flag for indicating deflation is in use.
+ */
+ @Override
+ public boolean isRsv1User()
+ {
+ return true;
+ }
+
+ @Override
+ public synchronized void outgoingFrame(Frame frame, WriteCallback callback)
+ {
+ if (OpCode.isControlFrame(frame.getOpCode()))
+ {
+ // skip, cannot compress control frames.
+ nextOutgoingFrame(frame,callback);
+ return;
+ }
+
+ if (!frame.hasPayload())
+ {
+ // pass through, nothing to do
+ nextOutgoingFrame(frame,callback);
+ return;
+ }
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("outgoingFrame({}, {}) - {}",OpCode.name(frame.getOpCode()),callback != null?callback.getClass().getSimpleName():"<null>",
+ BufferUtil.toDetailString(frame.getPayload()));
+ }
+
+ // Prime the compressor
+ byte uncompressed[] = BufferUtil.toArray(frame.getPayload());
+
+ // Perform the compression
+ if (!compressor.finished())
+ {
+ compressor.setInput(uncompressed,0,uncompressed.length);
+ byte compressed[] = new byte[uncompressed.length + OVERHEAD];
+
+ while (!compressor.needsInput())
+ {
+ int len = compressor.deflate(compressed,0,compressed.length,Deflater.SYNC_FLUSH);
+ ByteBuffer outbuf = getBufferPool().acquire(len,true);
+ BufferUtil.clearToFill(outbuf);
+
+ if (len > 0)
+ {
+ outbuf.put(compressed,0,len - 4);
+ }
+
+ BufferUtil.flipToFlush(outbuf,0);
+
+ if (len > 0 && BFINAL_HACK)
+ {
+ /*
+ * Per the spec, it says that BFINAL 1 or 0 are allowed.
+ *
+ * However, Java always uses BFINAL 1, whereas the browsers Chromium and Safari fail to decompress when it encounters BFINAL 1.
+ *
+ * This hack will always set BFINAL 0
+ */
+ byte b0 = outbuf.get(0);
+ if ((b0 & 1) != 0) // if BFINAL 1
+ {
+ outbuf.put(0,(b0 ^= 1)); // flip bit to BFINAL 0
+ }
+ }
+
+ DataFrame out = new DataFrame(frame);
+ out.setRsv1(true);
+ out.setPooledBuffer(true);
+ out.setPayload(outbuf);
+
+ if (!compressor.needsInput())
+ {
+ // this is fragmented
+ out.setFin(false);
+ nextOutgoingFrame(out,null); // non final frames have no callback
+ }
+ else
+ {
+ // pass through the callback
+ nextOutgoingFrame(out,callback);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setConfig(final ExtensionConfig config)
+ {
+ ExtensionConfig negotiated = new ExtensionConfig(config.getName());
+
+ boolean nowrap = true;
+ compressor = new Deflater(Deflater.BEST_COMPRESSION,nowrap);
+ compressor.setStrategy(Deflater.DEFAULT_STRATEGY);
+
+ decompressor = new Inflater(nowrap);
+
+ for (String key : config.getParameterKeys())
+ {
+ key = key.trim();
+ String value = config.getParameter(key,null);
+ switch (key)
+ {
+ case "c2s_max_window_bits":
+ negotiated.setParameter("s2c_max_window_bits",value);
+ break;
+ case "c2s_no_context_takeover":
+ negotiated.setParameter("s2c_no_context_takeover",value);
+ break;
+ case "s2c_max_window_bits":
+ negotiated.setParameter("c2s_max_window_bits",value);
+ break;
+ case "s2c_no_context_takeover":
+ negotiated.setParameter("c2s_no_context_takeover",value);
+ break;
+ }
+ }
+
+ super.setConfig(negotiated);
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder();
+ str.append(this.getClass().getSimpleName());
+ str.append('[');
+ str.append(']');
+ return str.toString();
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/XWebkitDeflateFrameExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/XWebkitDeflateFrameExtension.java
new file mode 100644
index 0000000..94b09d4
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/XWebkitDeflateFrameExtension.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+/**
+ * Implementation of the <a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate-05.txt">x-webkit-deflate-frame</a> extension seen out
+ * in the wild. Using the alternate extension identification
+ */
+public class XWebkitDeflateFrameExtension extends DeflateFrameExtension
+{
+ @Override
+ public String getName()
+ {
+ return "x-webkit-deflate-frame";
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.getClass().getSimpleName() + "[]";
+ }
+}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/fragment/FragmentExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/fragment/FragmentExtension.java
index 13f13a5..8bbc633 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/fragment/FragmentExtension.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/fragment/FragmentExtension.java
@@ -21,13 +21,12 @@
import java.nio.ByteBuffer;
-import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.OpCode;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
/**
* Fragment Extension
@@ -35,9 +34,15 @@
public class FragmentExtension extends AbstractExtension
{
private int maxLength = -1;
+
+ @Override
+ public String getName()
+ {
+ return "fragment";
+ }
@Override
- public void incomingError(WebSocketException e)
+ public void incomingError(Throwable e)
{
// Pass thru
nextIncomingError(e);
@@ -53,7 +58,7 @@
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
{
- if (frame.getType().isControl())
+ if (OpCode.isControlFrame(frame.getOpCode()))
{
// Cannot fragment Control Frames
nextOutgoingFrame(frame,callback);
@@ -62,7 +67,6 @@
int length = frame.getPayloadLength();
- byte opcode = frame.getType().getOpCode(); // original opcode
ByteBuffer payload = frame.getPayload().slice();
int originalLimit = payload.limit();
int currentPosition = payload.position();
@@ -79,10 +83,8 @@
// break apart payload based on maxLength rules
while (length > maxLength)
{
- WebSocketFrame frag = new WebSocketFrame(frame);
- frag.setOpCode(opcode);
+ DataFrame frag = new DataFrame(frame,continuation);
frag.setFin(false); // always false here
- frag.setContinuation(continuation);
payload.position(currentPosition);
payload.limit(Math.min(payload.position() + maxLength,originalLimit));
frag.setPayload(payload);
@@ -91,16 +93,13 @@
nextOutgoingFrame(frag,null);
length -= maxLength;
- opcode = OpCode.CONTINUATION;
continuation = true;
currentPosition = payload.limit();
}
// write remaining
- WebSocketFrame frag = new WebSocketFrame(frame);
- frag.setOpCode(opcode);
+ DataFrame frag = new DataFrame(frame,continuation);
frag.setFin(frame.isFin()); // use original fin
- frag.setContinuation(continuation);
payload.position(currentPosition);
payload.limit(originalLimit);
frag.setPayload(payload);
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/identity/IdentityExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/identity/IdentityExtension.java
index fa6b535..e57f166 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/identity/IdentityExtension.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/identity/IdentityExtension.java
@@ -20,7 +20,6 @@
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.annotation.ManagedObject;
-import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame;
@@ -35,9 +34,15 @@
{
return getConfig().getParameter(key,"?");
}
+
+ @Override
+ public String getName()
+ {
+ return "identity";
+ }
@Override
- public void incomingError(WebSocketException e)
+ public void incomingError(Throwable e)
{
// pass through
nextIncomingError(e);
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/AbstractMuxExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/AbstractMuxExtension.java
deleted file mode 100644
index f6db8af..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/AbstractMuxExtension.java
+++ /dev/null
@@ -1,65 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import org.eclipse.jetty.websocket.api.WriteCallback;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.common.LogicalConnection;
-import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
-
-/**
- * Multiplexing Extension for WebSockets.
- * <p>
- * Supporting <a href="https://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-08">draft-ietf-hybi-websocket-multiplexing-08</a> Specification.
- */
-public abstract class AbstractMuxExtension extends AbstractExtension
-{
- private Muxer muxer;
-
- public AbstractMuxExtension()
- {
- super();
- }
-
- public abstract void configureMuxer(Muxer muxer);
-
- @Override
- public void incomingFrame(Frame frame)
- {
- this.muxer.incomingFrame(frame);
- }
-
- @Override
- public void outgoingFrame(Frame frame, WriteCallback callback)
- {
- /* do nothing here, allow Muxer to handle this aspect */
- }
-
- @Override
- public void setConnection(LogicalConnection connection)
- {
- super.setConnection(connection);
- if (muxer != null)
- {
- throw new RuntimeException("Cannot reset muxer physical connection once established");
- }
- this.muxer = new Muxer(connection);
- configureMuxer(this.muxer);
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java
deleted file mode 100644
index c3c42ce..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java
+++ /dev/null
@@ -1,253 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import java.net.InetSocketAddress;
-import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.StatusCode;
-import org.eclipse.jetty.websocket.api.SuspendToken;
-import org.eclipse.jetty.websocket.api.WebSocketException;
-import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.api.WriteCallback;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
-import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.ConnectionState;
-import org.eclipse.jetty.websocket.common.LogicalConnection;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.eclipse.jetty.websocket.common.WebSocketSession;
-import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
-import org.eclipse.jetty.websocket.common.io.IOState;
-import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
-
-/**
- * MuxChannel, acts as WebSocketConnection for specific sub-channel.
- */
-public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendToken, ConnectionStateListener
-{
- private static final Logger LOG = Log.getLogger(MuxChannel.class);
-
- private final long channelId;
- private final Muxer muxer;
- private final AtomicBoolean inputClosed;
- private final AtomicBoolean outputClosed;
- private final AtomicBoolean suspendToken;
- private IOState ioState;
- private WebSocketPolicy policy;
- private WebSocketSession session;
- private IncomingFrames incoming;
- private String subProtocol;
-
- public MuxChannel(long channelId, Muxer muxer)
- {
- this.channelId = channelId;
- this.muxer = muxer;
- this.policy = muxer.getPolicy().clonePolicy();
-
- this.suspendToken = new AtomicBoolean(false);
- this.ioState = new IOState();
- this.ioState.addListener(this);
-
- this.inputClosed = new AtomicBoolean(false);
- this.outputClosed = new AtomicBoolean(false);
- }
-
- @Override
- public void close()
- {
- close(StatusCode.NORMAL,null);
- }
-
- @Override
- public void close(int statusCode, String reason)
- {
- CloseInfo close = new CloseInfo(statusCode,reason);
- // TODO: disconnect callback?
- outgoingFrame(close.asFrame(),null);
- }
-
- @Override
- public void disconnect()
- {
- // TODO: disconnect the virtual end-point?
- }
-
- public long getChannelId()
- {
- return channelId;
- }
-
- @Override
- public IOState getIOState()
- {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public InetSocketAddress getLocalAddress()
- {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public long getMaxIdleTimeout()
- {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public WebSocketPolicy getPolicy()
- {
- return policy;
- }
-
- @Override
- public InetSocketAddress getRemoteAddress()
- {
- return muxer.getRemoteAddress();
- }
-
- @Override
- public WebSocketSession getSession()
- {
- return session;
- }
-
- /**
- * Incoming exceptions from Muxer.
- */
- @Override
- public void incomingError(WebSocketException e)
- {
- incoming.incomingError(e);
- }
-
- /**
- * Incoming frames from Muxer
- */
- @Override
- public void incomingFrame(Frame frame)
- {
- incoming.incomingFrame(frame);
- }
-
- public boolean isActive()
- {
- return (ioState.isOpen());
- }
-
- @Override
- public boolean isOpen()
- {
- return isActive() && muxer.isOpen();
- }
-
- @Override
- public boolean isReading()
- {
- return true;
- }
-
- public void onClose()
- {
- }
-
- @Override
- public void onConnectionStateChange(ConnectionState state)
- {
- // TODO Auto-generated method stub
-
- }
-
- public void onOpen()
- {
- this.ioState.onOpened();
- }
-
- /**
- * Internal
- *
- * @param frame the frame to write
- * @return the future for the network write of the frame
- */
- private Future<Void> outgoingAsyncFrame(WebSocketFrame frame)
- {
- FutureWriteCallback future = new FutureWriteCallback();
- outgoingFrame(frame,future);
- return future;
- }
-
- /**
- * Frames destined for the Muxer
- */
- @Override
- public void outgoingFrame(Frame frame, WriteCallback callback)
- {
- muxer.output(channelId,frame,callback);
- }
-
- @Override
- public void resume()
- {
- if (suspendToken.getAndSet(false))
- {
- // TODO: Start reading again. (how?)
- }
- }
-
- @Override
- public void setMaxIdleTimeout(long ms)
- {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void setNextIncomingFrames(IncomingFrames incoming)
- {
- this.incoming = incoming;
- }
-
- @Override
- public void setSession(WebSocketSession session)
- {
- this.session = session;
- // session.setOutgoing(this);
- }
-
- public void setSubProtocol(String subProtocol)
- {
- this.subProtocol = subProtocol;
- }
-
- @Override
- public SuspendToken suspend()
- {
- suspendToken.set(true);
- // TODO: how to suspend reading?
- return this;
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxControlBlock.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxControlBlock.java
deleted file mode 100644
index 7e364a4..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxControlBlock.java
+++ /dev/null
@@ -1,24 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-public interface MuxControlBlock
-{
- public int getOpCode();
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxException.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxException.java
deleted file mode 100644
index ff8469f..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxException.java
+++ /dev/null
@@ -1,40 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import org.eclipse.jetty.websocket.api.WebSocketException;
-
-@SuppressWarnings("serial")
-public class MuxException extends WebSocketException
-{
- public MuxException(String message)
- {
- super(message);
- }
-
- public MuxException(String message, Throwable cause)
- {
- super(message,cause);
- }
-
- public MuxException(Throwable cause)
- {
- super(cause);
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGenerator.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGenerator.java
deleted file mode 100644
index cf12f37..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGenerator.java
+++ /dev/null
@@ -1,271 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.io.ArrayByteBufferPool;
-import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.websocket.api.WriteCallback;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelRequest;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelResponse;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxDropChannel;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxFlowControl;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxNewChannelSlot;
-
-/**
- * Generate Mux frames destined for the physical connection.
- */
-public class MuxGenerator
-{
- private static final int CONTROL_BUFFER_SIZE = 2 * 1024;
- /** 4 bytes for channel ID + 1 for fin/rsv/opcode */
- private static final int DATA_FRAME_OVERHEAD = 5;
- private ByteBufferPool bufferPool;
- private OutgoingFrames outgoing;
-
- public MuxGenerator()
- {
- this(new ArrayByteBufferPool());
- }
-
- public MuxGenerator(ByteBufferPool bufferPool)
- {
- this.bufferPool = bufferPool;
- }
-
- public void generate(long channelId, Frame frame, WriteCallback callback)
- {
- ByteBuffer muxPayload = bufferPool.acquire(frame.getPayloadLength() + DATA_FRAME_OVERHEAD,false);
- BufferUtil.flipToFill(muxPayload);
-
- // start building mux payload
- writeChannelId(muxPayload,channelId);
- byte b = (byte)(frame.isFin()?0x80:0x00); // fin
- b |= (byte)(frame.isRsv1()?0x40:0x00); // rsv1
- b |= (byte)(frame.isRsv2()?0x20:0x00); // rsv2
- b |= (byte)(frame.isRsv3()?0x10:0x00); // rsv3
- b |= (byte)(frame.getType().getOpCode() & 0x0F); // opcode
- muxPayload.put(b);
- BufferUtil.put(frame.getPayload(),muxPayload);
-
- // build muxed frame
- WebSocketFrame muxFrame = WebSocketFrame.binary();
- BufferUtil.flipToFlush(muxPayload,0);
- muxFrame.setPayload(muxPayload);
- // NOTE: the physical connection will handle masking rules for this frame.
-
- // release original buffer (no longer needed)
- bufferPool.release(frame.getPayload());
-
- // send muxed frame down to the physical connection.
- outgoing.outgoingFrame(muxFrame,callback);
- }
-
- public void generate(WriteCallback callback,MuxControlBlock... blocks) throws IOException
- {
- if ((blocks == null) || (blocks.length <= 0))
- {
- return; // nothing to do
- }
-
- ByteBuffer payload = bufferPool.acquire(CONTROL_BUFFER_SIZE,false);
- BufferUtil.flipToFill(payload);
-
- writeChannelId(payload,0); // control channel
-
- for (MuxControlBlock block : blocks)
- {
- switch (block.getOpCode())
- {
- case MuxOp.ADD_CHANNEL_REQUEST:
- {
- MuxAddChannelRequest op = (MuxAddChannelRequest)block;
- byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
- b |= (byte)((op.getRsv() & 0x07) << 2); // rsv
- b |= (op.getEncoding() & 0x03); // enc
- payload.put(b); // opcode + rsv + enc
- writeChannelId(payload,op.getChannelId());
- write139Buffer(payload,op.getHandshake());
- break;
- }
- case MuxOp.ADD_CHANNEL_RESPONSE:
- {
- MuxAddChannelResponse op = (MuxAddChannelResponse)block;
- byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
- b |= (op.isFailed()?0x10:0x00); // failure bit
- b |= (byte)((op.getRsv() & 0x03) << 2); // rsv
- b |= (op.getEncoding() & 0x03); // enc
- payload.put(b); // opcode + f + rsv + enc
- writeChannelId(payload,op.getChannelId());
- if (op.getHandshake() != null)
- {
- write139Buffer(payload,op.getHandshake());
- }
- else
- {
- // no handshake details
- write139Size(payload,0);
- }
- break;
- }
- case MuxOp.DROP_CHANNEL:
- {
- MuxDropChannel op = (MuxDropChannel)block;
- byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
- b |= (byte)(op.getRsv() & 0x1F); // rsv
- payload.put(b); // opcode + rsv
- writeChannelId(payload,op.getChannelId());
- write139Buffer(payload,op.asReasonBuffer());
- break;
- }
- case MuxOp.FLOW_CONTROL:
- {
- MuxFlowControl op = (MuxFlowControl)block;
- byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
- b |= (byte)(op.getRsv() & 0x1F); // rsv
- payload.put(b); // opcode + rsv
- writeChannelId(payload,op.getChannelId());
- write139Size(payload,op.getSendQuotaSize());
- break;
- }
- case MuxOp.NEW_CHANNEL_SLOT:
- {
- MuxNewChannelSlot op = (MuxNewChannelSlot)block;
- byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
- b |= (byte)(op.getRsv() & 0x0F) << 1; // rsv
- b |= (byte)(op.isFallback()?0x01:0x00); // fallback bit
- payload.put(b); // opcode + rsv + fallback bit
- write139Size(payload,op.getNumberOfSlots());
- write139Size(payload,op.getInitialSendQuota());
- break;
- }
- }
- }
- BufferUtil.flipToFlush(payload,0);
- WebSocketFrame frame = WebSocketFrame.binary();
- frame.setPayload(payload);
- outgoing.outgoingFrame(frame,callback);
- }
-
- public OutgoingFrames getOutgoing()
- {
- return outgoing;
- }
-
- public void setOutgoing(OutgoingFrames outgoing)
- {
- this.outgoing = outgoing;
- }
-
- /**
- * Write a 1/3/9 encoded size, then a byte buffer of that size.
- *
- * @param payload
- * @param buffer
- */
- public void write139Buffer(ByteBuffer payload, ByteBuffer buffer)
- {
- write139Size(payload,buffer.remaining());
- writeBuffer(payload,buffer);
- }
-
- /**
- * Write a 1/3/9 encoded size.
- *
- * @param payload
- * @param size
- */
- public void write139Size(ByteBuffer payload, long size)
- {
- if (size > 0xFF_FF)
- {
- // 9 byte encoded
- payload.put((byte)0x7F);
- payload.putLong(size);
- return;
- }
-
- if (size >= 0x7E)
- {
- // 3 byte encoded
- payload.put((byte)0x7E);
- payload.put((byte)(size >> 8));
- payload.put((byte)(size & 0xFF));
- return;
- }
-
- // 1 byte (7 bit) encoded
- payload.put((byte)(size & 0x7F));
- }
-
- public void writeBuffer(ByteBuffer payload, ByteBuffer buffer)
- {
- BufferUtil.put(buffer,payload);
- }
-
- /**
- * Write multiplexing channel id, using logical channel id encoding (of 1,2,3, or 4 octets)
- *
- * @param payload
- * @param channelId
- */
- public void writeChannelId(ByteBuffer payload, long channelId)
- {
- if (channelId > 0x1F_FF_FF_FF)
- {
- throw new MuxException("Illegal Channel ID: too big");
- }
-
- if (channelId > 0x1F_FF_FF)
- {
- // 29 bit channel id (4 bytes)
- payload.put((byte)(0xE0 | ((channelId >> 24) & 0x1F)));
- payload.put((byte)((channelId >> 16) & 0xFF));
- payload.put((byte)((channelId >> 8) & 0xFF));
- payload.put((byte)(channelId & 0xFF));
- return;
- }
-
- if (channelId > 0x3F_FF)
- {
- // 21 bit channel id (3 bytes)
- payload.put((byte)(0xC0 | ((channelId >> 16) & 0x1F)));
- payload.put((byte)((channelId >> 8) & 0xFF));
- payload.put((byte)(channelId & 0xFF));
- return;
- }
-
- if (channelId > 0x7F)
- {
- // 14 bit channel id (2 bytes)
- payload.put((byte)(0x80 | ((channelId >> 8) & 0x3F)));
- payload.put((byte)(channelId & 0xFF));
- return;
- }
-
- // 7 bit channel id
- payload.put((byte)(channelId & 0x7F));
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxOp.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxOp.java
deleted file mode 100644
index f41383a..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxOp.java
+++ /dev/null
@@ -1,28 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-public final class MuxOp
-{
- public static final byte ADD_CHANNEL_REQUEST = 0;
- public static final byte ADD_CHANNEL_RESPONSE = 1;
- public static final byte FLOW_CONTROL = 2;
- public static final byte DROP_CHANNEL = 3;
- public static final byte NEW_CHANNEL_SLOT = 4;
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParser.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParser.java
deleted file mode 100644
index 0fb2a49..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParser.java
+++ /dev/null
@@ -1,410 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.common.OpCode;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelRequest;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelResponse;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxDropChannel;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxFlowControl;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxNewChannelSlot;
-
-public class MuxParser
-{
- public static interface Listener
- {
- public void onMuxAddChannelRequest(MuxAddChannelRequest request);
-
- public void onMuxAddChannelResponse(MuxAddChannelResponse response);
-
- public void onMuxDropChannel(MuxDropChannel drop);
-
- public void onMuxedFrame(MuxedFrame frame);
-
- public void onMuxException(MuxException e);
-
- public void onMuxFlowControl(MuxFlowControl flow);
-
- public void onMuxNewChannelSlot(MuxNewChannelSlot slot);
- }
-
- private final static Logger LOG = Log.getLogger(MuxParser.class);
-
- private MuxedFrame muxframe = new MuxedFrame();
- private MuxParser.Listener events;
- private long channelId;
-
- public MuxParser.Listener getEvents()
- {
- return events;
- }
-
- /**
- * Parse the raw {@link WebSocketFrame} payload data for various Mux frames.
- *
- * @param frame
- * the WebSocketFrame to parse for mux payload
- */
- public synchronized void parse(Frame frame)
- {
- if (events == null)
- {
- throw new RuntimeException("No " + MuxParser.Listener.class + " specified");
- }
-
- if (!frame.hasPayload())
- {
- LOG.debug("No payload data, skipping");
- return; // nothing to parse
- }
-
- if (frame.getType().getOpCode() != OpCode.BINARY)
- {
- LOG.debug("Not a binary opcode (base frame), skipping");
- return; // not a binary opcode
- }
-
- LOG.debug("Parsing Mux Payload of {}",frame);
-
- try
- {
- ByteBuffer buffer = frame.getPayload().slice();
-
- if (buffer.remaining() <= 0)
- {
- return;
- }
-
- if (frame.isContinuation())
- {
- muxframe.reset();
- muxframe.setFin(frame.isFin());
- muxframe.setFin(frame.isRsv1());
- muxframe.setFin(frame.isRsv2());
- muxframe.setFin(frame.isRsv3());
- muxframe.setContinuation(true);
- parseDataFramePayload(buffer);
- }
- else
- {
- // new frame
- channelId = readChannelId(buffer);
- if (channelId == 0)
- {
- parseControlBlocks(buffer);
- }
- else
- {
- parseDataFrame(buffer);
- }
- }
- }
- catch (MuxException e)
- {
- events.onMuxException(e);
- }
- catch (Throwable t)
- {
- events.onMuxException(new MuxException(t));
- }
- }
-
- private void parseControlBlocks(ByteBuffer buffer)
- {
- // process the remaining buffer here.
- while (buffer.remaining() > 0)
- {
- byte b = buffer.get();
- byte opc = (byte)((byte)(b >> 5) & 0xFF);
- b = (byte)(b & 0x1F);
-
- try {
- switch (opc)
- {
- case MuxOp.ADD_CHANNEL_REQUEST:
- {
- MuxAddChannelRequest op = new MuxAddChannelRequest();
- op.setRsv((byte)((b & 0x1C) >> 2));
- op.setEncoding((byte)(b & 0x03));
- op.setChannelId(readChannelId(buffer));
- long handshakeSize = read139EncodedSize(buffer);
- op.setHandshake(readBlock(buffer,handshakeSize));
- events.onMuxAddChannelRequest(op);
- break;
- }
- case MuxOp.ADD_CHANNEL_RESPONSE:
- {
- MuxAddChannelResponse op = new MuxAddChannelResponse();
- op.setFailed((b & 0x10) != 0);
- op.setRsv((byte)((byte)(b & 0x0C) >> 2));
- op.setEncoding((byte)(b & 0x03));
- op.setChannelId(readChannelId(buffer));
- long handshakeSize = read139EncodedSize(buffer);
- op.setHandshake(readBlock(buffer,handshakeSize));
- events.onMuxAddChannelResponse(op);
- break;
- }
- case MuxOp.DROP_CHANNEL:
- {
- int rsv = (b & 0x1F);
- long channelId = readChannelId(buffer);
- long reasonSize = read139EncodedSize(buffer);
- ByteBuffer reasonBuf = readBlock(buffer,reasonSize);
- MuxDropChannel op = MuxDropChannel.parse(channelId,reasonBuf);
- op.setRsv(rsv);
- events.onMuxDropChannel(op);
- break;
- }
- case MuxOp.FLOW_CONTROL:
- {
- MuxFlowControl op = new MuxFlowControl();
- op.setRsv((byte)(b & 0x1F));
- op.setChannelId(readChannelId(buffer));
- op.setSendQuotaSize(read139EncodedSize(buffer));
- events.onMuxFlowControl(op);
- break;
- }
- case MuxOp.NEW_CHANNEL_SLOT:
- {
- MuxNewChannelSlot op = new MuxNewChannelSlot();
- op.setRsv((byte)((b & 0x1E) >> 1));
- op.setFallback((b & 0x01) != 0);
- op.setNumberOfSlots(read139EncodedSize(buffer));
- op.setInitialSendQuota(read139EncodedSize(buffer));
- events.onMuxNewChannelSlot(op);
- break;
- }
- default:
- {
- String err = String.format("Unknown Mux Control Code OPC [0x%X]",opc);
- throw new MuxException(err);
- }
- }
- }
- catch (Throwable t)
- {
- LOG.warn(t);
- throw new MuxException(t);
- }
- }
- }
-
- private void parseDataFrame(ByteBuffer buffer)
- {
- byte b = buffer.get();
- boolean fin = ((b & 0x80) != 0);
- boolean rsv1 = ((b & 0x40) != 0);
- boolean rsv2 = ((b & 0x20) != 0);
- boolean rsv3 = ((b & 0x10) != 0);
- byte opcode = (byte)(b & 0x0F);
-
- if (opcode == OpCode.CONTINUATION)
- {
- muxframe.setContinuation(true);
- }
- else
- {
- muxframe.reset();
- muxframe.setOpCode(opcode);
- }
-
- muxframe.setChannelId(channelId);
- muxframe.setFin(fin);
- muxframe.setRsv1(rsv1);
- muxframe.setRsv2(rsv2);
- muxframe.setRsv3(rsv3);
-
- parseDataFramePayload(buffer);
- }
-
- private void parseDataFramePayload(ByteBuffer buffer)
- {
- int capacity = buffer.remaining();
- ByteBuffer payload = ByteBuffer.allocate(capacity);
- payload.put(buffer);
- BufferUtil.flipToFlush(payload,0);
- muxframe.setPayload(payload);
- try
- {
- LOG.debug("notifyFrame() - {}",muxframe);
- events.onMuxedFrame(muxframe);
- }
- catch (Throwable t)
- {
- LOG.warn(t);
- }
- }
-
- /**
- * Per section <a href="https://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing#section-9.1">9.1. Number Encoding in Multiplex Control
- * Blocks</a>, read the 1/3/9 byte length using <a href="https://tools.ietf.org/html/rfc6455#section-5.2">Section 5.2 of RFC 6455</a>.
- *
- * @param buffer
- * the buffer to read from
- * @return the decoded size
- * @throws MuxException
- * when the encoding does not make sense per the spec, or it is a value above {@link Long#MAX_VALUE}
- */
- public long read139EncodedSize(ByteBuffer buffer)
- {
- long ret = -1;
- long minValue = 0x00; // used to validate minimum # of bytes (per spec)
- int cursor = 0;
-
- byte b = buffer.get();
- ret = (b & 0x7F);
-
- if (ret == 0x7F)
- {
- // 9 byte length
- ret = 0;
- minValue = 0xFF_FF;
- cursor = 8;
- }
- else if (ret == 0x7E)
- {
- // 3 byte length
- ret = 0;
- minValue = 0x7F;
- cursor = 2;
- }
- else
- {
- // 1 byte length
- // no validation of minimum bytes needed here
- return ret;
- }
-
- // parse multi-byte length
- while (cursor > 0)
- {
- ret = ret << 8;
- b = buffer.get();
- ret |= (b & 0xFF);
- --cursor;
- }
-
- // validate minimum value per spec.
- if (ret <= minValue)
- {
- String err = String.format("Invalid 1/3/9 length 0x%X (minimum value for chosen encoding is 0x%X)",ret,minValue);
- throw new MuxException(err);
- }
-
- return ret;
- }
-
- private ByteBuffer readBlock(ByteBuffer buffer, long size)
- {
- if (size == 0)
- {
- return null;
- }
-
- if (size > buffer.remaining())
- {
- String err = String.format("Truncated data, expected %,d byte(s), but only %,d byte(s) remain",size,buffer.remaining());
- throw new MuxException(err);
- }
-
- if (size > Integer.MAX_VALUE)
- {
- String err = String.format("[Int-Sane!] Buffer size %,d is too large to be supported (max allowed is %,d)",size,Integer.MAX_VALUE);
- throw new MuxException(err);
- }
-
- ByteBuffer ret = ByteBuffer.allocate((int)size);
- BufferUtil.put(buffer,ret);
- BufferUtil.flipToFlush(ret,0);
- return ret;
- }
-
- /**
- * Read Channel ID using <a href="https://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing#section-7">Section 7. Framing</a> techniques
- *
- * @param buffer
- * the buffer to parse from.
- * @return the channel Id
- * @throws MuxException
- * when the encoding does not make sense per the spec.
- */
- public long readChannelId(ByteBuffer buffer)
- {
- long id = -1;
- long minValue = 0x00; // used to validate minimum # of bytes (per spec)
- byte b = buffer.get();
- int cursor = -1;
- if ((b & 0x80) == 0)
- {
- // 7 bit channel id
- // no validation of minimum bytes needed here
- return (b & 0x7F);
- }
- else if ((b & 0x40) == 0)
- {
- // 14 bit channel id
- id = (b & 0x3F);
- minValue = 0x7F;
- cursor = 1;
- }
- else if ((b & 0x20) == 0)
- {
- // 21 bit channel id
- id = (b & 0x1F);
- minValue = 0x3F_FF;
- cursor = 2;
- }
- else
- {
- // 29 bit channel id
- id = (b & 0x1F);
- minValue = 0x1F_FF_FF;
- cursor = 3;
- }
-
- while (cursor > 0)
- {
- id = id << 8;
- b = buffer.get();
- id |= (b & 0xFF);
- --cursor;
- }
-
- // validate minimum value per spec.
- if (id <= minValue)
- {
- String err = String.format("Invalid Channel ID 0x%X (minimum value for chosen encoding is 0x%X)",id,minValue);
- throw new MuxException(err);
- }
-
- return id;
- }
-
- public void setEvents(MuxParser.Listener events)
- {
- this.events = events;
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxPhysicalConnectionException.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxPhysicalConnectionException.java
deleted file mode 100644
index a472cb2..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxPhysicalConnectionException.java
+++ /dev/null
@@ -1,44 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxDropChannel;
-
-public class MuxPhysicalConnectionException extends MuxException
-{
- private static final long serialVersionUID = 1L;
- private MuxDropChannel drop;
-
- public MuxPhysicalConnectionException(MuxDropChannel.Reason code, String phrase)
- {
- super(phrase);
- drop = new MuxDropChannel(0,code,phrase);
- }
-
- public MuxPhysicalConnectionException(MuxDropChannel.Reason code, String phrase, Throwable t)
- {
- super(phrase,t);
- drop = new MuxDropChannel(0,code,phrase);
- }
-
- public MuxDropChannel getMuxDropChannel()
- {
- return drop;
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxRequest.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxRequest.java
deleted file mode 100644
index 1b430e5..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxRequest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-
-public class MuxRequest extends UpgradeRequest
-{
- public static final String HEADER_VALUE_DELIM="\"\\\n\r\t\f\b%+ ;=";
-
- public static UpgradeRequest merge(UpgradeRequest baseReq, UpgradeRequest deltaReq)
- {
- MuxRequest req = new MuxRequest(baseReq);
-
- // TODO: finish
-
- return req;
- }
-
- private static String overlay(String val, String defVal)
- {
- if (val == null)
- {
- return defVal;
- }
- return val;
- }
-
- public static UpgradeRequest parse(ByteBuffer handshake)
- {
- MuxRequest req = new MuxRequest();
- // TODO Auto-generated method stub
- return req;
- }
-
- public MuxRequest()
- {
- super();
- }
-
- public MuxRequest(UpgradeRequest copy)
- {
- super();
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxResponse.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxResponse.java
deleted file mode 100644
index 0a34448..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxResponse.java
+++ /dev/null
@@ -1,26 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
-
-public class MuxResponse extends UpgradeResponse
-{
-
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxedFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxedFrame.java
deleted file mode 100644
index 219b6b7..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxedFrame.java
+++ /dev/null
@@ -1,73 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import org.eclipse.jetty.websocket.common.OpCode;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
-
-public class MuxedFrame extends WebSocketFrame
-{
- private long channelId = -1;
-
- public MuxedFrame()
- {
- super();
- }
-
- public MuxedFrame(MuxedFrame frame)
- {
- super(frame);
- this.channelId = frame.channelId;
- }
-
- public long getChannelId()
- {
- return channelId;
- }
-
- @Override
- public void reset()
- {
- super.reset();
- this.channelId = -1;
- }
-
- public void setChannelId(long channelId)
- {
- this.channelId = channelId;
- }
-
- @Override
- public String toString()
- {
- StringBuilder b = new StringBuilder();
- b.append(OpCode.name(getOpCode()));
- b.append('[');
- b.append("channel=").append(channelId);
- b.append(",len=").append(getPayloadLength());
- b.append(",fin=").append(isFin());
- b.append(",rsv=");
- b.append(isRsv1()?'1':'.');
- b.append(isRsv2()?'1':'.');
- b.append(isRsv3()?'1':'.');
- b.append(",continuation=").append(isContinuation());
- b.append(']');
- return b.toString();
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/Muxer.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/Muxer.java
deleted file mode 100644
index ab93663..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/Muxer.java
+++ /dev/null
@@ -1,440 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.StatusCode;
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
-import org.eclipse.jetty.websocket.api.WebSocketBehavior;
-import org.eclipse.jetty.websocket.api.WebSocketException;
-import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.api.WriteCallback;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
-import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
-import org.eclipse.jetty.websocket.common.LogicalConnection;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.eclipse.jetty.websocket.common.extensions.mux.add.MuxAddClient;
-import org.eclipse.jetty.websocket.common.extensions.mux.add.MuxAddServer;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelRequest;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelResponse;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxDropChannel;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxFlowControl;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxNewChannelSlot;
-
-/**
- * Muxer responsible for managing sub-channels.
- * <p>
- * Maintains a 1 (incoming and outgoing mux encapsulated frames) to many (per-channel incoming/outgoing standard websocket frames) relationship, along with
- * routing of {@link MuxControlBlock} events.
- * <p>
- * Control Channel events (channel ID == 0) are handled by the Muxer.
- */
-public class Muxer implements IncomingFrames, MuxParser.Listener
-{
- private static final int CONTROL_CHANNEL_ID = 0;
-
- private static final Logger LOG = Log.getLogger(Muxer.class);
-
- /**
- * Map of sub-channels, key is the channel Id.
- */
- private Map<Long, MuxChannel> channels = new HashMap<Long, MuxChannel>();
-
- private final WebSocketPolicy policy;
- private final LogicalConnection physicalConnection;
- private InetSocketAddress remoteAddress;
- /** Parsing frames destined for sub-channels */
- private MuxParser parser;
- /** Generating frames destined for physical connection */
- private MuxGenerator generator;
- private MuxAddServer addServer;
- private MuxAddClient addClient;
- /** The original request headers, used for delta encoded AddChannelRequest blocks */
- private UpgradeRequest physicalRequestHeaders;
- /** The original response headers, used for delta encoded AddChannelResponse blocks */
- private UpgradeResponse physicalResponseHeaders;
-
- public Muxer(final LogicalConnection connection)
- {
- this.physicalConnection = connection;
- this.policy = connection.getPolicy().clonePolicy();
- this.parser = new MuxParser();
- this.parser.setEvents(this);
- this.generator = new MuxGenerator();
- }
-
- public MuxAddClient getAddClient()
- {
- return addClient;
- }
-
- public MuxAddServer getAddServer()
- {
- return addServer;
- }
-
- public MuxChannel getChannel(long channelId, boolean create)
- {
- if (channelId == CONTROL_CHANNEL_ID)
- {
- throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Invalid Channel ID");
- }
-
- MuxChannel channel = channels.get(channelId);
- if (channel == null)
- {
- if (create)
- {
- channel = new MuxChannel(channelId,this);
- channels.put(channelId,channel);
- }
- else
- {
- throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Unknown Channel ID");
- }
- }
- return channel;
- }
-
- public WebSocketPolicy getPolicy()
- {
- return policy;
- }
-
- /**
- * Get the remote address of the physical connection.
- *
- * @return the remote address of the physical connection
- */
- public InetSocketAddress getRemoteAddress()
- {
- return this.remoteAddress;
- }
-
- /**
- * Incoming parser errors
- */
- @Override
- public void incomingError(WebSocketException e)
- {
- MuxDropChannel.Reason reason = MuxDropChannel.Reason.PHYSICAL_CONNECTION_FAILED;
- String phrase = String.format("%s: %s", e.getClass().getName(), e.getMessage());
- mustFailPhysicalConnection(new MuxPhysicalConnectionException(reason,phrase));
- }
-
- /**
- * Incoming mux encapsulated frames.
- */
- @Override
- public void incomingFrame(Frame frame)
- {
- parser.parse(frame);
- }
-
- /**
- * Is the muxer and the physical connection still open?
- *
- * @return true if open
- */
- public boolean isOpen()
- {
- return physicalConnection.isOpen();
- }
-
- public String mergeHeaders(List<String> physicalHeaders, String deltaHeaders)
- {
- // TODO Auto-generated method stub
- return null;
- }
-
- /**
- * Per spec, the physical connection must be failed.
- * <p>
- * <a href="https://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-08#section-18">Section 18. Fail the Physical Connection.</a>
- *
- * <blockquote> To _Fail the Physical Connection_, an endpoint MUST send a DropChannel multiplex control block with objective channel ID of 0 and drop
- * reason code in the range of 2000-2999, and then _Fail the WebSocket Connection_ on the physical connection with status code of 1011. </blockquote>
- */
- private void mustFailPhysicalConnection(MuxPhysicalConnectionException muxe)
- {
- // TODO: stop muxer from receiving incoming sub-channel traffic.
-
- MuxDropChannel drop = muxe.getMuxDropChannel();
- LOG.warn(muxe);
- try
- {
- generator.generate(null,drop);
- }
- catch (IOException ioe)
- {
- LOG.warn("Unable to send mux DropChannel",ioe);
- }
-
- String reason = "Mux[MUST FAIL]" + drop.getPhrase();
- reason = StringUtil.truncate(reason,WebSocketFrame.MAX_CONTROL_PAYLOAD);
- this.physicalConnection.close(StatusCode.SERVER_ERROR,reason);
-
- // TODO: trigger abnormal close for all sub-channels.
- }
-
- /**
- * Incoming mux control block, destined for the control channel (id 0)
- */
- @Override
- public void onMuxAddChannelRequest(MuxAddChannelRequest request)
- {
- if (policy.getBehavior() == WebSocketBehavior.CLIENT)
- {
- throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"AddChannelRequest not allowed per spec");
- }
-
- if (request.getRsv() != 0)
- {
- throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_REQUEST_ENCODING,"RSV Not allowed to be set");
- }
-
- // Pre-allocate channel.
- long channelId = request.getChannelId();
- MuxChannel channel = getChannel(channelId, true);
-
- // submit to upgrade handshake process
- try
- {
- switch (request.getEncoding())
- {
- case MuxAddChannelRequest.IDENTITY_ENCODING:
- {
- UpgradeRequest idenReq = MuxRequest.parse(request.getHandshake());
- addServer.handshake(this,channel,idenReq);
- break;
- }
- case MuxAddChannelRequest.DELTA_ENCODING:
- {
- UpgradeRequest baseReq = addServer.getPhysicalHandshakeRequest();
- UpgradeRequest deltaReq = MuxRequest.parse(request.getHandshake());
- UpgradeRequest mergedReq = MuxRequest.merge(baseReq,deltaReq);
-
- addServer.handshake(this,channel,mergedReq);
- break;
- }
- default:
- {
- throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.BAD_REQUEST,"Unrecognized request encoding");
- }
- }
- }
- catch (MuxPhysicalConnectionException e)
- {
- throw e;
- }
- catch (Throwable t)
- {
- throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.BAD_REQUEST,"Unable to parse request",t);
- }
- }
-
- /**
- * Incoming mux control block, destined for the control channel (id 0)
- */
- @Override
- public void onMuxAddChannelResponse(MuxAddChannelResponse response)
- {
- if (policy.getBehavior() == WebSocketBehavior.SERVER)
- {
- throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"AddChannelResponse not allowed per spec");
- }
-
- if (response.getRsv() != 0)
- {
- throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_RESPONSE_ENCODING,"RSV Not allowed to be set");
- }
-
- // Process channel
- long channelId = response.getChannelId();
- MuxChannel channel = getChannel(channelId,false);
-
- // Process Response headers
- try
- {
- // Parse Response
-
- // TODO: Sec-WebSocket-Accept header
- // TODO: Sec-WebSocket-Extensions header
- // TODO: Setup extensions
- // TODO: Setup sessions
-
- // Trigger channel open
- channel.onOpen();
- }
- catch (Throwable t)
- {
- throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.BAD_RESPONSE,"Unable to parse response",t);
- }
- }
-
- /**
- * Incoming mux control block, destined for the control channel (id 0)
- */
- @Override
- public void onMuxDropChannel(MuxDropChannel drop)
- {
- // Process channel
- long channelId = drop.getChannelId();
- MuxChannel channel = getChannel(channelId,false);
-
- String reason = "Mux " + drop.toString();
- reason = StringUtil.truncate(reason,(WebSocketFrame.MAX_CONTROL_PAYLOAD - 2));
- channel.close(StatusCode.PROTOCOL,reason);
- // TODO: set channel to inactive?
- }
-
- /**
- * Incoming mux-unwrapped frames, destined for a sub-channel
- */
- @Override
- public void onMuxedFrame(MuxedFrame frame)
- {
- MuxChannel subchannel = channels.get(frame.getChannelId());
- subchannel.incomingFrame(frame);
- }
-
- @Override
- public void onMuxException(MuxException e)
- {
- if (e instanceof MuxPhysicalConnectionException)
- {
- mustFailPhysicalConnection((MuxPhysicalConnectionException)e);
- }
-
- LOG.warn(e);
- // TODO: handle other (non physical) mux exceptions how?
- }
-
- /**
- * Incoming mux control block, destined for the control channel (id 0)
- */
- @Override
- public void onMuxFlowControl(MuxFlowControl flow)
- {
- if (flow.getSendQuotaSize() > 0x7F_FF_FF_FF_FF_FF_FF_FFL)
- {
- throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.SEND_QUOTA_OVERFLOW,"Send Quota Overflow");
- }
-
- // Process channel
- long channelId = flow.getChannelId();
- MuxChannel channel = getChannel(channelId,false);
-
- // TODO: set channel quota
- }
-
- /**
- * Incoming mux control block, destined for the control channel (id 0)
- */
- @Override
- public void onMuxNewChannelSlot(MuxNewChannelSlot slot)
- {
- if (policy.getBehavior() == WebSocketBehavior.SERVER)
- {
- throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"NewChannelSlot not allowed per spec");
- }
-
- if (slot.isFallback())
- {
- if (slot.getNumberOfSlots() == 0)
- {
- throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Cannot have 0 number of slots during fallback");
- }
- if (slot.getInitialSendQuota() == 0)
- {
- throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Cannot have 0 initial send quota during fallback");
- }
- }
-
- // TODO: handle channel slot
- }
-
- /**
- * Outgoing frame, without mux encapsulated payload.
- */
- public void output(long channelId, Frame frame, WriteCallback callback)
- {
- if (LOG.isDebugEnabled())
- {
- LOG.debug("output({}, {})",channelId,frame,callback);
- }
- generator.generate(channelId,frame,callback);
- }
-
- /**
- * Write an OP out the physical connection.
- *
- * @param op
- * the mux operation to write
- * @throws IOException
- */
- public void output(MuxControlBlock op) throws IOException
- {
- generator.generate(null,op);
- }
-
- public void setAddClient(MuxAddClient addClient)
- {
- this.addClient = addClient;
- }
-
- public void setAddServer(MuxAddServer addServer)
- {
- this.addServer = addServer;
- }
-
- public void setOutgoingFramesHandler(OutgoingFrames outgoing)
- {
- this.generator.setOutgoing(outgoing);
- }
-
- /**
- * Set the remote address of the physical connection.
- * <p>
- * This address made available to sub-channels.
- *
- * @param remoteAddress
- * the remote address
- */
- public void setRemoteAddress(InetSocketAddress remoteAddress)
- {
- this.remoteAddress = remoteAddress;
- }
-
- @Override
- public String toString()
- {
- return String.format("Muxer[subChannels.size=%d]",channels.size());
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxAddClient.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxAddClient.java
deleted file mode 100644
index 155c615..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxAddClient.java
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux.add;
-
-import org.eclipse.jetty.websocket.common.WebSocketSession;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelResponse;
-
-/**
- * Interface for Mux Client to handle receiving a AddChannelResponse
- */
-public interface MuxAddClient
-{
- WebSocketSession createSession(MuxAddChannelResponse response);
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxAddServer.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxAddServer.java
deleted file mode 100644
index fa5b4f8..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxAddServer.java
+++ /dev/null
@@ -1,52 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux.add;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
-import org.eclipse.jetty.websocket.common.WebSocketSession;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxChannel;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxException;
-import org.eclipse.jetty.websocket.common.extensions.mux.Muxer;
-
-/**
- * Server interface, for dealing with incoming AddChannelRequest / AddChannelResponse flows.
- */
-public interface MuxAddServer
-{
- public UpgradeRequest getPhysicalHandshakeRequest();
-
- public UpgradeResponse getPhysicalHandshakeResponse();
-
- /**
- * Perform the handshake.
- *
- * @param channel
- * the channel to attach the {@link WebSocketSession} to.
- * @param requestHandshake
- * the request handshake (request headers)
- * @throws AbstractMuxException
- * if unable to handshake
- * @throws IOException
- * if unable to parse request headers
- */
- void handshake(Muxer muxer, MuxChannel channel, UpgradeRequest request) throws MuxException, IOException;
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/package-info.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/package-info.java
deleted file mode 100644
index 87985ef..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-/**
- * Jetty WebSocket Common : MUX Extension Add Channel Handling [<em>Unstable Early Draft</em>]
- */
-package org.eclipse.jetty.websocket.common.extensions.mux.add;
-
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxAddChannelRequest.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxAddChannelRequest.java
deleted file mode 100644
index 62e1fc0..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxAddChannelRequest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux.op;
-
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxControlBlock;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxOp;
-
-public class MuxAddChannelRequest implements MuxControlBlock
-{
- public static final byte IDENTITY_ENCODING = (byte)0x00;
- public static final byte DELTA_ENCODING = (byte)0x01;
-
- private long channelId = -1;
- private byte encoding;
- private ByteBuffer handshake;
- private byte rsv;
-
- public long getChannelId()
- {
- return channelId;
- }
-
- public byte getEncoding()
- {
- return encoding;
- }
-
- public ByteBuffer getHandshake()
- {
- return handshake;
- }
-
- public long getHandshakeSize()
- {
- if (handshake == null)
- {
- return 0;
- }
- return handshake.remaining();
- }
-
- @Override
- public int getOpCode()
- {
- return MuxOp.ADD_CHANNEL_REQUEST;
- }
-
- public byte getRsv()
- {
- return rsv;
- }
-
- public boolean isDeltaEncoded()
- {
- return (encoding == DELTA_ENCODING);
- }
-
- public boolean isIdentityEncoded()
- {
- return (encoding == IDENTITY_ENCODING);
- }
-
- public void setChannelId(long channelId)
- {
- this.channelId = channelId;
- }
-
- public void setEncoding(byte enc)
- {
- this.encoding = enc;
- }
-
- public void setHandshake(ByteBuffer handshake)
- {
- if (handshake == null)
- {
- this.handshake = null;
- }
- else
- {
- this.handshake = handshake.slice();
- }
- }
-
- public void setHandshake(String rawstring)
- {
- setHandshake(BufferUtil.toBuffer(rawstring, StandardCharsets.UTF_8));
- }
-
- public void setRsv(byte rsv)
- {
- this.rsv = rsv;
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxAddChannelResponse.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxAddChannelResponse.java
deleted file mode 100644
index 6273a7c..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxAddChannelResponse.java
+++ /dev/null
@@ -1,126 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux.op;
-
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxControlBlock;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxOp;
-
-public class MuxAddChannelResponse implements MuxControlBlock
-{
- public static final byte IDENTITY_ENCODING = (byte)0x00;
- public static final byte DELTA_ENCODING = (byte)0x01;
-
- private long channelId;
- private byte encoding;
- private byte rsv;
- private boolean failed = false;
- private ByteBuffer handshake;
-
- public long getChannelId()
- {
- return channelId;
- }
-
- public byte getEncoding()
- {
- return encoding;
- }
-
- public ByteBuffer getHandshake()
- {
- return handshake;
- }
-
- public long getHandshakeSize()
- {
- if (handshake == null)
- {
- return 0;
- }
- return handshake.remaining();
- }
-
- @Override
- public int getOpCode()
- {
- return MuxOp.ADD_CHANNEL_RESPONSE;
- }
-
- public byte getRsv()
- {
- return rsv;
- }
-
- public boolean isDeltaEncoded()
- {
- return (encoding == DELTA_ENCODING);
- }
-
- public boolean isFailed()
- {
- return failed;
- }
-
- public boolean isIdentityEncoded()
- {
- return (encoding == IDENTITY_ENCODING);
- }
-
- public void setChannelId(long channelId)
- {
- this.channelId = channelId;
- }
-
- public void setEncoding(byte enc)
- {
- this.encoding = enc;
- }
-
- public void setFailed(boolean failed)
- {
- this.failed = failed;
- }
-
- public void setHandshake(ByteBuffer handshake)
- {
- if (handshake == null)
- {
- this.handshake = null;
- }
- else
- {
- this.handshake = handshake.slice();
- }
- }
-
- public void setHandshake(String responseHandshake)
- {
- setHandshake(BufferUtil.toBuffer(responseHandshake, StandardCharsets.UTF_8));
- }
-
- public void setRsv(byte rsv)
- {
- this.rsv = rsv;
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxDropChannel.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxDropChannel.java
deleted file mode 100644
index 1d062e1..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxDropChannel.java
+++ /dev/null
@@ -1,183 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux.op;
-
-import java.nio.ByteBuffer;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxControlBlock;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxOp;
-
-public class MuxDropChannel implements MuxControlBlock
-{
- /**
- * Outlined in <a href="https://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-05#section-9.4.1">Section 9.4.1. Drop Reason Codes</a>
- */
- public static enum Reason
- {
- // Normal Close : (1000-1999)
- NORMAL_CLOSURE(1000),
-
- // Failures in Physical Connection : (2000-2999)
- PHYSICAL_CONNECTION_FAILED(2000),
- INVALID_ENCAPSULATING_MESSAGE(2001),
- CHANNEL_ID_TRUNCATED(2002),
- ENCAPSULATED_FRAME_TRUNCATED(2003),
- UNKNOWN_MUX_CONTROL_OPC(2004),
- UNKNOWN_MUX_CONTROL_BLOCK(2005),
- CHANNEL_ALREADY_EXISTS(2006),
- NEW_CHANNEL_SLOT_VIOLATION(2007),
- NEW_CHANNEL_SLOT_OVERFLOW(2008),
- BAD_REQUEST(2009),
- UNKNOWN_REQUEST_ENCODING(2010),
- BAD_RESPONSE(2011),
- UNKNOWN_RESPONSE_ENCODING(2012),
-
- // Failures in Logical Connections : (3000-3999)
- LOGICAL_CHANNEL_FAILED(3000),
- SEND_QUOTA_VIOLATION(3005),
- SEND_QUOTA_OVERFLOW(3006),
- IDLE_TIMEOUT(3007),
- DROP_CHANNEL_ACK(3008),
-
- // Other Peer Actions : (4000-4999)
- USE_ANOTHER_PHYSICAL_CONNECTION(4001),
- BUSY(4002);
-
- private static final Map<Integer, Reason> codeMap;
-
- static
- {
- codeMap = new HashMap<>();
- for (Reason r : values())
- {
- codeMap.put(r.getValue(),r);
- }
- }
-
- public static Reason valueOf(int code)
- {
- return codeMap.get(code);
- }
-
- private final int code;
-
- private Reason(int code)
- {
- this.code = code;
- }
-
- public int getValue()
- {
- return code;
- }
- }
-
- public static MuxDropChannel parse(long channelId, ByteBuffer payload)
- {
- // TODO Auto-generated method stub
- return null;
- }
-
- private final long channelId;
- private final Reason code;
- private String phrase;
- private int rsv;
-
- /**
- * Normal Drop. no reason Phrase.
- *
- * @param channelId
- * the logical channel Id to perform drop against.
- */
- public MuxDropChannel(long channelId)
- {
- this(channelId,Reason.NORMAL_CLOSURE,null);
- }
-
- /**
- * Drop with reason code and optional phrase
- *
- * @param channelId
- * the logical channel Id to perform drop against.
- * @param code
- * reason code
- * @param phrase
- * optional human readable phrase
- */
- public MuxDropChannel(long channelId, int code, String phrase)
- {
- this(channelId, Reason.valueOf(code), phrase);
- }
-
- /**
- * Drop with reason code and optional phrase
- *
- * @param channelId
- * the logical channel Id to perform drop against.
- * @param code
- * reason code
- * @param phrase
- * optional human readable phrase
- */
- public MuxDropChannel(long channelId, Reason code, String phrase)
- {
- this.channelId = channelId;
- this.code = code;
- this.phrase = phrase;
- }
-
- public ByteBuffer asReasonBuffer()
- {
- // TODO: convert to reason buffer
- return null;
- }
-
- public long getChannelId()
- {
- return channelId;
- }
-
- public Reason getCode()
- {
- return code;
- }
-
- @Override
- public int getOpCode()
- {
- return MuxOp.DROP_CHANNEL;
- }
-
- public String getPhrase()
- {
- return phrase;
- }
-
- public int getRsv()
- {
- return rsv;
- }
-
- public void setRsv(int rsv)
- {
- this.rsv = rsv;
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxFlowControl.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxFlowControl.java
deleted file mode 100644
index 32b6d96..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxFlowControl.java
+++ /dev/null
@@ -1,65 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux.op;
-
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxControlBlock;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxOp;
-
-public class MuxFlowControl implements MuxControlBlock
-{
- private long channelId;
- private byte rsv;
- private long sendQuotaSize;
-
- public long getChannelId()
- {
- return channelId;
- }
-
- @Override
- public int getOpCode()
- {
- return MuxOp.FLOW_CONTROL;
- }
-
- public byte getRsv()
- {
- return rsv;
- }
-
- public long getSendQuotaSize()
- {
- return sendQuotaSize;
- }
-
- public void setChannelId(long channelId)
- {
- this.channelId = channelId;
- }
-
- public void setRsv(byte rsv)
- {
- this.rsv = rsv;
- }
-
- public void setSendQuotaSize(long sendQuotaSize)
- {
- this.sendQuotaSize = sendQuotaSize;
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxNewChannelSlot.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxNewChannelSlot.java
deleted file mode 100644
index 09aadf1..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxNewChannelSlot.java
+++ /dev/null
@@ -1,76 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux.op;
-
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxControlBlock;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxOp;
-
-public class MuxNewChannelSlot implements MuxControlBlock
-{
- private boolean fallback;
- private long initialSendQuota;
- private long numberOfSlots;
- private byte rsv;
-
- public long getInitialSendQuota()
- {
- return initialSendQuota;
- }
-
- public long getNumberOfSlots()
- {
- return numberOfSlots;
- }
-
- @Override
- public int getOpCode()
- {
- return MuxOp.NEW_CHANNEL_SLOT;
- }
-
- public byte getRsv()
- {
- return rsv;
- }
-
- public boolean isFallback()
- {
- return fallback;
- }
-
- public void setFallback(boolean fallback)
- {
- this.fallback = fallback;
- }
-
- public void setInitialSendQuota(long initialSendQuota)
- {
- this.initialSendQuota = initialSendQuota;
- }
-
- public void setNumberOfSlots(long numberOfSlots)
- {
- this.numberOfSlots = numberOfSlots;
- }
-
- public void setRsv(byte rsv)
- {
- this.rsv = rsv;
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/package-info.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/package-info.java
deleted file mode 100644
index cd5c7ac..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-/**
- * Jetty WebSocket Common : MUX Extension OpCode Handling [<em>Unstable Early Draft</em>]
- */
-package org.eclipse.jetty.websocket.common.extensions.mux.op;
-
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/package-info.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/package-info.java
deleted file mode 100644
index 7112f3b..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-/**
- * Jetty WebSocket Common : MUX Extension Core [<em>Unstable Early Draft</em>]
- */
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/BinaryFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/BinaryFrame.java
new file mode 100644
index 0000000..3b35563
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/BinaryFrame.java
@@ -0,0 +1,56 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+public class BinaryFrame extends DataFrame
+{
+ public BinaryFrame()
+ {
+ super(OpCode.BINARY);
+ }
+
+ public BinaryFrame setPayload(ByteBuffer buf)
+ {
+ super.setPayload(buf);
+ return this;
+ }
+
+ public BinaryFrame setPayload(byte[] buf)
+ {
+ setPayload(ByteBuffer.wrap(buf));
+ return this;
+ }
+
+ public BinaryFrame setPayload(String payload)
+ {
+ setPayload(StringUtil.getUtf8Bytes(payload));
+ return this;
+ }
+
+ @Override
+ public Type getType()
+ {
+ return Type.BINARY;
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/CloseFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/CloseFrame.java
new file mode 100644
index 0000000..1298435
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/CloseFrame.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+public class CloseFrame extends ControlFrame
+{
+ public CloseFrame()
+ {
+ super(OpCode.CLOSE);
+ }
+
+ @Override
+ public Type getType()
+ {
+ return Type.CLOSE;
+ }
+
+ /**
+ * Truncate arbitrary reason into something that will fit into the CloseFrame limits.
+ *
+ * @param reason
+ * the arbitrary reason to possibly truncate.
+ * @return the possibly truncated reason string.
+ */
+ public static String truncate(String reason)
+ {
+ return StringUtil.truncate(reason,(ControlFrame.MAX_CONTROL_PAYLOAD - 2));
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/ContinuationFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/ContinuationFrame.java
new file mode 100644
index 0000000..403a5a1
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/ContinuationFrame.java
@@ -0,0 +1,54 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+public class ContinuationFrame extends DataFrame
+{
+ public ContinuationFrame()
+ {
+ super(OpCode.CONTINUATION);
+ }
+
+ public ContinuationFrame setPayload(ByteBuffer buf)
+ {
+ super.setPayload(buf);
+ return this;
+ }
+
+ public ContinuationFrame setPayload(byte buf[])
+ {
+ return this.setPayload(ByteBuffer.wrap(buf));
+ }
+
+ public ContinuationFrame setPayload(String message)
+ {
+ return this.setPayload(StringUtil.getUtf8Bytes(message));
+ }
+
+ @Override
+ public Type getType()
+ {
+ return Type.CONTINUATION;
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/ControlFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/ControlFrame.java
new file mode 100644
index 0000000..421b7cd
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/ControlFrame.java
@@ -0,0 +1,137 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import org.eclipse.jetty.websocket.api.ProtocolException;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+
+public abstract class ControlFrame extends WebSocketFrame
+{
+ /** Maximum size of Control frame, per RFC 6455 */
+ public static final int MAX_CONTROL_PAYLOAD = 125;
+
+ public ControlFrame(byte opcode)
+ {
+ super(opcode);
+ }
+
+ public void assertValid()
+ {
+ if (isControlFrame())
+ {
+ if (getPayloadLength() > ControlFrame.MAX_CONTROL_PAYLOAD)
+ {
+ throw new ProtocolException("Desired payload length [" + getPayloadLength() + "] exceeds maximum control payload length ["
+ + MAX_CONTROL_PAYLOAD + "]");
+ }
+
+ if ((finRsvOp & 0x80) == 0)
+ {
+ throw new ProtocolException("Cannot have FIN==false on Control frames");
+ }
+
+ if ((finRsvOp & 0x40) != 0)
+ {
+ throw new ProtocolException("Cannot have RSV1==true on Control frames");
+ }
+
+ if ((finRsvOp & 0x20) != 0)
+ {
+ throw new ProtocolException("Cannot have RSV2==true on Control frames");
+ }
+
+ if ((finRsvOp & 0x10) != 0)
+ {
+ throw new ProtocolException("Cannot have RSV3==true on Control frames");
+ }
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ ControlFrame other = (ControlFrame)obj;
+ if (data == null)
+ {
+ if (other.data != null)
+ {
+ return false;
+ }
+ }
+ else if (!data.equals(other.data))
+ {
+ return false;
+ }
+ if (finRsvOp != other.finRsvOp)
+ {
+ return false;
+ }
+ if (!Arrays.equals(mask,other.mask))
+ {
+ return false;
+ }
+ if (masked != other.masked)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public boolean isControlFrame()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean isDataFrame()
+ {
+ return false;
+ }
+
+ @Override
+ public WebSocketFrame setPayload(ByteBuffer buf)
+ {
+ if (buf == null)
+ {
+ data = null;
+ return this;
+ }
+
+ if (buf.remaining() > ControlFrame.MAX_CONTROL_PAYLOAD)
+ {
+ throw new ProtocolException("Control Payloads can not exceed 125 bytes in length.");
+ }
+ return super.setPayload(buf);
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/DataFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/DataFrame.java
new file mode 100644
index 0000000..4490f23
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/DataFrame.java
@@ -0,0 +1,104 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+
+/**
+ * A Data Frame
+ */
+public class DataFrame extends WebSocketFrame
+{
+ private boolean isPooledBuffer = false;
+
+ protected DataFrame(byte opcode)
+ {
+ super(opcode);
+ }
+
+ /**
+ * Construct new DataFrame based on headers of provided frame.
+ * <p>
+ * Useful for when working in extensions and a new frame needs to be created.
+ */
+ public DataFrame(Frame basedOn)
+ {
+ this(basedOn,false);
+ }
+
+ /**
+ * Construct new DataFrame based on headers of provided frame, overriding for continuations if needed.
+ * <p>
+ * Useful for when working in extensions and a new frame needs to be created.
+ */
+ public DataFrame(Frame basedOn, boolean continuation)
+ {
+ super(basedOn.getOpCode());
+ copyHeaders(basedOn);
+ if (continuation)
+ {
+ setOpCode(OpCode.CONTINUATION);
+ }
+ }
+
+ @Override
+ public void assertValid()
+ {
+ /* no extra validation for data frames (yet) here */
+ }
+
+ @Override
+ public boolean isControlFrame()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isDataFrame()
+ {
+ return true;
+ }
+
+ /**
+ * @return true if payload buffer is from a {@link ByteBufferPool} and can be released when appropriate to do so
+ */
+ public boolean isPooledBuffer()
+ {
+ return isPooledBuffer;
+ }
+
+ /**
+ * Set the data frame to continuation mode
+ */
+ public void setIsContinuation()
+ {
+ setOpCode(OpCode.CONTINUATION);
+ }
+
+ /**
+ * Sets a flag indicating that the underlying payload is from a {@link ByteBufferPool} and can be released when appropriate to do so
+ */
+ public void setPooledBuffer(boolean isPooledBuffer)
+ {
+ this.isPooledBuffer = isPooledBuffer;
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/PingFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/PingFrame.java
new file mode 100644
index 0000000..59ffac8
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/PingFrame.java
@@ -0,0 +1,50 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+public class PingFrame extends ControlFrame
+{
+ public PingFrame()
+ {
+ super(OpCode.PING);
+ }
+
+ public PingFrame setPayload(byte[] bytes)
+ {
+ setPayload(ByteBuffer.wrap(bytes));
+ return this;
+ }
+
+ public PingFrame setPayload(String payload)
+ {
+ setPayload(StringUtil.getUtf8Bytes(payload));
+ return this;
+ }
+
+ @Override
+ public Type getType()
+ {
+ return Type.PING;
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/PongFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/PongFrame.java
new file mode 100644
index 0000000..b3a1ca1
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/PongFrame.java
@@ -0,0 +1,50 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+public class PongFrame extends ControlFrame
+{
+ public PongFrame()
+ {
+ super(OpCode.PONG);
+ }
+
+ public PongFrame setPayload(byte[] bytes)
+ {
+ setPayload(ByteBuffer.wrap(bytes));
+ return this;
+ }
+
+ public PongFrame setPayload(String payload)
+ {
+ setPayload(StringUtil.getUtf8Bytes(payload));
+ return this;
+ }
+
+ @Override
+ public Type getType()
+ {
+ return Type.PONG;
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/TextFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/TextFrame.java
new file mode 100644
index 0000000..4100f04
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/TextFrame.java
@@ -0,0 +1,54 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+public class TextFrame extends DataFrame
+{
+ public TextFrame()
+ {
+ super(OpCode.TEXT);
+ }
+
+ @Override
+ public Type getType()
+ {
+ return Type.TEXT;
+ }
+
+ public TextFrame setPayload(String str)
+ {
+ setPayload(ByteBuffer.wrap(StringUtil.getUtf8Bytes(str)));
+ return this;
+ }
+
+ public String getPayloadAsUTF8()
+ {
+ if (data == null)
+ {
+ return null;
+ }
+ return BufferUtil.toUTF8String(data);
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java
index 032c45c..041916b 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java
@@ -21,6 +21,7 @@
import java.io.EOFException;
import java.io.IOException;
import java.net.InetSocketAddress;
+import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -31,7 +32,6 @@
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
@@ -46,7 +46,6 @@
import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.api.WebSocketTimeoutException;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame;
@@ -59,7 +58,7 @@
import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
/**
- * Provides the implementation of {@link WebSocketConnection} within the framework of the new {@link Connection} framework of jetty-io
+ * Provides the implementation of {@link LogicalConnection} within the framework of the new {@link Connection} framework of jetty-io
*/
public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection, ConnectionStateListener
{
@@ -71,6 +70,12 @@
@Override
public void failed(Throwable x)
{
+ if (ioState.wasAbnormalClose())
+ {
+ LOG.ignore(x);
+ return;
+ }
+
LOG.debug("Write flush failure",x);
// Unable to write? can't notify other side of close, so disconnect.
@@ -96,7 +101,7 @@
// Abnormal Close
reason = CloseStatus.trimMaxReasonLength(reason);
- session.incomingError(new WebSocketException(x)); // TODO: JSR-356 change to Throwable
+ session.notifyError(x);
session.notifyClose(StatusCode.NO_CLOSE,reason);
disconnect(); // disconnect endpoint & connection
@@ -141,7 +146,7 @@
return String.format("%s@%x",FlushInvoker.class.getSimpleName(),hashCode());
}
}
-
+
public class OnDisconnectCallback implements WriteCallback
{
@Override
@@ -218,15 +223,40 @@
}
@Override
+ public Executor getExecutor()
+ {
+ return super.getExecutor();
+ }
+
+ @Override
public void close()
{
close(StatusCode.NORMAL,null);
}
+ /**
+ * Close the connection.
+ * <p>
+ * This can result in a close handshake over the network, or a simple local abnormal close
+ *
+ * @param statusCode
+ * the WebSocket status code.
+ * @param reason
+ * the (optional) reason string. (null is allowed)
+ * @see StatusCode
+ */
@Override
public void close(int statusCode, String reason)
{
- enqueClose(statusCode,reason);
+ CloseInfo close = new CloseInfo(statusCode,reason);
+ if (statusCode == StatusCode.ABNORMAL)
+ {
+ ioState.onAbnormalClose(close);
+ }
+ else
+ {
+ ioState.onCloseLocal(close);
+ }
}
public void complete(final Callback callback)
@@ -248,7 +278,7 @@
@Override
public void disconnect()
{
- LOG.debug("{} disconnect()", policy.getBehavior());
+ LOG.debug("{} disconnect()",policy.getBehavior());
synchronized (writeBytes)
{
if (!writeBytes.isClosed())
@@ -259,7 +289,7 @@
disconnect(false);
}
- public void disconnect(boolean onlyOutput)
+ private void disconnect(boolean onlyOutput)
{
EndPoint endPoint = getEndPoint();
// We need to gently close first, to allow
@@ -273,21 +303,6 @@
}
}
- /**
- * Enqueue a close frame.
- *
- * @param statusCode
- * the WebSocket status code.
- * @param reason
- * the (optional) reason string. (null is allowed)
- * @see StatusCode
- */
- private void enqueClose(int statusCode, String reason)
- {
- CloseInfo close = new CloseInfo(statusCode,reason);
- ioState.onCloseLocal(close);
- }
-
protected void execute(Runnable task)
{
try
@@ -309,12 +324,13 @@
public void flush()
{
- ByteBuffer buffer = null;
+ List<ByteBuffer> buffers = null;
synchronized (writeBytes)
{
if (flushing)
{
+ LOG.debug("Actively flushing");
return;
}
@@ -330,24 +346,20 @@
return;
}
- buffer = writeBytes.getByteBuffer();
+ buffers = writeBytes.getByteBuffers();
- if (buffer == null)
+ if ((buffers == null) || (buffers.size() <= 0))
{
return;
}
flushing = true;
-
- if (LOG.isDebugEnabled())
- {
- LOG.debug("Flushing {} - {}",BufferUtil.toDetailString(buffer),writeBytes);
- }
}
- write(buffer);
+ write(buffers);
}
+ @Override
public ByteBufferPool getBufferPool()
{
return bufferPool;
@@ -371,6 +383,12 @@
}
@Override
+ public long getIdleTimeout()
+ {
+ return getEndPoint().getIdleTimeout();
+ }
+
+ @Override
public IOState getIOState()
{
return ioState;
@@ -450,7 +468,17 @@
fillInterested();
break;
case CLOSED:
- this.disconnect();
+ if (ioState.wasAbnormalClose())
+ {
+ // Fire out a close frame, indicating abnormal shutdown, then disconnect
+ CloseInfo abnormal = new CloseInfo(StatusCode.SHUTDOWN,"Abnormal Close - " + ioState.getCloseInfo().getReason());
+ outgoingFrame(abnormal.asFrame(),new OnDisconnectCallback());
+ }
+ else
+ {
+ // Just disconnect
+ this.disconnect();
+ }
break;
case CLOSING:
CloseInfo close = ioState.getCloseInfo();
@@ -466,7 +494,7 @@
{
LOG.debug("{} onFillable()",policy.getBehavior());
stats.countOnFillableEvents.incrementAndGet();
- ByteBuffer buffer = bufferPool.acquire(getInputBufferSize(),false);
+ ByteBuffer buffer = bufferPool.acquire(getInputBufferSize(),true);
BufferUtil.clear(buffer);
boolean readMore = false;
try
@@ -507,7 +535,7 @@
@Override
protected boolean onReadTimeout()
{
- LOG.debug("Read Timeout");
+ LOG.debug("{} Read Timeout",policy.getBehavior());
IOState state = getIOState();
if ((state.getConnectionState() == ConnectionState.CLOSING) || (state.getConnectionState() == ConnectionState.CLOSED))
@@ -518,8 +546,9 @@
}
// Initiate close - politely send close frame.
- session.incomingError(new WebSocketTimeoutException("Timeout on Read"));
- close(StatusCode.SHUTDOWN,"Idle Timeout");
+ session.notifyError(new SocketTimeoutException("Timeout on Read"));
+ // This is an Abnormal Close condition
+ close(StatusCode.ABNORMAL,"Idle Timeout");
return false;
}
@@ -545,7 +574,7 @@
EndPoint endPoint = getEndPoint();
try
{
- while (true)
+ while (true) // TODO: should this honor the LogicalConnection.suspend() ?
{
int filled = endPoint.fill(buffer);
if (filled == 0)
@@ -565,19 +594,20 @@
LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
}
parser.parse(buffer);
+ // TODO: has the end user application already consumed what it was given?
}
}
}
catch (IOException e)
{
LOG.warn(e);
- enqueClose(StatusCode.PROTOCOL,e.getMessage());
+ close(StatusCode.PROTOCOL,e.getMessage());
return -1;
}
catch (CloseException e)
{
LOG.warn(e);
- enqueClose(e.getStatusCode(),e.getMessage());
+ close(e.getStatusCode(),e.getMessage());
return -1;
}
}
@@ -639,19 +669,24 @@
return String.format("%s{g=%s,p=%s}",super.toString(),generator,parser);
}
- private <C> void write(ByteBuffer buffer)
+ private <C> void write(List<ByteBuffer> buffer)
{
EndPoint endpoint = getEndPoint();
- if (!isOpen())
- {
- writeBytes.failAll(new IOException("Connection closed"));
- return;
- }
-
try
{
- endpoint.write(writeBytes,buffer);
+ int bufsize = buffer.size();
+ if (bufsize == 1)
+ {
+ // simple case
+ endpoint.write(writeBytes,buffer.get(0));
+ }
+ else
+ {
+ // gathered writes case
+ ByteBuffer bbarr[] = buffer.toArray(new ByteBuffer[bufsize]);
+ endpoint.write(writeBytes,bbarr);
+ }
}
catch (Throwable t)
{
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FramePipes.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FramePipes.java
index c349877..229ea71 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FramePipes.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FramePipes.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.websocket.common.io;
-import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
@@ -36,7 +35,7 @@
}
@Override
- public void incomingError(WebSocketException e)
+ public void incomingError(Throwable t)
{
/* cannot send exception on */
}
@@ -60,7 +59,15 @@
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
{
- this.incoming.incomingFrame(frame);
+ try
+ {
+ this.incoming.incomingFrame(frame);
+ callback.writeSuccess();
+ }
+ catch (Throwable t)
+ {
+ callback.writeFailed(t);
+ }
}
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java
index e5bfd08..bf07996 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java
@@ -21,8 +21,6 @@
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -64,12 +62,11 @@
private ConnectionState state;
private final List<ConnectionStateListener> listeners = new CopyOnWriteArrayList<>();
- private final AtomicBoolean inputAvailable;
- private final AtomicBoolean outputAvailable;
- private final AtomicReference<CloseHandshakeSource> closeHandshakeSource;
- private final AtomicReference<CloseInfo> closeInfo;
-
- private final AtomicBoolean cleanClose;
+ private boolean inputAvailable;
+ private boolean outputAvailable;
+ private CloseHandshakeSource closeHandshakeSource;
+ private CloseInfo closeInfo;
+ private boolean cleanClose;
/**
* Create a new IOState, initialized to {@link ConnectionState#CONNECTING}
@@ -77,11 +74,11 @@
public IOState()
{
this.state = ConnectionState.CONNECTING;
- this.inputAvailable = new AtomicBoolean(false);
- this.outputAvailable = new AtomicBoolean(false);
- this.closeHandshakeSource = new AtomicReference<>(CloseHandshakeSource.NONE);
- this.closeInfo = new AtomicReference<>();
- this.cleanClose = new AtomicBoolean(false);
+ this.inputAvailable = false;
+ this.outputAvailable = false;
+ this.closeHandshakeSource = CloseHandshakeSource.NONE;
+ this.closeInfo = null;
+ this.cleanClose = false;
}
public void addListener(ConnectionStateListener listener)
@@ -107,7 +104,7 @@
public CloseInfo getCloseInfo()
{
- return closeInfo.get();
+ return closeInfo;
}
public ConnectionState getConnectionState()
@@ -125,7 +122,7 @@
public boolean isInputAvailable()
{
- return inputAvailable.get();
+ return inputAvailable;
}
public boolean isOpen()
@@ -135,7 +132,7 @@
public boolean isOutputAvailable()
{
- return outputAvailable.get();
+ return outputAvailable;
}
private void notifyStateListeners(ConnectionState state)
@@ -153,8 +150,9 @@
*/
public void onAbnormalClose(CloseInfo close)
{
+ LOG.debug("onAbnormalClose({})",close);
ConnectionState event = null;
- synchronized (this.state)
+ synchronized (this)
{
if (this.state == ConnectionState.CLOSED)
{
@@ -164,14 +162,15 @@
if (this.state == ConnectionState.OPEN)
{
- this.cleanClose.set(false);
+ this.cleanClose = false;
}
this.state = ConnectionState.CLOSED;
- this.closeInfo.compareAndSet(null,close);
- this.inputAvailable.set(false);
- this.outputAvailable.set(false);
- this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL);
+ if (closeInfo == null)
+ this.closeInfo = close;
+ this.inputAvailable = false;
+ this.outputAvailable = false;
+ this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL;
event = this.state;
}
notifyStateListeners(event);
@@ -182,9 +181,9 @@
*/
public void onCloseLocal(CloseInfo close)
{
- LOG.debug("onCloseLocal({})",close);
ConnectionState event = null;
ConnectionState initialState = this.state;
+ LOG.debug("onCloseLocal({}) : {}",close,initialState);
if (initialState == ConnectionState.CLOSED)
{
// already closed
@@ -200,22 +199,26 @@
onOpened();
}
- synchronized (this.state)
+ synchronized (this)
{
- closeInfo.compareAndSet(null,close);
+ if (closeInfo == null)
+ closeInfo = close;
- boolean in = inputAvailable.get();
- boolean out = outputAvailable.get();
- closeHandshakeSource.compareAndSet(CloseHandshakeSource.NONE,CloseHandshakeSource.LOCAL);
+ boolean in = inputAvailable;
+ boolean out = outputAvailable;
+ if (closeHandshakeSource == CloseHandshakeSource.NONE)
+ {
+ closeHandshakeSource = CloseHandshakeSource.LOCAL;
+ }
out = false;
- outputAvailable.set(false);
+ outputAvailable = false;
LOG.debug("onCloseLocal(), input={}, output={}",in,out);
if (!in && !out)
{
LOG.debug("Close Handshake satisfied, disconnecting");
- cleanClose.set(true);
+ cleanClose = true;
this.state = ConnectionState.CLOSED;
event = this.state;
}
@@ -226,8 +229,6 @@
event = this.state;
}
}
-
- LOG.debug("event = {}",event);
// Only notify on state change events
if (event != null)
@@ -235,22 +236,24 @@
LOG.debug("notifying state listeners: {}",event);
notifyStateListeners(event);
- // if harsh, we don't expect an answer.
- if (close.isHarsh())
+ /*
+ // if abnormal, we don't expect an answer.
+ if (close.isAbnormal())
{
- LOG.debug("Harsh close, disconnecting");
- synchronized (this.state)
+ LOG.debug("Abnormal close, disconnecting");
+ synchronized (this)
{
- this.state = ConnectionState.CLOSED;
- cleanClose.set(false);
- outputAvailable.set(false);
- inputAvailable.set(false);
- this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL);
+ state = ConnectionState.CLOSED;
+ cleanClose = false;
+ outputAvailable = false;
+ inputAvailable = false;
+ closeHandshakeSource = CloseHandshakeSource.ABNORMAL;
event = this.state;
}
notifyStateListeners(event);
return;
}
+ */
}
}
@@ -261,7 +264,7 @@
{
LOG.debug("onCloseRemote({})",close);
ConnectionState event = null;
- synchronized (this.state)
+ synchronized (this)
{
if (this.state == ConnectionState.CLOSED)
{
@@ -269,21 +272,25 @@
return;
}
- closeInfo.compareAndSet(null,close);
+ if (closeInfo == null)
+ closeInfo = close;
- boolean in = inputAvailable.get();
- boolean out = outputAvailable.get();
- closeHandshakeSource.compareAndSet(CloseHandshakeSource.NONE,CloseHandshakeSource.REMOTE);
+ boolean in = inputAvailable;
+ boolean out = outputAvailable;
+ if (closeHandshakeSource == CloseHandshakeSource.NONE)
+ {
+ closeHandshakeSource = CloseHandshakeSource.REMOTE;
+ }
in = false;
- inputAvailable.set(false);
+ inputAvailable = false;
LOG.debug("onCloseRemote(), input={}, output={}",in,out);
if (!in && !out)
{
LOG.debug("Close Handshake satisfied, disconnecting");
- cleanClose.set(true);
- this.state = ConnectionState.CLOSED;
+ cleanClose = true;
+ state = ConnectionState.CLOSED;
event = this.state;
}
else if (this.state == ConnectionState.OPEN)
@@ -315,11 +322,11 @@
}
ConnectionState event = null;
- synchronized (this.state)
+ synchronized (this)
{
this.state = ConnectionState.CONNECTED;
- this.inputAvailable.set(false); // cannot read (yet)
- this.outputAvailable.set(true); // write allowed
+ inputAvailable = false; // cannot read (yet)
+ outputAvailable = true; // write allowed
event = this.state;
}
notifyStateListeners(event);
@@ -332,12 +339,12 @@
{
assert (this.state == ConnectionState.CONNECTING);
ConnectionState event = null;
- synchronized (this.state)
+ synchronized (this)
{
this.state = ConnectionState.CLOSED;
- this.cleanClose.set(false);
- this.inputAvailable.set(false);
- this.outputAvailable.set(false);
+ cleanClose = false;
+ inputAvailable = false;
+ outputAvailable = false;
event = this.state;
}
notifyStateListeners(event);
@@ -357,11 +364,11 @@
assert (this.state == ConnectionState.CONNECTED);
ConnectionState event = null;
- synchronized (this.state)
+ synchronized (this)
{
this.state = ConnectionState.OPEN;
- this.inputAvailable.set(true);
- this.outputAvailable.set(true);
+ this.inputAvailable = true;
+ this.outputAvailable = true;
event = this.state;
}
notifyStateListeners(event);
@@ -375,7 +382,7 @@
public void onReadEOF()
{
ConnectionState event = null;
- synchronized (this.state)
+ synchronized (this)
{
if (this.state == ConnectionState.CLOSED)
{
@@ -385,12 +392,13 @@
CloseInfo close = new CloseInfo(StatusCode.NO_CLOSE,"Read EOF");
- this.cleanClose.set(false);
+ this.cleanClose = false;
this.state = ConnectionState.CLOSED;
- this.closeInfo.compareAndSet(null,close);
- this.inputAvailable.set(false);
- this.outputAvailable.set(false);
- this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL);
+ if (closeInfo == null)
+ this.closeInfo = close;
+ this.inputAvailable = false;
+ this.outputAvailable = false;
+ this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL;
event = this.state;
}
notifyStateListeners(event);
@@ -398,21 +406,21 @@
public boolean wasAbnormalClose()
{
- return closeHandshakeSource.get() == CloseHandshakeSource.ABNORMAL;
+ return closeHandshakeSource == CloseHandshakeSource.ABNORMAL;
}
public boolean wasCleanClose()
{
- return cleanClose.get();
+ return cleanClose;
}
public boolean wasLocalCloseInitiated()
{
- return closeHandshakeSource.get() == CloseHandshakeSource.LOCAL;
+ return closeHandshakeSource == CloseHandshakeSource.LOCAL;
}
public boolean wasRemoteCloseInitiated()
{
- return closeHandshakeSource.get() == CloseHandshakeSource.REMOTE;
+ return closeHandshakeSource == CloseHandshakeSource.REMOTE;
}
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java
index 1cf29b2..bfe683b 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java
@@ -21,18 +21,21 @@
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.LinkedList;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
/**
* Interface for working with bytes destined for {@link EndPoint#write(Callback, ByteBuffer...)}
@@ -44,6 +47,8 @@
protected final AtomicBoolean failed = new AtomicBoolean(false);
protected final Frame frame;
protected final Callback callback;
+ /** holds reference to header ByteBuffer, as it needs to be released on success/failure */
+ private ByteBuffer headerBuffer;
public FrameEntry(Frame frame, Callback callback)
{
@@ -51,23 +56,62 @@
this.callback = callback;
}
- public ByteBuffer getByteBuffer()
+ public ByteBuffer getHeaderBytes()
{
- ByteBuffer buffer = generator.generate(bufferSize,frame);
- if (LOG.isDebugEnabled())
- {
- LOG.debug("getByteBuffer() - {}",BufferUtil.toDetailString(buffer));
- }
- return buffer;
+ ByteBuffer buf = generator.generateHeaderBytes(frame);
+ headerBuffer = buf;
+ return buf;
+ }
+
+ public ByteBuffer getPayloadWindow()
+ {
+ // There is no need to release this ByteBuffer, as it is just a slice of the user provided payload
+ return generator.getPayloadWindow(bufferSize,frame);
}
public void notifyFailure(Throwable t)
{
+ freeBuffers();
if (failed.getAndSet(true) == false)
{
notifySafeFailure(callback,t);
}
}
+
+ public void notifySucceeded()
+ {
+ freeBuffers();
+ if (callback == null)
+ {
+ return;
+ }
+ try
+ {
+ callback.succeeded();
+ }
+ catch (Throwable t)
+ {
+ LOG.debug(t);
+ }
+ }
+
+ public void freeBuffers()
+ {
+ if (headerBuffer != null)
+ {
+ generator.getBufferPool().release(headerBuffer);
+ headerBuffer = null;
+ }
+ releasePayloadBuffer(frame);
+ }
+
+ /**
+ * Indicate that the frame entry is done generating
+ */
+ public boolean isDone()
+ {
+ return frame.remaining() <= 0;
+ }
}
private static final Logger LOG = Log.getLogger(WriteBytesProvider.class);
@@ -80,12 +124,14 @@
private LinkedList<FrameEntry> queue;
/** the buffer input size */
private int bufferSize = 2048;
+ /** the gathered write bytebuffer array limit */
+ private int gatheredBufferLimit = 10;
+ /** Past Frames, not yet notified (from gathered generation/write) */
+ private LinkedList<FrameEntry> past;
/** Currently active frame */
private FrameEntry active;
/** Tracking for failure */
private Throwable failure;
- /** The last requested buffer */
- private ByteBuffer buffer;
/** Is WriteBytesProvider closed to more WriteBytes being enqueued? */
private AtomicBoolean closed;
@@ -104,6 +150,7 @@
this.generator = Objects.requireNonNull(generator);
this.flushCallback = Objects.requireNonNull(flushCallback);
this.queue = new LinkedList<>();
+ this.past = new LinkedList<>();
this.closed = new AtomicBoolean(false);
}
@@ -112,6 +159,7 @@
*/
public void close()
{
+ LOG.debug(".close()");
// Set queue closed, no new enqueue allowed.
this.closed.set(true);
// flush out backlog in queue
@@ -145,12 +193,12 @@
FrameEntry entry = new FrameEntry(frame,callback);
- switch (frame.getType())
+ switch (frame.getOpCode())
{
- case PING:
+ case OpCode.PING:
queue.addFirst(entry);
break;
- case CLOSE:
+ case OpCode.CLOSE:
closed.set(true);
// drop the rest of the queue?
queue.addLast(entry);
@@ -163,32 +211,36 @@
public void failAll(Throwable t)
{
+ // Collect entries for callback
+ List<FrameEntry> callbacks = new ArrayList<>();
+
synchronized (this)
{
- boolean notified = false;
-
// fail active (if set)
if (active != null)
{
- active.notifyFailure(t);
- notified = true;
+ FrameEntry entry = active;
+ active = null;
+ callbacks.add(entry);
}
- failure = t;
+ callbacks.addAll(past);
+ callbacks.addAll(queue);
- // fail others
- for (FrameEntry fe : queue)
- {
- fe.notifyFailure(t);
- notified = true;
- }
-
+ past.clear();
queue.clear();
+ }
- if (notified)
+ // notify flush callback
+ if (!callbacks.isEmpty())
+ {
+ // TODO: always notify instead?
+ flushCallback.failed(t);
+
+ // notify entry callbacks
+ for (FrameEntry entry : callbacks)
{
- // notify flush callback
- flushCallback.failed(t);
+ entry.notifyFailure(t);
}
}
}
@@ -213,34 +265,54 @@
}
/**
- * Get the next ByteBuffer to write.
+ * Get the next set of ByteBuffers to write.
*
- * @return the next ByteBuffer (or null if nothing to write)
+ * @return the next set of ByteBuffers to write
*/
- public ByteBuffer getByteBuffer()
+ public List<ByteBuffer> getByteBuffers()
{
+ List<ByteBuffer> bufs = null;
+ int count = 0;
synchronized (this)
{
- if (active == null)
+ for (; count < gatheredBufferLimit; count++)
{
- if (queue.isEmpty())
+ if (active == null)
{
- // nothing in queue
- return null;
+ if (queue.isEmpty())
+ {
+ // nothing in queue
+ return bufs;
+ }
+
+ // get current topmost entry
+ active = queue.pop();
+
+ // generate header
+ if (bufs == null)
+ {
+ bufs = new ArrayList<>();
+ }
+ bufs.add(active.getHeaderBytes());
+ count++;
}
- // get current topmost entry
- active = queue.pop();
- }
- if (active == null)
- {
- // no active frame available, even in queue.
- return null;
+ // collect payload window
+ if (bufs == null)
+ {
+ bufs = new ArrayList<>();
+ }
+ bufs.add(active.getPayloadWindow());
+ if (active.isDone())
+ {
+ past.add(active);
+ active = null;
+ }
}
-
- buffer = active.getByteBuffer();
}
- return buffer;
+
+ LOG.debug("Collected {} ByteBuffers",bufs.size());
+ return bufs;
}
/**
@@ -272,6 +344,24 @@
}
}
+ public void releasePayloadBuffer(Frame frame)
+ {
+ if (!frame.hasPayload())
+ {
+ return;
+ }
+
+ if (frame instanceof DataFrame)
+ {
+ DataFrame data = (DataFrame)frame;
+ if (data.isPooledBuffer())
+ {
+ ByteBuffer payload = frame.getPayload();
+ generator.getBufferPool().release(payload);
+ }
+ }
+ }
+
/**
* Set the buffer size used for generating ByteBuffers from the frames.
* <p>
@@ -291,42 +381,30 @@
@Override
public void succeeded()
{
- Callback successCallback = null;
+ // Collect entries for callback
+ List<FrameEntry> callbacks = new ArrayList<>();
synchronized (this)
{
- // Release the active byte buffer first
- generator.getBufferPool().release(buffer);
-
- if (active == null)
- {
- return;
- }
-
- if (active.frame.remaining() <= 0)
+ if ((active != null) && (active.frame.remaining() <= 0))
{
// All done with active FrameEntry
- successCallback = active.callback;
- // Forget active
+ FrameEntry entry = active;
active = null;
+ callbacks.add(entry);
}
- // notify flush callback
- flushCallback.succeeded();
+ callbacks.addAll(past);
+ past.clear();
}
- // Notify success (outside of synchronize lock)
- if (successCallback != null)
+ // notify flush callback
+ flushCallback.succeeded();
+
+ // notify entry callbacks outside of synchronize
+ for (FrameEntry entry : callbacks)
{
- try
- {
- // notify of success
- successCallback.succeeded();
- }
- catch (Throwable t)
- {
- LOG.warn("Callback failure",t);
- }
+ entry.notifySucceeded();
}
}
@@ -345,6 +423,7 @@
{
b.append(",active=").append(active);
b.append(",queue.size=").append(queue.size());
+ b.append(",past.size=").append(past.size());
}
b.append(']');
return b.toString();
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteCallbackWrapper.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteCallbackWrapper.java
index 34b2bf1..e2cb9aa 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteCallbackWrapper.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteCallbackWrapper.java
@@ -22,7 +22,7 @@
import org.eclipse.jetty.websocket.api.WriteCallback;
/**
- * Wraps the exposed {@link WriteCallback} API with a Jetty {@link Callback}.
+ * Wraps the exposed {@link WriteCallback} WebSocket API with a Jetty {@link Callback}.
* <p>
* We don't expose the jetty {@link Callback} object to the webapp, as that makes things complicated for the WebAppContext's Classloader.
*/
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/CloseReasonValidator.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/CloseReasonValidator.java
deleted file mode 100644
index 5a9af00..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/CloseReasonValidator.java
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.io.payload;
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.websocket.common.OpCode;
-
-/**
- * Validate UTF8 correctness for {@link OpCode#CLOSE} Reason message.
- */
-public class CloseReasonValidator extends UTF8Validator implements PayloadProcessor
-{
- private int statusCodeBytes = 2;
-
- @Override
- public void process(ByteBuffer payload)
- {
- if ((payload == null) || (payload.remaining() <= 2))
- {
- // no validation needed
- return;
- }
-
- ByteBuffer copy = payload.slice();
- while (statusCodeBytes > 0)
- {
- copy.get();
- statusCodeBytes--;
- }
-
- super.process(copy);
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessor.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessor.java
index ead2bc9..be702a3 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessor.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessor.java
@@ -24,39 +24,49 @@
public class DeMaskProcessor implements PayloadProcessor
{
- private boolean isMasked;
- private byte mask[];
- private int offset;
+ private byte maskBytes[];
+ private int maskOffset;
@Override
public void process(ByteBuffer payload)
{
- if (!isMasked)
+ if (maskBytes == null)
{
return;
}
+ int maskInt = ByteBuffer.wrap(maskBytes).getInt();
int start = payload.position();
int end = payload.limit();
- for (int i = start; i < end; i++, offset++)
+ int offset = this.maskOffset;
+ int remaining;
+ while ((remaining = end - start) > 0)
{
- payload.put(i,(byte)(payload.get(i) ^ mask[offset % 4]));
+ if (remaining >= 4 && (offset % 4) == 0)
+ {
+ payload.putInt(start,payload.getInt(start) ^ maskInt);
+ start += 4;
+ offset += 4;
+ }
+ else
+ {
+ payload.put(start,(byte)(payload.get(start) ^ maskBytes[offset & 3]));
+ ++start;
+ ++offset;
+ }
}
+ maskOffset = offset;
+ }
+
+ public void reset(byte mask[])
+ {
+ this.maskBytes = mask;
+ this.maskOffset = 0;
}
@Override
public void reset(Frame frame)
{
- this.isMasked = frame.isMasked();
- if (isMasked)
- {
- this.mask = frame.getMask();
- }
- else
- {
- this.mask = null;
- }
-
- offset = 0;
+ reset(frame.getMask());
}
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/NoOpValidator.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/NoOpValidator.java
deleted file mode 100644
index f4d4e90..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/NoOpValidator.java
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.io.payload;
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-
-/**
- * payload validator does no validation.
- */
-public class NoOpValidator implements PayloadProcessor
-{
- public static final NoOpValidator INSTANCE = new NoOpValidator();
-
- @Override
- public void process(ByteBuffer payload)
- {
- /* all payloads are valid in this case */
- }
-
- @Override
- public void reset(Frame frame)
- {
- /* do nothing */
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/UTF8Validator.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/UTF8Validator.java
deleted file mode 100644
index c2dfd95..0000000
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/UTF8Validator.java
+++ /dev/null
@@ -1,111 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.io.payload;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.Utf8Appendable;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.BadPayloadException;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-
-/**
- * Used to perform validation of UTF8 payload contents (for fast-fail reasons)
- */
-public class UTF8Validator extends Utf8Appendable implements PayloadProcessor
-{
- private static class EmptyAppender implements Appendable
- {
- private int length = 0;
-
- @Override
- public Appendable append(char c) throws IOException
- {
- length++;
- return this;
- }
-
- @Override
- public Appendable append(CharSequence csq) throws IOException
- {
- length += csq.length();
- return this;
- }
-
- @Override
- public Appendable append(CharSequence csq, int start, int end) throws IOException
- {
- length += (end - start);
- return this;
- }
-
- public int getLength()
- {
- return length;
- }
- }
-
- private static final Logger LOG = Log.getLogger(UTF8Validator.class);
-
- private EmptyAppender buffer;
-
- public UTF8Validator()
- {
- super(new EmptyAppender());
- this.buffer = (EmptyAppender)_appendable;
- }
-
- @Override
- public int length()
- {
- return this.buffer.getLength();
- }
-
- @Override
- public void process(ByteBuffer payload)
- {
- if (LOG.isDebugEnabled())
- {
- LOG.debug("Payload: {}",BufferUtil.toDetailString(payload));
- }
-
- if ((payload == null) || (payload.remaining() <= 0))
- {
- return;
- }
-
- try
- {
- append(payload.slice());
- }
- catch (NotUtf8Exception e)
- {
- throw new BadPayloadException(e);
- }
- }
-
- @Override
- public void reset(Frame frame)
- {
- /* do nothing */
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageAppender.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageAppender.java
index fb6ba7f..96940eb 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageAppender.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageAppender.java
@@ -31,13 +31,17 @@
*
* @param payload
* the payload to append.
+ * @param isLast
+ * flag indicating if this is the last part of the message or not.
* @throws IOException
* if unable to append the payload
*/
- abstract void appendMessage(ByteBuffer payload) throws IOException;
+ abstract void appendMessage(ByteBuffer payload, boolean isLast) throws IOException;
/**
* Notification that message is to be considered complete.
+ * <p>
+ * Any cleanup or final actions should be taken here.
*/
abstract void messageComplete();
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageInputStream.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageInputStream.java
index 21f4ce1..08e03a7 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageInputStream.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageInputStream.java
@@ -21,98 +21,151 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
+import java.util.concurrent.BlockingDeque;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.websocket.common.events.AnnotatedEventDriver;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
/**
- * Support class for reading binary message data as an InputStream.
+ * Support class for reading a (single) WebSocket BINARY message via a InputStream.
+ * <p>
+ * An InputStream that can access a queue of ByteBuffer payloads, along with expected InputStream blocking behavior.
*/
public class MessageInputStream extends InputStream implements MessageAppender
{
- private static final int BUFFER_SIZE = 65535;
+ private static final Logger LOG = Log.getLogger(MessageInputStream.class);
/**
- * Threshold (of bytes) to perform compaction at
+ * Used for controlling read suspend/resume behavior if the queue is full, but the read operations haven't caught up yet.
*/
- private static final int COMPACT_THRESHOLD = 5;
- private final AnnotatedEventDriver driver;
- private final ByteBuffer buf;
- private int size;
- private boolean finished;
- private boolean needsNotification;
- private int readPosition;
+ @SuppressWarnings("unused")
+ private final LogicalConnection connection;
+ private final BlockingDeque<ByteBuffer> buffers = new LinkedBlockingDeque<>();
+ private AtomicBoolean closed = new AtomicBoolean(false);
+ // EOB / End of Buffers
+ private AtomicBoolean buffersExhausted = new AtomicBoolean(false);
+ private ByteBuffer activeBuffer = null;
- public MessageInputStream(AnnotatedEventDriver driver)
+ public MessageInputStream(LogicalConnection connection)
{
- this.driver = driver;
- this.buf = ByteBuffer.allocate(BUFFER_SIZE);
- BufferUtil.clearToFill(this.buf);
- size = 0;
- readPosition = this.buf.position();
- finished = false;
- needsNotification = true;
+ this.connection = connection;
}
@Override
- public void appendMessage(ByteBuffer payload) throws IOException
+ public void appendMessage(ByteBuffer payload, boolean isLast) throws IOException
{
- if (finished)
+ if (LOG.isDebugEnabled())
{
- throw new IOException("Cannot append to finished buffer");
+ LOG.debug("appendMessage(ByteBuffer,{}): {}",isLast,BufferUtil.toDetailString(payload));
+ }
+
+ if (buffersExhausted.get())
+ {
+ // This indicates a programming mistake/error and must be bug fixed
+ throw new RuntimeException("Last frame already received");
}
- if (payload == null)
+ // if closed, we should just toss incoming payloads into the bit bucket.
+ if (closed.get())
{
- // empty payload is valid
return;
}
- driver.getPolicy().assertValidMessageSize(size + payload.remaining());
- size += payload.remaining();
-
- synchronized (buf)
+ // Put the payload into the queue
+ try
{
- // TODO: grow buffer till max binary message size?
- // TODO: compact this buffer to fit incoming buffer?
- // TODO: tell connection to suspend if buffer too full?
- BufferUtil.put(payload,buf);
+ buffers.put(payload);
+ if (isLast)
+ {
+ buffersExhausted.set(true);
+ }
}
-
- if (needsNotification)
+ catch (InterruptedException e)
{
- needsNotification = true;
- this.driver.onInputStream(this);
+ throw new IOException(e);
}
}
@Override
public void close() throws IOException
{
- finished = true;
+ closed.set(true);
super.close();
}
@Override
+ public synchronized void mark(int readlimit)
+ {
+ /* do nothing */
+ }
+
+ @Override
+ public boolean markSupported()
+ {
+ return false;
+ }
+
+ @Override
public void messageComplete()
{
- finished = true;
+ LOG.debug("messageComplete()");
+
+ buffersExhausted.set(true);
+ // toss an empty ByteBuffer into queue to let it drain
+ try
+ {
+ buffers.put(ByteBuffer.wrap(new byte[0]));
+ }
+ catch (InterruptedException ignore)
+ {
+ /* ignore */
+ }
}
@Override
public int read() throws IOException
{
- synchronized (buf)
+ LOG.debug("read()");
+
+ try
{
- byte b = buf.get(readPosition);
- readPosition++;
- if (readPosition <= (buf.limit() - COMPACT_THRESHOLD))
+ if (closed.get())
{
- int curPos = buf.position();
- buf.compact();
- int offsetPos = buf.position() - curPos;
- readPosition += offsetPos;
+ return -1;
}
- return b;
+
+ if (activeBuffer == null)
+ {
+ activeBuffer = buffers.take();
+ }
+
+ while (activeBuffer.remaining() <= 0)
+ {
+ if (buffersExhausted.get())
+ {
+ closed.set(true);
+ return -1;
+ }
+ activeBuffer = buffers.take();
+ }
+
+ return activeBuffer.get();
}
+ catch (InterruptedException e)
+ {
+ LOG.warn(e);
+ closed.set(true);
+ return -1;
+// throw new IOException(e);
+ }
+ }
+
+ @Override
+ public synchronized void reset() throws IOException
+ {
+ throw new IOException("reset() not supported");
}
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageOutputStream.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageOutputStream.java
index 29764e6..66993eb 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageOutputStream.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageOutputStream.java
@@ -20,30 +20,224 @@
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ExecutionException;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
-import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
+/**
+ * Support for writing a single WebSocket BINARY message via a {@link OutputStream}
+ */
public class MessageOutputStream extends OutputStream
{
- private final LogicalConnection connection;
+ private static final Logger LOG = Log.getLogger(MessageOutputStream.class);
private final OutgoingFrames outgoing;
+ private final ByteBufferPool bufferPool;
+ private long frameCount = 0;
+ private BinaryFrame frame;
+ private ByteBuffer buffer;
+ private FutureWriteCallback blocker;
+ private WriteCallback callback;
+ private boolean closed = false;
- public MessageOutputStream(LogicalConnection connection, OutgoingFrames outgoing)
+ public MessageOutputStream(OutgoingFrames outgoing, int bufferSize, ByteBufferPool bufferPool)
{
- this.connection = connection;
this.outgoing = outgoing;
+ this.bufferPool = bufferPool;
+ this.buffer = bufferPool.acquire(bufferSize,true);
+ BufferUtil.flipToFill(buffer);
+ this.frame = new BinaryFrame();
}
- public boolean isClosed()
+ public MessageOutputStream(WebSocketSession session)
{
- // TODO Auto-generated method stub
- return false;
+ this(session.getOutgoingHandler(),session.getPolicy().getMaxBinaryMessageBufferSize(),session.getBufferPool());
+ }
+
+ private void assertNotClosed() throws IOException
+ {
+ if (closed)
+ {
+ IOException e = new IOException("Stream is closed");
+ notifyFailure(e);
+ throw e;
+ }
}
@Override
- public void write(int b) throws IOException
+ public synchronized void close() throws IOException
{
- // TODO Auto-generated method stub
+ assertNotClosed();
+ LOG.debug("close()");
+
+ // finish sending whatever in the buffer with FIN=true
+ flush(true);
+
+ // close stream
+ LOG.debug("Sent Frame Count: {}",frameCount);
+ closed = true;
+ try
+ {
+ if (callback != null)
+ {
+ callback.writeSuccess();
+ }
+ super.close();
+ bufferPool.release(buffer);
+ LOG.debug("closed");
+ }
+ catch (IOException e)
+ {
+ notifyFailure(e);
+ throw e;
+ }
+ }
+
+ @Override
+ public synchronized void flush() throws IOException
+ {
+ LOG.debug("flush()");
+ assertNotClosed();
+
+ // flush whatever is in the buffer with FIN=false
+ flush(false);
+ try
+ {
+ super.flush();
+ LOG.debug("flushed");
+ }
+ catch (IOException e)
+ {
+ notifyFailure(e);
+ throw e;
+ }
+ }
+
+ /**
+ * Flush whatever is in the buffer.
+ *
+ * @param fin
+ * fin flag
+ * @throws IOException
+ */
+ private synchronized void flush(boolean fin) throws IOException
+ {
+ BufferUtil.flipToFlush(buffer,0);
+ LOG.debug("flush({}): {}",fin,BufferUtil.toDetailString(buffer));
+ frame.setPayload(buffer);
+ frame.setFin(fin);
+
+ try
+ {
+ blocker = new FutureWriteCallback();
+ outgoing.outgoingFrame(frame,blocker);
+ try
+ {
+ // block on write
+ blocker.get();
+ // block success
+ frameCount++;
+ frame.setIsContinuation();
+ }
+ catch (ExecutionException e)
+ {
+ Throwable cause = e.getCause();
+ if (cause != null)
+ {
+ if (cause instanceof IOException)
+ {
+ throw (IOException)cause;
+ }
+ else
+ {
+ throw new IOException(cause);
+ }
+ }
+ throw new IOException("Failed to flush",e);
+ }
+ catch (InterruptedException e)
+ {
+ throw new IOException("Failed to flush",e);
+ }
+ }
+ catch (IOException e)
+ {
+ notifyFailure(e);
+ throw e;
+ }
+ }
+
+ private void notifyFailure(IOException e)
+ {
+ if (callback != null)
+ {
+ callback.writeFailed(e);
+ }
+ }
+
+ public void setCallback(WriteCallback callback)
+ {
+ this.callback = callback;
+ }
+
+ @Override
+ public synchronized void write(byte[] b) throws IOException
+ {
+ try
+ {
+ this.write(b,0,b.length);
+ }
+ catch (IOException e)
+ {
+ notifyFailure(e);
+ throw e;
+ }
+ }
+
+ @Override
+ public synchronized void write(byte[] b, int off, int len) throws IOException
+ {
+ LOG.debug("write(byte[{}], {}, {})",b.length,off,len);
+ int left = len; // bytes left to write
+ int offset = off; // offset within provided array
+ while (left > 0)
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("buffer: {}",BufferUtil.toDetailString(buffer));
+ }
+ int space = buffer.remaining();
+ assert (space > 0);
+ int size = Math.min(space,left);
+ buffer.put(b,offset,size);
+ assert (size > 0);
+ left -= size; // decrement bytes left
+ if (left > 0)
+ {
+ flush(false);
+ }
+ offset += size; // increment offset
+ }
+ }
+
+ @Override
+ public synchronized void write(int b) throws IOException
+ {
+ assertNotClosed();
+
+ // buffer up to limit, flush once buffer reached.
+ buffer.put((byte)b);
+ if (buffer.remaining() <= 0)
+ {
+ flush(false);
+ }
}
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java
index e790989..37c065a 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java
@@ -19,79 +19,36 @@
package org.eclipse.jetty.websocket.common.message;
import java.io.IOException;
-import java.io.Reader;
+import java.io.InputStreamReader;
import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
-import org.eclipse.jetty.util.Utf8StringBuilder;
-import org.eclipse.jetty.websocket.common.events.AnnotatedEventDriver;
+import org.eclipse.jetty.util.StringUtil;
/**
- * Support class for reading text message data as an Reader.
+ * Support class for reading a (single) WebSocket TEXT message via a Reader.
* <p>
- * Due to the spec, this reader is forced to use the UTF8 charset.
+ * In compliance to the WebSocket spec, this reader always uses the UTF8 {@link Charset}.
*/
-public class MessageReader extends Reader implements MessageAppender
+public class MessageReader extends InputStreamReader implements MessageAppender
{
- private final AnnotatedEventDriver driver;
- private final Utf8StringBuilder utf;
- private int size;
- private boolean finished;
- private boolean needsNotification;
+ private final MessageInputStream stream;
- public MessageReader(AnnotatedEventDriver driver)
+ public MessageReader(MessageInputStream stream)
{
- this.driver = driver;
- this.utf = new Utf8StringBuilder();
- size = 0;
- finished = false;
- needsNotification = true;
+ super(stream,StringUtil.__UTF8_CHARSET);
+ this.stream = stream;
}
@Override
- public void appendMessage(ByteBuffer payload) throws IOException
+ public void appendMessage(ByteBuffer payload, boolean isLast) throws IOException
{
- if (finished)
- {
- throw new IOException("Cannot append to finished buffer");
- }
-
- if (payload == null)
- {
- // empty payload is valid
- return;
- }
-
- driver.getPolicy().assertValidMessageSize(size + payload.remaining());
- size += payload.remaining();
-
- synchronized (utf)
- {
- utf.append(payload);
- }
-
- if (needsNotification)
- {
- needsNotification = true;
- this.driver.onReader(this);
- }
- }
-
- @Override
- public void close() throws IOException
- {
- finished = true;
+ this.stream.appendMessage(payload,isLast);
}
@Override
public void messageComplete()
{
- finished = true;
- }
-
- @Override
- public int read(char[] cbuf, int off, int len) throws IOException
- {
- // TODO Auto-generated method stub
- return 0;
+ this.stream.messageComplete();
}
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageWriter.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageWriter.java
index 3aed776..b324e45 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageWriter.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageWriter.java
@@ -20,46 +20,203 @@
import java.io.IOException;
import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ExecutionException;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
-import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
+/**
+ * Support for writing a single WebSocket TEXT message via a {@link Writer}
+ * <p>
+ * Note: Per WebSocket spec, all WebSocket TEXT messages must be encoded in UTF-8
+ */
public class MessageWriter extends Writer
{
- private final LogicalConnection connection;
+ private static final Logger LOG = Log.getLogger(MessageWriter.class);
private final OutgoingFrames outgoing;
+ private final ByteBufferPool bufferPool;
+ private long frameCount = 0;
+ private TextFrame frame;
+ private ByteBuffer buffer;
+ private Utf8CharBuffer utf;
+ private FutureWriteCallback blocker;
+ private WriteCallback callback;
+ private boolean closed = false;
- public MessageWriter(LogicalConnection connection, OutgoingFrames outgoing)
+ public MessageWriter(OutgoingFrames outgoing, int bufferSize, ByteBufferPool bufferPool)
{
- this.connection = connection;
this.outgoing = outgoing;
+ this.bufferPool = bufferPool;
+ this.buffer = bufferPool.acquire(bufferSize,true);
+ BufferUtil.flipToFill(buffer);
+ this.utf = Utf8CharBuffer.wrap(buffer);
+ this.frame = new TextFrame();
+ }
+
+ public MessageWriter(WebSocketSession session)
+ {
+ this(session.getOutgoingHandler(),session.getPolicy().getMaxTextMessageBufferSize(),session.getBufferPool());
+ }
+
+ private void assertNotClosed() throws IOException
+ {
+ if (closed)
+ {
+ IOException e = new IOException("Stream is closed");
+ notifyFailure(e);
+ throw e;
+ }
}
@Override
- public void close() throws IOException
+ public synchronized void close() throws IOException
{
- // TODO Auto-generated method stub
+ assertNotClosed();
+ // finish sending whatever in the buffer with FIN=true
+ flush(true);
+
+ // close stream
+ closed = true;
+ if (callback != null)
+ {
+ callback.writeSuccess();
+ }
+ bufferPool.release(buffer);
+ LOG.debug("closed (frame count={})",frameCount);
}
@Override
public void flush() throws IOException
{
- // TODO Auto-generated method stub
+ assertNotClosed();
+ // flush whatever is in the buffer with FIN=false
+ flush(false);
}
- public boolean isClosed()
+ /**
+ * Flush whatever is in the buffer.
+ *
+ * @param fin
+ * fin flag
+ * @throws IOException
+ */
+ private synchronized void flush(boolean fin) throws IOException
{
- // TODO Auto-generated method stub
- return false;
+ ByteBuffer data = utf.getByteBuffer();
+ frame.setPayload(data);
+ frame.setFin(fin);
+
+ try
+ {
+ blocker = new FutureWriteCallback();
+ outgoing.outgoingFrame(frame,blocker);
+ try
+ {
+ // block on write
+ blocker.get();
+ // write success
+ // clear utf buffer
+ utf.clear();
+ frameCount++;
+ frame.setIsContinuation();
+ }
+ catch (ExecutionException e)
+ {
+ Throwable cause = e.getCause();
+ if (cause != null)
+ {
+ if (cause instanceof IOException)
+ {
+ throw (IOException)cause;
+ }
+ else
+ {
+ throw new IOException(cause);
+ }
+ }
+ throw new IOException("Failed to flush",e);
+ }
+ catch (InterruptedException e)
+ {
+ throw new IOException("Failed to flush",e);
+ }
+ }
+ catch (IOException e)
+ {
+ notifyFailure(e);
+ throw e;
+ }
+ }
+
+ private void notifyFailure(IOException e)
+ {
+ if (callback != null)
+ {
+ callback.writeFailed(e);
+ }
+ }
+
+ public void setCallback(WriteCallback callback)
+ {
+ this.callback = callback;
+ }
+
+ @Override
+ public void write(char[] cbuf) throws IOException
+ {
+ try
+ {
+ this.write(cbuf,0,cbuf.length);
+ }
+ catch (IOException e)
+ {
+ notifyFailure(e);
+ throw e;
+ }
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException
{
- // TODO Auto-generated method stub
-
+ assertNotClosed();
+ int left = len; // bytes left to write
+ int offset = off; // offset within provided array
+ while (left > 0)
+ {
+ int space = utf.remaining();
+ int size = Math.min(space,left);
+ assert (space > 0);
+ assert (size > 0);
+ utf.append(cbuf,offset,size); // append with utf logic
+ left -= size; // decrement char left
+ if (left > 0)
+ {
+ flush(false);
+ }
+ offset += size; // increment offset
+ }
}
+ @Override
+ public void write(int c) throws IOException
+ {
+ assertNotClosed();
+
+ // buffer up to limit, flush once buffer reached.
+ utf.append(c); // append with utf logic
+ if (utf.remaining() <= 0)
+ {
+ flush(false);
+ }
+ }
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleBinaryMessage.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleBinaryMessage.java
index d8802f8..274f731 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleBinaryMessage.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleBinaryMessage.java
@@ -29,9 +29,9 @@
{
private static final int BUFFER_SIZE = 65535;
private final EventDriver onEvent;
- private final ByteArrayOutputStream out;
+ protected final ByteArrayOutputStream out;
private int size;
- private boolean finished;
+ protected boolean finished;
public SimpleBinaryMessage(EventDriver onEvent)
{
@@ -41,7 +41,7 @@
}
@Override
- public void appendMessage(ByteBuffer payload) throws IOException
+ public void appendMessage(ByteBuffer payload, boolean isLast) throws IOException
{
if (finished)
{
@@ -54,7 +54,7 @@
return;
}
- onEvent.getPolicy().assertValidMessageSize(size + payload.remaining());
+ onEvent.getPolicy().assertValidBinaryMessageSize(size + payload.remaining());
size += payload.remaining();
BufferUtil.writeTo(payload,out);
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleTextMessage.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleTextMessage.java
index 8b60aae..e002e12 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleTextMessage.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleTextMessage.java
@@ -27,9 +27,9 @@
public class SimpleTextMessage implements MessageAppender
{
private final EventDriver onEvent;
- private final Utf8StringBuilder utf;
+ protected final Utf8StringBuilder utf;
private int size = 0;
- private boolean finished;
+ protected boolean finished;
public SimpleTextMessage(EventDriver onEvent)
{
@@ -40,7 +40,7 @@
}
@Override
- public void appendMessage(ByteBuffer payload) throws IOException
+ public void appendMessage(ByteBuffer payload, boolean isLast) throws IOException
{
if (finished)
{
@@ -53,7 +53,7 @@
return;
}
- onEvent.getPolicy().assertValidMessageSize(size + payload.remaining());
+ onEvent.getPolicy().assertValidTextMessageSize(size + payload.remaining());
size += payload.remaining();
// allow for fast fail of BAD utf (incomplete utf will trigger on messageComplete)
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/Utf8CharBuffer.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/Utf8CharBuffer.java
new file mode 100644
index 0000000..9d634fb
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/Utf8CharBuffer.java
@@ -0,0 +1,115 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Utf8Appendable;
+
+/**
+ * A CharBuffer wrapped with the Utf8Appendable logic.
+ */
+public class Utf8CharBuffer extends Utf8Appendable
+{
+ private static final Charset UTF8 = StringUtil.__UTF8_CHARSET;
+
+ /**
+ * Convenience method to wrap a ByteBuffer with a {@link Utf8CharBuffer}
+ *
+ * @param buffer
+ * the buffer to wrap
+ * @return the Utf8ByteBuffer for the provided ByteBuffer
+ */
+ public static Utf8CharBuffer wrap(ByteBuffer buffer)
+ {
+ return new Utf8CharBuffer(buffer.asCharBuffer());
+ }
+
+ private final CharBuffer buffer;
+
+ private Utf8CharBuffer(CharBuffer buffer)
+ {
+ super(buffer);
+ this.buffer = buffer;
+ }
+
+ public void append(char[] cbuf, int offset, int size)
+ {
+ append(BufferUtil.toDirectBuffer(new String(cbuf,offset,size),UTF8));
+ }
+
+ public void append(int c)
+ {
+ buffer.append((char)c);
+ }
+
+ public void clear()
+ {
+ buffer.position(0);
+ buffer.limit(buffer.capacity());
+ }
+
+ public ByteBuffer getByteBuffer()
+ {
+ // remember settings
+ int limit = buffer.limit();
+ int position = buffer.position();
+
+ // flip to flush
+ buffer.limit(buffer.position());
+ buffer.position(0);
+
+ // get byte buffer
+ ByteBuffer bb = UTF8.encode(buffer);
+
+ // restor settings
+ buffer.limit(limit);
+ buffer.position(position);
+
+ return bb;
+ }
+
+ @Override
+ public int length()
+ {
+ return buffer.capacity();
+ }
+
+ public int remaining()
+ {
+ return buffer.remaining();
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder();
+ str.append("Utf8CharBuffer@").append(hashCode());
+ str.append("[p=").append(buffer.position());
+ str.append(",l=").append(buffer.limit());
+ str.append(",c=").append(buffer.capacity());
+ str.append(",r=").append(buffer.remaining());
+ str.append("]");
+ return str.toString();
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java
new file mode 100644
index 0000000..eb6e3c5
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java
@@ -0,0 +1,395 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+public class ReflectUtils
+{
+ private static class GenericRef
+ {
+ // The base class reference lookup started from
+ private final Class<?> baseClass;
+ // The interface that we are interested in
+ private final Class<?> ifaceClass;
+
+ // The actual class generic interface was found on
+ Class<?> genericClass;
+
+ // The found genericType
+ public Type genericType;
+ private int genericIndex;
+
+ public GenericRef(final Class<?> baseClass, final Class<?> ifaceClass)
+ {
+ this.baseClass = baseClass;
+ this.ifaceClass = ifaceClass;
+ }
+
+ public boolean needsUnwrap()
+ {
+ return (genericClass == null) && (genericType != null) && (genericType instanceof TypeVariable<?>);
+ }
+
+ public void setGenericFromType(Type type, int index)
+ {
+ // debug("setGenericFromType(%s,%d)",toShortName(type),index);
+ this.genericType = type;
+ this.genericIndex = index;
+ if (type instanceof Class)
+ {
+ this.genericClass = (Class<?>)type;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append("GenericRef [baseClass=");
+ builder.append(baseClass);
+ builder.append(", ifaceClass=");
+ builder.append(ifaceClass);
+ builder.append(", genericType=");
+ builder.append(genericType);
+ builder.append(", genericClass=");
+ builder.append(genericClass);
+ builder.append("]");
+ return builder.toString();
+ }
+ }
+
+ private static StringBuilder appendTypeName(StringBuilder sb, Type type, boolean ellipses)
+ {
+ if (type instanceof Class<?>)
+ {
+ Class<?> ctype = (Class<?>)type;
+ if (ctype.isArray())
+ {
+ try
+ {
+ int dimensions = 0;
+ while (ctype.isArray())
+ {
+ dimensions++;
+ ctype = ctype.getComponentType();
+ }
+ sb.append(ctype.getName());
+ for (int i = 0; i < dimensions; i++)
+ {
+ if (ellipses)
+ {
+ sb.append("...");
+ }
+ else
+ {
+ sb.append("[]");
+ }
+ }
+ return sb;
+ }
+ catch (Throwable ignore)
+ {
+ // ignore
+ }
+ }
+
+ sb.append(ctype.getName());
+ }
+ else
+ {
+ sb.append(type.toString());
+ }
+
+ return sb;
+ }
+
+ /**
+ * Given a Base (concrete) Class, find the interface specified, and return its concrete Generic class declaration.
+ *
+ * @param baseClass
+ * the base (concrete) class to look in
+ * @param ifaceClass
+ * the interface of interest
+ * @return the (concrete) generic class that the interface exposes
+ */
+ public static Class<?> findGenericClassFor(Class<?> baseClass, Class<?> ifaceClass)
+ {
+ GenericRef ref = new GenericRef(baseClass,ifaceClass);
+ if (resolveGenericRef(ref,baseClass))
+ {
+ // debug("Generic Found: %s",ref.genericClass);
+ return ref.genericClass;
+ }
+
+ // debug("Generic not found: %s",ref);
+ return null;
+ }
+
+ private static int findTypeParameterIndex(Class<?> clazz, TypeVariable<?> needVar)
+ {
+ // debug("findTypeParameterIndex(%s, [%s])",toShortName(clazz),toShortName(needVar));
+ TypeVariable<?> params[] = clazz.getTypeParameters();
+ for (int i = 0; i < params.length; i++)
+ {
+ if (params[i].getName().equals(needVar.getName()))
+ {
+ // debug("Type Parameter found at index: [%d]",i);
+ return i;
+ }
+ }
+ // debug("Type Parameter NOT found");
+ return -1;
+ }
+
+ public static boolean isDefaultConstructable(Class<?> clazz)
+ {
+ int mods = clazz.getModifiers();
+ if (Modifier.isAbstract(mods) || !Modifier.isPublic(mods))
+ {
+ // Needs to be public, non-abstract
+ return false;
+ }
+
+ Class<?>[] noargs = new Class<?>[0];
+ try
+ {
+ // Needs to have a no-args constructor
+ Constructor<?> constructor = clazz.getConstructor(noargs);
+ // Constructor needs to be public
+ return Modifier.isPublic(constructor.getModifiers());
+ }
+ catch (NoSuchMethodException | SecurityException e)
+ {
+ return false;
+ }
+ }
+
+ private static boolean resolveGenericRef(GenericRef ref, Class<?> clazz, Type type)
+ {
+ if (type instanceof Class)
+ {
+ if (type == ref.ifaceClass)
+ {
+ // is this a straight ref or a TypeVariable?
+ // debug("Found ref (as class): %s",toShortName(type));
+ ref.setGenericFromType(type,0);
+ return true;
+ }
+ else
+ {
+ // Keep digging
+ return resolveGenericRef(ref,type);
+ }
+ }
+
+ if (type instanceof ParameterizedType)
+ {
+ ParameterizedType ptype = (ParameterizedType)type;
+ Type rawType = ptype.getRawType();
+ if (rawType == ref.ifaceClass)
+ {
+ // debug("Found ref on [%s] as ParameterizedType [%s]",toShortName(clazz),toShortName(ptype));
+ // Always get the raw type parameter, let unwrap() solve for what it is
+ ref.setGenericFromType(ptype.getActualTypeArguments()[0],0);
+ return true;
+ }
+ else
+ {
+ // Keep digging
+ return resolveGenericRef(ref,rawType);
+ }
+ }
+ return false;
+ }
+
+ private static boolean resolveGenericRef(GenericRef ref, Type type)
+ {
+ if ((type == null) || (type == Object.class))
+ {
+ return false;
+ }
+
+ if (type instanceof Class)
+ {
+ Class<?> clazz = (Class<?>)type;
+ // prevent spinning off into Serialization and other parts of the
+ // standard tree that we could care less about
+ if (clazz.getName().matches("^javax*\\..*"))
+ {
+ return false;
+ }
+
+ Type ifaces[] = clazz.getGenericInterfaces();
+ for (Type iface : ifaces)
+ {
+ // debug("resolve %s interface[]: %s",toShortName(clazz),toShortName(iface));
+ if (resolveGenericRef(ref,clazz,iface))
+ {
+ if (ref.needsUnwrap())
+ {
+ // debug("## Unwrap class %s::%s",toShortName(clazz),toShortName(iface));
+ TypeVariable<?> needVar = (TypeVariable<?>)ref.genericType;
+ // debug("needs unwrap of type var [%s] - index [%d]",toShortName(needVar),ref.genericIndex);
+
+ // attempt to find typeParameter on class itself
+ int typeParamIdx = findTypeParameterIndex(clazz,needVar);
+ // debug("type param index for %s[%s] is [%d]",toShortName(clazz),toShortName(needVar),typeParamIdx);
+
+ if (typeParamIdx >= 0)
+ {
+ // found a type parameter, use it
+ // debug("unwrap from class [%s] - typeParameters[%d]",toShortName(clazz),typeParamIdx);
+ TypeVariable<?> params[] = clazz.getTypeParameters();
+ if (params.length >= typeParamIdx)
+ {
+ ref.setGenericFromType(params[typeParamIdx],typeParamIdx);
+ }
+ }
+ else if (iface instanceof ParameterizedType)
+ {
+ // use actual args on interface
+ Type arg = ((ParameterizedType)iface).getActualTypeArguments()[ref.genericIndex];
+ ref.setGenericFromType(arg,ref.genericIndex);
+ }
+ }
+ return true;
+ }
+ }
+
+ type = clazz.getGenericSuperclass();
+ return resolveGenericRef(ref,type);
+ }
+
+ if (type instanceof ParameterizedType)
+ {
+ ParameterizedType ptype = (ParameterizedType)type;
+ Class<?> rawClass = (Class<?>)ptype.getRawType();
+ if (resolveGenericRef(ref,rawClass))
+ {
+ if (ref.needsUnwrap())
+ {
+ // debug("## Unwrap ParameterizedType %s::%s",toShortName(type),toShortName(rawClass));
+ TypeVariable<?> needVar = (TypeVariable<?>)ref.genericType;
+ // debug("needs unwrap of type var [%s] - index [%d]",toShortName(needVar),ref.genericIndex);
+ int typeParamIdx = findTypeParameterIndex(rawClass,needVar);
+ // debug("type paramIdx of %s::%s is index [%d]",toShortName(rawClass),toShortName(needVar),typeParamIdx);
+
+ Type arg = ptype.getActualTypeArguments()[typeParamIdx];
+ ref.setGenericFromType(arg,typeParamIdx);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public static String toShortName(Type type)
+ {
+ if (type == null)
+ {
+ return "<null>";
+ }
+
+ if (type instanceof Class)
+ {
+ String name = ((Class<?>)type).getName();
+ return trimClassName(name);
+ }
+
+ if (type instanceof ParameterizedType)
+ {
+ ParameterizedType ptype = (ParameterizedType)type;
+ StringBuilder str = new StringBuilder();
+ str.append(trimClassName(((Class<?>)ptype.getRawType()).getName()));
+ str.append("<");
+ Type args[] = ptype.getActualTypeArguments();
+ for (int i = 0; i < args.length; i++)
+ {
+ if (i > 0)
+ {
+ str.append(",");
+ }
+ str.append(args[i]);
+ }
+ str.append(">");
+ return str.toString();
+ }
+
+ return type.toString();
+ }
+
+ public static String toString(Class<?> pojo, Method method)
+ {
+ StringBuilder str = new StringBuilder();
+
+ // method modifiers
+ int mod = method.getModifiers() & Modifier.methodModifiers();
+ if (mod != 0)
+ {
+ str.append(Modifier.toString(mod)).append(' ');
+ }
+
+ // return type
+ Type retType = method.getGenericReturnType();
+ appendTypeName(str,retType,false).append(' ');
+
+ // class name
+ str.append(pojo.getName());
+ str.append("#");
+
+ // method name
+ str.append(method.getName());
+
+ // method parameters
+ str.append('(');
+ Type[] params = method.getGenericParameterTypes();
+ for (int j = 0; j < params.length; j++)
+ {
+ boolean ellipses = method.isVarArgs() && (j == (params.length - 1));
+ appendTypeName(str,params[j],ellipses);
+ if (j < (params.length - 1))
+ {
+ str.append(", ");
+ }
+ }
+ str.append(')');
+
+ // TODO: show exceptions?
+ return str.toString();
+ }
+
+ public static String trimClassName(String name)
+ {
+ int idx = name.lastIndexOf('.');
+ name = name.substring(idx + 1);
+ idx = name.lastIndexOf('$');
+ if (idx >= 0)
+ {
+ name = name.substring(idx + 1);
+ }
+ return name;
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/TextUtil.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/TextUtil.java
new file mode 100644
index 0000000..85633b7
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/TextUtil.java
@@ -0,0 +1,84 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.util;
+
+/**
+ * Collection of utility methods for Text content
+ */
+public final class TextUtil
+{
+ /**
+ * Create a hint of what the text is like.
+ * <p>
+ * Used by logging and error messages to get a hint of what the text is like.
+ *
+ * @param text
+ * the text to abbreviate, quote, and generally give you a hint of what the value is.
+ * @return the abbreviated text
+ */
+ public static String hint(String text)
+ {
+ if (text == null)
+ {
+ return "<null>";
+ }
+ return '"' + maxStringLength(30,text) + '"';
+ }
+
+ /**
+ * Smash a long string to fit within the max string length, by taking the middle section of the string and replacing them with an ellipsis "..."
+ * <p>
+ *
+ * <pre>
+ * Examples:
+ * .maxStringLength( 9, "Eatagramovabits") == "Eat...its"
+ * .maxStringLength(10, "Eatagramovabits") == "Eat...bits"
+ * .maxStringLength(11, "Eatagramovabits") == "Eata...bits"
+ * </pre>
+ *
+ * @param max
+ * the maximum size of the string (minimum size supported is 9)
+ * @param raw
+ * the raw string to smash
+ * @return the ellipsis'd version of the string.
+ */
+ public static String maxStringLength(int max, String raw)
+ {
+ int length = raw.length();
+ if (length <= max)
+ {
+ // already short enough
+ return raw;
+ }
+
+ if (max < 9)
+ {
+ // minimum supported
+ return raw.substring(0,max);
+ }
+
+ StringBuilder ret = new StringBuilder();
+ int startLen = (int)Math.round((double)max / (double)3);
+ ret.append(raw.substring(0,startLen));
+ ret.append("...");
+ ret.append(raw.substring(length - (max - startLen - 3)));
+
+ return ret.toString();
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.api.extensions.Extension b/jetty-websocket/websocket-common/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.api.extensions.Extension
new file mode 100644
index 0000000..11b4f35
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.api.extensions.Extension
@@ -0,0 +1,5 @@
+org.eclipse.jetty.websocket.common.extensions.identity.IdentityExtension
+org.eclipse.jetty.websocket.common.extensions.compress.DeflateFrameExtension
+org.eclipse.jetty.websocket.common.extensions.compress.XWebkitDeflateFrameExtension
+org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension
+org.eclipse.jetty.websocket.common.extensions.fragment.FragmentExtension
\ No newline at end of file
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryStreamSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryStreamSocket.java
index d0e3b90..114afdf 100644
--- a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryStreamSocket.java
+++ b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryStreamSocket.java
@@ -35,6 +35,10 @@
@OnWebSocketMessage
public void onBinary(InputStream stream)
{
+ if (stream == null)
+ {
+ new RuntimeException("Stream cannot be null").printStackTrace(System.err);
+ }
capture.add("onBinary(%s)",stream);
}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java
index 0b71bb5..002569c 100644
--- a/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java
+++ b/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java
@@ -25,7 +25,7 @@
/**
* Example EchoSocket using Annotations.
*/
-@WebSocket(maxMessageSize = 64 * 1024)
+@WebSocket(maxTextMessageSize = 64 * 1024)
public class AnnotatedEchoSocket
{
@OnWebSocketMessage
@@ -35,7 +35,7 @@
{
System.out.printf("Echoing back message [%s]%n",message);
// echo the message back
- session.getRemote().sendStringByFuture(message);
+ session.getRemote().sendString(message,null);
}
}
}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java
index 6d76f0e..fbfa027 100644
--- a/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java
+++ b/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java
@@ -59,7 +59,7 @@
{
System.out.printf("Echoing back message [%s]%n",message);
// echo the message back
- outbound.getRemote().sendStringByFuture(message);
+ outbound.getRemote().sendString(message,null);
}
}
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/AcceptHashTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/AcceptHashTest.java
index f84909f..607ad7d 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/AcceptHashTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/AcceptHashTest.java
@@ -18,14 +18,13 @@
package org.eclipse.jetty.websocket.common;
+import static org.hamcrest.Matchers.is;
+
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.websocket.common.AcceptHash;
import org.junit.Assert;
import org.junit.Test;
-import static org.hamcrest.Matchers.is;
-
public class AcceptHashTest
{
@Test
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ByteBufferAssert.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ByteBufferAssert.java
index 99cc35b..2762fe5 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ByteBufferAssert.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ByteBufferAssert.java
@@ -18,13 +18,13 @@
package org.eclipse.jetty.websocket.common;
+import static org.hamcrest.Matchers.is;
+
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
import org.junit.Assert;
-import static org.hamcrest.Matchers.is;
-
public class ByteBufferAssert
{
public static void assertEquals(String message, byte[] expected, byte[] actual)
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ClosePayloadParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ClosePayloadParserTest.java
index defae5b..ccaec81 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ClosePayloadParserTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ClosePayloadParserTest.java
@@ -18,12 +18,11 @@
package org.eclipse.jetty.websocket.common;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorParserRoundtripTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorParserRoundtripTest.java
index 6eaa773..16d4cea 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorParserRoundtripTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorParserRoundtripTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.common;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.nio.ByteBuffer;
import java.util.Arrays;
@@ -27,6 +27,7 @@
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.junit.Assert;
import org.junit.Test;
@@ -49,10 +50,14 @@
{
// Generate Buffer
BufferUtil.flipToFill(out);
- WebSocketFrame frame = WebSocketFrame.text(message);
- out = gen.generate(frame);
+ WebSocketFrame frame = new TextFrame().setPayload(message);
+ ByteBuffer header = gen.generateHeaderBytes(frame);
+ ByteBuffer payload = gen.getPayloadWindow(frame.getPayloadLength(),frame);
+ out.put(header);
+ out.put(payload);
// Parse Buffer
+ BufferUtil.flipToFlush(out,0);
parser.parse(out);
}
finally
@@ -64,7 +69,7 @@
capture.assertNoErrors();
capture.assertHasFrame(OpCode.TEXT,1);
- WebSocketFrame txt = capture.getFrames().get(0);
+ TextFrame txt = (TextFrame)capture.getFrames().get(0);
Assert.assertThat("Text parsed",txt.getPayloadAsUTF8(),is(message));
}
@@ -81,10 +86,11 @@
String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
ByteBuffer out = bufferPool.acquire(8192,false);
+ BufferUtil.flipToFill(out);
try
{
// Setup Frame
- WebSocketFrame frame = WebSocketFrame.text(message);
+ WebSocketFrame frame = new TextFrame().setPayload(message);
// Add masking
byte mask[] = new byte[4];
@@ -92,9 +98,13 @@
frame.setMask(mask);
// Generate Buffer
- out = gen.generate(8192,frame);
+ ByteBuffer header = gen.generateHeaderBytes(frame);
+ ByteBuffer payload = gen.getPayloadWindow(8192,frame);
+ out.put(header);
+ out.put(payload);
// Parse Buffer
+ BufferUtil.flipToFlush(out,0);
parser.parse(out);
}
finally
@@ -106,7 +116,7 @@
capture.assertNoErrors();
capture.assertHasFrame(OpCode.TEXT,1);
- WebSocketFrame txt = capture.getFrames().get(0);
+ TextFrame txt = (TextFrame)capture.getFrames().get(0);
Assert.assertTrue("Text.isMasked",txt.isMasked());
Assert.assertThat("Text parsed",txt.getPayloadAsUTF8(),is(message));
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorTest.java
index d14e1b9..f290984 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorTest.java
@@ -18,60 +18,228 @@
package org.eclipse.jetty.websocket.common;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
import java.nio.ByteBuffer;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.junit.Assert;
import org.junit.Test;
public class GeneratorTest
{
+ private static final Logger LOG = Log.getLogger(GeneratorTest.WindowHelper.class);
+
+ public static class WindowHelper
+ {
+ final int windowSize;
+ int totalParts;
+ int totalBytes;
+
+ public WindowHelper(int windowSize)
+ {
+ this.windowSize = windowSize;
+ this.totalParts = 0;
+ this.totalBytes = 0;
+ }
+
+ public ByteBuffer generateWindowed(Frame... frames)
+ {
+ // Create Buffer to hold all generated frames in a single buffer
+ int completeBufSize = 0;
+ for (Frame f : frames)
+ {
+ completeBufSize += Generator.OVERHEAD + f.getPayloadLength();
+ }
+
+ ByteBuffer completeBuf = ByteBuffer.allocate(completeBufSize);
+ BufferUtil.clearToFill(completeBuf);
+
+ // Generate from all frames
+ Generator generator = new UnitGenerator();
+
+ for (Frame f : frames)
+ {
+ ByteBuffer header = generator.generateHeaderBytes(f);
+ totalBytes += BufferUtil.put(header,completeBuf);
+
+ // Generate using windowing
+ boolean done = false;
+ while (!done)
+ {
+ ByteBuffer window = generator.getPayloadWindow(windowSize,f);
+ Assert.assertThat("Generated should not exceed window size",window.remaining(),lessThanOrEqualTo(windowSize));
+
+ totalBytes += window.remaining();
+ completeBuf.put(window);
+ totalParts++;
+
+ done = (f.remaining() <= 0);
+ }
+ }
+
+ // Return results
+ BufferUtil.flipToFlush(completeBuf,0);
+ return completeBuf;
+ }
+
+ public void assertTotalParts(int expectedParts)
+ {
+ Assert.assertThat("Generated Parts",totalParts,is(expectedParts));
+ }
+
+ public void assertTotalBytes(int expectedBytes)
+ {
+ Assert.assertThat("Generated Bytes",totalBytes,is(expectedBytes));
+ }
+ }
+
+ private void assertGeneratedBytes(CharSequence expectedBytes, Frame... frames)
+ {
+ // collect up all frames as single ByteBuffer
+ ByteBuffer allframes = UnitGenerator.generate(frames);
+ // Get hex String form of all frames bytebuffer.
+ String actual = Hex.asHex(allframes);
+ // Validate
+ Assert.assertThat("Buffer",actual,is(expectedBytes.toString()));
+ }
+
+ private String asMaskedHex(String str, byte[] maskingKey)
+ {
+ byte utf[] = StringUtil.getUtf8Bytes(str);
+ mask(utf,maskingKey);
+ return Hex.asHex(utf);
+ }
+
+ private void mask(byte[] buf, byte[] maskingKey)
+ {
+ int size = buf.length;
+ for (int i = 0; i < size; i++)
+ {
+ buf[i] ^= maskingKey[i % 4];
+ }
+ }
+
+ @Test
+ public void testClose_Empty()
+ {
+ // 0 byte payload (no status code)
+ assertGeneratedBytes("8800",new CloseFrame());
+ }
+
+ @Test
+ public void testClose_CodeNoReason()
+ {
+ CloseInfo close = new CloseInfo(StatusCode.NORMAL);
+ // 2 byte payload (2 bytes for status code)
+ assertGeneratedBytes("880203E8",close.asFrame());
+ }
+
+ @Test
+ public void testClose_CodeOkReason()
+ {
+ CloseInfo close = new CloseInfo(StatusCode.NORMAL,"OK");
+ // 4 byte payload (2 bytes for status code, 2 more for "OK")
+ assertGeneratedBytes("880403E84F4B",close.asFrame());
+ }
+
+ @Test
+ public void testText_Hello()
+ {
+ WebSocketFrame frame = new TextFrame().setPayload("Hello");
+ byte utf[] = StringUtil.getUtf8Bytes("Hello");
+ assertGeneratedBytes("8105" + Hex.asHex(utf),frame);
+ }
+
+ @Test
+ public void testText_Masked()
+ {
+ WebSocketFrame frame = new TextFrame().setPayload("Hello");
+ byte maskingKey[] = Hex.asByteArray("11223344");
+ frame.setMask(maskingKey);
+
+ // what is expected
+ StringBuilder expected = new StringBuilder();
+ expected.append("8185").append("11223344");
+ expected.append(asMaskedHex("Hello",maskingKey));
+
+ // validate
+ assertGeneratedBytes(expected,frame);
+ }
+
+ @Test
+ public void testText_Masked_OffsetSourceByteBuffer()
+ {
+ ByteBuffer payload = ByteBuffer.allocate(100);
+ payload.position(5);
+ payload.put(StringUtil.getUtf8Bytes("Hello"));
+ payload.flip();
+ payload.position(5);
+ // at this point, we have a ByteBuffer of 100 bytes.
+ // but only a few bytes in the middle are made available for the payload.
+ // we are testing that masking works as intended, even if the provided
+ // payload does not start at position 0.
+ LOG.debug("Payload = {}",BufferUtil.toDetailString(payload));
+ WebSocketFrame frame = new TextFrame().setPayload(payload);
+ byte maskingKey[] = Hex.asByteArray("11223344");
+ frame.setMask(maskingKey);
+
+ // what is expected
+ StringBuilder expected = new StringBuilder();
+ expected.append("8185").append("11223344");
+ expected.append(asMaskedHex("Hello",maskingKey));
+
+ // validate
+ assertGeneratedBytes(expected,frame);
+ }
+
/**
* Prevent regression of masking of many packets.
*/
@Test
public void testManyMasked()
{
- int pingCount = 10;
+ int pingCount = 2;
// Prepare frames
- List<WebSocketFrame> send = new ArrayList<>();
+ WebSocketFrame[] frames = new WebSocketFrame[pingCount + 1];
for (int i = 0; i < pingCount; i++)
{
- String payload = String.format("ping-%d[%X]",i,i);
- send.add(WebSocketFrame.ping().setPayload(payload));
+ frames[i] = new PingFrame().setPayload(String.format("ping-%d",i));
}
- send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+ frames[pingCount] = new CloseInfo(StatusCode.NORMAL).asFrame();
- ByteBuffer completeBuf = UnitGenerator.generate(send);
-
- // Parse complete buffer (5 bytes at a time)
- UnitParser parser = new UnitParser();
- IncomingFramesCapture capture = new IncomingFramesCapture();
- parser.setIncomingFramesHandler(capture);
-
- int segmentSize = 5;
- parser.parseSlowly(completeBuf,segmentSize);
-
- // Assert validity of frame
- int frameCount = send.size();
- capture.assertFrameCount(frameCount);
- for (int i = 0; i < frameCount; i++)
+ // Mask All Frames
+ byte maskingKey[] = Hex.asByteArray("11223344");
+ for (WebSocketFrame f : frames)
{
- WebSocketFrame actual = capture.getFrames().get(i);
- WebSocketFrame expected = send.get(i);
- String prefix = "Frame[" + i + "]";
- Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(expected.getOpCode()));
- Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.getPayloadLength()));
- ByteBufferAssert.assertEquals(prefix + ".payload",expected.getPayload(),actual.getPayload());
+ f.setMask(maskingKey);
}
+
+ // Validate result of generation
+ StringBuilder expected = new StringBuilder();
+ expected.append("8986").append("11223344");
+ expected.append(asMaskedHex("ping-0",maskingKey)); // ping 0
+ expected.append("8986").append("11223344");
+ expected.append(asMaskedHex("ping-1",maskingKey)); // ping 1
+ expected.append("8882").append("11223344");
+ byte closure[] = Hex.asByteArray("03E8");
+ mask(closure,maskingKey);
+ expected.append(Hex.asHex(closure)); // normal closure
+
+ assertGeneratedBytes(expected,frames);
}
/**
@@ -84,37 +252,22 @@
byte payload[] = new byte[10240];
Arrays.fill(payload,(byte)0x44);
- WebSocketFrame frame = WebSocketFrame.binary(payload);
+ WebSocketFrame frame = new BinaryFrame().setPayload(payload);
- // tracking values
- int totalParts = 0;
- int totalBytes = 0;
+ // Generate
int windowSize = 1024;
+ WindowHelper helper = new WindowHelper(windowSize);
+ ByteBuffer completeBuffer = helper.generateWindowed(frame);
+
+ // Validate
int expectedHeaderSize = 4;
- int expectedParts = (int)Math.ceil((double)(payload.length + expectedHeaderSize) / windowSize);
+ int expectedSize = payload.length + expectedHeaderSize;
+ int expectedParts = (int)Math.ceil((double)(payload.length) / windowSize);
- // the generator
- Generator generator = new UnitGenerator();
+ helper.assertTotalParts(expectedParts);
+ helper.assertTotalBytes(payload.length + expectedHeaderSize);
- // lets see how many parts the generator makes
- boolean done = false;
- while (!done)
- {
- // sanity check in loop, our test should fail if this is true.
- Assert.assertThat("Too many parts",totalParts,lessThanOrEqualTo(expectedParts));
-
- ByteBuffer buf = generator.generate(windowSize,frame);
- Assert.assertThat("Generated should not exceed window size",buf.remaining(),lessThanOrEqualTo(windowSize));
-
- totalBytes += buf.remaining();
- totalParts++;
-
- done = (frame.remaining() <= 0);
- }
-
- // validate
- Assert.assertThat("Created Parts",totalParts,is(expectedParts));
- Assert.assertThat("Created Bytes",totalBytes,is(payload.length + expectedHeaderSize));
+ Assert.assertThat("Generated Buffer",completeBuffer.remaining(),is(expectedSize));
}
@Test
@@ -125,45 +278,25 @@
Arrays.fill(payload,(byte)0x55);
byte mask[] = new byte[]
- { 0x2A, (byte)0xF0, 0x0F, 0x00 };
+ { 0x2A, (byte)0xF0, 0x0F, 0x00 };
- WebSocketFrame frame = WebSocketFrame.binary(payload);
+ WebSocketFrame frame = new BinaryFrame().setPayload(payload);
frame.setMask(mask); // masking!
- // tracking values
- int totalParts = 0;
- int totalBytes = 0;
- int windowSize = 2929; // important for test, use an odd # window size to test masking across window barriers
+ // Generate
+ int windowSize = 2929;
+ WindowHelper helper = new WindowHelper(windowSize);
+ ByteBuffer completeBuffer = helper.generateWindowed(frame);
+
+ // Validate
int expectedHeaderSize = 8;
- int expectedParts = (int)Math.ceil((double)(payload.length + expectedHeaderSize) / windowSize);
+ int expectedSize = payload.length + expectedHeaderSize;
+ int expectedParts = (int)Math.ceil((double)(payload.length) / windowSize);
- // Buffer to capture generated bytes (we do this to validate that the masking
- // is working correctly
- ByteBuffer completeBuf = ByteBuffer.allocate(payload.length + expectedHeaderSize);
- BufferUtil.clearToFill(completeBuf);
+ helper.assertTotalParts(expectedParts);
+ helper.assertTotalBytes(payload.length + expectedHeaderSize);
- // Generate and capture generator output
- Generator generator = new UnitGenerator();
-
- boolean done = false;
- while (!done)
- {
- // sanity check in loop, our test should fail if this is true.
- Assert.assertThat("Too many parts",totalParts,lessThanOrEqualTo(expectedParts));
-
- ByteBuffer buf = generator.generate(windowSize,frame);
- Assert.assertThat("Generated should not exceed window size",buf.remaining(),lessThanOrEqualTo(windowSize));
-
- totalBytes += buf.remaining();
- totalParts++;
-
- BufferUtil.put(buf,completeBuf);
-
- done = (frame.remaining() <= 0);
- }
-
- Assert.assertThat("Created Parts",totalParts,is(expectedParts));
- Assert.assertThat("Created Bytes",totalBytes,is(payload.length + expectedHeaderSize));
+ Assert.assertThat("Generated Buffer",completeBuffer.remaining(),is(expectedSize));
// Parse complete buffer.
WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
@@ -171,8 +304,7 @@
IncomingFramesCapture capture = new IncomingFramesCapture();
parser.setIncomingFramesHandler(capture);
- BufferUtil.flipToFlush(completeBuf,0);
- parser.parse(completeBuf);
+ parser.parse(completeBuffer);
// Assert validity of frame
WebSocketFrame actual = capture.getFrames().get(0);
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/Hex.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/Hex.java
new file mode 100644
index 0000000..4ca8306
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/Hex.java
@@ -0,0 +1,80 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public final class Hex
+{
+ private static final char[] hexcodes = "0123456789ABCDEF".toCharArray();
+
+ public static byte[] asByteArray(String hstr)
+ {
+ if ((hstr.length() < 0) || ((hstr.length() % 2) != 0))
+ {
+ throw new IllegalArgumentException(String.format("Invalid string length of <%d>",hstr.length()));
+ }
+
+ int size = hstr.length() / 2;
+ byte buf[] = new byte[size];
+ byte hex;
+ int len = hstr.length();
+
+ int idx = (int)Math.floor(((size * 2) - (double)len) / 2);
+ for (int i = 0; i < len; i++)
+ {
+ hex = 0;
+ if (i >= 0)
+ {
+ hex = (byte)(Character.digit(hstr.charAt(i),16) << 4);
+ }
+ i++;
+ hex += (byte)(Character.digit(hstr.charAt(i),16));
+
+ buf[idx] = hex;
+ idx++;
+ }
+
+ return buf;
+ }
+
+ public static ByteBuffer asByteBuffer(String hstr)
+ {
+ return ByteBuffer.wrap(asByteArray(hstr));
+ }
+
+ public static String asHex(byte buf[])
+ {
+ int len = buf.length;
+ char out[] = new char[len * 2];
+ for (int i = 0; i < len; i++)
+ {
+ out[i * 2] = hexcodes[(buf[i] & 0xF0) >> 4];
+ out[(i * 2) + 1] = hexcodes[(buf[i] & 0x0F)];
+ }
+ return String.valueOf(out);
+ }
+
+ public static String asHex(ByteBuffer buffer)
+ {
+ return asHex(BufferUtil.toArray(buffer));
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/IncomingFramesCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/IncomingFramesCapture.java
index 81d207a..5471530 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/IncomingFramesCapture.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/IncomingFramesCapture.java
@@ -18,7 +18,8 @@
package org.eclipse.jetty.websocket.common;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
import java.util.LinkedList;
@@ -35,7 +36,7 @@
private static final Logger LOG = Log.getLogger(IncomingFramesCapture.class);
private LinkedList<WebSocketFrame> frames = new LinkedList<>();
- private LinkedList<WebSocketException> errors = new LinkedList<>();
+ private LinkedList<Throwable> errors = new LinkedList<>();
public void assertErrorCount(int expectedCount)
{
@@ -44,6 +45,19 @@
public void assertFrameCount(int expectedCount)
{
+ if (frames.size() != expectedCount)
+ {
+ // dump details
+ System.err.printf("Expected %d frame(s)%n",expectedCount);
+ System.err.printf("But actually captured %d frame(s)%n",frames.size());
+ for (int i = 0; i < frames.size(); i++)
+ {
+ Frame frame = frames.get(i);
+ System.err.printf(" [%d] Frame[%s] - %s%n", i,
+ OpCode.name(frame.getOpCode()),
+ BufferUtil.toDetailString(frame.getPayload()));
+ }
+ }
Assert.assertThat("Captured frame count",frames.size(),is(expectedCount));
}
@@ -59,17 +73,18 @@
public void assertHasFrame(byte op, int expectedCount)
{
- Assert.assertThat(OpCode.name(op),getFrameCount(op),is(expectedCount));
+ String msg = String.format("%s frame count",OpCode.name(op));
+ Assert.assertThat(msg,getFrameCount(op),is(expectedCount));
}
public void assertHasNoFrames()
{
- Assert.assertThat("Has no frames",frames.size(),is(0));
+ Assert.assertThat("Frame count",frames.size(),is(0));
}
public void assertNoErrors()
{
- Assert.assertThat("Has no errors",errors.size(),is(0));
+ Assert.assertThat("Error count",errors.size(),is(0));
}
public void dump()
@@ -86,7 +101,7 @@
public int getErrorCount(Class<? extends WebSocketException> errorType)
{
int count = 0;
- for (WebSocketException error : errors)
+ for (Throwable error : errors)
{
if (errorType.isInstance(error))
{
@@ -96,7 +111,7 @@
return count;
}
- public LinkedList<WebSocketException> getErrors()
+ public LinkedList<Throwable> getErrors()
{
return errors;
}
@@ -120,7 +135,7 @@
}
@Override
- public void incomingError(WebSocketException e)
+ public void incomingError(Throwable e)
{
LOG.debug(e);
errors.add(e);
@@ -129,8 +144,7 @@
@Override
public void incomingFrame(Frame frame)
{
- WebSocketFrame copy = new WebSocketFrame(frame);
- frames.add(copy);
+ frames.add(WebSocketFrame.copy(frame));
}
public int size()
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingFramesCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingFramesCapture.java
index 46d19b9..dc36170 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingFramesCapture.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingFramesCapture.java
@@ -18,7 +18,8 @@
package org.eclipse.jetty.websocket.common;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
import java.util.LinkedList;
@@ -84,8 +85,7 @@
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
{
- WebSocketFrame copy = new WebSocketFrame(frame);
- frames.add(copy);
+ frames.add(WebSocketFrame.copy(frame));
if (callback != null)
{
callback.writeSuccess();
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingNetworkBytesCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingNetworkBytesCapture.java
index 963b67c..adbff38 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingNetworkBytesCapture.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingNetworkBytesCapture.java
@@ -18,7 +18,8 @@
package org.eclipse.jetty.websocket.common;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThan;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -62,8 +63,10 @@
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
{
- ByteBuffer buf = generator.generate(frame);
- captured.add(buf.slice());
+ ByteBuffer buf = ByteBuffer.allocate(Generator.OVERHEAD + frame.getPayloadLength());
+ generator.generateWholeFrame(frame,buf);
+ BufferUtil.flipToFlush(buf,0);
+ captured.add(buf);
if (callback != null)
{
callback.writeSuccess();
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ParserTest.java
index abc5205..070bc8c 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ParserTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ParserTest.java
@@ -18,39 +18,27 @@
package org.eclipse.jetty.websocket.common;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
-import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.BadPayloadException;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.junit.Assert;
import org.junit.Test;
public class ParserTest
{
- private static final Logger LOG = Log.getLogger(ParserTest.class);
-
- /** Parse, but be quiet about stack traces */
- private void parseQuietly(UnitParser parser, ByteBuffer buf)
- {
- LogShush.disableStacks(Parser.class);
- try {
- parser.parse(buf);
- } finally {
- LogShush.enableStacks(Parser.class);
- }
- }
-
/**
* Similar to the server side 5.15 testcase. A normal 2 fragment text text message, followed by another continuation.
*/
@@ -58,10 +46,10 @@
public void testParseCase5_15()
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment1").setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment2").setFin(true));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment3").setFin(false)); // bad frame
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment4").setFin(true));
+ send.add(new TextFrame().setPayload("fragment1").setFin(false));
+ send.add(new ContinuationFrame().setPayload("fragment2").setFin(true));
+ send.add(new ContinuationFrame().setPayload("fragment3").setFin(false)); // bad frame
+ send.add(new TextFrame().setPayload("fragment4").setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
ByteBuffer completeBuf = UnitGenerator.generate(send);
@@ -69,10 +57,11 @@
IncomingFramesCapture capture = new IncomingFramesCapture();
parser.setIncomingFramesHandler(capture);
- parseQuietly(parser,completeBuf);
+ parser.parseQuietly(completeBuf);
capture.assertErrorCount(1);
- capture.assertHasFrame(OpCode.TEXT,2);
+ capture.assertHasFrame(OpCode.TEXT,1);
+ capture.assertHasFrame(OpCode.CONTINUATION,1);
}
/**
@@ -82,45 +71,45 @@
public void testParseCase5_18()
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment1").setFin(false));
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment2").setFin(true)); // bad frame, must be continuation
+ send.add(new TextFrame().setPayload("fragment1").setFin(false));
+ send.add(new TextFrame().setPayload("fragment2").setFin(true)); // bad frame, must be continuation
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
ByteBuffer completeBuf = UnitGenerator.generate(send);
UnitParser parser = new UnitParser();
IncomingFramesCapture capture = new IncomingFramesCapture();
parser.setIncomingFramesHandler(capture);
- parseQuietly(parser,completeBuf);
+ parser.parseQuietly(completeBuf);
capture.assertErrorCount(1);
capture.assertHasFrame(OpCode.TEXT,1); // fragment 1
}
/**
- * Similar to the server side 5.19 testcase.
- * text message, send in 5 frames/fragments, with 2 pings in the mix.
+ * Similar to the server side 5.19 testcase. text message, send in 5 frames/fragments, with 2 pings in the mix.
*/
@Test
public void testParseCase5_19()
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("f1").setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f2").setFin(false));
- send.add(new WebSocketFrame(OpCode.PING).setPayload("pong-1"));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f3").setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f4").setFin(false));
- send.add(new WebSocketFrame(OpCode.PING).setPayload("pong-2"));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f5").setFin(true));
+ send.add(new TextFrame().setPayload("f1").setFin(false));
+ send.add(new ContinuationFrame().setPayload(",f2").setFin(false));
+ send.add(new PingFrame().setPayload("pong-1"));
+ send.add(new ContinuationFrame().setPayload(",f3").setFin(false));
+ send.add(new ContinuationFrame().setPayload(",f4").setFin(false));
+ send.add(new PingFrame().setPayload("pong-2"));
+ send.add(new ContinuationFrame().setPayload(",f5").setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
ByteBuffer completeBuf = UnitGenerator.generate(send);
UnitParser parser = new UnitParser();
IncomingFramesCapture capture = new IncomingFramesCapture();
parser.setIncomingFramesHandler(capture);
- parseQuietly(parser,completeBuf);
+ parser.parseQuietly(completeBuf);
capture.assertErrorCount(0);
- capture.assertHasFrame(OpCode.TEXT,5);
+ capture.assertHasFrame(OpCode.TEXT,1);
+ capture.assertHasFrame(OpCode.CONTINUATION,4);
capture.assertHasFrame(OpCode.CLOSE,1);
capture.assertHasFrame(OpCode.PING,2);
}
@@ -132,8 +121,8 @@
public void testParseCase5_6()
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.pong().setPayload("ping"));
- send.add(WebSocketFrame.text("hello, world"));
+ send.add(new PongFrame().setPayload("ping"));
+ send.add(new TextFrame().setPayload("hello, world"));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
ByteBuffer completeBuf = UnitGenerator.generate(send);
@@ -158,19 +147,31 @@
byte msg[] = StringUtil.getUtf8Bytes(utf8);
List<WebSocketFrame> send = new ArrayList<>();
+ int textCount = 0;
+ int continuationCount = 0;
int len = msg.length;
- byte opcode = OpCode.TEXT;
+ boolean continuation = false;
byte mini[];
for (int i = 0; i < len; i++)
{
- WebSocketFrame frame = new WebSocketFrame(opcode);
+ DataFrame frame = null;
+ if (continuation)
+ {
+ frame = new ContinuationFrame();
+ continuationCount++;
+ }
+ else
+ {
+ frame = new TextFrame();
+ textCount++;
+ }
mini = new byte[1];
mini[0] = msg[i];
- frame.setPayload(mini);
+ frame.setPayload(ByteBuffer.wrap(mini));
boolean isLast = (i >= (len - 1));
frame.setFin(isLast);
send.add(frame);
- opcode = OpCode.CONTINUATION;
+ continuation = true;
}
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
@@ -181,57 +182,11 @@
parser.parse(completeBuf);
capture.assertErrorCount(0);
- capture.assertHasFrame(OpCode.TEXT,len);
+ capture.assertHasFrame(OpCode.TEXT,textCount);
+ capture.assertHasFrame(OpCode.CONTINUATION,continuationCount);
capture.assertHasFrame(OpCode.CLOSE,1);
}
- /**
- * Similar to the server side 6.4.3 testcase.
- */
- @Test
- public void testParseCase6_4_3()
- {
- ByteBuffer payload = ByteBuffer.allocate(64);
- BufferUtil.clearToFill(payload);
- payload.put(TypeUtil.fromHexString("cebae1bdb9cf83cebcceb5")); // good
- payload.put(TypeUtil.fromHexString("f4908080")); // INVALID
- payload.put(TypeUtil.fromHexString("656469746564")); // good
- BufferUtil.flipToFlush(payload,0);
-
- WebSocketFrame text = new WebSocketFrame();
- text.setMask(TypeUtil.fromHexString("11223344"));
- text.setPayload(payload);
- text.setOpCode(OpCode.TEXT);
-
- ByteBuffer buf = new UnitGenerator().generate(text);
-
- ByteBuffer part1 = ByteBuffer.allocate(17); // header + good
- ByteBuffer part2 = ByteBuffer.allocate(4); // invalid
- ByteBuffer part3 = ByteBuffer.allocate(10); // the rest (all good utf)
-
- BufferUtil.put(buf,part1);
- BufferUtil.put(buf,part2);
- BufferUtil.put(buf,part3);
-
- BufferUtil.flipToFlush(part1,0);
- BufferUtil.flipToFlush(part2,0);
- BufferUtil.flipToFlush(part3,0);
-
- LOG.debug("Part1: {}",BufferUtil.toDetailString(part1));
- LOG.debug("Part2: {}",BufferUtil.toDetailString(part2));
- LOG.debug("Part3: {}",BufferUtil.toDetailString(part3));
-
- UnitParser parser = new UnitParser();
- IncomingFramesCapture capture = new IncomingFramesCapture();
- parser.setIncomingFramesHandler(capture);
-
- parseQuietly(parser,part1);
- capture.assertErrorCount(0);
- parseQuietly(parser,part2);
- capture.assertErrorCount(1);
- capture.assertHasErrors(BadPayloadException.class,1);
- }
-
@Test
public void testParseNothing()
{
@@ -248,4 +203,49 @@
capture.assertNoErrors();
Assert.assertThat("Frame Count",capture.getFrames().size(),is(0));
}
+
+ @Test
+ public void testWindowedParseLargeFrame()
+ {
+ // Create frames
+ byte payload[] = new byte[65536];
+ Arrays.fill(payload,(byte)'*');
+
+ List<WebSocketFrame> frames = new ArrayList<>();
+ TextFrame text = new TextFrame();
+ text.setPayload(ByteBuffer.wrap(payload));
+ text.setMask(Hex.asByteArray("11223344"));
+ frames.add(text);
+ frames.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+ // Build up raw (network bytes) buffer
+ ByteBuffer networkBytes = UnitGenerator.generate(frames);
+
+ // Parse, in 4096 sized windows
+ WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+ Parser parser = new UnitParser(policy);
+ IncomingFramesCapture capture = new IncomingFramesCapture();
+ parser.setIncomingFramesHandler(capture);
+
+ while (networkBytes.remaining() > 0)
+ {
+ ByteBuffer window = networkBytes.slice();
+ int windowSize = Math.min(window.remaining(),4096);
+ window.limit(windowSize);
+ parser.parse(window);
+ networkBytes.position(networkBytes.position() + windowSize);
+ }
+
+ capture.assertNoErrors();
+ Assert.assertThat("Frame Count",capture.getFrames().size(),is(2));
+ WebSocketFrame frame = capture.getFrames().pop();
+ Assert.assertThat("Frame[0].opcode",frame.getOpCode(),is(OpCode.TEXT));
+ ByteBuffer actualPayload = frame.getPayload();
+ Assert.assertThat("Frame[0].payload.length",actualPayload.remaining(),is(payload.length));
+ // Should be all '*' characters (if masking is correct)
+ for (int i = actualPayload.position(); i < actualPayload.remaining(); i++)
+ {
+ Assert.assertThat("Frame[0].payload[i]",actualPayload.get(i),is((byte)'*'));
+ }
+ }
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/PingPayloadParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/PingPayloadParserTest.java
index ed212db..3af35df 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/PingPayloadParserTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/PingPayloadParserTest.java
@@ -18,13 +18,14 @@
package org.eclipse.jetty.websocket.common;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
import org.junit.Assert;
import org.junit.Test;
@@ -47,8 +48,9 @@
capture.assertNoErrors();
capture.assertHasFrame(OpCode.PING,1);
- WebSocketFrame ping = capture.getFrames().get(0);
+ PingFrame ping = (PingFrame)capture.getFrames().get(0);
- Assert.assertThat("PingFrame.payload",ping.getPayloadAsUTF8(),is("Hello"));
+ String actual = BufferUtil.toUTF8String(ping.getPayload());
+ Assert.assertThat("PingFrame.payload",actual,is("Hello"));
}
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesGeneratorTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesGeneratorTest.java
index 81ec460..f22b913 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesGeneratorTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesGeneratorTest.java
@@ -21,9 +21,11 @@
import java.nio.ByteBuffer;
import java.util.Arrays;
-import org.eclipse.jetty.websocket.common.Generator;
-import org.eclipse.jetty.websocket.common.OpCode;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.junit.Test;
public class RFC6455ExamplesGeneratorTest
@@ -33,13 +35,11 @@
@Test
public void testFragmentedUnmaskedTextMessage()
{
- WebSocketFrame text1 = WebSocketFrame.text("Hel").setFin(false);
- WebSocketFrame text2 = new WebSocketFrame(OpCode.CONTINUATION).setPayload("lo");
+ WebSocketFrame text1 = new TextFrame().setPayload("Hel").setFin(false);
+ WebSocketFrame text2 = new ContinuationFrame().setPayload("lo");
- Generator generator = new UnitGenerator();
-
- ByteBuffer actual1 = generator.generate(text1);
- ByteBuffer actual2 = generator.generate(text2);
+ ByteBuffer actual1 = UnitGenerator.generate(text1);
+ ByteBuffer actual2 = UnitGenerator.generate(text2);
ByteBuffer expected1 = ByteBuffer.allocate(5);
@@ -61,14 +61,11 @@
@Test
public void testSingleMaskedPongRequest()
{
- WebSocketFrame pong = new WebSocketFrame(OpCode.PONG);
- pong.setPayload("Hello");
+ PongFrame pong = new PongFrame().setPayload("Hello");
pong.setMask(new byte[]
{ 0x37, (byte)0xfa, 0x21, 0x3d });
- Generator gen = new UnitGenerator();
-
- ByteBuffer actual = gen.generate(pong);
+ ByteBuffer actual = UnitGenerator.generate(pong);
ByteBuffer expected = ByteBuffer.allocate(11);
// Raw bytes as found in RFC 6455, Section 5.7 - Examples
@@ -83,12 +80,11 @@
@Test
public void testSingleMaskedTextMessage()
{
- WebSocketFrame text = WebSocketFrame.text("Hello");
+ WebSocketFrame text = new TextFrame().setPayload("Hello");
text.setMask(new byte[]
{ 0x37, (byte)0xfa, 0x21, 0x3d });
- Generator gen = new UnitGenerator();
- ByteBuffer actual = gen.generate(text);
+ ByteBuffer actual = UnitGenerator.generate(text);
ByteBuffer expected = ByteBuffer.allocate(11);
// Raw bytes as found in RFC 6455, Section 5.7 - Examples
@@ -105,14 +101,12 @@
{
int dataSize = 256;
- WebSocketFrame binary = WebSocketFrame.binary();
+ BinaryFrame binary = new BinaryFrame();
byte payload[] = new byte[dataSize];
Arrays.fill(payload,(byte)0x44);
- binary.setPayload(payload);
+ binary.setPayload(ByteBuffer.wrap(payload));
- Generator gen = new UnitGenerator();
-
- ByteBuffer actual = gen.generate(binary);
+ ByteBuffer actual = UnitGenerator.generate(binary);
ByteBuffer expected = ByteBuffer.allocate(dataSize + FUDGE);
// Raw bytes as found in RFC 6455, Section 5.7 - Examples
@@ -136,14 +130,12 @@
{
int dataSize = 1024 * 64;
- WebSocketFrame binary = WebSocketFrame.binary();
+ BinaryFrame binary = new BinaryFrame();
byte payload[] = new byte[dataSize];
Arrays.fill(payload,(byte)0x44);
- binary.setPayload(payload);
+ binary.setPayload(ByteBuffer.wrap(payload));
- Generator gen = new UnitGenerator();
-
- ByteBuffer actual = gen.generate(binary);
+ ByteBuffer actual = UnitGenerator.generate(binary);
ByteBuffer expected = ByteBuffer.allocate(dataSize + 10);
// Raw bytes as found in RFC 6455, Section 5.7 - Examples
@@ -166,10 +158,9 @@
@Test
public void testSingleUnmaskedPingRequest() throws Exception
{
- WebSocketFrame ping = new WebSocketFrame(OpCode.PING).setPayload("Hello");
+ PingFrame ping = new PingFrame().setPayload("Hello");
- Generator gen = new UnitGenerator();
- ByteBuffer actual = gen.generate(ping);
+ ByteBuffer actual = UnitGenerator.generate(ping);
ByteBuffer expected = ByteBuffer.allocate(10);
expected.put(new byte[]
@@ -182,11 +173,9 @@
@Test
public void testSingleUnmaskedTextMessage()
{
- WebSocketFrame text = WebSocketFrame.text("Hello");
+ WebSocketFrame text = new TextFrame().setPayload("Hello");
- Generator generator = new UnitGenerator();
-
- ByteBuffer actual = generator.generate(text);
+ ByteBuffer actual = UnitGenerator.generate(text);
ByteBuffer expected = ByteBuffer.allocate(10);
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesParserTest.java
index fb36b4b..ccea138 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesParserTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesParserTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.common;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.nio.ByteBuffer;
@@ -64,12 +64,15 @@
parser.parse(buf);
capture.assertNoErrors();
- capture.assertHasFrame(OpCode.TEXT,2);
+ capture.assertHasFrame(OpCode.TEXT,1);
+ capture.assertHasFrame(OpCode.CONTINUATION,1);
WebSocketFrame txt = capture.getFrames().get(0);
- Assert.assertThat("TextFrame[0].data",txt.getPayloadAsUTF8(),is("Hel"));
+ String actual = BufferUtil.toUTF8String(txt.getPayload());
+ Assert.assertThat("TextFrame[0].data",actual,is("Hel"));
txt = capture.getFrames().get(1);
- Assert.assertThat("TextFrame[1].data",txt.getPayloadAsUTF8(),is("lo"));
+ actual = BufferUtil.toUTF8String(txt.getPayload());
+ Assert.assertThat("TextFrame[1].data",actual,is("lo"));
}
@Test
@@ -92,7 +95,8 @@
capture.assertHasFrame(OpCode.PONG,1);
WebSocketFrame pong = capture.getFrames().get(0);
- Assert.assertThat("PongFrame.payload",pong.getPayloadAsUTF8(),is("Hello"));
+ String actual = BufferUtil.toUTF8String(pong.getPayload());
+ Assert.assertThat("PongFrame.payload",actual,is("Hello"));
}
@Test
@@ -115,7 +119,8 @@
capture.assertHasFrame(OpCode.TEXT,1);
WebSocketFrame txt = capture.getFrames().get(0);
- Assert.assertThat("TextFrame.payload",txt.getPayloadAsUTF8(),is("Hello"));
+ String actual = BufferUtil.toUTF8String(txt.getPayload());
+ Assert.assertThat("TextFrame.payload",actual,is("Hello"));
}
@Test
@@ -215,7 +220,8 @@
capture.assertHasFrame(OpCode.PING,1);
WebSocketFrame ping = capture.getFrames().get(0);
- Assert.assertThat("PingFrame.payload",ping.getPayloadAsUTF8(),is("Hello"));
+ String actual = BufferUtil.toUTF8String(ping.getPayload());
+ Assert.assertThat("PingFrame.payload",actual,is("Hello"));
}
@Test
@@ -238,6 +244,7 @@
capture.assertHasFrame(OpCode.TEXT,1);
WebSocketFrame txt = capture.getFrames().get(0);
- Assert.assertThat("TextFrame.payload",txt.getPayloadAsUTF8(),is("Hello"));
+ String actual = BufferUtil.toUTF8String(txt.getPayload());
+ Assert.assertThat("TextFrame.payload",actual,is("Hello"));
}
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RawFrameBuilder.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RawFrameBuilder.java
new file mode 100644
index 0000000..9237cd1
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RawFrameBuilder.java
@@ -0,0 +1,110 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Assert;
+
+public class RawFrameBuilder
+{
+ public static void putOpFin(ByteBuffer buf, byte opcode, boolean fin)
+ {
+ byte b = 0x00;
+ if (fin)
+ {
+ b |= 0x80;
+ }
+ b |= opcode & 0x0F;
+ buf.put(b);
+ }
+
+ public static void putLengthAndMask(ByteBuffer buf, int length, byte mask[])
+ {
+ if (mask != null)
+ {
+ Assert.assertThat("Mask.length",mask.length,is(4));
+ putLength(buf,length,(mask != null));
+ buf.put(mask);
+ }
+ else
+ {
+ putLength(buf,length,false);
+ }
+ }
+
+ public static byte[] mask(final byte[] data, final byte mask[])
+ {
+ Assert.assertThat("Mask.length",mask.length,is(4));
+ int len = data.length;
+ byte ret[] = new byte[len];
+ System.arraycopy(data,0,ret,0,len);
+ for (int i = 0; i < len; i++)
+ {
+ ret[i] ^= mask[i % 4];
+ }
+ return ret;
+ }
+
+ public static void putLength(ByteBuffer buf, int length, boolean masked)
+ {
+ if (length < 0)
+ {
+ throw new IllegalArgumentException("Length cannot be negative");
+ }
+ byte b = (masked?(byte)0x80:0x00);
+
+ // write the uncompressed length
+ if (length > 0xFF_FF)
+ {
+ buf.put((byte)(b | 0x7F));
+ buf.put((byte)0x00);
+ buf.put((byte)0x00);
+ buf.put((byte)0x00);
+ buf.put((byte)0x00);
+ buf.put((byte)((length >> 24) & 0xFF));
+ buf.put((byte)((length >> 16) & 0xFF));
+ buf.put((byte)((length >> 8) & 0xFF));
+ buf.put((byte)(length & 0xFF));
+ }
+ else if (length >= 0x7E)
+ {
+ buf.put((byte)(b | 0x7E));
+ buf.put((byte)(length >> 8));
+ buf.put((byte)(length & 0xFF));
+ }
+ else
+ {
+ buf.put((byte)(b | length));
+ }
+ }
+
+ public static void putMask(ByteBuffer buf, byte mask[])
+ {
+ Assert.assertThat("Mask.length",mask.length,is(4));
+ buf.put(mask);
+ }
+
+ public static void putPayloadLength(ByteBuffer buf, int length)
+ {
+ putLength(buf,length,true);
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/TextPayloadParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/TextPayloadParserTest.java
index 9dae24d..55a509f 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/TextPayloadParserTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/TextPayloadParserTest.java
@@ -18,13 +18,15 @@
package org.eclipse.jetty.websocket.common;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThan;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.MessageTooLargeException;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
@@ -39,14 +41,16 @@
{
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
// Artificially small buffer/payload
- policy.setMaxMessageSize(1024);
+ policy.setInputBufferSize(1024); // read buffer
+ policy.setMaxTextMessageBufferSize(1024); // streaming buffer (not used in this test)
+ policy.setMaxTextMessageSize(1024); // actual maximum text message size policy
byte utf[] = new byte[2048];
Arrays.fill(utf,(byte)'a');
Assert.assertThat("Must be a medium length payload",utf.length,allOf(greaterThan(0x7E),lessThan(0xFFFF)));
ByteBuffer buf = ByteBuffer.allocate(utf.length + 8);
- buf.put((byte)0x81);
+ buf.put((byte)0x81); // text frame, fin = true
buf.put((byte)(0x80 | 0x7E)); // 0x7E == 126 (a 2 byte payload length)
buf.putShort((short)utf.length);
MaskedByteBuffer.putMask(buf);
@@ -81,7 +85,7 @@
Assert.assertThat("Must be a long length payload",utf.length,greaterThan(0xFFFF));
ByteBuffer buf = ByteBuffer.allocate(utf.length + 32);
- buf.put((byte)0x81);
+ buf.put((byte)0x81); // text frame, fin = true
buf.put((byte)(0x80 | 0x7F)); // 0x7F == 127 (a 8 byte payload length)
buf.putLong(utf.length);
MaskedByteBuffer.putMask(buf);
@@ -89,7 +93,7 @@
buf.flip();
WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
- policy.setMaxMessageSize(100000);
+ policy.setMaxTextMessageSize(100000);
Parser parser = new UnitParser(policy);
IncomingFramesCapture capture = new IncomingFramesCapture();
parser.setIncomingFramesHandler(capture);
@@ -168,7 +172,8 @@
parser.parse(buf);
capture.assertNoErrors();
- capture.assertHasFrame(OpCode.TEXT,2);
+ capture.assertHasFrame(OpCode.TEXT,1);
+ capture.assertHasFrame(OpCode.CONTINUATION,1);
WebSocketFrame txt = capture.getFrames().get(0);
Assert.assertThat("TextFrame[0].data",txt.getPayloadAsUTF8(),is(part1));
txt = capture.getFrames().get(1);
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/UnitGenerator.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/UnitGenerator.java
index 0fbef69..377e896 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/UnitGenerator.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/UnitGenerator.java
@@ -23,6 +23,8 @@
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame;
@@ -31,11 +33,58 @@
*/
public class UnitGenerator extends Generator
{
+ private static final Logger LOG = Log.getLogger(UnitGenerator.class);
+
+ public static ByteBuffer generate(Frame frame)
+ {
+ return generate(new Frame[]
+ { frame });
+ }
+
+ /**
+ * Generate All Frames into a single ByteBuffer.
+ * <p>
+ * This is highly inefficient and is not used in production! (This exists to make testing of the Generator easier)
+ *
+ * @param frames
+ * the frames to generate from
+ * @return the ByteBuffer representing all of the generated frames provided.
+ */
+ public static ByteBuffer generate(Frame[] frames)
+ {
+ Generator generator = new UnitGenerator();
+
+ // Generate into single bytebuffer
+ int buflen = 0;
+ for (Frame f : frames)
+ {
+ buflen += f.getPayloadLength() + Generator.OVERHEAD;
+ }
+ ByteBuffer completeBuf = ByteBuffer.allocate(buflen);
+ BufferUtil.clearToFill(completeBuf);
+
+ // Generate frames
+ for (Frame f : frames)
+ {
+ generator.generateWholeFrame(f,completeBuf);
+ }
+
+ BufferUtil.flipToFlush(completeBuf,0);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("generate({} frames) - {}",frames.length,BufferUtil.toDetailString(completeBuf));
+ }
+ return completeBuf;
+ }
+
+ /**
+ * Generate a single giant buffer of all provided frames Not appropriate for production code, but useful for testing.
+ */
public static ByteBuffer generate(List<WebSocketFrame> frames)
{
// Create non-symmetrical mask (helps show mask bytes order issues)
byte[] MASK =
- { 0x11, 0x22, 0x33, 0x44 };
+ { 0x11, 0x22, 0x33, 0x44 };
// the generator
Generator generator = new UnitGenerator();
@@ -52,13 +101,20 @@
// Generate frames
for (WebSocketFrame f : frames)
{
- f.setMask(MASK); // make sure we have mask set
- ByteBuffer slice = f.getPayload().slice();
- BufferUtil.put(generator.generate(f),completeBuf);
- f.setPayload(slice);
+ f.setMask(MASK); // make sure we have the test mask set
+ BufferUtil.put(generator.generateHeaderBytes(f),completeBuf);
+ ByteBuffer window = generator.getPayloadWindow(f.getPayloadLength(),f);
+ if (BufferUtil.hasContent(window))
+ {
+ BufferUtil.put(window,completeBuf);
+ }
}
BufferUtil.flipToFlush(completeBuf,0);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("generate({} frames) - {}",frames.size(),BufferUtil.toDetailString(completeBuf));
+ }
return completeBuf;
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/UnitParser.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/UnitParser.java
index 447fe28..a650f61 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/UnitParser.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/UnitParser.java
@@ -22,6 +22,7 @@
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
public class UnitParser extends Parser
@@ -33,12 +34,12 @@
public UnitParser(ByteBufferPool bufferPool, WebSocketPolicy policy)
{
- super(policy, bufferPool);
+ super(policy,bufferPool);
}
public UnitParser(WebSocketPolicy policy)
{
- this(new MappedByteBufferPool(), policy);
+ this(new MappedByteBufferPool(),policy);
}
private void parsePartial(ByteBuffer buf, int numBytes)
@@ -56,14 +57,13 @@
*/
public void parseQuietly(ByteBuffer buf)
{
- try
+ try (StacklessLogging suppress = new StacklessLogging(Parser.class))
{
- LogShush.disableStacks(Parser.class);
parse(buf);
}
- finally
+ catch (Exception ignore)
{
- LogShush.enableStacks(Parser.class);
+ /* ignore */
}
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/WebSocketFrameTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/WebSocketFrameTest.java
index bd12408..85a7f77 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/WebSocketFrameTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/WebSocketFrameTest.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.websocket.common;
+import static org.hamcrest.Matchers.is;
+
import java.nio.ByteBuffer;
import org.eclipse.jetty.io.ByteBufferPool;
@@ -25,10 +27,11 @@
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.Generator;
-import org.eclipse.jetty.websocket.common.OpCode;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -37,6 +40,14 @@
private static Generator strictGenerator;
private static Generator laxGenerator;
+ private ByteBuffer generateWholeFrame(Generator generator, Frame frame)
+ {
+ ByteBuffer buf = ByteBuffer.allocate(frame.getPayloadLength() + Generator.OVERHEAD);
+ generator.generateWholeFrame(frame,buf);
+ BufferUtil.flipToFlush(buf,0);
+ return buf;
+ }
+
@BeforeClass
public static void initGenerator()
{
@@ -46,60 +57,78 @@
laxGenerator = new Generator(policy,bufferPool,false);
}
- private void assertEqual(String message, ByteBuffer expected, ByteBuffer actual)
+ private void assertFrameHex(String message, String expectedHex, ByteBuffer actual)
{
- BufferUtil.flipToFlush(expected,0);
-
- ByteBufferAssert.assertEquals(message,expected,actual);
+ String actualHex = Hex.asHex(actual);
+ Assert.assertThat("Generated Frame:" + message,actualHex,is(expectedHex));
}
@Test
public void testLaxInvalidClose()
{
- WebSocketFrame frame = new WebSocketFrame(OpCode.CLOSE).setFin(false);
- ByteBuffer actual = laxGenerator.generate(frame);
- ByteBuffer expected = ByteBuffer.allocate(2);
- expected.put((byte)0x08);
- expected.put((byte)0x00);
-
- assertEqual("Lax Invalid Close Frame",expected,actual);
+ WebSocketFrame frame = new CloseFrame().setFin(false);
+ ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
+ String expected = "0800";
+ assertFrameHex("Lax Invalid Close Frame",expected,actual);
}
@Test
public void testLaxInvalidPing()
{
- WebSocketFrame frame = new WebSocketFrame(OpCode.PING).setFin(false);
- ByteBuffer actual = laxGenerator.generate(frame);
- ByteBuffer expected = ByteBuffer.allocate(2);
- expected.put((byte)0x09);
- expected.put((byte)0x00);
-
- assertEqual("Lax Invalid Ping Frame",expected,actual);
+ WebSocketFrame frame = new PingFrame().setFin(false);
+ ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
+ String expected = "0900";
+ assertFrameHex("Lax Invalid Ping Frame",expected,actual);
}
@Test
public void testStrictValidClose()
{
CloseInfo close = new CloseInfo(StatusCode.NORMAL);
- ByteBuffer actual = strictGenerator.generate(close.asFrame());
- ByteBuffer expected = ByteBuffer.allocate(4);
- expected.put((byte)0x88);
- expected.put((byte)0x02);
- expected.put((byte)0x03);
- expected.put((byte)0xE8);
-
- assertEqual("Strict Valid Close Frame",expected,actual);
+ ByteBuffer actual = generateWholeFrame(strictGenerator,close.asFrame());
+ String expected = "880203E8";
+ assertFrameHex("Strict Valid Close Frame",expected,actual);
}
@Test
public void testStrictValidPing()
{
- WebSocketFrame frame = new WebSocketFrame(OpCode.PING);
- ByteBuffer actual = strictGenerator.generate(frame);
- ByteBuffer expected = ByteBuffer.allocate(2);
- expected.put((byte)0x89);
- expected.put((byte)0x00);
-
- assertEqual("Strict Valid Ping Frame",expected,actual);
+ WebSocketFrame frame = new PingFrame();
+ ByteBuffer actual = generateWholeFrame(strictGenerator,frame);
+ String expected = "8900";
+ assertFrameHex("Strict Valid Ping Frame",expected,actual);
+ }
+
+ @Test
+ public void testRsv1()
+ {
+ TextFrame frame = new TextFrame();
+ frame.setPayload("Hi");
+ frame.setRsv1(true);
+ ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
+ String expected = "C1024869";
+ assertFrameHex("Lax Text Frame with RSV1",expected,actual);
+ }
+
+ @Test
+ public void testRsv2()
+ {
+ TextFrame frame = new TextFrame();
+ frame.setPayload("Hi");
+ frame.setRsv2(true);
+ ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
+ String expected = "A1024869";
+ assertFrameHex("Lax Text Frame with RSV2",expected,actual);
+ }
+
+ @Test
+ public void testRsv3()
+ {
+ TextFrame frame = new TextFrame();
+ frame.setPayload("Hi");
+ frame.setRsv3(true);
+ ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
+ String expected = "91024869";
+ assertFrameHex("Lax Text Frame with RSV3",expected,actual);
}
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/WebSocketRemoteEndpointTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/WebSocketRemoteEndpointTest.java
new file mode 100644
index 0000000..61ec948
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/WebSocketRemoteEndpointTest.java
@@ -0,0 +1,86 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import static org.hamcrest.Matchers.containsString;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.common.io.LocalWebSocketConnection;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class WebSocketRemoteEndpointTest
+{
+ @Rule
+ public TestName testname = new TestName();
+
+ @Test
+ public void testTextBinaryText() throws IOException
+ {
+ LocalWebSocketConnection conn = new LocalWebSocketConnection(testname);
+ OutgoingFramesCapture outgoing = new OutgoingFramesCapture();
+ WebSocketRemoteEndpoint remote = new WebSocketRemoteEndpoint(conn,outgoing);
+ conn.connect();
+ conn.open();
+
+ // Start text message
+ remote.sendPartialString("Hello ",false);
+
+ try
+ {
+ // Attempt to start Binary Message
+ ByteBuffer bytes = ByteBuffer.wrap(new byte[]
+ { 0, 1, 2 });
+ remote.sendPartialBytes(bytes,false);
+ Assert.fail("Expected " + IllegalStateException.class.getName());
+ }
+ catch (IllegalStateException e)
+ {
+ // Expected path
+ Assert.assertThat("Exception",e.getMessage(),containsString("message pending"));
+ }
+
+ // End text message
+ remote.sendPartialString("World!",true);
+ }
+
+ @Test
+ public void testTextPingText() throws IOException
+ {
+ LocalWebSocketConnection conn = new LocalWebSocketConnection(testname);
+ OutgoingFramesCapture outgoing = new OutgoingFramesCapture();
+ WebSocketRemoteEndpoint remote = new WebSocketRemoteEndpoint(conn,outgoing);
+ conn.connect();
+ conn.open();
+
+ // Start text message
+ remote.sendPartialString("Hello ",false);
+
+ // Attempt to send Ping Message
+ remote.sendPing(ByteBuffer.wrap(new byte[]
+ { 0 }));
+
+ // End text message
+ remote.sendPartialString("World!",true);
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_1.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_1.java
index 755c501..80c4dbc 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_1.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_1.java
@@ -18,10 +18,12 @@
package org.eclipse.jetty.websocket.common.ab;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame;
@@ -33,6 +35,7 @@
import org.eclipse.jetty.websocket.common.UnitGenerator;
import org.eclipse.jetty.websocket.common.UnitParser;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.junit.Assert;
import org.junit.Test;
@@ -47,23 +50,18 @@
public void testGenerate125ByteTextCase1_1_2()
{
int length = 125;
+ byte buf[] = new byte[length];
+ Arrays.fill(buf,(byte)'*');
+ String text = new String(buf,StringUtil.__UTF8_CHARSET);
- StringBuilder builder = new StringBuilder();
+ Frame textFrame = new TextFrame().setPayload(text);
- for (int i = 0; i < length; ++i)
- {
- builder.append("*");
- }
-
- WebSocketFrame textFrame = WebSocketFrame.text(builder.toString());
-
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(textFrame);
+ ByteBuffer actual = UnitGenerator.generate(textFrame);
ByteBuffer expected = ByteBuffer.allocate(length + 5);
expected.put(new byte[]
- { (byte)0x81 });
+ { (byte)0x81 });
byte b = 0x00; // no masking
b |= length & 0x7F;
@@ -91,15 +89,14 @@
builder.append("*");
}
- WebSocketFrame textFrame = WebSocketFrame.text(builder.toString());
+ WebSocketFrame textFrame = new TextFrame().setPayload(builder.toString());
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(textFrame);
+ ByteBuffer actual = UnitGenerator.generate(textFrame);
ByteBuffer expected = ByteBuffer.allocate(length + 5);
expected.put(new byte[]
- { (byte)0x81 });
+ { (byte)0x81 });
byte b = 0x00; // no masking
b |= length & 0x7E;
@@ -131,15 +128,14 @@
builder.append("*");
}
- WebSocketFrame textFrame = WebSocketFrame.text(builder.toString());
+ WebSocketFrame textFrame = new TextFrame().setPayload(builder.toString());
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(textFrame);
+ ByteBuffer actual = UnitGenerator.generate(textFrame);
ByteBuffer expected = ByteBuffer.allocate(length + 5);
expected.put(new byte[]
- { (byte)0x81 });
+ { (byte)0x81 });
byte b = 0x00; // no masking
b |= length & 0x7E;
@@ -171,15 +167,14 @@
builder.append("*");
}
- WebSocketFrame textFrame = WebSocketFrame.text(builder.toString());
+ WebSocketFrame textFrame = new TextFrame().setPayload(builder.toString());
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(textFrame);
+ ByteBuffer actual = UnitGenerator.generate(textFrame);
ByteBuffer expected = ByteBuffer.allocate(length + 5);
expected.put(new byte[]
- { (byte)0x81 });
+ { (byte)0x81 });
byte b = 0x00; // no masking
b |= 0x7E;
@@ -211,22 +206,20 @@
builder.append("*");
}
- WebSocketFrame textFrame = WebSocketFrame.text(builder.toString());
+ WebSocketFrame textFrame = new TextFrame().setPayload(builder.toString());
- Generator generator = new UnitGenerator();
-
- ByteBuffer actual = generator.generate(textFrame);
+ ByteBuffer actual = UnitGenerator.generate(textFrame);
ByteBuffer expected = ByteBuffer.allocate(length + 5);
expected.put(new byte[]
- { (byte)0x81 });
+ { (byte)0x81 });
byte b = 0x00; // no masking
b |= 0x7E;
expected.put(b);
expected.put(new byte[]
- { (byte)0xff, (byte)0xff });
+ { (byte)0xff, (byte)0xff });
for (int i = 0; i < length; ++i)
{
@@ -250,21 +243,20 @@
builder.append("*");
}
- WebSocketFrame textFrame = WebSocketFrame.text(builder.toString());
+ WebSocketFrame textFrame = new TextFrame().setPayload(builder.toString());
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(textFrame);
+ ByteBuffer actual = UnitGenerator.generate(textFrame);
ByteBuffer expected = ByteBuffer.allocate(length + 11);
expected.put(new byte[]
- { (byte)0x81 });
+ { (byte)0x81 });
byte b = 0x00; // no masking
b |= 0x7F;
expected.put(b);
expected.put(new byte[]
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 });
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 });
for (int i = 0; i < length; ++i)
{
@@ -279,15 +271,14 @@
@Test
public void testGenerateEmptyTextCase1_1_1()
{
- WebSocketFrame textFrame = WebSocketFrame.text("");
+ WebSocketFrame textFrame = new TextFrame().setPayload("");
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(textFrame);
+ ByteBuffer actual = UnitGenerator.generate(textFrame);
ByteBuffer expected = ByteBuffer.allocate(5);
expected.put(new byte[]
- { (byte)0x81, (byte)0x00 });
+ { (byte)0x81, (byte)0x00 });
expected.flip();
@@ -302,7 +293,7 @@
ByteBuffer expected = ByteBuffer.allocate(length + 5);
expected.put(new byte[]
- { (byte)0x81 });
+ { (byte)0x81 });
byte b = 0x00; // no masking
b |= length & 0x7F;
expected.put(b);
@@ -335,7 +326,7 @@
ByteBuffer expected = ByteBuffer.allocate(length + 5);
expected.put(new byte[]
- { (byte)0x81 });
+ { (byte)0x81 });
byte b = 0x00; // no masking
b |= length & 0x7E;
expected.put(b);
@@ -369,7 +360,7 @@
ByteBuffer expected = ByteBuffer.allocate(length + 5);
expected.put(new byte[]
- { (byte)0x81 });
+ { (byte)0x81 });
byte b = 0x00; // no masking
b |= length & 0x7E;
expected.put(b);
@@ -403,7 +394,7 @@
ByteBuffer expected = ByteBuffer.allocate(length + 5);
expected.put(new byte[]
- { (byte)0x81 });
+ { (byte)0x81 });
byte b = 0x00; // no masking
b |= 0x7E;
expected.put(b);
@@ -437,12 +428,12 @@
ByteBuffer expected = ByteBuffer.allocate(length + 5);
expected.put(new byte[]
- { (byte)0x81 });
+ { (byte)0x81 });
byte b = 0x00; // no masking
b |= 0x7E;
expected.put(b);
expected.put(new byte[]
- { (byte)0xff, (byte)0xff });
+ { (byte)0xff, (byte)0xff });
for (int i = 0; i < length; ++i)
{
@@ -451,7 +442,7 @@
expected.flip();
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
- policy.setMaxMessageSize(length);
+ policy.setMaxTextMessageSize(length);
Parser parser = new UnitParser(policy);
IncomingFramesCapture capture = new IncomingFramesCapture();
parser.setIncomingFramesHandler(capture);
@@ -473,12 +464,12 @@
ByteBuffer expected = ByteBuffer.allocate(length + 11);
expected.put(new byte[]
- { (byte)0x81 });
+ { (byte)0x81 });
byte b = 0x00; // no masking
b |= 0x7F;
expected.put(b);
expected.put(new byte[]
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 });
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 });
for (int i = 0; i < length; ++i)
{
@@ -488,7 +479,7 @@
expected.flip();
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
- policy.setMaxMessageSize(length);
+ policy.setMaxTextMessageSize(length);
Parser parser = new UnitParser(policy);
IncomingFramesCapture capture = new IncomingFramesCapture();
parser.setIncomingFramesHandler(capture);
@@ -509,7 +500,7 @@
ByteBuffer expected = ByteBuffer.allocate(5);
expected.put(new byte[]
- { (byte)0x81, (byte)0x00 });
+ { (byte)0x81, (byte)0x00 });
expected.flip();
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_2.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_2.java
index dd6d6ea..21fbda4 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_2.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_2.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.common.ab;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.nio.ByteBuffer;
@@ -34,6 +34,7 @@
import org.eclipse.jetty.websocket.common.UnitGenerator;
import org.eclipse.jetty.websocket.common.UnitParser;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
import org.junit.Assert;
import org.junit.Test;
@@ -58,10 +59,9 @@
bb.flip();
- WebSocketFrame binaryFrame = WebSocketFrame.binary().setPayload(bb);
+ WebSocketFrame binaryFrame = new BinaryFrame().setPayload(bb);
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(binaryFrame);
+ ByteBuffer actual = UnitGenerator.generate(binaryFrame);
ByteBuffer expected = ByteBuffer.allocate(length + 5);
@@ -97,10 +97,9 @@
bb.flip();
- WebSocketFrame binaryFrame = WebSocketFrame.binary().setPayload(bb);
+ WebSocketFrame binaryFrame = new BinaryFrame().setPayload(bb);
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(binaryFrame);
+ ByteBuffer actual = UnitGenerator.generate(binaryFrame);
ByteBuffer expected = ByteBuffer.allocate(length + 5);
@@ -140,10 +139,9 @@
bb.flip();
- WebSocketFrame binaryFrame = WebSocketFrame.binary().setPayload(bb);
+ WebSocketFrame binaryFrame = new BinaryFrame().setPayload(bb);
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(binaryFrame);
+ ByteBuffer actual = UnitGenerator.generate(binaryFrame);
ByteBuffer expected = ByteBuffer.allocate(length + 5);
@@ -182,10 +180,9 @@
}
bb.flip();
- WebSocketFrame binaryFrame = WebSocketFrame.binary().setPayload(bb);
+ WebSocketFrame binaryFrame = new BinaryFrame().setPayload(bb);
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(binaryFrame);
+ ByteBuffer actual = UnitGenerator.generate(binaryFrame);
ByteBuffer expected = ByteBuffer.allocate(length + 5);
@@ -226,10 +223,9 @@
bb.flip();
- WebSocketFrame binaryFrame = WebSocketFrame.binary().setPayload(bb);
+ WebSocketFrame binaryFrame = new BinaryFrame().setPayload(bb);
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(binaryFrame);
+ ByteBuffer actual = UnitGenerator.generate(binaryFrame);
ByteBuffer expected = ByteBuffer.allocate(length + 5);
@@ -266,10 +262,9 @@
bb.flip();
- WebSocketFrame binaryFrame = WebSocketFrame.binary().setPayload(bb);
+ WebSocketFrame binaryFrame = new BinaryFrame().setPayload(bb);
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(binaryFrame);
+ ByteBuffer actual = UnitGenerator.generate(binaryFrame);
ByteBuffer expected = ByteBuffer.allocate(length + 11);
@@ -295,10 +290,9 @@
@Test
public void testGenerateEmptyBinaryCase1_2_1()
{
- WebSocketFrame binaryFrame = WebSocketFrame.binary(new byte[] {});
+ WebSocketFrame binaryFrame = new BinaryFrame().setPayload(new byte[] {});
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(binaryFrame);
+ ByteBuffer actual = UnitGenerator.generate(binaryFrame);
ByteBuffer expected = ByteBuffer.allocate(5);
@@ -466,7 +460,7 @@
expected.flip();
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
- policy.setMaxMessageSize(length);
+ policy.setMaxBinaryMessageSize(length);
Parser parser = new UnitParser(policy);
IncomingFramesCapture capture = new IncomingFramesCapture();
parser.setIncomingFramesHandler(capture);
@@ -503,7 +497,7 @@
expected.flip();
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
- policy.setMaxMessageSize(length);
+ policy.setMaxBinaryMessageSize(length);
Parser parser = new UnitParser(policy);
IncomingFramesCapture capture = new IncomingFramesCapture();
parser.setIncomingFramesHandler(capture);
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase2.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase2.java
index 13843bb..99e8880 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase2.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase2.java
@@ -18,11 +18,12 @@
package org.eclipse.jetty.websocket.common.ab;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.nio.ByteBuffer;
import java.util.Arrays;
+import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.ProtocolException;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketException;
@@ -36,6 +37,7 @@
import org.eclipse.jetty.websocket.common.UnitGenerator;
import org.eclipse.jetty.websocket.common.UnitParser;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
import org.junit.Assert;
import org.junit.Test;
@@ -53,10 +55,9 @@
bytes[i] = Integer.valueOf(Integer.toOctalString(i)).byteValue();
}
- WebSocketFrame pingFrame = WebSocketFrame.ping().setPayload(bytes);
+ WebSocketFrame pingFrame = new PingFrame().setPayload(bytes);
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(pingFrame);
+ ByteBuffer actual = UnitGenerator.generate(pingFrame);
ByteBuffer expected = ByteBuffer.allocate(bytes.length + 32);
@@ -78,10 +79,9 @@
{
byte[] bytes = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
- WebSocketFrame pingFrame = WebSocketFrame.ping().setPayload(bytes);
+ PingFrame pingFrame = new PingFrame().setPayload(bytes);
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(pingFrame);
+ ByteBuffer actual = UnitGenerator.generate(pingFrame);
ByteBuffer expected = ByteBuffer.allocate(32);
@@ -102,11 +102,9 @@
@Test
public void testGenerateEmptyPingCase2_1()
{
- WebSocketFrame pingFrame = WebSocketFrame.ping();
+ WebSocketFrame pingFrame = new PingFrame();
-
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(pingFrame);
+ ByteBuffer actual = UnitGenerator.generate(pingFrame);
ByteBuffer expected = ByteBuffer.allocate(5);
@@ -122,12 +120,11 @@
public void testGenerateHelloPingCase2_2()
{
String message = "Hello, world!";
- byte[] messageBytes = message.getBytes();
+ byte[] messageBytes = StringUtil.getUtf8Bytes(message);
- WebSocketFrame pingFrame = WebSocketFrame.ping().setPayload(messageBytes);
+ PingFrame pingFrame = new PingFrame().setPayload(messageBytes);
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(pingFrame);
+ ByteBuffer actual = UnitGenerator.generate(pingFrame);
ByteBuffer expected = ByteBuffer.allocate(32);
@@ -148,29 +145,22 @@
public void testGenerateOversizedBinaryPingCase2_5_A()
{
byte[] bytes = new byte[126];
+ Arrays.fill(bytes,(byte)0x00);
- for ( int i = 0 ; i < bytes.length ; ++i )
- {
- bytes[i] = 0x00;
- }
-
- WebSocketFrame.ping().setPayload(bytes);
+ PingFrame pingFrame = new PingFrame();
+ pingFrame.setPayload(ByteBuffer.wrap(bytes)); // should throw exception
}
@Test( expected=WebSocketException.class )
public void testGenerateOversizedBinaryPingCase2_5_B()
{
byte[] bytes = new byte[126];
+ Arrays.fill(bytes, (byte)0x00);
- for ( int i = 0 ; i < bytes.length ; ++i )
- {
- bytes[i] = 0x00;
- }
+ PingFrame pingFrame = new PingFrame();
+ pingFrame.setPayload(ByteBuffer.wrap(bytes)); // should throw exception
- WebSocketFrame pingFrame = WebSocketFrame.ping().setPayload(bytes);
-
- Generator generator = new UnitGenerator();
- generator.generate(pingFrame);
+ // FIXME: Remove? UnitGenerator.generate(pingFrame);
}
@Test
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase3.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase3.java
index c2c13dc..ce6e363 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase3.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase3.java
@@ -25,9 +25,10 @@
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.websocket.api.ProtocolException;
import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.Generator;
import org.eclipse.jetty.websocket.common.UnitGenerator;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,21 +47,21 @@
List<WebSocketFrame[]> data = new ArrayList<>();
// @formatter:off
data.add(new WebSocketFrame[]
- { WebSocketFrame.ping().setFin(false) });
+ { new PingFrame().setFin(false) });
data.add(new WebSocketFrame[]
- { WebSocketFrame.ping().setRsv1(true) });
+ { new PingFrame().setRsv1(true) });
data.add(new WebSocketFrame[]
- { WebSocketFrame.ping().setRsv2(true) });
+ { new PingFrame().setRsv2(true) });
data.add(new WebSocketFrame[]
- { WebSocketFrame.ping().setRsv3(true) });
+ { new PingFrame().setRsv3(true) });
data.add(new WebSocketFrame[]
- { WebSocketFrame.pong().setFin(false) });
+ { new PongFrame().setFin(false) });
data.add(new WebSocketFrame[]
- { WebSocketFrame.ping().setRsv1(true) });
+ { new PingFrame().setRsv1(true) });
data.add(new WebSocketFrame[]
- { WebSocketFrame.pong().setRsv2(true) });
+ { new PongFrame().setRsv2(true) });
data.add(new WebSocketFrame[]
- { WebSocketFrame.pong().setRsv3(true) });
+ { new PongFrame().setRsv3(true) });
data.add(new WebSocketFrame[]
{ new CloseInfo().asFrame().setFin(false) });
data.add(new WebSocketFrame[]
@@ -86,9 +87,7 @@
@Test(expected = ProtocolException.class)
public void testGenerateInvalidControlFrame()
{
- Generator generator = new UnitGenerator();
-
- generator.generate(invalidFrame);
+ UnitGenerator.generate(invalidFrame);
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase4.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase4.java
index 562c0b2..1af04d2 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase4.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase4.java
@@ -65,7 +65,7 @@
Assert.assertEquals( "error on undefined opcode", 1, capture.getErrorCount(WebSocketException.class)) ;
- WebSocketException known = capture.getErrors().get(0);
+ Throwable known = capture.getErrors().get(0);
Assert.assertTrue("undefined option should be in message",known.getMessage().contains("Unknown opcode: 11"));
}
@@ -87,7 +87,7 @@
Assert.assertEquals( "error on undefined opcode", 1, capture.getErrorCount(WebSocketException.class)) ;
- WebSocketException known = capture.getErrors().get(0);
+ Throwable known = capture.getErrors().get(0);
Assert.assertTrue("undefined option should be in message",known.getMessage().contains("Unknown opcode: 12"));
}
@@ -110,7 +110,7 @@
Assert.assertEquals( "error on undefined opcode", 1, capture.getErrorCount(WebSocketException.class)) ;
- WebSocketException known = capture.getErrors().get(0);
+ Throwable known = capture.getErrors().get(0);
Assert.assertTrue("undefined option should be in message",known.getMessage().contains("Unknown opcode: 3"));
}
@@ -132,7 +132,7 @@
Assert.assertEquals( "error on undefined opcode", 1, capture.getErrorCount(WebSocketException.class)) ;
- WebSocketException known = capture.getErrors().get(0);
+ Throwable known = capture.getErrors().get(0);
Assert.assertTrue("undefined option should be in message",known.getMessage().contains("Unknown opcode: 4"));
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase7_3.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase7_3.java
index 117e8f8..c405c8d 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase7_3.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase7_3.java
@@ -18,27 +18,27 @@
package org.eclipse.jetty.websocket.common.ab;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.ProtocolException;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.ByteBufferAssert;
import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.Hex;
import org.eclipse.jetty.websocket.common.IncomingFramesCapture;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.UnitGenerator;
import org.eclipse.jetty.websocket.common.UnitParser;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
import org.junit.Assert;
import org.junit.Test;
@@ -51,8 +51,7 @@
{
CloseInfo close = new CloseInfo();
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(close.asFrame());
+ ByteBuffer actual = UnitGenerator.generate(close.asFrame());
ByteBuffer expected = ByteBuffer.allocate(5);
@@ -91,22 +90,16 @@
@Test(expected = ProtocolException.class)
public void testCase7_3_2Generate1BytePayloadClose()
{
- WebSocketFrame closeFrame = new WebSocketFrame(OpCode.CLOSE).setPayload(new byte[]
- { 0x00 });
+ CloseFrame closeFrame = new CloseFrame();
+ closeFrame.setPayload(Hex.asByteBuffer("00"));
- Generator generator = new UnitGenerator();
- generator.generate(closeFrame);
+ UnitGenerator.generate(closeFrame);
}
@Test
public void testCase7_3_2Parse1BytePayloadClose()
{
- ByteBuffer expected = ByteBuffer.allocate(32);
-
- expected.put(new byte[]
- { (byte)0x88, 0x01, 0x00 });
-
- expected.flip();
+ ByteBuffer expected = Hex.asByteBuffer("880100");
UnitParser parser = new UnitParser(policy);
IncomingFramesCapture capture = new IncomingFramesCapture();
@@ -125,8 +118,7 @@
{
CloseInfo close = new CloseInfo(1000);
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(close.asFrame());
+ ByteBuffer actual = UnitGenerator.generate(close.asFrame());
ByteBuffer expected = ByteBuffer.allocate(5);
@@ -170,8 +162,7 @@
CloseInfo close = new CloseInfo(1000,message);
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(close.asFrame());
+ ByteBuffer actual = UnitGenerator.generate(close.asFrame());
ByteBuffer expected = ByteBuffer.allocate(32);
@@ -231,8 +222,7 @@
CloseInfo close = new CloseInfo(1000,message.toString());
- Generator generator = new UnitGenerator();
- ByteBuffer actual = generator.generate(close.asFrame());
+ ByteBuffer actual = UnitGenerator.generate(close.asFrame());
ByteBuffer expected = ByteBuffer.allocate(132);
byte messageBytes[] = message.toString().getBytes(StandardCharsets.UTF_8);
@@ -300,19 +290,18 @@
byte[] messageBytes = message.toString().getBytes();
- WebSocketFrame closeFrame = new WebSocketFrame(OpCode.CLOSE);
+ CloseFrame closeFrame = new CloseFrame();
- ByteBuffer bb = ByteBuffer.allocate(WebSocketFrame.MAX_CONTROL_PAYLOAD + 1); // 126 which is too big for control
+ ByteBuffer bb = ByteBuffer.allocate(CloseFrame.MAX_CONTROL_PAYLOAD + 1); // 126 which is too big for control
bb.putChar((char)1000);
bb.put(messageBytes);
BufferUtil.flipToFlush(bb,0);
- closeFrame.setPayload(BufferUtil.toArray(bb));
+ closeFrame.setPayload(bb);
- Generator generator = new UnitGenerator();
- generator.generate(closeFrame);
+ UnitGenerator.generate(closeFrame);
}
@Test
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/MyStatelessEchoSocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/MyStatelessEchoSocket.java
index 1a36a94..cd12eda 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/MyStatelessEchoSocket.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/MyStatelessEchoSocket.java
@@ -36,6 +36,6 @@
@OnWebSocketMessage
public void onText(Session session, String text)
{
- session.getRemote().sendStringByFuture(text);
+ session.getRemote().sendString(text,null);
}
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventCapture.java
index f4c13fd..0b33f08 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventCapture.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventCapture.java
@@ -18,31 +18,58 @@
package org.eclipse.jetty.websocket.common.events;
-import java.util.ArrayList;
-import java.util.regex.Pattern;
-
-import org.junit.Assert;
-
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.junit.Assert;
+
@SuppressWarnings("serial")
-public class EventCapture extends ArrayList<String>
+public class EventCapture extends EventQueue<String>
{
+ public static class Assertable
+ {
+ private final String event;
+
+ public Assertable(String event)
+ {
+ this.event = event;
+ }
+
+ public void assertEventContains(String expected)
+ {
+ Assert.assertThat("Event",event,containsString(expected));
+ }
+
+ public void assertEventRegex(String regex)
+ {
+ Assert.assertTrue("Event: regex:[" + regex + "] in [" + event + "]",Pattern.matches(regex,event));
+ }
+
+ public void assertEventStartsWith(String expected)
+ {
+ Assert.assertThat("Event",event,startsWith(expected));
+ }
+
+ public void assertEvent(String expected)
+ {
+ Assert.assertThat("Event",event,is(expected));
+ }
+ }
+
public void add(String format, Object... args)
{
- super.add(String.format(format,args));
+ String msg = String.format(format,args);
+ System.err.printf("### EVENT: %s%n",msg);
+ super.offer(msg);
}
- public void assertEvent(int eventNum, String expected)
+ public Assertable pop()
{
- Assert.assertThat("Event[" + eventNum + "]",get(eventNum),is(expected));
- }
-
- public void assertEventContains(int eventNum, String expected)
- {
- Assert.assertThat("Event[" + eventNum + "]",get(eventNum),containsString(expected));
+ return new Assertable(super.poll());
}
public void assertEventCount(int expectedCount)
@@ -50,17 +77,6 @@
Assert.assertThat("Event Count",size(),is(expectedCount));
}
- public void assertEventRegex(int eventNum, String regex)
- {
- String event = get(eventNum);
- Assert.assertTrue("Event[" + eventNum + "]: regex:[" + regex + "] in [" + event + "]",Pattern.matches(regex,event));
- }
-
- public void assertEventStartsWith(int eventNum, String expected)
- {
- Assert.assertThat("Event[" + eventNum + "]",get(eventNum),startsWith(expected));
- }
-
public String q(String str)
{
if (str == null)
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverFactoryTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverFactoryTest.java
index 8ba6bee..83923f8 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverFactoryTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverFactoryTest.java
@@ -18,47 +18,23 @@
package org.eclipse.jetty.websocket.common.events;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.instanceOf;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
-import org.eclipse.jetty.websocket.common.annotations.BadBinarySignatureSocket;
-import org.eclipse.jetty.websocket.common.annotations.BadDuplicateBinarySocket;
-import org.eclipse.jetty.websocket.common.annotations.BadDuplicateFrameSocket;
-import org.eclipse.jetty.websocket.common.annotations.BadTextSignatureSocket;
-import org.eclipse.jetty.websocket.common.annotations.FrameSocket;
-import org.eclipse.jetty.websocket.common.annotations.MyEchoBinarySocket;
-import org.eclipse.jetty.websocket.common.annotations.MyEchoSocket;
-import org.eclipse.jetty.websocket.common.annotations.MyStatelessEchoSocket;
-import org.eclipse.jetty.websocket.common.annotations.NoopSocket;
import org.eclipse.jetty.websocket.common.annotations.NotASocket;
import org.junit.Assert;
import org.junit.Test;
import examples.AdapterConnectCloseSocket;
-import examples.AnnotatedBinaryArraySocket;
-import examples.AnnotatedBinaryStreamSocket;
-import examples.AnnotatedTextSocket;
-import examples.AnnotatedTextStreamSocket;
import examples.ListenerBasicSocket;
public class EventDriverFactoryTest
{
- private void assertHasEventMethod(String message, EventMethod actual)
- {
- Assert.assertThat(message + " EventMethod",actual,notNullValue());
-
- Assert.assertThat(message + " EventMethod.pojo",actual.pojo,notNullValue());
- Assert.assertThat(message + " EventMethod.method",actual.method,notNullValue());
- }
-
- private void assertNoEventMethod(String message, EventMethod actual)
- {
- Assert.assertThat(message + "Event method",actual,nullValue());
- }
-
/**
* Test Case for no exceptions and 5 methods (extends WebSocketAdapter)
*/
@@ -70,291 +46,7 @@
EventDriver driver = factory.wrap(socket);
String classId = AdapterConnectCloseSocket.class.getSimpleName();
- Assert.assertThat("EventDriver for " + classId,driver,instanceOf(ListenerEventDriver.class));
- }
-
- /**
- * Test Case for bad declaration (duplicate OnWebSocketBinary declarations)
- */
- @Test
- public void testAnnotatedBadDuplicateBinarySocket()
- {
- EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
- try
- {
- // Should toss exception
- factory.getMethods(BadDuplicateBinarySocket.class);
- Assert.fail("Should have thrown " + InvalidWebSocketException.class);
- }
- catch (InvalidWebSocketException e)
- {
- // Validate that we have clear error message to the developer
- Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketMessage declaration"));
- }
- }
-
- /**
- * Test Case for bad declaration (duplicate frame type methods)
- */
- @Test
- public void testAnnotatedBadDuplicateFrameSocket()
- {
- EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
- try
- {
- // Should toss exception
- factory.getMethods(BadDuplicateFrameSocket.class);
- Assert.fail("Should have thrown " + InvalidWebSocketException.class);
- }
- catch (InvalidWebSocketException e)
- {
- // Validate that we have clear error message to the developer
- Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketFrame"));
- }
- }
-
- /**
- * Test Case for bad declaration a method with a non-void return type
- */
- @Test
- public void testAnnotatedBadSignature_NonVoidReturn()
- {
- EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
- try
- {
- // Should toss exception
- factory.getMethods(BadBinarySignatureSocket.class);
- Assert.fail("Should have thrown " + InvalidWebSocketException.class);
- }
- catch (InvalidWebSocketException e)
- {
- // Validate that we have clear error message to the developer
- Assert.assertThat(e.getMessage(),containsString("must be void"));
- }
- }
-
- /**
- * Test Case for bad declaration a method with a public static method
- */
- @Test
- public void testAnnotatedBadSignature_Static()
- {
- EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
- try
- {
- // Should toss exception
- factory.getMethods(BadTextSignatureSocket.class);
- Assert.fail("Should have thrown " + InvalidWebSocketException.class);
- }
- catch (InvalidWebSocketException e)
- {
- // Validate that we have clear error message to the developer
- Assert.assertThat(e.getMessage(),containsString("may not be static"));
- }
- }
-
- /**
- * Test Case for socket for binary array messages
- */
- @Test
- public void testAnnotatedBinaryArraySocket()
- {
- EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
- EventMethods methods = factory.getMethods(AnnotatedBinaryArraySocket.class);
-
- String classId = AnnotatedBinaryArraySocket.class.getSimpleName();
-
- Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
-
- assertHasEventMethod(classId + ".onBinary",methods.onBinary);
- assertHasEventMethod(classId + ".onClose",methods.onClose);
- assertHasEventMethod(classId + ".onConnect",methods.onConnect);
- assertNoEventMethod(classId + ".onException",methods.onError);
- assertNoEventMethod(classId + ".onText",methods.onText);
- assertNoEventMethod(classId + ".onFrame",methods.onFrame);
-
- Assert.assertFalse(classId + ".onBinary.hasSession",methods.onBinary.isHasSession());
- Assert.assertFalse(classId + ".onBinary.isStreaming",methods.onBinary.isStreaming());
- }
-
- /**
- * Test Case for socket for binary stream messages
- */
- @Test
- public void testAnnotatedBinaryStreamSocket()
- {
- EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
- EventMethods methods = factory.getMethods(AnnotatedBinaryStreamSocket.class);
-
- String classId = AnnotatedBinaryStreamSocket.class.getSimpleName();
-
- Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
-
- assertHasEventMethod(classId + ".onBinary",methods.onBinary);
- assertHasEventMethod(classId + ".onClose",methods.onClose);
- assertHasEventMethod(classId + ".onConnect",methods.onConnect);
- assertNoEventMethod(classId + ".onException",methods.onError);
- assertNoEventMethod(classId + ".onText",methods.onText);
- assertNoEventMethod(classId + ".onFrame",methods.onFrame);
-
- Assert.assertFalse(classId + ".onBinary.hasSession",methods.onBinary.isHasSession());
- Assert.assertTrue(classId + ".onBinary.isStreaming",methods.onBinary.isStreaming());
- }
-
- /**
- * Test Case for no exceptions and 4 methods (3 methods from parent)
- */
- @Test
- public void testAnnotatedMyEchoBinarySocket()
- {
- EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
- EventMethods methods = factory.getMethods(MyEchoBinarySocket.class);
-
- String classId = MyEchoBinarySocket.class.getSimpleName();
-
- Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
-
- assertHasEventMethod(classId + ".onBinary",methods.onBinary);
- assertHasEventMethod(classId + ".onClose",methods.onClose);
- assertHasEventMethod(classId + ".onConnect",methods.onConnect);
- assertNoEventMethod(classId + ".onException",methods.onError);
- assertHasEventMethod(classId + ".onText",methods.onText);
- assertNoEventMethod(classId + ".onFrame",methods.onFrame);
- }
-
- /**
- * Test Case for no exceptions and 3 methods
- */
- @Test
- public void testAnnotatedMyEchoSocket()
- {
- EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
- EventMethods methods = factory.getMethods(MyEchoSocket.class);
-
- String classId = MyEchoSocket.class.getSimpleName();
-
- Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
-
- assertNoEventMethod(classId + ".onBinary",methods.onBinary);
- assertHasEventMethod(classId + ".onClose",methods.onClose);
- assertHasEventMethod(classId + ".onConnect",methods.onConnect);
- assertNoEventMethod(classId + ".onException",methods.onError);
- assertHasEventMethod(classId + ".onText",methods.onText);
- assertNoEventMethod(classId + ".onFrame",methods.onFrame);
- }
-
- /**
- * Test Case for annotated for text messages w/connection param
- */
- @Test
- public void testAnnotatedMyStatelessEchoSocket()
- {
- EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
- EventMethods methods = factory.getMethods(MyStatelessEchoSocket.class);
-
- String classId = MyStatelessEchoSocket.class.getSimpleName();
-
- Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
-
- assertNoEventMethod(classId + ".onBinary",methods.onBinary);
- assertNoEventMethod(classId + ".onClose",methods.onClose);
- assertNoEventMethod(classId + ".onConnect",methods.onConnect);
- assertNoEventMethod(classId + ".onException",methods.onError);
- assertHasEventMethod(classId + ".onText",methods.onText);
- assertNoEventMethod(classId + ".onFrame",methods.onFrame);
-
- Assert.assertTrue(classId + ".onText.hasSession",methods.onText.isHasSession());
- Assert.assertFalse(classId + ".onText.isStreaming",methods.onText.isStreaming());
- }
-
- /**
- * Test Case for no exceptions and no methods
- */
- @Test
- public void testAnnotatedNoop()
- {
- EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
- EventMethods methods = factory.getMethods(NoopSocket.class);
-
- String classId = NoopSocket.class.getSimpleName();
-
- Assert.assertThat("Methods for " + classId,methods,notNullValue());
-
- assertNoEventMethod(classId + ".onBinary",methods.onBinary);
- assertNoEventMethod(classId + ".onClose",methods.onClose);
- assertNoEventMethod(classId + ".onConnect",methods.onConnect);
- assertNoEventMethod(classId + ".onException",methods.onError);
- assertNoEventMethod(classId + ".onText",methods.onText);
- assertNoEventMethod(classId + ".onFrame",methods.onFrame);
- }
-
- /**
- * Test Case for no exceptions and 1 methods
- */
- @Test
- public void testAnnotatedOnFrame()
- {
- EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
- EventMethods methods = factory.getMethods(FrameSocket.class);
-
- String classId = FrameSocket.class.getSimpleName();
-
- Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
-
- assertNoEventMethod(classId + ".onBinary",methods.onBinary);
- assertNoEventMethod(classId + ".onClose",methods.onClose);
- assertNoEventMethod(classId + ".onConnect",methods.onConnect);
- assertNoEventMethod(classId + ".onException",methods.onError);
- assertNoEventMethod(classId + ".onText",methods.onText);
- assertHasEventMethod(classId + ".onFrame",methods.onFrame);
- }
-
- /**
- * Test Case for socket for simple text messages
- */
- @Test
- public void testAnnotatedTextSocket()
- {
- EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
- EventMethods methods = factory.getMethods(AnnotatedTextSocket.class);
-
- String classId = AnnotatedTextSocket.class.getSimpleName();
-
- Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
-
- assertNoEventMethod(classId + ".onBinary",methods.onBinary);
- assertHasEventMethod(classId + ".onClose",methods.onClose);
- assertHasEventMethod(classId + ".onConnect",methods.onConnect);
- assertHasEventMethod(classId + ".onException",methods.onError);
- assertHasEventMethod(classId + ".onText",methods.onText);
- assertNoEventMethod(classId + ".onFrame",methods.onFrame);
-
- Assert.assertFalse(classId + ".onText.hasSession",methods.onText.isHasSession());
- Assert.assertFalse(classId + ".onText.isStreaming",methods.onText.isStreaming());
- }
-
- /**
- * Test Case for socket for text stream messages
- */
- @Test
- public void testAnnotatedTextStreamSocket()
- {
- EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
- EventMethods methods = factory.getMethods(AnnotatedTextStreamSocket.class);
-
- String classId = AnnotatedTextStreamSocket.class.getSimpleName();
-
- Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
-
- assertNoEventMethod(classId + ".onBinary",methods.onBinary);
- assertHasEventMethod(classId + ".onClose",methods.onClose);
- assertHasEventMethod(classId + ".onConnect",methods.onConnect);
- assertNoEventMethod(classId + ".onException",methods.onError);
- assertHasEventMethod(classId + ".onText",methods.onText);
- assertNoEventMethod(classId + ".onFrame",methods.onFrame);
-
- Assert.assertFalse(classId + ".onText.hasSession",methods.onText.isHasSession());
- Assert.assertTrue(classId + ".onText.isStreaming",methods.onText.isStreaming());
+ Assert.assertThat("EventDriver for " + classId,driver,instanceOf(JettyListenerEventDriver.class));
}
/**
@@ -388,6 +80,6 @@
EventDriver driver = factory.wrap(socket);
String classId = ListenerBasicSocket.class.getSimpleName();
- Assert.assertThat("EventDriver for " + classId,driver,instanceOf(ListenerEventDriver.class));
+ Assert.assertThat("EventDriver for " + classId,driver,instanceOf(JettyListenerEventDriver.class));
}
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java
index ecb5adb..01f29b4 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java
@@ -19,14 +19,16 @@
package org.eclipse.jetty.websocket.common.events;
import java.io.IOException;
+import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.OpCode;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.common.io.LocalWebSocketSession;
import org.junit.Rule;
import org.junit.Test;
@@ -46,7 +48,7 @@
private Frame makeBinaryFrame(String content, boolean fin)
{
- return WebSocketFrame.binary().setFin(fin).setPayload(content);
+ return new BinaryFrame().setPayload(content).setFin(fin);
}
@Test
@@ -61,8 +63,8 @@
driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame());
socket.capture.assertEventCount(2);
- socket.capture.assertEventStartsWith(0,"onWebSocketConnect");
- socket.capture.assertEventStartsWith(1,"onWebSocketClose");
+ socket.capture.pop().assertEventStartsWith("onWebSocketConnect");
+ socket.capture.pop().assertEventStartsWith("onWebSocketClose");
}
}
@@ -79,9 +81,9 @@
driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame());
socket.capture.assertEventCount(3);
- socket.capture.assertEventStartsWith(0,"onConnect");
- socket.capture.assertEvent(1,"onBinary([11],0,11)");
- socket.capture.assertEventStartsWith(2,"onClose(1000,");
+ socket.capture.pop().assertEventStartsWith("onConnect");
+ socket.capture.pop().assertEvent("onBinary([11],0,11)");
+ socket.capture.pop().assertEventStartsWith("onClose(1000,");
}
}
@@ -98,9 +100,9 @@
driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame());
socket.capture.assertEventCount(3);
- socket.capture.assertEventStartsWith(0,"onConnect");
- socket.capture.assertEventStartsWith(1,"onError(WebSocketException: oof)");
- socket.capture.assertEventStartsWith(2,"onClose(1000,");
+ socket.capture.pop().assertEventStartsWith("onConnect");
+ socket.capture.pop().assertEventStartsWith("onError(WebSocketException: oof)");
+ socket.capture.pop().assertEventStartsWith("onClose(1000,");
}
}
@@ -113,23 +115,23 @@
try (LocalWebSocketSession conn = new LocalWebSocketSession(testname,driver))
{
conn.open();
- driver.incomingFrame(new WebSocketFrame(OpCode.PING).setPayload("PING"));
- driver.incomingFrame(WebSocketFrame.text("Text Me"));
- driver.incomingFrame(WebSocketFrame.binary().setPayload("Hello Bin"));
+ driver.incomingFrame(new PingFrame().setPayload("PING"));
+ driver.incomingFrame(new TextFrame().setPayload("Text Me"));
+ driver.incomingFrame(new BinaryFrame().setPayload("Hello Bin"));
driver.incomingFrame(new CloseInfo(StatusCode.SHUTDOWN,"testcase").asFrame());
socket.capture.assertEventCount(6);
- socket.capture.assertEventStartsWith(0,"onConnect(");
- socket.capture.assertEventStartsWith(1,"onFrame(PING[");
- socket.capture.assertEventStartsWith(2,"onFrame(TEXT[");
- socket.capture.assertEventStartsWith(3,"onFrame(BINARY[");
- socket.capture.assertEventStartsWith(4,"onFrame(CLOSE[");
- socket.capture.assertEventStartsWith(5,"onClose(1001,");
+ socket.capture.pop().assertEventStartsWith("onConnect(");
+ socket.capture.pop().assertEventStartsWith("onFrame(PING[");
+ socket.capture.pop().assertEventStartsWith("onFrame(TEXT[");
+ socket.capture.pop().assertEventStartsWith("onFrame(BINARY[");
+ socket.capture.pop().assertEventStartsWith("onFrame(CLOSE[");
+ socket.capture.pop().assertEventStartsWith("onClose(1001,");
}
}
@Test
- public void testAnnotated_InputStream() throws IOException
+ public void testAnnotated_InputStream() throws IOException, TimeoutException, InterruptedException
{
AnnotatedBinaryStreamSocket socket = new AnnotatedBinaryStreamSocket();
EventDriver driver = wrap(socket);
@@ -141,9 +143,9 @@
driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame());
socket.capture.assertEventCount(3);
- socket.capture.assertEventStartsWith(0,"onConnect");
- socket.capture.assertEventRegex(1,"^onBinary\\(.*InputStream.*");
- socket.capture.assertEventStartsWith(2,"onClose(1000,");
+ socket.capture.pop().assertEventStartsWith("onConnect");
+ socket.capture.pop().assertEventRegex("^onBinary\\(.*InputStream.*");
+ socket.capture.pop().assertEventStartsWith("onClose(1000,");
}
}
@@ -157,13 +159,13 @@
{
conn.start();
conn.open();
- driver.incomingFrame(WebSocketFrame.text("Hello World"));
+ driver.incomingFrame(new TextFrame().setPayload("Hello World"));
driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame());
socket.capture.assertEventCount(3);
- socket.capture.assertEventStartsWith(0,"onWebSocketConnect");
- socket.capture.assertEventStartsWith(1,"onWebSocketText(\"Hello World\")");
- socket.capture.assertEventStartsWith(2,"onWebSocketClose(1000,");
+ socket.capture.pop().assertEventStartsWith("onWebSocketConnect");
+ socket.capture.pop().assertEventStartsWith("onWebSocketText(\"Hello World\")");
+ socket.capture.pop().assertEventStartsWith("onWebSocketClose(1000,");
}
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScannerTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScannerTest.java
new file mode 100644
index 0000000..1ef93d2
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScannerTest.java
@@ -0,0 +1,342 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.common.annotations.BadBinarySignatureSocket;
+import org.eclipse.jetty.websocket.common.annotations.BadDuplicateBinarySocket;
+import org.eclipse.jetty.websocket.common.annotations.BadDuplicateFrameSocket;
+import org.eclipse.jetty.websocket.common.annotations.BadTextSignatureSocket;
+import org.eclipse.jetty.websocket.common.annotations.FrameSocket;
+import org.eclipse.jetty.websocket.common.annotations.MyEchoBinarySocket;
+import org.eclipse.jetty.websocket.common.annotations.MyEchoSocket;
+import org.eclipse.jetty.websocket.common.annotations.MyStatelessEchoSocket;
+import org.eclipse.jetty.websocket.common.annotations.NoopSocket;
+import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod;
+import org.junit.Assert;
+import org.junit.Test;
+
+import examples.AnnotatedBinaryArraySocket;
+import examples.AnnotatedBinaryStreamSocket;
+import examples.AnnotatedTextSocket;
+import examples.AnnotatedTextStreamSocket;
+
+public class JettyAnnotatedScannerTest
+{
+ private void assertHasEventMethod(String message, CallableMethod actual)
+ {
+ Assert.assertThat(message + " CallableMethod",actual,notNullValue());
+
+ Assert.assertThat(message + " CallableMethod.pojo",actual.getPojo(),notNullValue());
+ Assert.assertThat(message + " CallableMethod.method",actual.getMethod(),notNullValue());
+ }
+
+ private void assertNoEventMethod(String message, CallableMethod actual)
+ {
+ Assert.assertThat(message + " CallableMethod",actual,nullValue());
+ }
+
+ /**
+ * Test Case for bad declaration (duplicate OnWebSocketBinary declarations)
+ */
+ @Test
+ public void testAnnotatedBadDuplicateBinarySocket()
+ {
+ JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+ try
+ {
+ // Should toss exception
+ impl.scan(BadDuplicateBinarySocket.class);
+ Assert.fail("Should have thrown " + InvalidWebSocketException.class);
+ }
+ catch (InvalidWebSocketException e)
+ {
+ // Validate that we have clear error message to the developer
+ Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketMessage declaration"));
+ }
+ }
+
+ /**
+ * Test Case for bad declaration (duplicate frame type methods)
+ */
+ @Test
+ public void testAnnotatedBadDuplicateFrameSocket()
+ {
+ JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+ try
+ {
+ // Should toss exception
+ impl.scan(BadDuplicateFrameSocket.class);
+ Assert.fail("Should have thrown " + InvalidWebSocketException.class);
+ }
+ catch (InvalidWebSocketException e)
+ {
+ // Validate that we have clear error message to the developer
+ Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketFrame"));
+ }
+ }
+
+ /**
+ * Test Case for bad declaration a method with a non-void return type
+ */
+ @Test
+ public void testAnnotatedBadSignature_NonVoidReturn()
+ {
+ JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+ try
+ {
+ // Should toss exception
+ impl.scan(BadBinarySignatureSocket.class);
+ Assert.fail("Should have thrown " + InvalidWebSocketException.class);
+ }
+ catch (InvalidWebSocketException e)
+ {
+ // Validate that we have clear error message to the developer
+ Assert.assertThat(e.getMessage(),containsString("must be void"));
+ }
+ }
+
+ /**
+ * Test Case for bad declaration a method with a public static method
+ */
+ @Test
+ public void testAnnotatedBadSignature_Static()
+ {
+ JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+ try
+ {
+ // Should toss exception
+ impl.scan(BadTextSignatureSocket.class);
+ Assert.fail("Should have thrown " + InvalidWebSocketException.class);
+ }
+ catch (InvalidWebSocketException e)
+ {
+ // Validate that we have clear error message to the developer
+ Assert.assertThat(e.getMessage(),containsString("may not be static"));
+ }
+ }
+
+ /**
+ * Test Case for socket for binary array messages
+ */
+ @Test
+ public void testAnnotatedBinaryArraySocket()
+ {
+ JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+ JettyAnnotatedMetadata metadata = impl.scan(AnnotatedBinaryArraySocket.class);
+
+ String classId = AnnotatedBinaryArraySocket.class.getSimpleName();
+
+ Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+ assertHasEventMethod(classId + ".onBinary",metadata.onBinary);
+ assertHasEventMethod(classId + ".onClose",metadata.onClose);
+ assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
+ assertNoEventMethod(classId + ".onException",metadata.onError);
+ assertNoEventMethod(classId + ".onText",metadata.onText);
+ assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+
+ Assert.assertFalse(classId + ".onBinary.isSessionAware",metadata.onBinary.isSessionAware());
+ Assert.assertFalse(classId + ".onBinary.isStreaming",metadata.onBinary.isStreaming());
+ }
+
+ /**
+ * Test Case for socket for binary stream messages
+ */
+ @Test
+ public void testAnnotatedBinaryStreamSocket()
+ {
+ JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+ JettyAnnotatedMetadata metadata = impl.scan(AnnotatedBinaryStreamSocket.class);
+
+ String classId = AnnotatedBinaryStreamSocket.class.getSimpleName();
+
+ Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+ assertHasEventMethod(classId + ".onBinary",metadata.onBinary);
+ assertHasEventMethod(classId + ".onClose",metadata.onClose);
+ assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
+ assertNoEventMethod(classId + ".onException",metadata.onError);
+ assertNoEventMethod(classId + ".onText",metadata.onText);
+ assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+
+ Assert.assertFalse(classId + ".onBinary.isSessionAware",metadata.onBinary.isSessionAware());
+ Assert.assertTrue(classId + ".onBinary.isStreaming",metadata.onBinary.isStreaming());
+ }
+
+ /**
+ * Test Case for no exceptions and 4 methods (3 methods from parent)
+ */
+ @Test
+ public void testAnnotatedMyEchoBinarySocket()
+ {
+ JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+ JettyAnnotatedMetadata metadata = impl.scan(MyEchoBinarySocket.class);
+
+ String classId = MyEchoBinarySocket.class.getSimpleName();
+
+ Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+ assertHasEventMethod(classId + ".onBinary",metadata.onBinary);
+ assertHasEventMethod(classId + ".onClose",metadata.onClose);
+ assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
+ assertNoEventMethod(classId + ".onException",metadata.onError);
+ assertHasEventMethod(classId + ".onText",metadata.onText);
+ assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+ }
+
+ /**
+ * Test Case for no exceptions and 3 methods
+ */
+ @Test
+ public void testAnnotatedMyEchoSocket()
+ {
+ JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+ JettyAnnotatedMetadata metadata = impl.scan(MyEchoSocket.class);
+
+ String classId = MyEchoSocket.class.getSimpleName();
+
+ Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+ assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
+ assertHasEventMethod(classId + ".onClose",metadata.onClose);
+ assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
+ assertNoEventMethod(classId + ".onException",metadata.onError);
+ assertHasEventMethod(classId + ".onText",metadata.onText);
+ assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+ }
+
+ /**
+ * Test Case for annotated for text messages w/connection param
+ */
+ @Test
+ public void testAnnotatedMyStatelessEchoSocket()
+ {
+ JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+ JettyAnnotatedMetadata metadata = impl.scan(MyStatelessEchoSocket.class);
+
+ String classId = MyStatelessEchoSocket.class.getSimpleName();
+
+ Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+ assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
+ assertNoEventMethod(classId + ".onClose",metadata.onClose);
+ assertNoEventMethod(classId + ".onConnect",metadata.onConnect);
+ assertNoEventMethod(classId + ".onException",metadata.onError);
+ assertHasEventMethod(classId + ".onText",metadata.onText);
+ assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+
+ Assert.assertTrue(classId + ".onText.isSessionAware",metadata.onText.isSessionAware());
+ Assert.assertFalse(classId + ".onText.isStreaming",metadata.onText.isStreaming());
+ }
+
+ /**
+ * Test Case for no exceptions and no methods
+ */
+ @Test
+ public void testAnnotatedNoop()
+ {
+ JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+ JettyAnnotatedMetadata metadata = impl.scan(NoopSocket.class);
+
+ String classId = NoopSocket.class.getSimpleName();
+
+ Assert.assertThat("Methods for " + classId,metadata,notNullValue());
+
+ assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
+ assertNoEventMethod(classId + ".onClose",metadata.onClose);
+ assertNoEventMethod(classId + ".onConnect",metadata.onConnect);
+ assertNoEventMethod(classId + ".onException",metadata.onError);
+ assertNoEventMethod(classId + ".onText",metadata.onText);
+ assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+ }
+
+ /**
+ * Test Case for no exceptions and 1 methods
+ */
+ @Test
+ public void testAnnotatedOnFrame()
+ {
+ JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+ JettyAnnotatedMetadata metadata = impl.scan(FrameSocket.class);
+
+ String classId = FrameSocket.class.getSimpleName();
+
+ Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+ assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
+ assertNoEventMethod(classId + ".onClose",metadata.onClose);
+ assertNoEventMethod(classId + ".onConnect",metadata.onConnect);
+ assertNoEventMethod(classId + ".onException",metadata.onError);
+ assertNoEventMethod(classId + ".onText",metadata.onText);
+ assertHasEventMethod(classId + ".onFrame",metadata.onFrame);
+ }
+
+ /**
+ * Test Case for socket for simple text messages
+ */
+ @Test
+ public void testAnnotatedTextSocket()
+ {
+ JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+ JettyAnnotatedMetadata metadata = impl.scan(AnnotatedTextSocket.class);
+
+ String classId = AnnotatedTextSocket.class.getSimpleName();
+
+ Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+ assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
+ assertHasEventMethod(classId + ".onClose",metadata.onClose);
+ assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
+ assertHasEventMethod(classId + ".onException",metadata.onError);
+ assertHasEventMethod(classId + ".onText",metadata.onText);
+ assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+
+ Assert.assertFalse(classId + ".onText.isSessionAware",metadata.onText.isSessionAware());
+ Assert.assertFalse(classId + ".onText.isStreaming",metadata.onText.isStreaming());
+ }
+
+ /**
+ * Test Case for socket for text stream messages
+ */
+ @Test
+ public void testAnnotatedTextStreamSocket()
+ {
+ JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+ JettyAnnotatedMetadata metadata = impl.scan(AnnotatedTextStreamSocket.class);
+
+ String classId = AnnotatedTextStreamSocket.class.getSimpleName();
+
+ Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+ assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
+ assertHasEventMethod(classId + ".onClose",metadata.onClose);
+ assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
+ assertNoEventMethod(classId + ".onException",metadata.onError);
+ assertHasEventMethod(classId + ".onText",metadata.onText);
+ assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+
+ Assert.assertFalse(classId + ".onText.isSessionAware",metadata.onText.isSessionAware());
+ Assert.assertTrue(classId + ".onText.isStreaming",metadata.onText.isStreaming());
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/AllTests.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/AllTests.java
index b33f3a5..4c5ed61 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/AllTests.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/AllTests.java
@@ -18,16 +18,15 @@
package org.eclipse.jetty.websocket.common.extensions;
-import org.eclipse.jetty.websocket.common.extensions.compress.DeflateCompressionMethodTest;
-import org.eclipse.jetty.websocket.common.extensions.compress.MessageCompressionExtensionTest;
-import org.eclipse.jetty.websocket.common.extensions.compress.FrameCompressionExtensionTest;
+import org.eclipse.jetty.websocket.common.extensions.compress.DeflateFrameExtensionTest;
+import org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtensionTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses(
- { ExtensionStackTest.class, DeflateCompressionMethodTest.class, MessageCompressionExtensionTest.class, FragmentExtensionTest.class,
- IdentityExtensionTest.class, FrameCompressionExtensionTest.class })
+ { ExtensionStackTest.class, PerMessageDeflateExtensionTest.class, FragmentExtensionTest.class,
+ IdentityExtensionTest.class, DeflateFrameExtensionTest.class })
public class AllTests
{
/* nothing to do here, its all done in the annotations */
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyIncomingFrames.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyIncomingFrames.java
index 39cfc7d..b9e045e 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyIncomingFrames.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyIncomingFrames.java
@@ -20,7 +20,6 @@
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
@@ -38,7 +37,7 @@
}
@Override
- public void incomingError(WebSocketException e)
+ public void incomingError(Throwable e)
{
LOG.debug("incomingError()",e);
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyOutgoingFrames.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyOutgoingFrames.java
index 278017d..fef0de6 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyOutgoingFrames.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyOutgoingFrames.java
@@ -23,6 +23,7 @@
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.junit.rules.TestName;
/**
* Dummy implementation of {@link OutgoingFrames} used for testing
@@ -37,6 +38,11 @@
this.id = id;
}
+ public DummyOutgoingFrames(TestName testname)
+ {
+ this(testname.getMethodName());
+ }
+
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
{
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java
index 096dc82..0f9a6ae 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.common.extensions;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.util.ArrayList;
import java.util.List;
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/FragmentExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/FragmentExtensionTest.java
index ce740e0..513bda5 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/FragmentExtensionTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/FragmentExtensionTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.common.extensions;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -29,7 +29,6 @@
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame;
@@ -39,6 +38,9 @@
import org.eclipse.jetty.websocket.common.OutgoingFramesCapture;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.extensions.fragment.FragmentExtension;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.junit.Assert;
import org.junit.Test;
@@ -69,7 +71,7 @@
// Manually create frame and pass into extension
for (String q : quote)
{
- Frame frame = WebSocketFrame.text(q);
+ Frame frame = new TextFrame().setPayload(q);
ext.incomingFrame(frame);
}
@@ -113,7 +115,7 @@
ext.setNextIncomingFrames(capture);
String payload = "Are you there?";
- Frame ping = WebSocketFrame.ping().setPayload(payload);
+ Frame ping = new PingFrame().setPayload(payload);
ext.incomingFrame(ping);
capture.assertFrameCount(1);
@@ -156,22 +158,22 @@
// Write quote as separate frames
for (String section : quote)
{
- Frame frame = WebSocketFrame.text(section);
+ Frame frame = new TextFrame().setPayload(section);
ext.outgoingFrame(frame,null);
}
// Expected Frames
List<WebSocketFrame> expectedFrames = new ArrayList<>();
- expectedFrames.add(new WebSocketFrame(OpCode.TEXT).setPayload("No amount of experim").setFin(false));
- expectedFrames.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("entation can ever pr").setFin(false));
- expectedFrames.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("ove me right;").setFin(true));
+ expectedFrames.add(new TextFrame().setPayload("No amount of experim").setFin(false));
+ expectedFrames.add(new ContinuationFrame().setPayload("entation can ever pr").setFin(false));
+ expectedFrames.add(new ContinuationFrame().setPayload("ove me right;").setFin(true));
- expectedFrames.add(new WebSocketFrame(OpCode.TEXT).setPayload("a single experiment ").setFin(false));
- expectedFrames.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("can prove me wrong.").setFin(true));
+ expectedFrames.add(new TextFrame().setPayload("a single experiment ").setFin(false));
+ expectedFrames.add(new ContinuationFrame().setPayload("can prove me wrong.").setFin(true));
- expectedFrames.add(new WebSocketFrame(OpCode.TEXT).setPayload("-- Albert Einstein").setFin(true));
+ expectedFrames.add(new TextFrame().setPayload("-- Albert Einstein").setFin(true));
- // capture.dump();
+ capture.dump();
int len = expectedFrames.size();
capture.assertFrameCount(len);
@@ -184,6 +186,9 @@
WebSocketFrame actualFrame = frames.get(i);
WebSocketFrame expectedFrame = expectedFrames.get(i);
+ System.out.printf("actual: %s%n",actualFrame);
+ System.out.printf("expect: %s%n",expectedFrame);
+
// Validate Frame
Assert.assertThat(prefix + ".opcode",actualFrame.getOpCode(),is(expectedFrame.getOpCode()));
Assert.assertThat(prefix + ".fin",actualFrame.isFin(),is(expectedFrame.isFin()));
@@ -225,15 +230,15 @@
// Write quote as separate frames
for (String section : quote)
{
- Frame frame = WebSocketFrame.text(section);
+ Frame frame = new TextFrame().setPayload(section);
ext.outgoingFrame(frame,null);
}
// Expected Frames
List<WebSocketFrame> expectedFrames = new ArrayList<>();
- expectedFrames.add(new WebSocketFrame(OpCode.TEXT).setPayload("No amount of experimentation can ever prove me right;"));
- expectedFrames.add(new WebSocketFrame(OpCode.TEXT).setPayload("a single experiment can prove me wrong."));
- expectedFrames.add(new WebSocketFrame(OpCode.TEXT).setPayload("-- Albert Einstein"));
+ expectedFrames.add(new TextFrame().setPayload("No amount of experimentation can ever prove me right;"));
+ expectedFrames.add(new TextFrame().setPayload("a single experiment can prove me wrong."));
+ expectedFrames.add(new TextFrame().setPayload("-- Albert Einstein"));
// capture.dump();
@@ -281,7 +286,7 @@
ext.setNextOutgoingFrames(capture);
String payload = "Are you there?";
- Frame ping = WebSocketFrame.ping().setPayload(payload);
+ Frame ping = new PingFrame().setPayload(payload);
ext.outgoingFrame(ping,null);
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/IdentityExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/IdentityExtensionTest.java
index 217c2eb..56e27f0 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/IdentityExtensionTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/IdentityExtensionTest.java
@@ -18,14 +18,13 @@
package org.eclipse.jetty.websocket.common.extensions;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.extensions.Extension;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.ByteBufferAssert;
@@ -34,6 +33,7 @@
import org.eclipse.jetty.websocket.common.OutgoingFramesCapture;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.extensions.identity.IdentityExtension;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.junit.Assert;
import org.junit.Test;
@@ -50,7 +50,7 @@
Extension ext = new IdentityExtension();
ext.setNextIncomingFrames(capture);
- Frame frame = WebSocketFrame.text("hello");
+ Frame frame = new TextFrame().setPayload("hello");
ext.incomingFrame(frame);
capture.assertFrameCount(1);
@@ -79,7 +79,7 @@
Extension ext = new IdentityExtension();
ext.setNextOutgoingFrames(capture);
- Frame frame = WebSocketFrame.text("hello");
+ Frame frame = new TextFrame().setPayload("hello");
ext.outgoingFrame(frame,null);
capture.assertFrameCount(1);
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/CapturedHexPayloads.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/CapturedHexPayloads.java
new file mode 100644
index 0000000..c7933f0
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/CapturedHexPayloads.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.Hex;
+
+public class CapturedHexPayloads implements OutgoingFrames
+{
+ private List<String> captured = new ArrayList<>();
+
+ @Override
+ public void outgoingFrame(Frame frame, WriteCallback callback)
+ {
+ String hexPayload = Hex.asHex(frame.getPayload());
+ captured.add(hexPayload);
+ if (callback != null)
+ {
+ callback.writeSuccess();
+ }
+ }
+
+ public List<String> getCaptured()
+ {
+ return captured;
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateCompressionMethodTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateCompressionMethodTest.java
deleted file mode 100644
index 92d3f55..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateCompressionMethodTest.java
+++ /dev/null
@@ -1,222 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.compress;
-
-import static org.hamcrest.Matchers.*;
-
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.junit.Assert;
-import org.junit.Test;
-
-/**
- * Test the Deflate Compression Method in use by several extensions.
- */
-public class DeflateCompressionMethodTest
-{
- private static final Logger LOG = Log.getLogger(DeflateCompressionMethodTest.class);
-
- private void assertRoundTrip(CompressionMethod method, CharSequence msg)
- {
- String expected = msg.toString();
-
- ByteBuffer orig = BufferUtil.toBuffer(expected,StandardCharsets.UTF_8);
-
- LOG.debug("orig: {}",BufferUtil.toDetailString(orig));
-
- // compress
- method.compress().begin();
- method.compress().input(orig);
- ByteBuffer compressed = method.compress().process();
- LOG.debug("compressed: {}",BufferUtil.toDetailString(compressed));
- Assert.assertThat("Compress.isDone",method.compress().isDone(),is(true));
- method.compress().end();
-
- // decompress
- ByteBuffer decompressed = ByteBuffer.allocate(msg.length());
- LOG.debug("decompressed(a): {}",BufferUtil.toDetailString(decompressed));
- method.decompress().begin();
- method.decompress().input(compressed);
- while (!method.decompress().isDone())
- {
- ByteBuffer window = method.decompress().process();
- BufferUtil.put(window,decompressed);
- }
- BufferUtil.flipToFlush(decompressed,0);
- LOG.debug("decompressed(f): {}",BufferUtil.toDetailString(decompressed));
- method.decompress().end();
-
- // validate
- String actual = BufferUtil.toUTF8String(decompressed);
- Assert.assertThat("Message Size",actual.length(),is(msg.length()));
- Assert.assertEquals("Message Contents",expected,actual);
- }
-
- /**
- * Test decompression with 2 buffers. First buffer is normal, second relies on back buffers created from first.
- */
- @Test
- public void testFollowupBackDistance()
- {
- // The Sample (Compressed) Data
- byte buf1[] = TypeUtil.fromHexString("2aC9Cc4dB50200"); // DEFLATE -> "time:"
- byte buf2[] = TypeUtil.fromHexString("2a01110000"); // DEFLATE -> "time:"
-
- // Setup Compression Method
- CompressionMethod method = new DeflateCompressionMethod();
-
- // Decompressed Data Holder
- ByteBuffer decompressed = ByteBuffer.allocate(32);
- BufferUtil.flipToFill(decompressed);
-
- // Perform Decompress on Buf 1
- BufferUtil.clearToFill(decompressed);
- // IGNORE method.decompress().begin();
- method.decompress().input(ByteBuffer.wrap(buf1));
- while (!method.decompress().isDone())
- {
- ByteBuffer window = method.decompress().process();
- BufferUtil.put(window,decompressed);
- }
- BufferUtil.flipToFlush(decompressed,0);
- LOG.debug("decompressed[1]: {}",BufferUtil.toDetailString(decompressed));
- // IGNORE method.decompress().end();
-
- // Perform Decompress on Buf 2
- BufferUtil.clearToFill(decompressed);
- // IGNORE method.decompress().begin();
- method.decompress().input(ByteBuffer.wrap(buf2));
- while (!method.decompress().isDone())
- {
- ByteBuffer window = method.decompress().process();
- BufferUtil.put(window,decompressed);
- }
- BufferUtil.flipToFlush(decompressed,0);
- LOG.debug("decompressed[2]: {}",BufferUtil.toDetailString(decompressed));
- // IGNORE method.decompress().end();
- }
-
- /**
- * Test a large payload (a payload length over 65535 bytes).
- *
- * Round Trip (RT) Compress then Decompress
- */
- @Test
- public void testRTLarge()
- {
- // large sized message
- StringBuilder msg = new StringBuilder();
- for (int i = 0; i < 5000; i++)
- {
- msg.append("0123456789ABCDEF ");
- }
- msg.append('X'); // so we can see the end in our debugging
-
- // ensure that test remains sane
- Assert.assertThat("Large Payload Length",msg.length(),greaterThan(0xFF_FF));
-
- // Setup Compression Method
- CompressionMethod method = new DeflateCompressionMethod();
-
- // Test round trip
- assertRoundTrip(method,msg);
- }
-
- /**
- * Test many small payloads (each payload length less than 126 bytes).
- *
- * Round Trip (RT) Compress then Decompress
- */
- @Test
- public void testRTManySmall()
- {
- // Quote
- List<String> quote = new ArrayList<>();
- quote.add("No amount of experimentation can ever prove me right;");
- quote.add("a single experiment can prove me wrong.");
- quote.add("-- Albert Einstein");
-
- // Setup Compression Method
- CompressionMethod method = new DeflateCompressionMethod();
-
- for (String msg : quote)
- {
- // Test round trip
- assertRoundTrip(method,msg);
- }
- }
-
- /**
- * Test a medium payload (a payload length between 126 - 65535 bytes).
- *
- * Round Trip (RT) Compress then Decompress
- */
- @Test
- public void testRTMedium()
- {
- // medium sized message
- StringBuilder msg = new StringBuilder();
- for (int i = 0; i < 1000; i++)
- {
- msg.append("0123456789ABCDEF ");
- }
- msg.append('X'); // so we can see the end in our debugging
-
- // ensure that test remains sane
- Assert.assertThat("Medium Payload Length",msg.length(),allOf(greaterThanOrEqualTo(0x7E),lessThanOrEqualTo(0xFF_FF)));
-
- // Setup Compression Method
- CompressionMethod method = new DeflateCompressionMethod();
-
- // Test round trip
- assertRoundTrip(method, msg);
- }
-
- /**
- * Test a small payload (a payload length less than 126 bytes).
- *
- * Round Trip (RT) Compress then Decompress
- */
- @Test
- public void testRTSmall()
- {
- // Quote
- StringBuilder quote = new StringBuilder();
- quote.append("No amount of experimentation can ever prove me right;\n");
- quote.append("a single experiment can prove me wrong.\n");
- quote.append("-- Albert Einstein");
-
- // ensure that test remains sane
- Assert.assertThat("Small Payload Length",quote.length(),lessThan(0x7E));
-
- // Setup Compression Method
- CompressionMethod method = new DeflateCompressionMethod();
-
- // Test round trip
- assertRoundTrip(method,quote);
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtensionTest.java
new file mode 100644
index 0000000..8b413cb
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtensionTest.java
@@ -0,0 +1,351 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.List;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.Hex;
+import org.eclipse.jetty.websocket.common.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.OutgoingNetworkBytesCapture;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.UnitParser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class DeflateFrameExtensionTest
+{
+ @Rule
+ public TestName testname = new TestName();
+
+ private void assertIncoming(byte[] raw, String... expectedTextDatas)
+ {
+ WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+
+ DeflateFrameExtension ext = new DeflateFrameExtension();
+ ext.setBufferPool(new MappedByteBufferPool());
+ ext.setPolicy(policy);
+
+ ExtensionConfig config = ExtensionConfig.parse("deflate-frame");
+ ext.setConfig(config);
+
+ // Setup capture of incoming frames
+ IncomingFramesCapture capture = new IncomingFramesCapture();
+
+ // Wire up stack
+ ext.setNextIncomingFrames(capture);
+
+ Parser parser = new UnitParser(policy);
+ parser.configureFromExtensions(Collections.singletonList(ext));
+ parser.setIncomingFramesHandler(ext);
+
+ parser.parse(ByteBuffer.wrap(raw));
+
+ int len = expectedTextDatas.length;
+ capture.assertFrameCount(len);
+ capture.assertHasFrame(OpCode.TEXT,len);
+
+ for (int i = 0; i < len; i++)
+ {
+ WebSocketFrame actual = capture.getFrames().get(i);
+ String prefix = "Frame[" + i + "]";
+ Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT));
+ Assert.assertThat(prefix + ".fin",actual.isFin(),is(true));
+ Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false)); // RSV1 should be unset at this point
+ Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false));
+ Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false));
+
+ ByteBuffer expected = BufferUtil.toBuffer(expectedTextDatas[i],StandardCharsets.UTF_8);
+ Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining()));
+ ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice());
+ }
+ }
+
+ private void assertOutgoing(String text, String expectedHex) throws IOException
+ {
+ WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+
+ DeflateFrameExtension ext = new DeflateFrameExtension();
+ ext.setBufferPool(new MappedByteBufferPool());
+ ext.setPolicy(policy);
+
+ ExtensionConfig config = ExtensionConfig.parse("deflate-frame");
+ ext.setConfig(config);
+
+ ByteBufferPool bufferPool = new MappedByteBufferPool();
+ boolean validating = true;
+ Generator generator = new Generator(policy,bufferPool,validating);
+ generator.configureFromExtensions(Collections.singletonList(ext));
+
+ OutgoingNetworkBytesCapture capture = new OutgoingNetworkBytesCapture(generator);
+ ext.setNextOutgoingFrames(capture);
+
+ Frame frame = new TextFrame().setPayload(text);
+ ext.outgoingFrame(frame,null);
+
+ capture.assertBytes(0,expectedHex);
+ }
+
+ @Test
+ public void testBlockheadClient_HelloThere()
+ {
+ // Captured from Blockhead Client - "Hello" then "There" via unit test
+ String hello = "c18700000000f248cdc9c90700";
+ String there = "c187000000000ac9482d4a0500";
+ byte rawbuf[] = Hex.asByteArray(hello + there);
+ assertIncoming(rawbuf,"Hello","There");
+ }
+
+ @Test
+ public void testChrome20_Hello()
+ {
+ // Captured from Chrome 20.x - "Hello" (sent from browser)
+ byte rawbuf[] = Hex.asByteArray("c187832b5c11716391d84a2c5c");
+ assertIncoming(rawbuf,"Hello");
+ }
+
+ @Test
+ public void testChrome20_HelloThere()
+ {
+ // Captured from Chrome 20.x - "Hello" then "There" (sent from browser)
+ String hello = "c1877b1971db8951bc12b21e71";
+ String there = "c18759edc8f4532480d913e8c8";
+ byte rawbuf[] = Hex.asByteArray(hello + there);
+ assertIncoming(rawbuf,"Hello","There");
+ }
+
+ @Test
+ public void testChrome20_Info()
+ {
+ // Captured from Chrome 20.x - "info:" (sent from browser)
+ byte rawbuf[] = Hex.asByteArray("c187ca4def7f0081a4b47d4fef");
+ assertIncoming(rawbuf,"info:");
+ }
+
+ @Test
+ public void testChrome20_TimeTime()
+ {
+ // Captured from Chrome 20.x - "time:" then "time:" once more (sent from browser)
+ String time1 = "c18782467424a88fb869374474";
+ String time2 = "c1853cfda17f16fcb07f3c";
+ byte rawbuf[] = Hex.asByteArray(time1 + time2);
+ assertIncoming(rawbuf,"time:","time:");
+ }
+
+ @Test
+ public void testPyWebSocket_TimeTimeTime()
+ {
+ // Captured from Pywebsocket (r781) - "time:" sent 3 times.
+ String time1 = "c1876b100104" + "41d9cd49de1201";
+ String time2 = "c1852ae3ff01" + "00e2ee012a";
+ String time3 = "c18435558caa" + "37468caa";
+ byte rawbuf[] = Hex.asByteArray(time1 + time2 + time3);
+ assertIncoming(rawbuf,"time:","time:","time:");
+ }
+
+ @Test
+ public void testCompress_TimeTimeTime()
+ {
+ // What pywebsocket produces for "time:", "time:", "time:"
+ String expected[] = new String[]
+ { "2AC9CC4DB50200", "2A01110000", "02130000" };
+
+ // Lets see what we produce
+ CapturedHexPayloads capture = new CapturedHexPayloads();
+ DeflateFrameExtension ext = new DeflateFrameExtension();
+ init(ext);
+ ext.setNextOutgoingFrames(capture);
+
+ ext.outgoingFrame(new TextFrame().setPayload("time:"),null);
+ ext.outgoingFrame(new TextFrame().setPayload("time:"),null);
+ ext.outgoingFrame(new TextFrame().setPayload("time:"),null);
+
+ List<String> actual = capture.getCaptured();
+
+ for (String entry : actual)
+ {
+ System.err.printf("actual: \"%s\"%n",entry);
+ }
+
+ Assert.assertThat("Compressed Payloads",actual,contains(expected));
+ }
+
+ private void init(DeflateFrameExtension ext)
+ {
+ ext.setConfig(new ExtensionConfig(ext.getName()));
+ ext.setBufferPool(new MappedByteBufferPool());
+ }
+
+ @Test
+ public void testDeflateBasics() throws Exception
+ {
+ // Setup deflater basics
+ boolean nowrap = true;
+ Deflater compressor = new Deflater(Deflater.BEST_COMPRESSION,nowrap);
+ compressor.setStrategy(Deflater.DEFAULT_STRATEGY);
+
+ // Text to compress
+ String text = "info:";
+ byte uncompressed[] = StringUtil.getUtf8Bytes(text);
+
+ // Prime the compressor
+ compressor.reset();
+ compressor.setInput(uncompressed,0,uncompressed.length);
+ compressor.finish();
+
+ // Perform compression
+ ByteBuffer outbuf = ByteBuffer.allocate(64);
+ BufferUtil.clearToFill(outbuf);
+
+ while (!compressor.finished())
+ {
+ byte out[] = new byte[64];
+ int len = compressor.deflate(out,0,out.length,Deflater.SYNC_FLUSH);
+ if (len > 0)
+ {
+ outbuf.put(out,0,len);
+ }
+ }
+ compressor.end();
+
+ BufferUtil.flipToFlush(outbuf,0);
+ byte b0 = outbuf.get(0);
+ if ((b0 & 1) != 0)
+ {
+ outbuf.put(0,(b0 ^= 1));
+ }
+ byte compressed[] = BufferUtil.toArray(outbuf);
+
+ String actual = TypeUtil.toHexString(compressed);
+ String expected = "CaCc4bCbB70200"; // what pywebsocket produces
+
+ Assert.assertThat("Compressed data",actual,is(expected));
+ }
+
+ @Test
+ public void testGeneratedTwoFrames() throws IOException
+ {
+ WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+
+ DeflateFrameExtension ext = new DeflateFrameExtension();
+ ext.setBufferPool(new MappedByteBufferPool());
+ ext.setPolicy(policy);
+ ext.setConfig(new ExtensionConfig(ext.getName()));
+
+ ByteBufferPool bufferPool = new MappedByteBufferPool();
+ boolean validating = true;
+ Generator generator = new Generator(policy,bufferPool,validating);
+ generator.configureFromExtensions(Collections.singletonList(ext));
+
+ OutgoingNetworkBytesCapture capture = new OutgoingNetworkBytesCapture(generator);
+ ext.setNextOutgoingFrames(capture);
+
+ ext.outgoingFrame(new TextFrame().setPayload("Hello"),null);
+ ext.outgoingFrame(new TextFrame().setPayload("There"),null);
+
+ capture.assertBytes(0,"c107f248cdc9c90700");
+ }
+
+ @Test
+ public void testInflateBasics() throws Exception
+ {
+ // should result in "info:" text if properly inflated
+ byte rawbuf[] = TypeUtil.fromHexString("CaCc4bCbB70200"); // what pywebsocket produces
+ // byte rawbuf[] = TypeUtil.fromHexString("CbCc4bCbB70200"); // what java produces
+
+ Inflater inflater = new Inflater(true);
+ inflater.reset();
+ inflater.setInput(rawbuf,0,rawbuf.length);
+
+ byte outbuf[] = new byte[64];
+ int len = inflater.inflate(outbuf);
+ inflater.end();
+ Assert.assertThat("Inflated length",len,greaterThan(4));
+
+ String actual = StringUtil.toUTF8String(outbuf,0,len);
+ Assert.assertThat("Inflated text",actual,is("info:"));
+ }
+
+ @Test
+ public void testPyWebSocketServer_Hello()
+ {
+ // Captured from PyWebSocket - "Hello" (echo from server)
+ byte rawbuf[] = TypeUtil.fromHexString("c107f248cdc9c90700");
+ assertIncoming(rawbuf,"Hello");
+ }
+
+ @Test
+ public void testPyWebSocketServer_Long()
+ {
+ // Captured from PyWebSocket - Long Text (echo from server)
+ byte rawbuf[] = TypeUtil.fromHexString("c1421cca410a80300c44d1abccce9df7" + "f018298634d05631138ab7b7b8fdef1f" + "dc0282e2061d575a45f6f2686bab25e1"
+ + "3fb7296fa02b5885eb3b0379c394f461" + "98cafd03");
+ assertIncoming(rawbuf,"It's a big enough umbrella but it's always me that ends up getting wet.");
+ }
+
+ @Test
+ public void testPyWebSocketServer_Medium()
+ {
+ // Captured from PyWebSocket - "stackoverflow" (echo from server)
+ byte rawbuf[] = TypeUtil.fromHexString("c10f2a2e494ccece2f4b2d4acbc92f0700");
+ assertIncoming(rawbuf,"stackoverflow");
+ }
+
+ /**
+ * Make sure that the server generated compressed form for "Hello" is consistent with what PyWebSocket creates.
+ */
+ @Test
+ public void testServerGeneratedHello() throws IOException
+ {
+ assertOutgoing("Hello","c107f248cdc9c90700");
+ }
+
+ /**
+ * Make sure that the server generated compressed form for "There" is consistent with what PyWebSocket creates.
+ */
+ @Test
+ public void testServerGeneratedThere() throws IOException
+ {
+ assertOutgoing("There","c1070ac9482d4a0500");
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateTest.java
new file mode 100644
index 0000000..7725c61
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateTest.java
@@ -0,0 +1,82 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import java.nio.ByteBuffer;
+import java.util.zip.Deflater;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.Hex;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class DeflateTest
+{
+ private int bufSize = 8 * 1024;
+
+ public String deflate(String inputHex, Deflater deflater, int flushMode)
+ {
+ byte uncompressed[] = Hex.asByteArray(inputHex);
+ deflater.setInput(uncompressed,0,uncompressed.length);
+ deflater.finish();
+
+ ByteBuffer out = ByteBuffer.allocate(bufSize);
+ byte buf[] = new byte[64];
+ while (!deflater.finished())
+ {
+ int len = deflater.deflate(buf,0,buf.length,flushMode);
+ out.put(buf,0,len);
+ }
+
+ out.flip();
+ return Hex.asHex(out);
+ }
+
+ @Test
+ @Ignore("just noisy")
+ public void deflateAllTypes()
+ {
+ int levels[] = new int[]
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ boolean nowraps[] = new boolean[]
+ { true, false };
+ int strategies[] = new int[]
+ { Deflater.DEFAULT_STRATEGY, Deflater.FILTERED, Deflater.HUFFMAN_ONLY };
+ int flushmodes[] = new int[]
+ { Deflater.NO_FLUSH, Deflater.SYNC_FLUSH, Deflater.FULL_FLUSH };
+
+ String inputHex = Hex.asHex(StringUtil.getUtf8Bytes("time:"));
+ for (int level : levels)
+ {
+ for (boolean nowrap : nowraps)
+ {
+ Deflater deflater = new Deflater(level,nowrap);
+ for (int strategy : strategies)
+ {
+ deflater.setStrategy(strategy);
+ for (int flushmode : flushmodes)
+ {
+ String result = deflate(inputHex,deflater,flushmode);
+ System.out.printf("%d | %b | %d | %d | \"%s\"%n",level,nowrap,strategy,flushmode,result);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/FrameCompressionExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/FrameCompressionExtensionTest.java
deleted file mode 100644
index 84f87b9..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/FrameCompressionExtensionTest.java
+++ /dev/null
@@ -1,301 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.compress;
-
-import static org.hamcrest.Matchers.*;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.zip.Deflater;
-import java.util.zip.Inflater;
-
-import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.io.MappedByteBufferPool;
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.common.ByteBufferAssert;
-import org.eclipse.jetty.websocket.common.Generator;
-import org.eclipse.jetty.websocket.common.IncomingFramesCapture;
-import org.eclipse.jetty.websocket.common.OpCode;
-import org.eclipse.jetty.websocket.common.OutgoingNetworkBytesCapture;
-import org.eclipse.jetty.websocket.common.Parser;
-import org.eclipse.jetty.websocket.common.UnitParser;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class FrameCompressionExtensionTest
-{
- private void assertIncoming(byte[] raw, String... expectedTextDatas)
- {
- WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
-
- FrameCompressionExtension ext = new FrameCompressionExtension();
- ext.setBufferPool(new MappedByteBufferPool());
- ext.setPolicy(policy);
-
- ExtensionConfig config = ExtensionConfig.parse("x-webkit-deflate-frame");
- ext.setConfig(config);
-
- // Setup capture of incoming frames
- IncomingFramesCapture capture = new IncomingFramesCapture();
-
- // Wire up stack
- ext.setNextIncomingFrames(capture);
-
- Parser parser = new UnitParser(policy);
- parser.configureFromExtensions(Collections.singletonList(ext));
- parser.setIncomingFramesHandler(ext);
-
- parser.parse(ByteBuffer.wrap(raw));
-
- int len = expectedTextDatas.length;
- capture.assertFrameCount(len);
- capture.assertHasFrame(OpCode.TEXT,len);
-
- for (int i = 0; i < len; i++)
- {
- WebSocketFrame actual = capture.getFrames().get(i);
- String prefix = "Frame[" + i + "]";
- Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT));
- Assert.assertThat(prefix + ".fin",actual.isFin(),is(true));
- Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false)); // RSV1 should be unset at this point
- Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false));
- Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false));
-
- ByteBuffer expected = BufferUtil.toBuffer(expectedTextDatas[i],StandardCharsets.UTF_8);
- Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining()));
- ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice());
- }
- }
-
- private void assertOutgoing(String text, String expectedHex) throws IOException
- {
- WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
-
- FrameCompressionExtension ext = new FrameCompressionExtension();
- ext.setBufferPool(new MappedByteBufferPool());
- ext.setPolicy(policy);
-
- ExtensionConfig config = ExtensionConfig.parse("x-webkit-deflate-frame");
- ext.setConfig(config);
-
- ByteBufferPool bufferPool = new MappedByteBufferPool();
- boolean validating = true;
- Generator generator = new Generator(policy,bufferPool,validating);
- generator.configureFromExtensions(Collections.singletonList(ext));
-
- OutgoingNetworkBytesCapture capture = new OutgoingNetworkBytesCapture(generator);
- ext.setNextOutgoingFrames(capture);
-
- Frame frame = WebSocketFrame.text(text);
- ext.outgoingFrame(frame,null);
-
- capture.assertBytes(0,expectedHex);
- }
-
- @Test
- public void testBlockheadClient_HelloThere()
- {
- // Captured from Blockhead Client - "Hello" then "There" via unit test
- String hello = "c1 87 00 00 00 00 f2 48 cd c9 c9 07 00".replaceAll("\\s*","");
- String there = "c1 87 00 00 00 00 0a c9 48 2d 4a 05 00".replaceAll("\\s*","");
- byte rawbuf[] = TypeUtil.fromHexString(hello + there);
- assertIncoming(rawbuf,"Hello","There");
- }
-
- @Test
- public void testChrome20_Hello()
- {
- // Captured from Chrome 20.x - "Hello" (sent from browser/client)
- byte rawbuf[] = TypeUtil.fromHexString("c187832b5c11716391d84a2c5c");
- assertIncoming(rawbuf,"Hello");
- }
-
- @Test
- public void testChrome20_HelloThere()
- {
- // Captured from Chrome 20.x - "Hello" then "There" (sent from browser/client)
- String hello = "c1 87 7b 19 71 db 89 51 bc 12 b2 1e 71".replaceAll("\\s*","");
- String there = "c1 87 59 ed c8 f4 53 24 80 d9 13 e8 c8".replaceAll("\\s*","");
- byte rawbuf[] = TypeUtil.fromHexString(hello + there);
- assertIncoming(rawbuf,"Hello","There");
- }
-
- @Test
- public void testChrome20_Info()
- {
- // Captured from Chrome 20.x - "info:" (sent from browser/client)
- byte rawbuf[] = TypeUtil.fromHexString("c187ca4def7f0081a4b47d4fef");
- assertIncoming(rawbuf,"info:");
- }
-
- @Test
- public void testChrome20_TimeTime()
- {
- // Captured from Chrome 20.x - "time:" then "time:" once more (sent from browser/client)
- String time1 = "c1 87 82 46 74 24 a8 8f b8 69 37 44 74".replaceAll("\\s*","");
- String time2 = "c1 85 3c fd a1 7f 16 fc b0 7f 3c".replaceAll("\\s*","");
- byte rawbuf[] = TypeUtil.fromHexString(time1 + time2);
- assertIncoming(rawbuf,"time:","time:");
- }
-
- @Test
- public void testDeflateBasics() throws Exception
- {
- // Setup deflater basics
- boolean nowrap = true;
- Deflater compressor = new Deflater(Deflater.BEST_COMPRESSION,nowrap);
- compressor.setStrategy(Deflater.DEFAULT_STRATEGY);
-
- // Text to compress
- String text = "info:";
- byte uncompressed[] = StringUtil.getUtf8Bytes(text);
-
- // Prime the compressor
- compressor.reset();
- compressor.setInput(uncompressed,0,uncompressed.length);
- compressor.finish();
-
- // Perform compression
- ByteBuffer outbuf = ByteBuffer.allocate(64);
- BufferUtil.clearToFill(outbuf);
-
- while (!compressor.finished())
- {
- byte out[] = new byte[64];
- int len = compressor.deflate(out,0,out.length,Deflater.SYNC_FLUSH);
- if (len > 0)
- {
- outbuf.put(out,0,len);
- }
- }
- compressor.end();
-
- BufferUtil.flipToFlush(outbuf,0);
- byte b0 = outbuf.get(0);
- if ((b0 & 1) != 0)
- {
- outbuf.put(0,(b0 ^= 1));
- }
- byte compressed[] = BufferUtil.toArray(outbuf);
-
- String actual = TypeUtil.toHexString(compressed);
- String expected = "CaCc4bCbB70200"; // what pywebsocket produces
- // String expected = "CbCc4bCbB70200"; // what java produces
-
- Assert.assertThat("Compressed data",actual,is(expected));
- }
-
- @Test
- public void testGeneratedTwoFrames() throws IOException
- {
- WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
-
- FrameCompressionExtension ext = new FrameCompressionExtension();
- ext.setBufferPool(new MappedByteBufferPool());
- ext.setPolicy(policy);
-
- ExtensionConfig config = ExtensionConfig.parse("x-webkit-deflate-frame");
- ext.setConfig(config);
-
- ByteBufferPool bufferPool = new MappedByteBufferPool();
- boolean validating = true;
- Generator generator = new Generator(policy,bufferPool,validating);
- generator.configureFromExtensions(Collections.singletonList(ext));
-
- OutgoingNetworkBytesCapture capture = new OutgoingNetworkBytesCapture(generator);
- ext.setNextOutgoingFrames(capture);
-
- ext.outgoingFrame(WebSocketFrame.text("Hello"),null);
- ext.outgoingFrame(WebSocketFrame.text("There"),null);
-
- capture.assertBytes(0,"c107f248cdc9c90700");
- capture.assertBytes(1,"c1070ac9482d4a0500");
- }
-
- @Test
- public void testInflateBasics() throws Exception
- {
- // should result in "info:" text if properly inflated
- byte rawbuf[] = TypeUtil.fromHexString("CaCc4bCbB70200"); // what pywebsocket produces
- // byte rawbuf[] = TypeUtil.fromHexString("CbCc4bCbB70200"); // what java produces
-
- Inflater inflater = new Inflater(true);
- inflater.reset();
- inflater.setInput(rawbuf,0,rawbuf.length);
-
- byte outbuf[] = new byte[64];
- int len = inflater.inflate(outbuf);
- inflater.end();
- Assert.assertThat("Inflated length",len,greaterThan(4));
-
- String actual = StringUtil.toUTF8String(outbuf,0,len);
- Assert.assertThat("Inflated text",actual,is("info:"));
- }
-
- @Test
- public void testPyWebSocketServer_Hello()
- {
- // Captured from PyWebSocket - "Hello" (echo from server)
- byte rawbuf[] = TypeUtil.fromHexString("c107f248cdc9c90700");
- assertIncoming(rawbuf,"Hello");
- }
-
- @Test
- public void testPyWebSocketServer_Long()
- {
- // Captured from PyWebSocket - Long Text (echo from server)
- byte rawbuf[] = TypeUtil.fromHexString("c1421cca410a80300c44d1abccce9df7" + "f018298634d05631138ab7b7b8fdef1f" + "dc0282e2061d575a45f6f2686bab25e1"
- + "3fb7296fa02b5885eb3b0379c394f461" + "98cafd03");
- assertIncoming(rawbuf,"It's a big enough umbrella but it's always me that ends up getting wet.");
- }
-
- @Test
- public void testPyWebSocketServer_Medium()
- {
- // Captured from PyWebSocket - "stackoverflow" (echo from server)
- byte rawbuf[] = TypeUtil.fromHexString("c10f2a2e494ccece2f4b2d4acbc92f0700");
- assertIncoming(rawbuf,"stackoverflow");
- }
-
- /**
- * Make sure that the server generated compressed form for "Hello" is consistent with what PyWebSocket creates.
- */
- @Test
- public void testServerGeneratedHello() throws IOException
- {
- assertOutgoing("Hello","c107f248cdc9c90700");
- }
-
- /**
- * Make sure that the server generated compressed form for "There" is consistent with what PyWebSocket creates.
- */
- @Test
- public void testServerGeneratedThere() throws IOException
- {
- assertOutgoing("There","c1070ac9482d4a0500");
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/MessageCompressionExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/MessageCompressionExtensionTest.java
deleted file mode 100644
index 546261d..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/MessageCompressionExtensionTest.java
+++ /dev/null
@@ -1,361 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.compress;
-
-import static org.hamcrest.Matchers.*;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.eclipse.jetty.io.MappedByteBufferPool;
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.common.ByteBufferAssert;
-import org.eclipse.jetty.websocket.common.IncomingFramesCapture;
-import org.eclipse.jetty.websocket.common.OpCode;
-import org.eclipse.jetty.websocket.common.OutgoingFramesCapture;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.eclipse.jetty.websocket.common.extensions.compress.CompressionMethod.Process;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class MessageCompressionExtensionTest
-{
- private void assertDraftExample(String hexStr, String expectedStr)
- {
- WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
-
- // Setup extension
- MessageCompressionExtension ext = new MessageCompressionExtension();
- ext.setBufferPool(new MappedByteBufferPool());
- ext.setPolicy(policy);
- ExtensionConfig config = ExtensionConfig.parse("permessage-compress");
- ext.setConfig(config);
-
- // Setup capture of incoming frames
- IncomingFramesCapture capture = new IncomingFramesCapture();
-
- // Wire up stack
- ext.setNextIncomingFrames(capture);
-
- // Receive frame
- String hex = hexStr.replaceAll("\\s*0x","");
- byte net[] = TypeUtil.fromHexString(hex);
- WebSocketFrame frame = WebSocketFrame.text();
- frame.setRsv1(true);
- frame.setPayload(net);
-
- // Send frame into stack
- ext.incomingFrame(frame);
-
- // Verify captured frames.
- capture.assertFrameCount(1);
- capture.assertHasFrame(OpCode.TEXT,1);
-
- WebSocketFrame actual = capture.getFrames().pop();
-
- String prefix = "frame";
- Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT));
- Assert.assertThat(prefix + ".fin",actual.isFin(),is(true));
- Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false)); // RSV1 should be unset at this point
- Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false));
- Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false));
-
- ByteBuffer expected = BufferUtil.toBuffer(expectedStr, StandardCharsets.UTF_8);
- Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining()));
- ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice());
- }
-
- /**
- * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01.
- */
- @Test
- public void testDraft01_Hello_UnCompressedBlock()
- {
- StringBuilder hex = new StringBuilder();
- // basic, 1 block, compressed with 0 compression level (aka, uncompressed).
- hex.append("0x00 0x05 0x00 0xfa 0xff 0x48 0x65 0x6c 0x6c 0x6f 0x00");
- assertDraftExample(hex.toString(),"Hello");
- }
-
- /**
- * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01.
- */
- @Test
- public void testDraft01_OneCompressedBlock()
- {
- // basic, 1 block, compressed.
- assertDraftExample("0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00","Hello");
- }
-
- /**
- * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01.
- */
- @Test
- public void testDraft01_TwoCompressedBlocks()
- {
- StringBuilder hex = new StringBuilder();
- // BFINAL 0, BTYPE 1, contains "He"
- hex.append("0xf2 0x48 0x05 0x00");
- // BFINAL 0, BTYPE 0, no compression, empty block
- hex.append("0x00 0x00 0xff 0xff");
- // Block containing "llo"
- hex.append("0xca 0xc9 0xc9 0x07 0x00");
- assertDraftExample(hex.toString(),"Hello");
- }
-
- /**
- * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01.
- */
- @Test
- public void testDraft01_TwoCompressedBlocks_BFinal1()
- {
- StringBuilder hex = new StringBuilder();
- // Compressed with BFINAL 1
- hex.append("0xf3 0x48 0xcd 0xc9 0xc9 0x07 0x00");
- // last octet at BFINAL 0 and BTYPE 0
- hex.append("0x00");
- assertDraftExample(hex.toString(),"Hello");
- }
-
- /**
- * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01.
- */
- @Test
- public void testDraft01_TwoCompressedBlocks_UsingSlidingWindow()
- {
- StringBuilder hex = new StringBuilder();
- // basic, 1 block, compressed.
- hex.append("0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00");
- // (HACK!) BFINAL 0, BTYPE 0, no compression, empty block
- hex.append("0x00 0x00 0xff 0xff");
- // if allowed, smaller sized compression using LZ77 sliding window
- hex.append("0xf2 0x00 0x11 0x00 0x00");
- assertDraftExample(hex.toString(),"HelloHello");
- }
-
- /**
- * Incoming PING (Control Frame) should pass through extension unmodified
- */
- @Test
- public void testIncomingPing() {
- MessageCompressionExtension ext = new MessageCompressionExtension();
- ext.setBufferPool(new MappedByteBufferPool());
- ext.setPolicy(WebSocketPolicy.newServerPolicy());
- ExtensionConfig config = ExtensionConfig.parse("permessage-compress");
- ext.setConfig(config);
-
- // Setup capture of incoming frames
- IncomingFramesCapture capture = new IncomingFramesCapture();
-
- // Wire up stack
- ext.setNextIncomingFrames(capture);
-
- String payload = "Are you there?";
- Frame ping = WebSocketFrame.ping().setPayload(payload);
- ext.incomingFrame(ping);
-
- capture.assertFrameCount(1);
- capture.assertHasFrame(OpCode.PING,1);
- WebSocketFrame actual = capture.getFrames().getFirst();
-
- Assert.assertThat("Frame.opcode",actual.getOpCode(),is(OpCode.PING));
- Assert.assertThat("Frame.fin",actual.isFin(),is(true));
- Assert.assertThat("Frame.rsv1",actual.isRsv1(),is(false));
- Assert.assertThat("Frame.rsv2",actual.isRsv2(),is(false));
- Assert.assertThat("Frame.rsv3",actual.isRsv3(),is(false));
-
- ByteBuffer expected = BufferUtil.toBuffer(payload,StandardCharsets.UTF_8);
- Assert.assertThat("Frame.payloadLength",actual.getPayloadLength(),is(expected.remaining()));
- ByteBufferAssert.assertEquals("Frame.payload",expected,actual.getPayload().slice());
- }
-
- /**
- * Verify that incoming uncompressed frames are properly passed through
- */
- @Test
- public void testIncomingUncompressedFrames()
- {
- MessageCompressionExtension ext = new MessageCompressionExtension();
- ext.setBufferPool(new MappedByteBufferPool());
- ext.setPolicy(WebSocketPolicy.newServerPolicy());
- ExtensionConfig config = ExtensionConfig.parse("permessage-compress");
- ext.setConfig(config);
-
- // Setup capture of incoming frames
- IncomingFramesCapture capture = new IncomingFramesCapture();
-
- // Wire up stack
- ext.setNextIncomingFrames(capture);
-
- // Quote
- List<String> quote = new ArrayList<>();
- quote.add("No amount of experimentation can ever prove me right;");
- quote.add("a single experiment can prove me wrong.");
- quote.add("-- Albert Einstein");
-
- // leave frames as-is, no compression, and pass into extension
- for (String q : quote)
- {
- WebSocketFrame frame = new WebSocketFrame(OpCode.TEXT);
- frame.setPayload(q);
- frame.setRsv1(false); // indication to extension that frame is not compressed (ie: a normal frame)
- ext.incomingFrame(frame);
- }
-
- int len = quote.size();
- capture.assertFrameCount(len);
- capture.assertHasFrame(OpCode.TEXT,len);
-
- String prefix;
- for (int i = 0; i < len; i++)
- {
- prefix = "Frame[" + i + "]";
-
- WebSocketFrame actual = capture.getFrames().get(i);
-
- Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT));
- Assert.assertThat(prefix + ".fin",actual.isFin(),is(true));
- Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false));
- Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false));
- Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false));
-
- ByteBuffer expected = BufferUtil.toBuffer(quote.get(i),StandardCharsets.UTF_8);
- Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining()));
- ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice());
- }
- }
-
- /**
- * Verify that outgoing text frames are compressed.
- */
- @Test
- public void testOutgoingFrames() throws IOException
- {
- MessageCompressionExtension ext = new MessageCompressionExtension();
- ext.setBufferPool(new MappedByteBufferPool());
- ext.setPolicy(WebSocketPolicy.newServerPolicy());
- ExtensionConfig config = ExtensionConfig.parse("permessage-compress");
- ext.setConfig(config);
-
- // Setup capture of outgoing frames
- OutgoingFramesCapture capture = new OutgoingFramesCapture();
-
- // Wire up stack
- ext.setNextOutgoingFrames(capture);
-
- // Quote
- List<String> quote = new ArrayList<>();
- quote.add("No amount of experimentation can ever prove me right;");
- quote.add("a single experiment can prove me wrong.");
- quote.add("-- Albert Einstein");
-
- // Expected compressed parts
- List<ByteBuffer> expectedBuffers = new ArrayList<>();
- CompressionMethod method = new DeflateCompressionMethod();
- for(String part: quote) {
- Process process = method.compress();
- process.begin();
- process.input(BufferUtil.toBuffer(part,StandardCharsets.UTF_8));
- expectedBuffers.add(process.process());
- process.end();
- }
-
- // Write quote as separate frames
- for (String section : quote)
- {
- Frame frame = WebSocketFrame.text(section);
- ext.outgoingFrame(frame,null);
- }
-
- int len = quote.size();
- capture.assertFrameCount(len);
- capture.assertHasFrame(OpCode.TEXT,len);
-
- String prefix;
- LinkedList<WebSocketFrame> frames = capture.getFrames();
- for (int i = 0; i < len; i++)
- {
- prefix = "Frame[" + i + "]";
- WebSocketFrame actual = frames.get(i);
-
- // Validate Frame
- Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT));
- Assert.assertThat(prefix + ".fin",actual.isFin(),is(true));
- Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(true));
- Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false));
- Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false));
-
- // Validate Payload
- ByteBuffer expected = expectedBuffers.get(i);
- // Decompress payload
- ByteBuffer compressed = actual.getPayload().slice();
-
- Assert.assertThat(prefix + ".payloadLength",compressed.remaining(),is(expected.remaining()));
- ByteBufferAssert.assertEquals(prefix + ".payload",expected,compressed);
- }
- }
-
- /**
- * Outgoing PING (Control Frame) should pass through extension unmodified
- */
- @Test
- public void testOutgoingPing() throws IOException
- {
- MessageCompressionExtension ext = new MessageCompressionExtension();
- ext.setBufferPool(new MappedByteBufferPool());
- ext.setPolicy(WebSocketPolicy.newServerPolicy());
- ExtensionConfig config = ExtensionConfig.parse("permessage-compress");
- ext.setConfig(config);
-
- // Setup capture of outgoing frames
- OutgoingFramesCapture capture = new OutgoingFramesCapture();
-
- // Wire up stack
- ext.setNextOutgoingFrames(capture);
-
- String payload = "Are you there?";
- Frame ping = WebSocketFrame.ping().setPayload(payload);
-
- ext.outgoingFrame(ping,null);
-
- capture.assertFrameCount(1);
- capture.assertHasFrame(OpCode.PING,1);
-
- WebSocketFrame actual = capture.getFrames().getFirst();
-
- Assert.assertThat("Frame.opcode",actual.getOpCode(),is(OpCode.PING));
- Assert.assertThat("Frame.fin",actual.isFin(),is(true));
- Assert.assertThat("Frame.rsv1",actual.isRsv1(),is(false));
- Assert.assertThat("Frame.rsv2",actual.isRsv2(),is(false));
- Assert.assertThat("Frame.rsv3",actual.isRsv3(),is(false));
-
- ByteBuffer expected = BufferUtil.toBuffer(payload,StandardCharsets.UTF_8);
- Assert.assertThat("Frame.payloadLength",actual.getPayloadLength(),is(expected.remaining()));
- ByteBufferAssert.assertEquals("Frame.payload",expected,actual.getPayload().slice());
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtensionTest.java
new file mode 100644
index 0000000..07f2b18
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtensionTest.java
@@ -0,0 +1,484 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import static org.hamcrest.Matchers.is;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.Hex;
+import org.eclipse.jetty.websocket.common.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.OutgoingFramesCapture;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.UnitParser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class PerMessageDeflateExtensionTest
+{
+ @Rule
+ public TestName testname = new TestName();
+
+ private void assertIncoming(byte[] raw, String... expectedTextDatas)
+ {
+ WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+
+ PerMessageDeflateExtension ext = new PerMessageDeflateExtension();
+ ext.setBufferPool(new MappedByteBufferPool());
+ ext.setPolicy(policy);
+
+ ExtensionConfig config = ExtensionConfig.parse("permessage-deflate; c2s_max_window_bits");
+ ext.setConfig(config);
+
+ // Setup capture of incoming frames
+ IncomingFramesCapture capture = new IncomingFramesCapture();
+
+ // Wire up stack
+ ext.setNextIncomingFrames(capture);
+
+ Parser parser = new UnitParser(policy);
+ parser.configureFromExtensions(Collections.singletonList(ext));
+ parser.setIncomingFramesHandler(ext);
+
+ parser.parse(ByteBuffer.wrap(raw));
+
+ int len = expectedTextDatas.length;
+ capture.assertFrameCount(len);
+ capture.assertHasFrame(OpCode.TEXT,len);
+
+ for (int i = 0; i < len; i++)
+ {
+ WebSocketFrame actual = capture.getFrames().get(i);
+ String prefix = "Frame[" + i + "]";
+ Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT));
+ Assert.assertThat(prefix + ".fin",actual.isFin(),is(true));
+ Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false)); // RSV1 should be unset at this point
+ Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false));
+ Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false));
+
+ ByteBuffer expected = BufferUtil.toBuffer(expectedTextDatas[i],StringUtil.__UTF8_CHARSET);
+ Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining()));
+ ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice());
+ }
+ }
+
+ private void assertDraftExample(String hexStr, String expectedStr)
+ {
+ WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
+
+ // Setup extension
+ PerMessageDeflateExtension ext = new PerMessageDeflateExtension();
+ ext.setBufferPool(new MappedByteBufferPool());
+ ext.setPolicy(policy);
+ ExtensionConfig config = ExtensionConfig.parse("permessage-deflate");
+ ext.setConfig(config);
+
+ // Setup capture of incoming frames
+ IncomingFramesCapture capture = new IncomingFramesCapture();
+
+ // Wire up stack
+ ext.setNextIncomingFrames(capture);
+
+ // Receive frame
+ String hex = hexStr.replaceAll("\\s*0x","");
+ byte net[] = TypeUtil.fromHexString(hex);
+ TextFrame frame = new TextFrame();
+ frame.setRsv1(true);
+ frame.setPayload(ByteBuffer.wrap(net));
+
+ // Send frame into stack
+ ext.incomingFrame(frame);
+
+ // Verify captured frames.
+ capture.assertFrameCount(1);
+ capture.assertHasFrame(OpCode.TEXT,1);
+
+ WebSocketFrame actual = capture.getFrames().pop();
+
+ String prefix = "frame";
+ Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT));
+ Assert.assertThat(prefix + ".fin",actual.isFin(),is(true));
+ Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false)); // RSV1 should be unset at this point
+ Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false));
+ Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false));
+
+ ByteBuffer expected = BufferUtil.toBuffer(expectedStr, StandardCharsets.UTF_8);
+ Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining()));
+ ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice());
+ }
+
+ private void assertDraft12Example(String hexStrCompleteFrame, String... expectedStrs)
+ {
+ WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+
+ // Setup extension
+ PerMessageDeflateExtension ext = new PerMessageDeflateExtension();
+ ext.setBufferPool(new MappedByteBufferPool());
+ ext.setPolicy(policy);
+ ExtensionConfig config = ExtensionConfig.parse("permessage-deflate");
+ ext.setConfig(config);
+
+ // Setup capture of incoming frames
+ IncomingFramesCapture capture = new IncomingFramesCapture();
+
+ // Wire up stack
+ ext.setNextIncomingFrames(capture);
+
+ // Receive frame
+ String hex = hexStrCompleteFrame.replaceAll("\\s*0x","");
+ byte net[] = TypeUtil.fromHexString(hex);
+
+ Parser parser = new UnitParser(policy);
+ parser.configureFromExtensions(Collections.singletonList(ext));
+ parser.setIncomingFramesHandler(ext);
+ parser.parse(ByteBuffer.wrap(net));
+
+ // Verify captured frames.
+ int expectedCount = expectedStrs.length;
+ capture.assertFrameCount(expectedCount);
+ capture.assertHasFrame(OpCode.TEXT,expectedCount);
+
+ for (int i = 0; i < expectedCount; i++)
+ {
+ WebSocketFrame actual = capture.getFrames().pop();
+
+ String prefix = String.format("frame[%d]",i);
+ Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT));
+ Assert.assertThat(prefix + ".fin",actual.isFin(),is(true));
+ Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false)); // RSV1 should be unset at this point
+ Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false));
+ Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false));
+
+ ByteBuffer expected = BufferUtil.toBuffer(expectedStrs[i],StringUtil.__UTF8_CHARSET);
+ Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining()));
+ ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice());
+ }
+ }
+
+ /**
+ * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01.
+ */
+ @Test
+ public void testDraft01_Hello_UnCompressedBlock()
+ {
+ StringBuilder hex = new StringBuilder();
+ // basic, 1 block, compressed with 0 compression level (aka, uncompressed).
+ hex.append("0x00 0x05 0x00 0xfa 0xff 0x48 0x65 0x6c 0x6c 0x6f 0x00");
+ assertDraftExample(hex.toString(),"Hello");
+ }
+
+ /**
+ * Decode payload example as seen in draft-ietf-hybi-permessage-compression-12. Section 8.2.3.1
+ */
+ @Test
+ public void testDraft12_Hello_UnCompressedBlock()
+ {
+ StringBuilder hex = new StringBuilder();
+ // basic, 1 block, compressed with 0 compression level (aka, uncompressed).
+ hex.append("0xc1 0x07 0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00");
+ assertDraft12Example(hex.toString(),"Hello");
+ }
+
+ /**
+ * Decode payload example as seen in draft-ietf-hybi-permessage-compression-12. Section 8.2.3.2
+ */
+ @Test
+ public void testDraft12_Hello_NoSharingLZ77SlidingWindow()
+ {
+ StringBuilder hex = new StringBuilder();
+ // message 1
+ hex.append("0xc1 0x07"); // (HEADER added for this test)
+ hex.append("0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00");
+ // message 2
+ hex.append("0xc1 0x07"); // (HEADER added for this test)
+ hex.append("0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00");
+ assertDraft12Example(hex.toString(),"Hello","Hello");
+ }
+
+ /**
+ * Decode payload example as seen in draft-ietf-hybi-permessage-compression-12. Section 8.2.3.2
+ */
+ @Test
+ public void testDraft12_Hello_SharingLZ77SlidingWindow()
+ {
+ StringBuilder hex = new StringBuilder();
+ // message 1
+ hex.append("0xc1 0x07"); // (HEADER added for this test)
+ hex.append("0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00");
+ // message 2
+ hex.append("0xc1 0x05"); // (HEADER added for this test)
+ hex.append("0xf2 0x00 0x11 0x00 0x00");
+ assertDraft12Example(hex.toString(),"Hello","Hello");
+ }
+
+ /**
+ * Decode payload example as seen in draft-ietf-hybi-permessage-compression-12. Section 8.2.3.3
+ */
+ @Test
+ public void testDraft12_Hello_NoCompressionBlock()
+ {
+ StringBuilder hex = new StringBuilder();
+ // basic, 1 block, compressed with no compression.
+ hex.append("0xc1 0x0b 0x00 0x05 0x00 0xfa 0xff 0x48 0x65 0x6c 0x6c 0x6f 0x00");
+ assertDraft12Example(hex.toString(),"Hello");
+ }
+
+ /**
+ * Decode payload example as seen in draft-ietf-hybi-permessage-compression-12. Section 8.2.3.4
+ */
+ @Test
+ public void testDraft12_Hello_Bfinal1()
+ {
+ StringBuilder hex = new StringBuilder();
+ // basic, 1 block, compressed with BFINAL set to 1.
+ hex.append("0xc1 0x08"); // (HEADER added for this test)
+ hex.append("0xf3 0x48 0xcd 0xc9 0xc9 0x07 0x00 0x00");
+ assertDraft12Example(hex.toString(),"Hello");
+ }
+
+ /**
+ * Decode payload example as seen in draft-ietf-hybi-permessage-compression-12. Section 8.2.3.5
+ */
+ @Test
+ public void testDraft12_Hello_TwoDeflateBlocks()
+ {
+ StringBuilder hex = new StringBuilder();
+ hex.append("0xc1 0x0d"); // (HEADER added for this test)
+ // 2 deflate blocks
+ hex.append("0xf2 0x48 0x05 0x00 0x00 0x00 0xff 0xff 0xca 0xc9 0xc9 0x07 0x00");
+ assertDraft12Example(hex.toString(),"Hello");
+ }
+
+ /**
+ * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01.
+ */
+ @Test
+ public void testDraft01_OneCompressedBlock()
+ {
+ // basic, 1 block, compressed.
+ assertDraftExample("0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00","Hello");
+ }
+
+ /**
+ * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01.
+ */
+ @Test
+ public void testDraft01_TwoCompressedBlocks()
+ {
+ StringBuilder hex = new StringBuilder();
+ // BFINAL 0, BTYPE 1, contains "He"
+ hex.append("0xf2 0x48 0x05 0x00");
+ // BFINAL 0, BTYPE 0, no compression, empty block
+ hex.append("0x00 0x00 0xff 0xff");
+ // Block containing "llo"
+ hex.append("0xca 0xc9 0xc9 0x07 0x00");
+ assertDraftExample(hex.toString(),"Hello");
+ }
+
+ /**
+ * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01.
+ */
+ @Test
+ public void testDraft01_TwoCompressedBlocks_BFinal1()
+ {
+ StringBuilder hex = new StringBuilder();
+ // Compressed with BFINAL 1
+ hex.append("0xf3 0x48 0xcd 0xc9 0xc9 0x07 0x00");
+ // last octet at BFINAL 0 and BTYPE 0
+ hex.append("0x00");
+ assertDraftExample(hex.toString(),"Hello");
+ }
+
+ /**
+ * Decode payload example as seen in draft-ietf-hybi-permessage-compression-01.
+ */
+ @Test
+ public void testDraft01_TwoCompressedBlocks_UsingSlidingWindow()
+ {
+ StringBuilder hex = new StringBuilder();
+ // basic, 1 block, compressed.
+ hex.append("0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00");
+ // (HACK!) BFINAL 0, BTYPE 0, no compression, empty block
+ hex.append("0x00 0x00 0xff 0xff");
+ // if allowed, smaller sized compression using LZ77 sliding window
+ hex.append("0xf2 0x00 0x11 0x00 0x00");
+ assertDraftExample(hex.toString(),"HelloHello");
+ }
+
+ @Test
+ public void testPyWebSocket_ToraToraTora()
+ {
+ // Captured from Pywebsocket (r781) - "tora" sent 3 times.
+ String tora1 = "c186b0c7fe48" + "9a0ed102b4c7";
+ String tora2 = "c185ccb6cb50" + "e6b7a950cc";
+ String tora3 = "c1847b9aac69" + "79fbac69";
+ byte rawbuf[] = Hex.asByteArray(tora1 + tora2 + tora3);
+ assertIncoming(rawbuf,"tora","tora","tora");
+ }
+
+ /**
+ * Incoming PING (Control Frame) should pass through extension unmodified
+ */
+ @Test
+ public void testIncomingPing()
+ {
+ PerMessageDeflateExtension ext = new PerMessageDeflateExtension();
+ ext.setBufferPool(new MappedByteBufferPool());
+ ext.setPolicy(WebSocketPolicy.newServerPolicy());
+ ExtensionConfig config = ExtensionConfig.parse("permessage-compress");
+ ext.setConfig(config);
+
+ // Setup capture of incoming frames
+ IncomingFramesCapture capture = new IncomingFramesCapture();
+
+ // Wire up stack
+ ext.setNextIncomingFrames(capture);
+
+ String payload = "Are you there?";
+ Frame ping = new PingFrame().setPayload(payload);
+ ext.incomingFrame(ping);
+
+ capture.assertFrameCount(1);
+ capture.assertHasFrame(OpCode.PING,1);
+ WebSocketFrame actual = capture.getFrames().getFirst();
+
+ Assert.assertThat("Frame.opcode",actual.getOpCode(),is(OpCode.PING));
+ Assert.assertThat("Frame.fin",actual.isFin(),is(true));
+ Assert.assertThat("Frame.rsv1",actual.isRsv1(),is(false));
+ Assert.assertThat("Frame.rsv2",actual.isRsv2(),is(false));
+ Assert.assertThat("Frame.rsv3",actual.isRsv3(),is(false));
+
+ ByteBuffer expected = BufferUtil.toBuffer(payload,StandardCharsets.UTF_8);
+ Assert.assertThat("Frame.payloadLength",actual.getPayloadLength(),is(expected.remaining()));
+ ByteBufferAssert.assertEquals("Frame.payload",expected,actual.getPayload().slice());
+ }
+
+ /**
+ * Verify that incoming uncompressed frames are properly passed through
+ */
+ @Test
+ public void testIncomingUncompressedFrames()
+ {
+ PerMessageDeflateExtension ext = new PerMessageDeflateExtension();
+ ext.setBufferPool(new MappedByteBufferPool());
+ ext.setPolicy(WebSocketPolicy.newServerPolicy());
+ ExtensionConfig config = ExtensionConfig.parse("permessage-compress");
+ ext.setConfig(config);
+
+ // Setup capture of incoming frames
+ IncomingFramesCapture capture = new IncomingFramesCapture();
+
+ // Wire up stack
+ ext.setNextIncomingFrames(capture);
+
+ // Quote
+ List<String> quote = new ArrayList<>();
+ quote.add("No amount of experimentation can ever prove me right;");
+ quote.add("a single experiment can prove me wrong.");
+ quote.add("-- Albert Einstein");
+
+ // leave frames as-is, no compression, and pass into extension
+ for (String q : quote)
+ {
+ TextFrame frame = new TextFrame().setPayload(q);
+ frame.setRsv1(false); // indication to extension that frame is not compressed (ie: a normal frame)
+ ext.incomingFrame(frame);
+ }
+
+ int len = quote.size();
+ capture.assertFrameCount(len);
+ capture.assertHasFrame(OpCode.TEXT,len);
+
+ String prefix;
+ for (int i = 0; i < len; i++)
+ {
+ prefix = "Frame[" + i + "]";
+
+ WebSocketFrame actual = capture.getFrames().get(i);
+
+ Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT));
+ Assert.assertThat(prefix + ".fin",actual.isFin(),is(true));
+ Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false));
+ Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false));
+ Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false));
+
+ ByteBuffer expected = BufferUtil.toBuffer(quote.get(i),StandardCharsets.UTF_8);
+ Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining()));
+ ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice());
+ }
+ }
+
+ /**
+ * Outgoing PING (Control Frame) should pass through extension unmodified
+ */
+ @Test
+ public void testOutgoingPing() throws IOException
+ {
+ PerMessageDeflateExtension ext = new PerMessageDeflateExtension();
+ ext.setBufferPool(new MappedByteBufferPool());
+ ext.setPolicy(WebSocketPolicy.newServerPolicy());
+ ExtensionConfig config = ExtensionConfig.parse("permessage-compress");
+ ext.setConfig(config);
+
+ // Setup capture of outgoing frames
+ OutgoingFramesCapture capture = new OutgoingFramesCapture();
+
+ // Wire up stack
+ ext.setNextOutgoingFrames(capture);
+
+ String payload = "Are you there?";
+ Frame ping = new PingFrame().setPayload(payload);
+
+ ext.outgoingFrame(ping,null);
+
+ capture.assertFrameCount(1);
+ capture.assertHasFrame(OpCode.PING,1);
+
+ WebSocketFrame actual = capture.getFrames().getFirst();
+
+ Assert.assertThat("Frame.opcode",actual.getOpCode(),is(OpCode.PING));
+ Assert.assertThat("Frame.fin",actual.isFin(),is(true));
+ Assert.assertThat("Frame.rsv1",actual.isRsv1(),is(false));
+ Assert.assertThat("Frame.rsv2",actual.isRsv2(),is(false));
+ Assert.assertThat("Frame.rsv3",actual.isRsv3(),is(false));
+
+ ByteBuffer expected = BufferUtil.toBuffer(payload,StandardCharsets.UTF_8);
+ Assert.assertThat("Frame.payloadLength",actual.getPayloadLength(),is(expected.remaining()));
+ ByteBufferAssert.assertEquals("Frame.payload",expected,actual.getPayload().slice());
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxDecoder.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxDecoder.java
deleted file mode 100644
index c06a741..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxDecoder.java
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import org.eclipse.jetty.websocket.api.WriteCallback;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
-
-/**
- * Helpful utility class to parse arbitrary mux events from a physical connection's OutgoingFrames.
- *
- * @see MuxEncoder
- */
-public class MuxDecoder extends MuxEventCapture implements OutgoingFrames
-{
- private MuxParser parser;
-
- public MuxDecoder()
- {
- parser = new MuxParser();
- parser.setEvents(this);
- }
-
- @Override
- public void outgoingFrame(Frame frame, WriteCallback callback)
- {
- parser.parse(frame);
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxEncoder.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxEncoder.java
deleted file mode 100644
index 079d5e4..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxEncoder.java
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.websocket.api.WriteCallback;
-import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
-import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.eclipse.jetty.websocket.common.io.FramePipes;
-
-/**
- * Helpful utility class to send arbitrary mux events into a physical connection's IncomingFrames.
- *
- * @see MuxDecoder
- */
-public class MuxEncoder
-{
- public static MuxEncoder toIncoming(IncomingFrames incoming)
- {
- return new MuxEncoder(FramePipes.to(incoming));
- }
-
- public static MuxEncoder toOutgoing(OutgoingFrames outgoing)
- {
- return new MuxEncoder(outgoing);
- }
-
- private MuxGenerator generator;
-
- private MuxEncoder(OutgoingFrames outgoing)
- {
- this.generator = new MuxGenerator();
- this.generator.setOutgoing(outgoing);
- }
-
- public void frame(long channelId, WebSocketFrame frame) throws IOException
- {
- this.generator.generate(channelId,frame,null);
- }
-
- public void op(MuxControlBlock op) throws IOException
- {
- WriteCallback callback = null;
- this.generator.generate(callback,op);
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxEventCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxEventCapture.java
deleted file mode 100644
index f9cfc91..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxEventCapture.java
+++ /dev/null
@@ -1,141 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import static org.hamcrest.Matchers.*;
-
-import java.util.LinkedList;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.common.OpCode;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxControlBlock;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxException;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxParser;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxedFrame;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelRequest;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelResponse;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxDropChannel;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxFlowControl;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxNewChannelSlot;
-import org.junit.Assert;
-
-public class MuxEventCapture implements MuxParser.Listener
-{
- private static final Logger LOG = Log.getLogger(MuxEventCapture.class);
-
- private LinkedList<MuxedFrame> frames = new LinkedList<>();
- private LinkedList<MuxControlBlock> ops = new LinkedList<>();
- private LinkedList<MuxException> errors = new LinkedList<>();
-
- public void assertFrameCount(int expected)
- {
- Assert.assertThat("Frame Count",frames.size(), is(expected));
- }
-
- public void assertHasFrame(byte opcode, long channelId, int expectedCount)
- {
- int actualCount = 0;
-
- for (MuxedFrame frame : frames)
- {
- if (frame.getChannelId() == channelId)
- {
- if (frame.getOpCode() == opcode)
- {
- actualCount++;
- }
- }
- }
-
- Assert.assertThat("Expected Count of " + OpCode.name(opcode) + " frames on Channel ID " + channelId,actualCount,is(expectedCount));
- }
-
- public void assertHasOp(byte opCode, int expectedCount)
- {
- int actualCount = 0;
- for (MuxControlBlock block : ops)
- {
- if (block.getOpCode() == opCode)
- {
- actualCount++;
- }
- }
- Assert.assertThat("Op[" + opCode + "] count",actualCount,is(expectedCount));
- }
-
- public LinkedList<MuxedFrame> getFrames()
- {
- return frames;
- }
-
- public LinkedList<MuxControlBlock> getOps()
- {
- return ops;
- }
-
- @Override
- public void onMuxAddChannelRequest(MuxAddChannelRequest request)
- {
- ops.add(request);
- }
-
- @Override
- public void onMuxAddChannelResponse(MuxAddChannelResponse response)
- {
- ops.add(response);
- }
-
- @Override
- public void onMuxDropChannel(MuxDropChannel drop)
- {
- ops.add(drop);
- }
-
- @Override
- public void onMuxedFrame(MuxedFrame frame)
- {
- frames.add(new MuxedFrame(frame));
- }
-
- @Override
- public void onMuxException(MuxException e)
- {
- LOG.debug(e);
- errors.add(e);
- }
-
- @Override
- public void onMuxFlowControl(MuxFlowControl flow)
- {
- ops.add(flow);
- }
-
- @Override
- public void onMuxNewChannelSlot(MuxNewChannelSlot slot)
- {
- ops.add(slot);
- }
-
- public void reset()
- {
- frames.clear();
- ops.clear();
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGeneratorWrite139SizeTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGeneratorWrite139SizeTest.java
deleted file mode 100644
index acdc204..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGeneratorWrite139SizeTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import static org.hamcrest.Matchers.*;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Locale;
-
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxGenerator;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class MuxGeneratorWrite139SizeTest
-{
- private static MuxGenerator generator = new MuxGenerator();
-
- @Parameters
- public static Collection<Object[]> data()
- {
- // Various good 1/3/9 encodings
- List<Object[]> data = new ArrayList<>();
-
- // @formatter:off
- // - 1 byte tests
- data.add(new Object[]{ 0L, "00"});
- data.add(new Object[]{ 1L, "01"});
- data.add(new Object[]{ 2L, "02"});
- data.add(new Object[]{ 55L, "37"});
- data.add(new Object[]{125L, "7D"});
-
- // - 3 byte tests
- data.add(new Object[]{0x00_80L, "7E0080"});
- data.add(new Object[]{0x00_ABL, "7E00AB"});
- data.add(new Object[]{0x00_FFL, "7E00FF"});
- data.add(new Object[]{0x3F_FFL, "7E3FFF"});
-
- // - 9 byte tests
- data.add(new Object[]{0x00_00_01_FF_FFL, "7F000000000001FFFF"});
- data.add(new Object[]{0x00_00_FF_FF_FFL, "7F0000000000FFFFFF"});
- data.add(new Object[]{0x00_FF_FF_FF_FFL, "7F00000000FFFFFFFF"});
- data.add(new Object[]{0xFF_FF_FF_FF_FFL, "7F000000FFFFFFFFFF"});
- // @formatter:on
-
- return data;
- }
-
- @Rule
- public TestName testname = new TestName();
-
- private long value;
- private String expectedHex;
-
- public MuxGeneratorWrite139SizeTest(long value, String expectedHex)
- {
- this.value = value;
- this.expectedHex = expectedHex;
- }
-
- @Test
- public void testWrite139Size()
- {
- System.err.printf("Running %s.%s - value: %,d%n",this.getClass().getName(),testname.getMethodName(),value);
- ByteBuffer bbuf = ByteBuffer.allocate(10);
- generator.write139Size(bbuf,value);
- BufferUtil.flipToFlush(bbuf,0);
- byte actual[] = BufferUtil.toArray(bbuf);
- String actualHex = TypeUtil.toHexString(actual).toUpperCase(Locale.ENGLISH);
- Assert.assertThat("1/3/9 encoded size of [" + value + "]",actualHex,is(expectedHex));
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGeneratorWriteChannelIdTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGeneratorWriteChannelIdTest.java
deleted file mode 100644
index 4fe5fdb..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGeneratorWriteChannelIdTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import static org.hamcrest.Matchers.*;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Locale;
-
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxGenerator;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-/**
- * Tests of valid ChannelID generation
- */
-@RunWith(Parameterized.class)
-public class MuxGeneratorWriteChannelIdTest
-{
- private static MuxGenerator generator = new MuxGenerator();
-
- @Parameters
- public static Collection<Object[]> data()
- {
- // Various good Channel IDs
- List<Object[]> data = new ArrayList<>();
-
- // @formatter:off
- // - 1 byte tests
- data.add(new Object[]{ 0L, "00"});
- data.add(new Object[]{ 1L, "01"});
- data.add(new Object[]{ 2L, "02"});
- data.add(new Object[]{ 55L, "37"});
- data.add(new Object[]{127L, "7F"});
-
- // - 2 byte tests
- data.add(new Object[]{0x00_80L, "8080"});
- data.add(new Object[]{0x00_FFL, "80FF"});
- data.add(new Object[]{0x3F_FFL, "BFFF"});
-
- // - 3 byte tests
- data.add(new Object[]{0x00_FF_FFL, "C0FFFF"});
- data.add(new Object[]{0x1F_FF_FFL, "DFFFFF"});
-
- // - 3 byte tests
- data.add(new Object[]{0x00_FF_FF_FFL, "E0FFFFFF"});
- data.add(new Object[]{0x1F_FF_FF_FFL, "FFFFFFFF"});
-
- // @formatter:on
- return data;
- }
-
- @Rule
- public TestName testname = new TestName();
-
- private long channelId;
- private String expectedHex;
-
- public MuxGeneratorWriteChannelIdTest(long channelId, String expectedHex)
- {
- this.channelId = channelId;
- this.expectedHex = expectedHex;
- }
-
- @Test
- public void testReadChannelId()
- {
- System.err.printf("Running %s.%s - channelId: %,d%n",this.getClass().getName(),testname.getMethodName(),channelId);
- ByteBuffer bbuf = ByteBuffer.allocate(10);
- generator.writeChannelId(bbuf,channelId);
- BufferUtil.flipToFlush(bbuf,0);
- byte actual[] = BufferUtil.toArray(bbuf);
- String actualHex = TypeUtil.toHexString(actual).toUpperCase(Locale.ENGLISH);
- Assert.assertThat("Channel ID [" + channelId + "]",actualHex,is(expectedHex));
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRFCTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRFCTest.java
deleted file mode 100644
index 5eceb20..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRFCTest.java
+++ /dev/null
@@ -1,245 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import static org.hamcrest.Matchers.*;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.api.extensions.Frame;
-import org.eclipse.jetty.websocket.common.IncomingFramesCapture;
-import org.eclipse.jetty.websocket.common.OpCode;
-import org.eclipse.jetty.websocket.common.Parser;
-import org.eclipse.jetty.websocket.common.UnitParser;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class MuxParserRFCTest
-{
- public static class DummyMuxExtension extends AbstractMuxExtension
- {
- @Override
- public void configureMuxer(Muxer muxer)
- {
- /* nothing to do */
- }
- }
-
- private LinkedList<WebSocketFrame> asFrames(byte[] buf)
- {
- IncomingFramesCapture capture = new IncomingFramesCapture();
- WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
- Parser parser = new UnitParser(policy);
- parser.setIncomingFramesHandler(capture);
- List<? extends AbstractExtension> muxList = Collections.singletonList(new DummyMuxExtension());
- parser.configureFromExtensions(muxList);
- ByteBuffer bbuf = ByteBuffer.wrap(buf);
- parser.parse(bbuf);
-
- return capture.getFrames();
- }
-
- private boolean isHexOnly(String part)
- {
- Pattern bytePat = Pattern.compile("(\\s*0x[0-9A-Fa-f]{2}+){1,}+");
- Matcher mat = bytePat.matcher(part);
- return mat.matches();
- }
-
- private MuxEventCapture parseMuxFrames(LinkedList<WebSocketFrame> frames)
- {
- MuxParser parser = new MuxParser();
- MuxEventCapture capture = new MuxEventCapture();
- parser.setEvents(capture);
- for(Frame frame: frames) {
- parser.parse(frame);
- }
- return capture;
- }
-
- @Test
- public void testIsHexOnly()
- {
- Assert.assertTrue(isHexOnly("0x00"));
- Assert.assertTrue(isHexOnly("0x00 0xaF"));
- Assert.assertFalse(isHexOnly("Hello World"));
- }
-
- @Test
- public void testRFCExample1() throws IOException
- {
- // Create RFC detailed frames
- byte buf[] = toByteArray("0x82 0x0d 0x01 0x81","Hello world");
- LinkedList<WebSocketFrame> frames = asFrames(buf);
- Assert.assertThat("Frame count",frames.size(),is(1));
-
- // Have mux parse frames
- MuxEventCapture capture = parseMuxFrames(frames);
- capture.assertFrameCount(1);
-
- MuxedFrame mux;
-
- mux = capture.getFrames().pop();
- String prefix = "MuxFrame[0]";
- Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L));
- Assert.assertThat(prefix + ".fin",mux.isFin(),is(true));
- Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false));
- Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false));
- Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false));
- Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false));
- Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT));
-
- String payload = mux.getPayloadAsUTF8();
- Assert.assertThat(prefix + ".payload/text",payload,is("Hello world"));
- }
-
- @Test
- public void testRFCExample2() throws IOException
- {
- // Create RFC detailed frames
- byte buf[] = toByteArray("0x02 0x07 0x01 0x81","Hello","0x80 0x06"," world");
- LinkedList<WebSocketFrame> frames = asFrames(buf);
- Assert.assertThat("Frame count",frames.size(),is(2));
-
- // Have mux parse frames
- MuxEventCapture capture = parseMuxFrames(frames);
- capture.assertFrameCount(2);
-
- MuxedFrame mux;
-
- // Text Frame
- mux = capture.getFrames().get(0);
- String prefix = "MuxFrame[0]";
- Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L));
- // (BUG IN DRAFT) Assert.assertThat(prefix + ".fin",mux.isFin(),is(false));
- Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false));
- Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false));
- Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false));
- Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false));
- Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT));
-
- String payload = mux.getPayloadAsUTF8();
- Assert.assertThat(prefix + ".payload/text",payload,is("Hello"));
-
- // Continuation Frame
- mux = capture.getFrames().get(1);
- prefix = "MuxFrame[1]";
- // (BUG IN DRAFT) Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L));
- // (BUG IN DRAFT) Assert.assertThat(prefix + ".fin",mux.isFin(),is(true));
- Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false));
- Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false));
- Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false));
- Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false));
- Assert.assertThat(prefix + ".continuation",mux.isContinuation(),is(true));
- // (BUG IN DRAFT) Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.BINARY));
-
- payload = mux.getPayloadAsUTF8();
- Assert.assertThat(prefix + ".payload/text",payload,is(" world"));
- }
-
- @Test
- public void testRFCExample3() throws IOException
- {
- // Create RFC detailed frames
- byte buf[] = toByteArray("0x82 0x07 0x01 0x01","Hello","0x82 0x05 0x02 0x81","bye","0x82 0x08 0x01 0x80"," world");
- LinkedList<WebSocketFrame> frames = asFrames(buf);
- Assert.assertThat("Frame count",frames.size(),is(3));
-
- // Have mux parse frames
- MuxEventCapture capture = parseMuxFrames(frames);
- capture.assertFrameCount(3);
-
- MuxedFrame mux;
-
- // Text Frame (Message 1)
- mux = capture.getFrames().pop();
- String prefix = "MuxFrame[0]";
- Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L));
- Assert.assertThat(prefix + ".fin",mux.isFin(),is(false));
- Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false));
- Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false));
- Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false));
- Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false));
- Assert.assertThat(prefix + ".continuation",mux.isContinuation(),is(false));
- Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT));
-
- String payload = mux.getPayloadAsUTF8();
- Assert.assertThat(prefix + ".payload/text",payload,is("Hello"));
-
- // Text Frame (Message 2)
- mux = capture.getFrames().pop();
- prefix = "MuxFrame[1]";
- Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(2L));
- Assert.assertThat(prefix + ".fin",mux.isFin(),is(true));
- Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false));
- Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false));
- Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false));
- Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false));
- Assert.assertThat(prefix + ".continuation",mux.isContinuation(),is(false));
- Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT));
-
- payload = mux.getPayloadAsUTF8();
- Assert.assertThat(prefix + ".payload/text",payload,is("bye"));
-
- // Continuation Frame (Message 1)
- mux = capture.getFrames().pop();
- prefix = "MuxFrame[2]";
- Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L));
- Assert.assertThat(prefix + ".fin",mux.isFin(),is(true));
- Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false));
- Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false));
- Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false));
- Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false));
- Assert.assertThat(prefix + ".continuation",mux.isContinuation(),is(true));
- Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT));
-
- payload = mux.getPayloadAsUTF8();
- Assert.assertThat(prefix + ".payload/text",payload,is(" world"));
- }
-
- private byte[] toByteArray(String... parts) throws IOException
- {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- for(String part: parts) {
- if (isHexOnly(part))
- {
- String hexonly = part.replaceAll("\\s*0x","");
- out.write(TypeUtil.fromHexString(hexonly));
- }
- else
- {
- out.write(part.getBytes(StandardCharsets.UTF_8));
- }
- }
- return out.toByteArray();
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRead139Size_BadEncodingTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRead139Size_BadEncodingTest.java
deleted file mode 100644
index 15c2e06..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRead139Size_BadEncodingTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import static org.hamcrest.Matchers.*;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxException;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxParser;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-/**
- * Tests for bad 1/3/9 size encoding.
- */
-@RunWith(Parameterized.class)
-public class MuxParserRead139Size_BadEncodingTest
-{
- private static MuxParser parser = new MuxParser();
-
- @Parameters
- public static Collection<Object[]> data()
- {
- // Various bad 1/3/9 encodings
- // Violating "minimal number of bytes necessary" rule.
- List<Object[]> data = new ArrayList<>();
-
- // @formatter:off
- // - 1 byte tests
- // all known 1 byte tests are valid
-
- // - 3 byte tests
- data.add(new Object[]{"7E0000"});
- data.add(new Object[]{"7E0001"});
- data.add(new Object[]{"7E0012"});
- data.add(new Object[]{"7E0059"});
- // extra bytes (not related to 1/3/9 size)
- data.add(new Object[]{"7E0012345678"});
-
- // - 9 byte tests
- data.add(new Object[]{"7F0000000000000000"});
- data.add(new Object[]{"7F0000000000000001"});
- data.add(new Object[]{"7F0000000000000012"});
- data.add(new Object[]{"7F0000000000001234"});
- data.add(new Object[]{"7F000000000000FFFF"});
-
- // @formatter:on
- return data;
- }
-
- @Rule
- public TestName testname = new TestName();
-
- private String rawhex;
- private byte buf[];
-
- public MuxParserRead139Size_BadEncodingTest(String rawhex)
- {
- this.rawhex = rawhex;
- this.buf = TypeUtil.fromHexString(rawhex);
- }
-
- @Test
- public void testRead139EncodedSize()
- {
- System.err.printf("Running %s.%s - hex: %s%n",this.getClass().getName(),testname.getMethodName(),rawhex);
- ByteBuffer bbuf = ByteBuffer.wrap(buf);
- try
- {
- parser.read139EncodedSize(bbuf);
- // unexpected path
- Assert.fail("Should have failed with an invalid parse");
- }
- catch (MuxException e)
- {
- // expected path
- Assert.assertThat(e.getMessage(),containsString("Invalid 1/3/9 length"));
- }
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRead139Size_GoodTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRead139Size_GoodTest.java
deleted file mode 100644
index eb450fc..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRead139Size_GoodTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import static org.hamcrest.Matchers.*;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxParser;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class MuxParserRead139Size_GoodTest
-{
- private static MuxParser parser = new MuxParser();
-
- @Parameters
- public static Collection<Object[]> data()
- {
- // Various good 1/3/9 encodings
- List<Object[]> data = new ArrayList<>();
-
- // @formatter:off
- // - 1 byte tests
- data.add(new Object[]{"00", 0L});
- data.add(new Object[]{"01", 1L});
- data.add(new Object[]{"02", 2L});
- data.add(new Object[]{"37", 55L});
- data.add(new Object[]{"7D", 125L});
- // extra bytes (not related to 1/3/9 size)
- data.add(new Object[]{"37FF", 55L});
- data.add(new Object[]{"0123456789", 0x01L});
-
- // - 3 byte tests
- data.add(new Object[]{"7E0080", 0x00_80L});
- data.add(new Object[]{"7E00AB", 0x00_ABL});
- data.add(new Object[]{"7E00FF", 0x00_FFL});
- data.add(new Object[]{"7E3FFF", 0x3F_FFL});
- // extra bytes (not related to 1/3/9 size)
- data.add(new Object[]{"7E0123456789", 0x01_23L});
-
- // - 9 byte tests
- data.add(new Object[]{"7F000000000001FFFF", 0x00_00_01_FF_FFL});
- data.add(new Object[]{"7F0000000000FFFFFF", 0x00_00_FF_FF_FFL});
- data.add(new Object[]{"7F00000000FFFFFFFF", 0x00_FF_FF_FF_FFL});
- data.add(new Object[]{"7F000000FFFFFFFFFF", 0xFF_FF_FF_FF_FFL});
-
- // @formatter:on
- return data;
- }
-
- @Rule
- public TestName testname = new TestName();
-
- private String rawhex;
- private byte buf[];
- private long expected;
-
- public MuxParserRead139Size_GoodTest(String rawhex, long expected)
- {
- this.rawhex = rawhex;
- this.buf = TypeUtil.fromHexString(rawhex);
- this.expected = expected;
- }
-
- @Test
- public void testRead139EncodedSize()
- {
- System.err.printf("Running %s.%s - hex: %s%n",this.getClass().getName(),testname.getMethodName(),rawhex);
- ByteBuffer bbuf = ByteBuffer.wrap(buf);
- long actual = parser.read139EncodedSize(bbuf);
- Assert.assertThat("1/3/9 size from buffer [" + rawhex + "]",actual,is(expected));
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserReadChannelId_BadEncodingTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserReadChannelId_BadEncodingTest.java
deleted file mode 100644
index 57c17a3..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserReadChannelId_BadEncodingTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import static org.hamcrest.Matchers.*;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxException;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxParser;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-/**
- * Tests of Invalid ChannelID parsing
- */
-@RunWith(Parameterized.class)
-public class MuxParserReadChannelId_BadEncodingTest
-{
- private static MuxParser parser = new MuxParser();
-
- @Parameters
- public static Collection<Object[]> data()
- {
- // Various Invalid Encoded Channel IDs.
- // Violating "minimal number of bytes necessary" rule.
- List<Object[]> data = new ArrayList<>();
-
- // @formatter:off
- // - 1 byte tests
- // all known 1 byte tests are valid
-
- // - 2 byte tests
- data.add(new Object[]{"8000"});
- data.add(new Object[]{"8001"});
- data.add(new Object[]{"807F"});
- // extra bytes (not related to channelId)
- data.add(new Object[]{"8023456789"});
-
- // - 3 byte tests
- data.add(new Object[]{"C00000"});
- data.add(new Object[]{"C01234"});
- data.add(new Object[]{"C03FFF"});
-
- // - 3 byte tests
- data.add(new Object[]{"E0000000"});
- data.add(new Object[]{"E0000001"});
- data.add(new Object[]{"E01FFFFF"});
-
- // @formatter:on
- return data;
- }
-
- @Rule
- public TestName testname = new TestName();
-
- private String rawhex;
- private byte buf[];
-
- public MuxParserReadChannelId_BadEncodingTest(String rawhex)
- {
- this.rawhex = rawhex;
- this.buf = TypeUtil.fromHexString(rawhex);
- }
-
- @Test
- public void testBadEncoding()
- {
- System.err.printf("Running %s.%s - hex: %s%n",this.getClass().getName(),testname.getMethodName(),rawhex);
- ByteBuffer bbuf = ByteBuffer.wrap(buf);
- try
- {
- parser.readChannelId(bbuf);
- // unexpected path
- Assert.fail("Should have failed with an invalid parse");
- }
- catch (MuxException e)
- {
- // expected path
- Assert.assertThat(e.getMessage(),containsString("Invalid Channel ID"));
- }
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserReadChannelId_GoodTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserReadChannelId_GoodTest.java
deleted file mode 100644
index 5a1ad5e..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserReadChannelId_GoodTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux;
-
-import static org.hamcrest.Matchers.*;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxParser;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-/**
- * Tests of valid ChannelID parsing
- */
-@RunWith(Parameterized.class)
-public class MuxParserReadChannelId_GoodTest
-{
- private static MuxParser parser = new MuxParser();
-
- @Parameters
- public static Collection<Object[]> data()
- {
- // Various good Channel IDs
- List<Object[]> data = new ArrayList<>();
-
- // @formatter:off
- // - 1 byte tests
- data.add(new Object[]{"00", 0L});
- data.add(new Object[]{"01", 1L});
- data.add(new Object[]{"02", 2L});
- data.add(new Object[]{"7F", 127L});
- // extra bytes (not related to channelId)
- data.add(new Object[]{"37FF", 55L});
- data.add(new Object[]{"0123456789", 0x01L});
-
- // - 2 byte tests
- data.add(new Object[]{"8080", 0x00_80L});
- data.add(new Object[]{"80FF", 0x00_FFL});
- data.add(new Object[]{"BFFF", 0x3F_FFL});
- // extra bytes (not related to channelId)
- data.add(new Object[]{"8123456789", 0x01_23L});
-
- // - 3 byte tests
- data.add(new Object[]{"C0FFFF", 0x00_FF_FFL});
- data.add(new Object[]{"DFFFFF", 0x1F_FF_FFL});
- // extra bytes (not related to channelId)
- data.add(new Object[]{"C123456789", 0x01_23_45L});
-
- // - 3 byte tests
- data.add(new Object[]{"E0FFFFFF", 0x00_FF_FF_FFL});
- data.add(new Object[]{"FFFFFFFF", 0x1F_FF_FF_FFL});
- // extra bytes (not related to channelId)
- data.add(new Object[]{"E123456789", 0x01_23_45_67L});
-
- // @formatter:on
- return data;
- }
-
- @Rule
- public TestName testname = new TestName();
-
- private String rawhex;
- private byte buf[];
- private long expected;
-
- public MuxParserReadChannelId_GoodTest(String rawhex, long expected)
- {
- this.rawhex = rawhex;
- this.buf = TypeUtil.fromHexString(rawhex);
- this.expected = expected;
- }
-
- @Test
- public void testReadChannelId()
- {
- System.err.printf("Running %s.%s - hex: %s%n",this.getClass().getName(),testname.getMethodName(),rawhex);
- ByteBuffer bbuf = ByteBuffer.wrap(buf);
- long actual = parser.readChannelId(bbuf);
- Assert.assertThat("Channel ID from buffer [" + rawhex + "]",actual,is(expected));
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/DummyMuxAddClient.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/DummyMuxAddClient.java
deleted file mode 100644
index 81ccc3d..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/DummyMuxAddClient.java
+++ /dev/null
@@ -1,32 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux.add;
-
-import org.eclipse.jetty.websocket.common.WebSocketSession;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelResponse;
-
-public class DummyMuxAddClient implements MuxAddClient
-{
- @Override
- public WebSocketSession createSession(MuxAddChannelResponse response)
- {
- // TODO Auto-generated method stub
- return null;
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/DummyMuxAddServer.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/DummyMuxAddServer.java
deleted file mode 100644
index 49b4fcb..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/DummyMuxAddServer.java
+++ /dev/null
@@ -1,98 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux.add;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
-import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.common.WebSocketSession;
-import org.eclipse.jetty.websocket.common.events.EventDriver;
-import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxChannel;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxException;
-import org.eclipse.jetty.websocket.common.extensions.mux.Muxer;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelResponse;
-
-import examples.echo.AdapterEchoSocket;
-
-/**
- * Dummy impl of MuxAddServer
- */
-public class DummyMuxAddServer implements MuxAddServer
-{
- @SuppressWarnings("unused")
- private static final Logger LOG = Log.getLogger(DummyMuxAddServer.class);
- private AdapterEchoSocket echo;
- private WebSocketPolicy policy;
- private EventDriverFactory eventDriverFactory;
-
- public DummyMuxAddServer()
- {
- this.policy = WebSocketPolicy.newServerPolicy();
- this.eventDriverFactory = new EventDriverFactory(policy);
- this.echo = new AdapterEchoSocket();
- }
-
- @Override
- public UpgradeRequest getPhysicalHandshakeRequest()
- {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public UpgradeResponse getPhysicalHandshakeResponse()
- {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public void handshake(Muxer muxer, MuxChannel channel, UpgradeRequest request) throws MuxException, IOException
- {
- StringBuilder response = new StringBuilder();
- response.append("HTTP/1.1 101 Switching Protocols\r\n");
- response.append("Connection: upgrade\r\n");
- // not meaningful (per Draft 08) hresp.append("Upgrade: websocket\r\n");
- // not meaningful (per Draft 08) hresp.append("Sec-WebSocket-Accept: Kgo85/8KVE8YPONSeyhgL3GwqhI=\r\n");
- response.append("\r\n");
-
- EventDriver websocket = this.eventDriverFactory.wrap(echo);
- WebSocketSession session = new WebSocketSession(request.getRequestURI(),websocket,channel);
- UpgradeResponse uresponse = new UpgradeResponse();
- uresponse.setAcceptedSubProtocol("echo");
- session.setUpgradeResponse(uresponse);
- channel.setSession(session);
- channel.setSubProtocol("echo");
- channel.onOpen();
- session.open();
-
- MuxAddChannelResponse addChannelResponse = new MuxAddChannelResponse();
- addChannelResponse.setChannelId(channel.getChannelId());
- addChannelResponse.setEncoding(MuxAddChannelResponse.IDENTITY_ENCODING);
- addChannelResponse.setFailed(false);
- addChannelResponse.setHandshake(response.toString());
-
- muxer.output(addChannelResponse);
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxerAddClientTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxerAddClientTest.java
deleted file mode 100644
index 2b87ff2..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxerAddClientTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux.add;
-
-import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxChannel;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxDecoder;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxEncoder;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxOp;
-import org.eclipse.jetty.websocket.common.extensions.mux.Muxer;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelRequest;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelResponse;
-import org.eclipse.jetty.websocket.common.io.LocalWebSocketConnection;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-
-public class MuxerAddClientTest
-{
- @Rule
- public TestName testname = new TestName();
-
- @Test
- @Ignore("Interrim, not functional yet")
- public void testAddChannel_Client() throws Exception
- {
- // Client side physical socket
- LocalWebSocketConnection physical = new LocalWebSocketConnection(testname);
- physical.setPolicy(WebSocketPolicy.newClientPolicy());
- physical.onOpen();
-
- // Server Reader
- MuxDecoder serverRead = new MuxDecoder();
-
- // Client side Muxer
- Muxer muxer = new Muxer(physical);
- DummyMuxAddClient addClient = new DummyMuxAddClient();
- muxer.setAddClient(addClient);
- muxer.setOutgoingFramesHandler(serverRead);
-
- // Server Writer
- MuxEncoder serverWrite = MuxEncoder.toIncoming(physical);
-
- // Build AddChannelRequest handshake data
- StringBuilder request = new StringBuilder();
- request.append("GET /echo HTTP/1.1\r\n");
- request.append("Host: localhost\r\n");
- request.append("Upgrade: websocket\r\n");
- request.append("Connection: Upgrade\r\n");
- request.append("Sec-WebSocket-Key: ZDTIRU5vU9xOfkg8JAgN3A==\r\n");
- request.append("Sec-WebSocket-Version: 13\r\n");
- request.append("\r\n");
-
- // Build AddChannelRequest
- long channelId = 1L;
- MuxAddChannelRequest req = new MuxAddChannelRequest();
- req.setChannelId(channelId);
- req.setEncoding((byte)0);
- req.setHandshake(request.toString());
-
- // Have client sent AddChannelRequest
- MuxChannel channel = muxer.getChannel(channelId,true);
- MuxEncoder clientWrite = MuxEncoder.toOutgoing(channel);
- clientWrite.op(req);
-
- // Have server read request
- serverRead.assertHasOp(MuxOp.ADD_CHANNEL_REQUEST,1);
-
- // prepare AddChannelResponse
- StringBuilder response = new StringBuilder();
- response.append("HTTP/1.1 101 Switching Protocols\r\n");
- response.append("Upgrade: websocket\r\n");
- response.append("Connection: upgrade\r\n");
- response.append("Sec-WebSocket-Accept: Kgo85/8KVE8YPONSeyhgL3GwqhI=\r\n");
- response.append("\r\n");
-
- MuxAddChannelResponse resp = new MuxAddChannelResponse();
- resp.setChannelId(channelId);
- resp.setFailed(false);
- resp.setEncoding((byte)0);
- resp.setHandshake(resp.toString());
-
- // Server writes add channel response
- serverWrite.op(resp);
-
- // TODO: handle the upgrade on client side.
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxerAddServerTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxerAddServerTest.java
deleted file mode 100644
index 0dfffa1..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxerAddServerTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.extensions.mux.add;
-
-import static org.hamcrest.Matchers.*;
-
-import org.eclipse.jetty.websocket.api.WebSocketPolicy;
-import org.eclipse.jetty.websocket.common.OpCode;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxDecoder;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxEncoder;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxOp;
-import org.eclipse.jetty.websocket.common.extensions.mux.Muxer;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelRequest;
-import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelResponse;
-import org.eclipse.jetty.websocket.common.io.LocalWebSocketConnection;
-import org.junit.Assert;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-
-public class MuxerAddServerTest
-{
- @Rule
- public TestName testname = new TestName();
-
- @Test
- @Ignore("Interrim, not functional yet")
- public void testAddChannel_Server() throws Exception
- {
- // Server side physical connection
- LocalWebSocketConnection physical = new LocalWebSocketConnection(testname);
- physical.setPolicy(WebSocketPolicy.newServerPolicy());
- physical.onOpen();
-
- // Client reader
- MuxDecoder clientRead = new MuxDecoder();
-
- // Build up server side muxer.
- Muxer muxer = new Muxer(physical);
- DummyMuxAddServer addServer = new DummyMuxAddServer();
- muxer.setAddServer(addServer);
- muxer.setOutgoingFramesHandler(clientRead);
-
- // Wire up physical connection to forward incoming frames to muxer
- physical.setNextIncomingFrames(muxer);
-
- // Client simulator
- // Can inject mux encapsulated frames into physical connection as if from
- // physical connection.
- MuxEncoder clientWrite = MuxEncoder.toIncoming(physical);
-
- // Build AddChannelRequest handshake data
- StringBuilder request = new StringBuilder();
- request.append("GET /echo HTTP/1.1\r\n");
- request.append("Host: localhost\r\n");
- request.append("Upgrade: websocket\r\n");
- request.append("Connection: Upgrade\r\n");
- request.append("Sec-WebSocket-Key: ZDTIRU5vU9xOfkg8JAgN3A==\r\n");
- request.append("Sec-WebSocket-Version: 13\r\n");
- request.append("\r\n");
-
- // Build AddChannelRequest
- MuxAddChannelRequest req = new MuxAddChannelRequest();
- req.setChannelId(1);
- req.setEncoding((byte)0);
- req.setHandshake(request.toString());
-
- // Have client sent AddChannelRequest
- clientWrite.op(req);
-
- // Make sure client got AddChannelResponse
- clientRead.assertHasOp(MuxOp.ADD_CHANNEL_RESPONSE,1);
- MuxAddChannelResponse response = (MuxAddChannelResponse)clientRead.getOps().pop();
- Assert.assertThat("AddChannelResponse.channelId",response.getChannelId(),is(1L));
- Assert.assertThat("AddChannelResponse.failed",response.isFailed(),is(false));
- Assert.assertThat("AddChannelResponse.handshake",response.getHandshake(),notNullValue());
- Assert.assertThat("AddChannelResponse.handshakeSize",response.getHandshakeSize(),is(57L));
-
- clientRead.reset();
-
- // Send simple echo request
- clientWrite.frame(1,WebSocketFrame.text("Hello World"));
-
- // Test for echo response (is there a user echo websocket connected to the sub-channel?)
- clientRead.assertHasFrame(OpCode.TEXT,1L,1);
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java
index 24393b1..daf108a 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java
@@ -18,8 +18,8 @@
package org.eclipse.jetty.websocket.common.io;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
import java.util.LinkedList;
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java
index 92e5e77..9494c3b 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java
@@ -19,12 +19,15 @@
package org.eclipse.jetty.websocket.common.io;
import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ExecutorThreadPool;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.SuspendToken;
-import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.Frame;
@@ -40,6 +43,8 @@
{
private static final Logger LOG = Log.getLogger(LocalWebSocketConnection.class);
private final String id;
+ private final ByteBufferPool bufferPool;
+ private final Executor executor;
private WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
private IncomingFrames incoming;
private IOState ioState = new IOState();
@@ -52,13 +57,20 @@
public LocalWebSocketConnection(String id)
{
this.id = id;
+ this.bufferPool = new MappedByteBufferPool();
+ this.executor = new ExecutorThreadPool();
this.ioState.addListener(this);
}
public LocalWebSocketConnection(TestName testname)
{
- this.id = testname.getMethodName();
- this.ioState.addListener(this);
+ this(testname.getMethodName());
+ }
+
+ @Override
+ public Executor getExecutor()
+ {
+ return executor;
}
@Override
@@ -75,12 +87,30 @@
ioState.onCloseLocal(close);
}
+ public void connect()
+ {
+ LOG.debug("connect()");
+ ioState.onConnected();
+ }
+
@Override
public void disconnect()
{
LOG.debug("disconnect()");
}
+ @Override
+ public ByteBufferPool getBufferPool()
+ {
+ return this.bufferPool;
+ }
+
+ @Override
+ public long getIdleTimeout()
+ {
+ return 0;
+ }
+
public IncomingFrames getIncoming()
{
return incoming;
@@ -101,7 +131,6 @@
@Override
public long getMaxIdleTimeout()
{
- // TODO Auto-generated method stub
return 0;
}
@@ -124,7 +153,7 @@
}
@Override
- public void incomingError(WebSocketException e)
+ public void incomingError(Throwable e)
{
incoming.incomingError(e);
}
@@ -169,8 +198,9 @@
}
}
- public void onOpen() {
- LOG.debug("onOpen()");
+ public void open()
+ {
+ LOG.debug("open()");
ioState.onOpened();
}
@@ -187,8 +217,6 @@
@Override
public void setMaxIdleTimeout(long ms)
{
- // TODO Auto-generated method stub
-
}
@Override
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketSession.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketSession.java
index f1c123c..d19eed0 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketSession.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketSession.java
@@ -29,30 +29,27 @@
{
private String id;
private OutgoingFramesCapture outgoingCapture;
- private LocalWebSocketConnection lwsconnection;
public LocalWebSocketSession(TestName testname, EventDriver driver)
{
super(URI.create("ws://localhost/LocalWebSocketSesssion/" + testname.getMethodName()),driver,new LocalWebSocketConnection(testname));
this.id = testname.getMethodName();
- this.lwsconnection = (LocalWebSocketConnection)getConnection();
outgoingCapture = new OutgoingFramesCapture();
setOutgoingHandler(outgoingCapture);
}
+ @Override
+ public void dispatch(Runnable runnable)
+ {
+ runnable.run();
+ }
+
public OutgoingFramesCapture getOutgoingCapture()
{
return outgoingCapture;
}
@Override
- public void open()
- {
- lwsconnection.onOpen();
- super.open();
- }
-
- @Override
public String toString()
{
return String.format("%s[%s]",LocalWebSocketSession.class.getSimpleName(),id);
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/TrackingCallback.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/TrackingCallback.java
new file mode 100644
index 0000000..1cdefdf
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/TrackingCallback.java
@@ -0,0 +1,68 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.util.Callback;
+
+/**
+ * Tracking Callback for testing how the callbacks are used.
+ */
+public class TrackingCallback implements Callback
+{
+ private AtomicInteger called = new AtomicInteger();
+ private boolean success = false;
+ private Throwable failure = null;
+
+ @Override
+ public void failed(Throwable x)
+ {
+ this.called.incrementAndGet();
+ this.success = false;
+ this.failure = x;
+ }
+
+ @Override
+ public void succeeded()
+ {
+ this.called.incrementAndGet();
+ this.success = false;
+ }
+
+ public Throwable getFailure()
+ {
+ return failure;
+ }
+
+ public boolean isSuccess()
+ {
+ return success;
+ }
+
+ public boolean isCalled()
+ {
+ return called.get() >= 1;
+ }
+
+ public int getCallCount()
+ {
+ return called.get();
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/WriteBytesProviderTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/WriteBytesProviderTest.java
new file mode 100644
index 0000000..8948c1b
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/WriteBytesProviderTest.java
@@ -0,0 +1,191 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.Hex;
+import org.eclipse.jetty.websocket.common.UnitGenerator;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class WriteBytesProviderTest
+{
+ private static final Logger LOG = Log.getLogger(WriteBytesProviderTest.class);
+ private WriteBytesProvider bytesProvider;
+
+ private void assertCallbackSuccessCount(TrackingCallback callback, int expectedSuccsesCount)
+ {
+ Assert.assertThat("Callback was called",callback.isCalled(),is(true));
+ Assert.assertThat("No Failed Callbacks",callback.getFailure(),nullValue());
+ Assert.assertThat("# of Success Callbacks",callback.getCallCount(),is(expectedSuccsesCount));
+ }
+
+ @Test
+ public void testSingleFrame()
+ {
+ UnitGenerator generator = new UnitGenerator();
+ TrackingCallback flushCallback = new TrackingCallback();
+ bytesProvider = new WriteBytesProvider(generator,flushCallback);
+
+ TrackingCallback frameCallback = new TrackingCallback();
+ Frame frame = new TextFrame().setPayload("Test");
+
+ // place in to bytes provider
+ bytesProvider.enqueue(frame,frameCallback);
+
+ // get bytes out
+ List<ByteBuffer> bytes = bytesProvider.getByteBuffers();
+ Assert.assertThat("Number of buffers",bytes.size(),is(2));
+
+ // Test byte values
+ assertExpectedBytes(bytes,"810454657374");
+
+ // Trigger success
+ bytesProvider.succeeded();
+
+ // Validate success
+ assertCallbackSuccessCount(flushCallback,1);
+ assertCallbackSuccessCount(frameCallback,1);
+ }
+
+ @Test
+ public void testTextClose()
+ {
+ UnitGenerator generator = new UnitGenerator();
+ TrackingCallback flushCallback = new TrackingCallback();
+ bytesProvider = new WriteBytesProvider(generator,flushCallback);
+
+ // Create frames for provider
+ TrackingCallback textCallback = new TrackingCallback();
+ TrackingCallback closeCallback = new TrackingCallback();
+ bytesProvider.enqueue(new TextFrame().setPayload("Bye"),textCallback);
+ bytesProvider.enqueue(new CloseInfo().asFrame(),closeCallback);
+
+ // get bytes out
+ List<ByteBuffer> bytes = bytesProvider.getByteBuffers();
+ Assert.assertThat("Number of buffers",bytes.size(),is(4));
+
+ // Test byte values
+ StringBuilder expected = new StringBuilder();
+ expected.append("8103427965"); // text frame
+ expected.append("8800"); // (empty) close frame
+ assertExpectedBytes(bytes,expected.toString());
+
+ // Trigger success
+ bytesProvider.succeeded();
+
+ // Validate success
+ assertCallbackSuccessCount(flushCallback,1);
+ assertCallbackSuccessCount(textCallback,1);
+ assertCallbackSuccessCount(closeCallback,1);
+ }
+
+ @Test
+ public void testTinyBufferSizeFrame()
+ {
+ UnitGenerator generator = new UnitGenerator();
+ TrackingCallback flushCallback = new TrackingCallback();
+ bytesProvider = new WriteBytesProvider(generator,flushCallback);
+ int bufferSize = 30;
+ bytesProvider.setBufferSize(bufferSize);
+
+ // Create frames for provider
+ TrackingCallback binCallback = new TrackingCallback();
+ TrackingCallback closeCallback = new TrackingCallback();
+ int binPayloadSize = 50;
+ byte bin[] = new byte[binPayloadSize];
+ Arrays.fill(bin,(byte)0x00);
+ BinaryFrame binFrame = new BinaryFrame().setPayload(bin);
+ byte maskingKey[] = Hex.asByteArray("11223344");
+ binFrame.setMask(maskingKey);
+ bytesProvider.enqueue(binFrame,binCallback);
+ bytesProvider.enqueue(new CloseInfo().asFrame(),closeCallback);
+
+ // get bytes out
+ List<ByteBuffer> bytes = bytesProvider.getByteBuffers();
+ Assert.assertThat("Number of buffers",bytes.size(),is(5));
+ assertBufferLengths(bytes,6,bufferSize,binPayloadSize-bufferSize,2,0);
+
+ // Test byte values
+ StringBuilder expected = new StringBuilder();
+ expected.append("82B2").append("11223344"); // bin frame
+ // build up masked bytes
+ byte masked[] = new byte[binPayloadSize];
+ Arrays.fill(masked,(byte)0x00);
+ for (int i = 0; i < binPayloadSize; i++)
+ {
+ masked[i] ^= maskingKey[i % 4];
+ }
+ expected.append(Hex.asHex(masked));
+ expected.append("8800"); // (empty) close frame
+ assertExpectedBytes(bytes,expected.toString());
+
+ // Trigger success
+ bytesProvider.succeeded();
+
+ // Validate success
+ assertCallbackSuccessCount(flushCallback,1);
+ assertCallbackSuccessCount(binCallback,1);
+ assertCallbackSuccessCount(closeCallback,1);
+ }
+
+ private void assertBufferLengths(List<ByteBuffer> bytes, int... expectedLengths)
+ {
+ for (int i = 0; i < expectedLengths.length; i++)
+ {
+ Assert.assertThat("Buffer[" + i + "].remaining",bytes.get(i).remaining(),is(expectedLengths[i]));
+ }
+ }
+
+ private void assertExpectedBytes(List<ByteBuffer> bytes, String expected)
+ {
+ String actual = gatheredHex(bytes);
+ Assert.assertThat("Expected Bytes",actual,is(expected));
+ }
+
+ private String gatheredHex(List<ByteBuffer> bytes)
+ {
+ int len = 0;
+ for (ByteBuffer buf : bytes)
+ {
+ LOG.debug("buffer[] {}", BufferUtil.toDetailString(buf));
+ len += buf.remaining();
+ }
+ len = len * 2;
+ StringBuilder ret = new StringBuilder(len);
+ for (ByteBuffer buf : bytes)
+ {
+ ret.append(Hex.asHex(buf));
+ }
+ return ret.toString();
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java
index 7532b0d..d21c77b 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java
@@ -18,8 +18,10 @@
package org.eclipse.jetty.websocket.common.io.http;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java
index a7973e6..168a633 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java
@@ -19,26 +19,25 @@
package org.eclipse.jetty.websocket.common.io.http;
import java.nio.ByteBuffer;
-import java.util.HashMap;
-import java.util.Locale;
import java.util.Map;
+import java.util.TreeMap;
public class HttpResponseParseCapture implements HttpResponseHeaderParseListener
{
private int statusCode;
private String statusReason;
- private Map<String, String> headers = new HashMap<>();
+ private Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private ByteBuffer remainingBuffer;
@Override
public void addHeader(String name, String value)
{
- headers.put(name.toLowerCase(Locale.ENGLISH),value);
+ headers.put(name,value);
}
public String getHeader(String name)
{
- return headers.get(name.toLowerCase(Locale.ENGLISH));
+ return headers.get(name);
}
public ByteBuffer getRemainingBuffer()
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessorTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessorTest.java
index 6d075a6..76fa4b7 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessorTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessorTest.java
@@ -18,16 +18,21 @@
package org.eclipse.jetty.websocket.common.io.payload;
+import static org.hamcrest.Matchers.is;
+
import java.nio.ByteBuffer;
+import java.util.Arrays;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.common.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.Hex;
import org.eclipse.jetty.websocket.common.UnitGenerator;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.eclipse.jetty.websocket.common.io.payload.DeMaskProcessor;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.junit.Assert;
import org.junit.Test;
public class DeMaskProcessorTest
@@ -37,13 +42,13 @@
@Test
public void testDeMaskText()
{
- String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
+ // Use a string that is not multiple of 4 in length to test if/else branches in DeMaskProcessor
+ String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF01";
- WebSocketFrame frame = WebSocketFrame.text(message);
+ WebSocketFrame frame = new TextFrame().setPayload(message);
frame.setMask(TypeUtil.fromHexString("11223344"));
- // frame.setMask(TypeUtil.fromHexString("00000000"));
- ByteBuffer buf = new UnitGenerator().generate(frame);
+ ByteBuffer buf = UnitGenerator.generate(frame);
LOG.debug("Buf: {}",BufferUtil.toDetailString(buf));
ByteBuffer payload = buf.slice();
payload.position(6); // where payload starts
@@ -55,4 +60,50 @@
ByteBufferAssert.assertEquals("DeMasked Text Payload",message,payload);
}
+
+ @Test
+ public void testDeMaskTextSliced()
+ {
+ final byte msgChar = '*';
+ final int messageSize = 25;
+
+ byte message[] = new byte[messageSize];
+ Arrays.fill(message,msgChar);
+
+ TextFrame frame = new TextFrame();
+ frame.setPayload(ByteBuffer.wrap(message));
+ frame.setMask(Hex.asByteArray("11223344"));
+
+ ByteBuffer buf = UnitGenerator.generate(frame);
+ LOG.debug("Buf: {}",BufferUtil.toDetailString(buf));
+ ByteBuffer payload = buf.slice();
+ payload.position(6); // where payload starts
+
+ LOG.debug("Payload: {}",BufferUtil.toDetailString(payload));
+ LOG.debug("Pre-Processed: {}",Hex.asHex(payload));
+
+ DeMaskProcessor demask = new DeMaskProcessor();
+ demask.reset(frame);
+ ByteBuffer slice1 = payload.slice();
+ ByteBuffer slice2 = payload.slice();
+
+ // slice at non-multiple of 4, but also where last buffer remaining
+ // is more than 4.
+ int slicePoint = 7;
+ slice1.limit(slicePoint);
+ slice2.position(slicePoint);
+
+ Assert.assertThat("Slices are setup right",slice1.remaining() + slice2.remaining(),is(messageSize));
+
+ demask.process(slice1);
+ demask.process(slice2);
+
+ LOG.debug("Post-Processed: {}",Hex.asHex(payload));
+
+ Assert.assertThat("Payload.remaining",payload.remaining(),is(messageSize));
+ for (int i = payload.position(); i < payload.limit(); i++)
+ {
+ Assert.assertThat("payload[" + i + "]",payload.get(i),is(msgChar));
+ }
+ }
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/payload/UTF8ValidatorTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/payload/UTF8ValidatorTest.java
deleted file mode 100644
index 0125cbd..0000000
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/payload/UTF8ValidatorTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.common.io.payload;
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.websocket.api.BadPayloadException;
-import org.eclipse.jetty.websocket.common.io.payload.UTF8Validator;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class UTF8ValidatorTest
-{
- private ByteBuffer asByteBuffer(String hexStr)
- {
- byte buf[] = TypeUtil.fromHexString(hexStr);
- return ByteBuffer.wrap(buf);
- }
-
- @Test
- public void testCase6_4_3()
- {
- ByteBuffer part1 = asByteBuffer("cebae1bdb9cf83cebcceb5"); // good
- ByteBuffer part2 = asByteBuffer("f4908080"); // INVALID
- ByteBuffer part3 = asByteBuffer("656469746564"); // good
-
- UTF8Validator validator = new UTF8Validator();
- validator.process(part1); // good
- try
- {
- validator.process(part2); // bad
- Assert.fail("Expected a " + BadPayloadException.class);
- }
- catch (BadPayloadException e)
- {
- // expected path
- }
- validator.process(part3); // good
- }
-}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/DummySocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/DummySocket.java
new file mode 100644
index 0000000..20e7208
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/DummySocket.java
@@ -0,0 +1,29 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+
+/**
+ * Do nothing Dummy Socket, used in testing.
+ */
+public class DummySocket extends WebSocketAdapter
+{
+ /* intentionally empty */
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageDebug.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageDebug.java
new file mode 100644
index 0000000..72436ca
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageDebug.java
@@ -0,0 +1,60 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import java.nio.ByteBuffer;
+
+public class MessageDebug
+{
+ public static String toDetailHint(byte[] data, int offset, int len)
+ {
+ StringBuilder buf = new StringBuilder();
+ ByteBuffer buffer = ByteBuffer.wrap(data,offset,len);
+
+ buf.append("byte[").append(data.length);
+ buf.append("](o=").append(offset);
+ buf.append(",len=").append(len);
+
+ buf.append(")<<<");
+ for (int i = buffer.position(); i < buffer.limit(); i++)
+ {
+ char c = (char)buffer.get(i);
+ if ((c >= ' ') && (c <= 127))
+ {
+ buf.append(c);
+ }
+ else if ((c == '\r') || (c == '\n'))
+ {
+ buf.append('|');
+ }
+ else
+ {
+ buf.append('\ufffd');
+ }
+ if ((i == (buffer.position() + 16)) && (buffer.limit() > (buffer.position() + 32)))
+ {
+ buf.append("...");
+ i = buffer.limit() - 16;
+ }
+ }
+ buf.append(">>>");
+
+ return buf.toString();
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageInputStreamTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageInputStreamTest.java
new file mode 100644
index 0000000..64291f1
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageInputStreamTest.java
@@ -0,0 +1,193 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import static org.hamcrest.Matchers.is;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.io.LocalWebSocketConnection;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class MessageInputStreamTest
+{
+ private static final Charset UTF8 = StringUtil.__UTF8_CHARSET;
+
+ @Rule
+ public TestName testname = new TestName();
+
+ @Test(timeout=10000)
+ public void testBasicAppendRead() throws IOException
+ {
+ LocalWebSocketConnection conn = new LocalWebSocketConnection(testname);
+
+ try (MessageInputStream stream = new MessageInputStream(conn))
+ {
+ // Append a message (simple, short)
+ ByteBuffer payload = BufferUtil.toBuffer("Hello World",UTF8);
+ System.out.printf("payload = %s%n",BufferUtil.toDetailString(payload));
+ boolean fin = true;
+ stream.appendMessage(payload,fin);
+
+ // Read it from the stream.
+ byte buf[] = new byte[32];
+ int len = stream.read(buf);
+ String message = new String(buf,0,len,UTF8);
+
+ // Test it
+ Assert.assertThat("Message",message,is("Hello World"));
+ }
+ }
+
+ @Test(timeout=2000)
+ public void testBlockOnRead() throws Exception
+ {
+ LocalWebSocketConnection conn = new LocalWebSocketConnection(testname);
+
+ try (MessageInputStream stream = new MessageInputStream(conn))
+ {
+ final AtomicBoolean hadError = new AtomicBoolean(false);
+ final CountDownLatch startLatch = new CountDownLatch(1);
+
+ new Thread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ startLatch.countDown();
+ boolean fin = false;
+ TimeUnit.MILLISECONDS.sleep(200);
+ stream.appendMessage(BufferUtil.toBuffer("Saved",UTF8),fin);
+ TimeUnit.MILLISECONDS.sleep(200);
+ stream.appendMessage(BufferUtil.toBuffer(" by ",UTF8),fin);
+ fin = true;
+ TimeUnit.MILLISECONDS.sleep(200);
+ stream.appendMessage(BufferUtil.toBuffer("Zero",UTF8),fin);
+ }
+ catch (IOException | InterruptedException e)
+ {
+ hadError.set(true);
+ e.printStackTrace(System.err);
+ }
+ }
+ }).start();
+
+ // wait for thread to start
+ startLatch.await();
+
+ // Read it from the stream.
+ byte buf[] = new byte[32];
+ int len = stream.read(buf);
+ String message = new String(buf,0,len,UTF8);
+
+ // Test it
+ Assert.assertThat("Error when appending",hadError.get(),is(false));
+ Assert.assertThat("Message",message,is("Saved by Zero"));
+ }
+ }
+
+ @Test(timeout=10000)
+ public void testBlockOnReadInitial() throws IOException
+ {
+ LocalWebSocketConnection conn = new LocalWebSocketConnection(testname);
+
+ try (MessageInputStream stream = new MessageInputStream(conn))
+ {
+ final AtomicBoolean hadError = new AtomicBoolean(false);
+
+ new Thread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ boolean fin = true;
+ // wait for a little bit before populating buffers
+ TimeUnit.MILLISECONDS.sleep(400);
+ stream.appendMessage(BufferUtil.toBuffer("I will conquer",UTF8),fin);
+ }
+ catch (IOException | InterruptedException e)
+ {
+ hadError.set(true);
+ e.printStackTrace(System.err);
+ }
+ }
+ }).start();
+
+ // Read byte from stream.
+ int b = stream.read();
+ // Should be a byte, blocking till byte received.
+
+ // Test it
+ Assert.assertThat("Error when appending",hadError.get(),is(false));
+ Assert.assertThat("Initial byte",b,is((int)'I'));
+ }
+ }
+
+ @Test(timeout=10000)
+ public void testReadByteNoBuffersClosed() throws IOException
+ {
+ LocalWebSocketConnection conn = new LocalWebSocketConnection(testname);
+
+ try (MessageInputStream stream = new MessageInputStream(conn))
+ {
+ final AtomicBoolean hadError = new AtomicBoolean(false);
+
+ new Thread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ // wait for a little bit before sending input closed
+ TimeUnit.MILLISECONDS.sleep(400);
+ stream.messageComplete();
+ }
+ catch (InterruptedException e)
+ {
+ hadError.set(true);
+ e.printStackTrace(System.err);
+ }
+ }
+ }).start();
+
+ // Read byte from stream.
+ int b = stream.read();
+ // Should be a -1, indicating the end of the stream.
+
+ // Test it
+ Assert.assertThat("Error when appending",hadError.get(),is(false));
+ Assert.assertThat("Initial byte",b,is(-1));
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageOutputStreamTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageOutputStreamTest.java
new file mode 100644
index 0000000..3d1c5b8
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageOutputStreamTest.java
@@ -0,0 +1,135 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+
+import java.util.Arrays;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
+import org.eclipse.jetty.websocket.common.io.FramePipes;
+import org.eclipse.jetty.websocket.common.io.LocalWebSocketSession;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class MessageOutputStreamTest
+{
+ private static final Logger LOG = Log.getLogger(MessageOutputStreamTest.class);
+
+ @Rule
+ public TestTracker testtracker = new TestTracker();
+
+ @Rule
+ public TestName testname = new TestName();
+
+ private WebSocketPolicy policy;
+ private TrackingSocket socket;
+ private LocalWebSocketSession session;
+
+ @After
+ public void closeSession()
+ {
+ session.close();
+ }
+
+ @Before
+ public void setupSession()
+ {
+ policy = WebSocketPolicy.newServerPolicy();
+ policy.setInputBufferSize(1024);
+ policy.setMaxBinaryMessageBufferSize(1024);
+
+ // Event Driver factory
+ EventDriverFactory factory = new EventDriverFactory(policy);
+
+ // local socket
+ EventDriver driver = factory.wrap(new TrackingSocket("local"));
+
+ // remote socket
+ socket = new TrackingSocket("remote");
+ OutgoingFrames socketPipe = FramePipes.to(factory.wrap(socket));
+
+ session = new LocalWebSocketSession(testname,driver);
+
+ session.setPolicy(policy);
+ // talk to our remote socket
+ session.setOutgoingHandler(socketPipe);
+ // open connection
+ session.open();
+ }
+
+ @Test
+ public void testMultipleWrites() throws Exception
+ {
+ try (MessageOutputStream stream = new MessageOutputStream(session))
+ {
+ stream.write("Hello".getBytes("UTF-8"));
+ stream.write(" ".getBytes("UTF-8"));
+ stream.write("World".getBytes("UTF-8"));
+ }
+
+ Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1));
+ String msg = socket.messageQueue.poll();
+ Assert.assertThat("Message",msg,allOf(containsString("byte[11]"),containsString("Hello World")));
+ }
+
+ @Test
+ public void testSingleWrite() throws Exception
+ {
+ try (MessageOutputStream stream = new MessageOutputStream(session))
+ {
+ stream.write("Hello World".getBytes("UTF-8"));
+ }
+
+ Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1));
+ String msg = socket.messageQueue.poll();
+ Assert.assertThat("Message",msg,allOf(containsString("byte[11]"),containsString("Hello World")));
+ }
+
+ @Test
+ public void testWriteMultipleBuffers() throws Exception
+ {
+ int bufsize = (int)(policy.getMaxBinaryMessageBufferSize() * 2.5);
+ byte buf[] = new byte[bufsize];
+ LOG.debug("Buffer size: {}",bufsize);
+ Arrays.fill(buf,(byte)'x');
+ buf[bufsize - 1] = (byte)'o'; // mark last entry for debugging
+
+ try (MessageOutputStream stream = new MessageOutputStream(session))
+ {
+ stream.write(buf);
+ }
+
+ Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1));
+ String msg = socket.messageQueue.poll();
+ Assert.assertThat("Message",msg,allOf(containsString("byte[" + bufsize + "]"),containsString("xxxo>>>")));
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageWriterTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageWriterTest.java
new file mode 100644
index 0000000..0e4e252
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageWriterTest.java
@@ -0,0 +1,134 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import static org.hamcrest.Matchers.is;
+
+import java.util.Arrays;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
+import org.eclipse.jetty.websocket.common.io.FramePipes;
+import org.eclipse.jetty.websocket.common.io.LocalWebSocketSession;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class MessageWriterTest
+{
+ private static final Logger LOG = Log.getLogger(MessageWriterTest.class);
+
+ @Rule
+ public TestTracker testtracker = new TestTracker();
+
+ @Rule
+ public TestName testname = new TestName();
+
+ private WebSocketPolicy policy;
+ private TrackingSocket socket;
+ private LocalWebSocketSession session;
+
+ @After
+ public void closeSession()
+ {
+ session.close();
+ }
+
+ @Before
+ public void setupSession()
+ {
+ policy = WebSocketPolicy.newServerPolicy();
+ policy.setInputBufferSize(1024);
+ policy.setMaxTextMessageBufferSize(1024);
+
+ // Event Driver factory
+ EventDriverFactory factory = new EventDriverFactory(policy);
+
+ // local socket
+ EventDriver driver = factory.wrap(new TrackingSocket("local"));
+
+ // remote socket
+ socket = new TrackingSocket("remote");
+ OutgoingFrames socketPipe = FramePipes.to(factory.wrap(socket));
+
+ session = new LocalWebSocketSession(testname,driver);
+
+ session.setPolicy(policy);
+ // talk to our remote socket
+ session.setOutgoingHandler(socketPipe);
+ // open connection
+ session.open();
+ }
+
+ @Test
+ public void testMultipleWrites() throws Exception
+ {
+ try (MessageWriter stream = new MessageWriter(session))
+ {
+ stream.write("Hello");
+ stream.write(" ");
+ stream.write("World");
+ }
+
+ Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1));
+ String msg = socket.messageQueue.poll();
+ Assert.assertThat("Message",msg,is("Hello World"));
+ }
+
+ @Test
+ public void testSingleWrite() throws Exception
+ {
+ try (MessageWriter stream = new MessageWriter(session))
+ {
+ stream.append("Hello World");
+ }
+
+ Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1));
+ String msg = socket.messageQueue.poll();
+ Assert.assertThat("Message",msg,is("Hello World"));
+ }
+
+ @Test
+ public void testWriteMultipleBuffers() throws Exception
+ {
+ int bufsize = (int)(policy.getMaxTextMessageBufferSize() * 2.5);
+ char buf[] = new char[bufsize];
+ LOG.debug("Buffer size: {}",bufsize);
+ Arrays.fill(buf,'x');
+ buf[bufsize - 1] = 'o'; // mark last entry for debugging
+
+ try (MessageWriter stream = new MessageWriter(session))
+ {
+ stream.write(buf);
+ }
+
+ Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1));
+ String msg = socket.messageQueue.poll();
+ String expected = new String(buf);
+ Assert.assertThat("Message",msg,is(expected));
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/TrackingInputStreamSocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/TrackingInputStreamSocket.java
new file mode 100644
index 0000000..0db83d8
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/TrackingInputStreamSocket.java
@@ -0,0 +1,110 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import static org.hamcrest.Matchers.is;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.junit.Assert;
+
+@WebSocket
+public class TrackingInputStreamSocket
+{
+ private static final Logger LOG = Log.getLogger(TrackingInputStreamSocket.class);
+ private final String id;
+ public int closeCode = -1;
+ public StringBuilder closeMessage = new StringBuilder();
+ public CountDownLatch closeLatch = new CountDownLatch(1);
+ public EventQueue<String> messageQueue = new EventQueue<>();
+ public EventQueue<Throwable> errorQueue = new EventQueue<>();
+
+ public TrackingInputStreamSocket()
+ {
+ this("socket");
+ }
+
+ public TrackingInputStreamSocket(String id)
+ {
+ this.id = id;
+ }
+
+ public void assertClose(int expectedStatusCode, String expectedReason) throws InterruptedException
+ {
+ assertCloseCode(expectedStatusCode);
+ assertCloseReason(expectedReason);
+ }
+
+ public void assertCloseCode(int expectedCode) throws InterruptedException
+ {
+ Assert.assertThat("Was Closed",closeLatch.await(50,TimeUnit.MILLISECONDS),is(true));
+ Assert.assertThat("Close Code",closeCode,is(expectedCode));
+ }
+
+ private void assertCloseReason(String expectedReason)
+ {
+ Assert.assertThat("Close Reason",closeMessage.toString(),is(expectedReason));
+ }
+
+ @OnWebSocketClose
+ public void onClose(int statusCode, String reason)
+ {
+ LOG.debug("{} onClose({},{})",id,statusCode,reason);
+ closeCode = statusCode;
+ closeMessage.append(reason);
+ closeLatch.countDown();
+ }
+
+ @OnWebSocketError
+ public void onError(Throwable cause)
+ {
+ errorQueue.add(cause);
+ }
+
+ @OnWebSocketMessage
+ public void onInputStream(InputStream stream)
+ {
+ LOG.debug("{} onInputStream({})",id,stream);
+ try
+ {
+ String msg = IO.toString(stream);
+ messageQueue.add(msg);
+ }
+ catch (IOException e)
+ {
+ errorQueue.add(e);
+ }
+ }
+
+ public void waitForClose(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+ {
+ Assert.assertThat("Client Socket Closed",closeLatch.await(timeoutDuration,timeoutUnit),is(true));
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/TrackingSocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/TrackingSocket.java
new file mode 100644
index 0000000..2c6f996
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/TrackingSocket.java
@@ -0,0 +1,170 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.junit.Assert;
+
+/**
+ * Testing Socket used on client side WebSocket testing.
+ */
+public class TrackingSocket extends WebSocketAdapter
+{
+ private static final Logger LOG = Log.getLogger(TrackingSocket.class);
+
+ private final String id;
+ public int closeCode = -1;
+ public StringBuilder closeMessage = new StringBuilder();
+ public CountDownLatch openLatch = new CountDownLatch(1);
+ public CountDownLatch closeLatch = new CountDownLatch(1);
+ public CountDownLatch dataLatch = new CountDownLatch(1);
+ public EventQueue<String> messageQueue = new EventQueue<>();
+ public EventQueue<Throwable> errorQueue = new EventQueue<>();
+
+ public TrackingSocket()
+ {
+ this("socket");
+ }
+
+ public TrackingSocket(String id)
+ {
+ this.id = id;
+ }
+
+ public void assertClose(int expectedStatusCode, String expectedReason) throws InterruptedException
+ {
+ assertCloseCode(expectedStatusCode);
+ assertCloseReason(expectedReason);
+ }
+
+ public void assertCloseCode(int expectedCode) throws InterruptedException
+ {
+ Assert.assertThat("Was Closed",closeLatch.await(50,TimeUnit.MILLISECONDS),is(true));
+ Assert.assertThat("Close Code",closeCode,is(expectedCode));
+ }
+
+ private void assertCloseReason(String expectedReason)
+ {
+ Assert.assertThat("Close Reason",closeMessage.toString(),is(expectedReason));
+ }
+
+ public void assertIsOpen() throws InterruptedException
+ {
+ assertWasOpened();
+ assertNotClosed();
+ }
+
+ public void assertMessage(String expected)
+ {
+ String actual = messageQueue.poll();
+ Assert.assertEquals("Message",expected,actual);
+ }
+
+ public void assertNotClosed()
+ {
+ Assert.assertThat("Closed Latch",closeLatch.getCount(),greaterThanOrEqualTo(1L));
+ }
+
+ public void assertNotOpened()
+ {
+ Assert.assertThat("Open Latch",openLatch.getCount(),greaterThanOrEqualTo(1L));
+ }
+
+ public void assertWasOpened() throws InterruptedException
+ {
+ Assert.assertThat("Was Opened",openLatch.await(500,TimeUnit.MILLISECONDS),is(true));
+ }
+
+ public void awaitMessage(int expectedMessageCount, TimeUnit timeoutUnit, int timeoutDuration) throws TimeoutException, InterruptedException
+ {
+ messageQueue.awaitEventCount(expectedMessageCount,timeoutDuration,timeoutUnit);
+ }
+
+ public void clear()
+ {
+ messageQueue.clear();
+ }
+
+ @Override
+ public void onWebSocketBinary(byte[] payload, int offset, int len)
+ {
+ LOG.debug("{} onWebSocketBinary(byte[{}],{},{})",id,payload.length,offset,len);
+ messageQueue.offer(MessageDebug.toDetailHint(payload,offset,len));
+ dataLatch.countDown();
+ }
+
+ @Override
+ public void onWebSocketClose(int statusCode, String reason)
+ {
+ LOG.debug("{} onWebSocketClose({},{})",id,statusCode,reason);
+ super.onWebSocketClose(statusCode,reason);
+ closeCode = statusCode;
+ closeMessage.append(reason);
+ closeLatch.countDown();
+ }
+
+ @Override
+ public void onWebSocketConnect(Session session)
+ {
+ super.onWebSocketConnect(session);
+ openLatch.countDown();
+ }
+
+ @Override
+ public void onWebSocketError(Throwable cause)
+ {
+ LOG.debug("{} onWebSocketError",id,cause);
+ Assert.assertThat("Error capture",errorQueue.offer(cause),is(true));
+ }
+
+ @Override
+ public void onWebSocketText(String message)
+ {
+ LOG.debug("{} onWebSocketText({})",id,message);
+ messageQueue.offer(message);
+ dataLatch.countDown();
+ }
+
+ public void waitForClose(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+ {
+ Assert.assertThat("Client Socket Closed",closeLatch.await(timeoutDuration,timeoutUnit),is(true));
+ }
+
+ public void waitForConnected(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+ {
+ Assert.assertThat("Client Socket Connected",openLatch.await(timeoutDuration,timeoutUnit),is(true));
+ }
+
+ public void waitForMessage(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+ {
+ LOG.debug("{} Waiting for message",id);
+ Assert.assertThat("Message Received",dataLatch.await(timeoutDuration,timeoutUnit),is(true));
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/Utf8CharBufferTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/Utf8CharBufferTest.java
new file mode 100644
index 0000000..7d19123
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/Utf8CharBufferTest.java
@@ -0,0 +1,125 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Utf8CharBufferTest
+{
+ private static String asString(ByteBuffer buffer)
+ {
+ return BufferUtil.toUTF8String(buffer);
+ }
+
+ private static byte[] asUTF(String str)
+ {
+ return str.getBytes(StringUtil.__UTF8_CHARSET);
+ }
+
+ @Test
+ public void testAppendGetAppendGet()
+ {
+ ByteBuffer buf = ByteBuffer.allocate(128);
+ Utf8CharBuffer utf = Utf8CharBuffer.wrap(buf);
+
+ byte hellobytes[] = asUTF("Hello ");
+ byte worldbytes[] = asUTF("World!");
+
+ utf.append(hellobytes, 0, hellobytes.length);
+ ByteBuffer hellobuf = utf.getByteBuffer();
+ utf.append(worldbytes, 0, worldbytes.length);
+ ByteBuffer worldbuf = utf.getByteBuffer();
+
+ Assert.assertThat("Hello buffer",asString(hellobuf),is("Hello "));
+ Assert.assertThat("World buffer",asString(worldbuf),is("Hello World!"));
+ }
+
+ @Test
+ public void testAppendGetClearAppendGet()
+ {
+ int bufsize = 128;
+ ByteBuffer buf = ByteBuffer.allocate(bufsize);
+ Utf8CharBuffer utf = Utf8CharBuffer.wrap(buf);
+
+ int expectedSize = bufsize / 2;
+ Assert.assertThat("Remaining (initial)",utf.remaining(),is(expectedSize));
+
+ byte hellobytes[] = asUTF("Hello World");
+
+ utf.append(hellobytes,0,hellobytes.length);
+ ByteBuffer hellobuf = utf.getByteBuffer();
+
+ Assert.assertThat("Remaining (after append)",utf.remaining(),is(expectedSize - hellobytes.length));
+ Assert.assertThat("Hello buffer",asString(hellobuf),is("Hello World"));
+
+ utf.clear();
+
+ Assert.assertThat("Remaining (after clear)",utf.remaining(),is(expectedSize));
+
+ byte whatnowbytes[] = asUTF("What Now?");
+ utf.append(whatnowbytes,0,whatnowbytes.length);
+ ByteBuffer whatnowbuf = utf.getByteBuffer();
+
+ Assert.assertThat("Remaining (after 2nd append)",utf.remaining(),is(expectedSize - whatnowbytes.length));
+ Assert.assertThat("What buffer",asString(whatnowbuf),is("What Now?"));
+ }
+
+ @Test
+ public void testAppendUnicodeGetBuffer()
+ {
+ ByteBuffer buf = ByteBuffer.allocate(64);
+ Utf8CharBuffer utf = Utf8CharBuffer.wrap(buf);
+
+ byte bb[] = asUTF("Hello A\u00ea\u00f1\u00fcC");
+ utf.append(bb,0,bb.length);
+
+ ByteBuffer actual = utf.getByteBuffer();
+ Assert.assertThat("Buffer length should be retained",actual.remaining(),is(bb.length));
+ Assert.assertThat("Message",asString(actual),is("Hello A\u00ea\u00f1\u00fcC"));
+ }
+
+ @Test
+ public void testSimpleGetBuffer()
+ {
+ int bufsize = 64;
+ ByteBuffer buf = ByteBuffer.allocate(bufsize);
+ Utf8CharBuffer utf = Utf8CharBuffer.wrap(buf);
+
+ int expectedSize = bufsize / 2;
+ Assert.assertThat("Remaining (initial)",utf.remaining(),is(expectedSize));
+
+ byte bb[] = asUTF("Hello World");
+ utf.append(bb,0,bb.length);
+
+ expectedSize -= bb.length;
+ Assert.assertThat("Remaining (after append)",utf.remaining(),is(expectedSize));
+
+ ByteBuffer actual = utf.getByteBuffer();
+ Assert.assertThat("Buffer length",actual.remaining(),is(bb.length));
+
+ Assert.assertThat("Message",asString(actual),is("Hello World"));
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-common/src/test/resources/jetty-logging.properties
index 17cb306..683b76a 100644
--- a/jetty-websocket/websocket-common/src/test/resources/jetty-logging.properties
+++ b/jetty-websocket/websocket-common/src/test/resources/jetty-logging.properties
@@ -1,6 +1,7 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.LEVEL=WARN
# org.eclipse.jetty.websocket.LEVEL=DEBUG
# org.eclipse.jetty.websocket.protocol.Parser.LEVEL=DEBUG
# org.eclipse.jetty.websocket.protocol.LEVEL=DEBUG
# org.eclipse.jetty.websocket.io.payload.LEVEL=DEBUG
-# org.eclipse.jetty.websocket.core.extensions.compress.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.common.extensions.LEVEL=DEBUG
diff --git a/jetty-websocket/websocket-mux-extension/pom.xml b/jetty-websocket/websocket-mux-extension/pom.xml
new file mode 100644
index 0000000..59e0b20
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/pom.xml
@@ -0,0 +1,56 @@
+<?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.websocket</groupId>
+ <artifactId>websocket-parent</artifactId>
+ <version>9.1.1-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>websocket-mux-extension</artifactId>
+ <name>Jetty :: Websocket :: Mux Extension</name>
+
+ <properties>
+ <bundle-symbolic-name>${project.groupId}.mux</bundle-symbolic-name>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>websocket-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>websocket-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-test-helper</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>tests-jar</id>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/AbstractMuxExtension.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/AbstractMuxExtension.java
new file mode 100644
index 0000000..d57559b
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/AbstractMuxExtension.java
@@ -0,0 +1,65 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
+
+/**
+ * Multiplexing Extension for WebSockets.
+ * <p>
+ * Supporting <a href="https://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-08">draft-ietf-hybi-websocket-multiplexing-08</a> Specification.
+ */
+public abstract class AbstractMuxExtension extends AbstractExtension
+{
+ private Muxer muxer;
+
+ public AbstractMuxExtension()
+ {
+ super();
+ }
+
+ public abstract void configureMuxer(Muxer muxer);
+
+ @Override
+ public void incomingFrame(Frame frame)
+ {
+ this.muxer.incomingFrame(frame);
+ }
+
+ @Override
+ public void outgoingFrame(Frame frame, WriteCallback callback)
+ {
+ /* do nothing here, allow Muxer to handle this aspect */
+ }
+
+ @Override
+ public void setConnection(LogicalConnection connection)
+ {
+ super.setConnection(connection);
+ if (muxer != null)
+ {
+ throw new RuntimeException("Cannot reset muxer physical connection once established");
+ }
+ this.muxer = new Muxer(connection);
+ configureMuxer(this.muxer);
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxChannel.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxChannel.java
new file mode 100644
index 0000000..41014b7
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxChannel.java
@@ -0,0 +1,275 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.SuspendToken;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.ConnectionState;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
+import org.eclipse.jetty.websocket.common.io.IOState;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
+
+/**
+ * MuxChannel, acts as WebSocketConnection for specific sub-channel.
+ */
+public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendToken, ConnectionStateListener
+{
+ private static final Logger LOG = Log.getLogger(MuxChannel.class);
+
+ private final long channelId;
+ private final Muxer muxer;
+ private final AtomicBoolean inputClosed;
+ private final AtomicBoolean outputClosed;
+ private final AtomicBoolean suspendToken;
+ private IOState ioState;
+ private WebSocketPolicy policy;
+ private WebSocketSession session;
+ private IncomingFrames incoming;
+ private String subProtocol;
+
+ public MuxChannel(long channelId, Muxer muxer)
+ {
+ this.channelId = channelId;
+ this.muxer = muxer;
+ this.policy = muxer.getPolicy().clonePolicy();
+
+ this.suspendToken = new AtomicBoolean(false);
+ this.ioState = new IOState();
+ this.ioState.addListener(this);
+
+ this.inputClosed = new AtomicBoolean(false);
+ this.outputClosed = new AtomicBoolean(false);
+ }
+
+ @Override
+ public Executor getExecutor()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void close()
+ {
+ close(StatusCode.NORMAL,null);
+ }
+
+ @Override
+ public void close(int statusCode, String reason)
+ {
+ CloseInfo close = new CloseInfo(statusCode,reason);
+ // TODO: disconnect callback?
+ outgoingFrame(close.asFrame(),null);
+ }
+
+ @Override
+ public void disconnect()
+ {
+ // TODO: disconnect the virtual end-point?
+ }
+
+ @Override
+ public ByteBufferPool getBufferPool()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public long getChannelId()
+ {
+ return channelId;
+ }
+
+ @Override
+ public long getIdleTimeout()
+ {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public IOState getIOState()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public InetSocketAddress getLocalAddress()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public long getMaxIdleTimeout()
+ {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public WebSocketPolicy getPolicy()
+ {
+ return policy;
+ }
+
+ @Override
+ public InetSocketAddress getRemoteAddress()
+ {
+ return muxer.getRemoteAddress();
+ }
+
+ @Override
+ public WebSocketSession getSession()
+ {
+ return session;
+ }
+
+ /**
+ * Incoming exceptions from Muxer.
+ */
+ @Override
+ public void incomingError(Throwable e)
+ {
+ incoming.incomingError(e);
+ }
+
+ /**
+ * Incoming frames from Muxer
+ */
+ @Override
+ public void incomingFrame(Frame frame)
+ {
+ incoming.incomingFrame(frame);
+ }
+
+ public boolean isActive()
+ {
+ return (ioState.isOpen());
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return isActive() && muxer.isOpen();
+ }
+
+ @Override
+ public boolean isReading()
+ {
+ return true;
+ }
+
+ public void onClose()
+ {
+ }
+
+ @Override
+ public void onConnectionStateChange(ConnectionState state)
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void onOpen()
+ {
+ this.ioState.onOpened();
+ }
+
+ /**
+ * Internal
+ *
+ * @param frame the frame to write
+ * @return the future for the network write of the frame
+ */
+ private Future<Void> outgoingAsyncFrame(WebSocketFrame frame)
+ {
+ FutureWriteCallback future = new FutureWriteCallback();
+ outgoingFrame(frame,future);
+ return future;
+ }
+
+ /**
+ * Frames destined for the Muxer
+ */
+ @Override
+ public void outgoingFrame(Frame frame, WriteCallback callback)
+ {
+ muxer.output(channelId,frame,callback);
+ }
+
+ @Override
+ public void resume()
+ {
+ if (suspendToken.getAndSet(false))
+ {
+ // TODO: Start reading again. (how?)
+ }
+ }
+
+ @Override
+ public void setMaxIdleTimeout(long ms)
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setNextIncomingFrames(IncomingFrames incoming)
+ {
+ this.incoming = incoming;
+ }
+
+ @Override
+ public void setSession(WebSocketSession session)
+ {
+ this.session = session;
+ // session.setOutgoing(this);
+ }
+
+ public void setSubProtocol(String subProtocol)
+ {
+ this.subProtocol = subProtocol;
+ }
+
+ @Override
+ public SuspendToken suspend()
+ {
+ suspendToken.set(true);
+ // TODO: how to suspend reading?
+ return this;
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxControlBlock.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxControlBlock.java
new file mode 100644
index 0000000..230a867
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxControlBlock.java
@@ -0,0 +1,24 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+public interface MuxControlBlock
+{
+ public int getOpCode();
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxException.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxException.java
new file mode 100644
index 0000000..162361f
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxException.java
@@ -0,0 +1,40 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import org.eclipse.jetty.websocket.api.WebSocketException;
+
+@SuppressWarnings("serial")
+public class MuxException extends WebSocketException
+{
+ public MuxException(String message)
+ {
+ super(message);
+ }
+
+ public MuxException(String message, Throwable cause)
+ {
+ super(message,cause);
+ }
+
+ public MuxException(Throwable cause)
+ {
+ super(cause);
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxGenerator.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxGenerator.java
new file mode 100644
index 0000000..20a9611
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxGenerator.java
@@ -0,0 +1,272 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.io.ArrayByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelRequest;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse;
+import org.eclipse.jetty.websocket.mux.op.MuxDropChannel;
+import org.eclipse.jetty.websocket.mux.op.MuxFlowControl;
+import org.eclipse.jetty.websocket.mux.op.MuxNewChannelSlot;
+
+/**
+ * Generate Mux frames destined for the physical connection.
+ */
+public class MuxGenerator
+{
+ private static final int CONTROL_BUFFER_SIZE = 2 * 1024;
+ /** 4 bytes for channel ID + 1 for fin/rsv/opcode */
+ private static final int DATA_FRAME_OVERHEAD = 5;
+ private ByteBufferPool bufferPool;
+ private OutgoingFrames outgoing;
+
+ public MuxGenerator()
+ {
+ this(new ArrayByteBufferPool());
+ }
+
+ public MuxGenerator(ByteBufferPool bufferPool)
+ {
+ this.bufferPool = bufferPool;
+ }
+
+ public void generate(long channelId, Frame frame, WriteCallback callback)
+ {
+ ByteBuffer muxPayload = bufferPool.acquire(frame.getPayloadLength() + DATA_FRAME_OVERHEAD,false);
+ BufferUtil.flipToFill(muxPayload);
+
+ // start building mux payload
+ writeChannelId(muxPayload,channelId);
+ byte b = (byte)(frame.isFin()?0x80:0x00); // fin
+ b |= (byte)(frame.isRsv1()?0x40:0x00); // rsv1
+ b |= (byte)(frame.isRsv2()?0x20:0x00); // rsv2
+ b |= (byte)(frame.isRsv3()?0x10:0x00); // rsv3
+ b |= (byte)(frame.getOpCode() & 0x0F); // opcode
+ muxPayload.put(b);
+ BufferUtil.put(frame.getPayload(),muxPayload);
+
+ // build muxed frame
+ WebSocketFrame muxFrame = new BinaryFrame();
+ BufferUtil.flipToFlush(muxPayload,0);
+ muxFrame.setPayload(muxPayload);
+ // NOTE: the physical connection will handle masking rules for this frame.
+
+ // release original buffer (no longer needed)
+ bufferPool.release(frame.getPayload());
+
+ // send muxed frame down to the physical connection.
+ outgoing.outgoingFrame(muxFrame,callback);
+ }
+
+ public void generate(WriteCallback callback,MuxControlBlock... blocks) throws IOException
+ {
+ if ((blocks == null) || (blocks.length <= 0))
+ {
+ return; // nothing to do
+ }
+
+ ByteBuffer payload = bufferPool.acquire(CONTROL_BUFFER_SIZE,false);
+ BufferUtil.flipToFill(payload);
+
+ writeChannelId(payload,0); // control channel
+
+ for (MuxControlBlock block : blocks)
+ {
+ switch (block.getOpCode())
+ {
+ case MuxOp.ADD_CHANNEL_REQUEST:
+ {
+ MuxAddChannelRequest op = (MuxAddChannelRequest)block;
+ byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
+ b |= (byte)((op.getRsv() & 0x07) << 2); // rsv
+ b |= (op.getEncoding() & 0x03); // enc
+ payload.put(b); // opcode + rsv + enc
+ writeChannelId(payload,op.getChannelId());
+ write139Buffer(payload,op.getHandshake());
+ break;
+ }
+ case MuxOp.ADD_CHANNEL_RESPONSE:
+ {
+ MuxAddChannelResponse op = (MuxAddChannelResponse)block;
+ byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
+ b |= (op.isFailed()?0x10:0x00); // failure bit
+ b |= (byte)((op.getRsv() & 0x03) << 2); // rsv
+ b |= (op.getEncoding() & 0x03); // enc
+ payload.put(b); // opcode + f + rsv + enc
+ writeChannelId(payload,op.getChannelId());
+ if (op.getHandshake() != null)
+ {
+ write139Buffer(payload,op.getHandshake());
+ }
+ else
+ {
+ // no handshake details
+ write139Size(payload,0);
+ }
+ break;
+ }
+ case MuxOp.DROP_CHANNEL:
+ {
+ MuxDropChannel op = (MuxDropChannel)block;
+ byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
+ b |= (byte)(op.getRsv() & 0x1F); // rsv
+ payload.put(b); // opcode + rsv
+ writeChannelId(payload,op.getChannelId());
+ write139Buffer(payload,op.asReasonBuffer());
+ break;
+ }
+ case MuxOp.FLOW_CONTROL:
+ {
+ MuxFlowControl op = (MuxFlowControl)block;
+ byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
+ b |= (byte)(op.getRsv() & 0x1F); // rsv
+ payload.put(b); // opcode + rsv
+ writeChannelId(payload,op.getChannelId());
+ write139Size(payload,op.getSendQuotaSize());
+ break;
+ }
+ case MuxOp.NEW_CHANNEL_SLOT:
+ {
+ MuxNewChannelSlot op = (MuxNewChannelSlot)block;
+ byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
+ b |= (byte)(op.getRsv() & 0x0F) << 1; // rsv
+ b |= (byte)(op.isFallback()?0x01:0x00); // fallback bit
+ payload.put(b); // opcode + rsv + fallback bit
+ write139Size(payload,op.getNumberOfSlots());
+ write139Size(payload,op.getInitialSendQuota());
+ break;
+ }
+ }
+ }
+ BufferUtil.flipToFlush(payload,0);
+ WebSocketFrame frame = new BinaryFrame();
+ frame.setPayload(payload);
+ outgoing.outgoingFrame(frame,callback);
+ }
+
+ public OutgoingFrames getOutgoing()
+ {
+ return outgoing;
+ }
+
+ public void setOutgoing(OutgoingFrames outgoing)
+ {
+ this.outgoing = outgoing;
+ }
+
+ /**
+ * Write a 1/3/9 encoded size, then a byte buffer of that size.
+ *
+ * @param payload
+ * @param buffer
+ */
+ public void write139Buffer(ByteBuffer payload, ByteBuffer buffer)
+ {
+ write139Size(payload,buffer.remaining());
+ writeBuffer(payload,buffer);
+ }
+
+ /**
+ * Write a 1/3/9 encoded size.
+ *
+ * @param payload
+ * @param size
+ */
+ public void write139Size(ByteBuffer payload, long size)
+ {
+ if (size > 0xFF_FF)
+ {
+ // 9 byte encoded
+ payload.put((byte)0x7F);
+ payload.putLong(size);
+ return;
+ }
+
+ if (size >= 0x7E)
+ {
+ // 3 byte encoded
+ payload.put((byte)0x7E);
+ payload.put((byte)(size >> 8));
+ payload.put((byte)(size & 0xFF));
+ return;
+ }
+
+ // 1 byte (7 bit) encoded
+ payload.put((byte)(size & 0x7F));
+ }
+
+ public void writeBuffer(ByteBuffer payload, ByteBuffer buffer)
+ {
+ BufferUtil.put(buffer,payload);
+ }
+
+ /**
+ * Write multiplexing channel id, using logical channel id encoding (of 1,2,3, or 4 octets)
+ *
+ * @param payload
+ * @param channelId
+ */
+ public void writeChannelId(ByteBuffer payload, long channelId)
+ {
+ if (channelId > 0x1F_FF_FF_FF)
+ {
+ throw new MuxException("Illegal Channel ID: too big");
+ }
+
+ if (channelId > 0x1F_FF_FF)
+ {
+ // 29 bit channel id (4 bytes)
+ payload.put((byte)(0xE0 | ((channelId >> 24) & 0x1F)));
+ payload.put((byte)((channelId >> 16) & 0xFF));
+ payload.put((byte)((channelId >> 8) & 0xFF));
+ payload.put((byte)(channelId & 0xFF));
+ return;
+ }
+
+ if (channelId > 0x3F_FF)
+ {
+ // 21 bit channel id (3 bytes)
+ payload.put((byte)(0xC0 | ((channelId >> 16) & 0x1F)));
+ payload.put((byte)((channelId >> 8) & 0xFF));
+ payload.put((byte)(channelId & 0xFF));
+ return;
+ }
+
+ if (channelId > 0x7F)
+ {
+ // 14 bit channel id (2 bytes)
+ payload.put((byte)(0x80 | ((channelId >> 8) & 0x3F)));
+ payload.put((byte)(channelId & 0xFF));
+ return;
+ }
+
+ // 7 bit channel id
+ payload.put((byte)(channelId & 0x7F));
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxOp.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxOp.java
new file mode 100644
index 0000000..17b61a7
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxOp.java
@@ -0,0 +1,28 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+public final class MuxOp
+{
+ public static final byte ADD_CHANNEL_REQUEST = 0;
+ public static final byte ADD_CHANNEL_RESPONSE = 1;
+ public static final byte FLOW_CONTROL = 2;
+ public static final byte DROP_CHANNEL = 3;
+ public static final byte NEW_CHANNEL_SLOT = 4;
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxParser.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxParser.java
new file mode 100644
index 0000000..fd1d9f0
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxParser.java
@@ -0,0 +1,410 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelRequest;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse;
+import org.eclipse.jetty.websocket.mux.op.MuxDropChannel;
+import org.eclipse.jetty.websocket.mux.op.MuxFlowControl;
+import org.eclipse.jetty.websocket.mux.op.MuxNewChannelSlot;
+
+public class MuxParser
+{
+ public static interface Listener
+ {
+ public void onMuxAddChannelRequest(MuxAddChannelRequest request);
+
+ public void onMuxAddChannelResponse(MuxAddChannelResponse response);
+
+ public void onMuxDropChannel(MuxDropChannel drop);
+
+ public void onMuxedFrame(MuxedFrame frame);
+
+ public void onMuxException(MuxException e);
+
+ public void onMuxFlowControl(MuxFlowControl flow);
+
+ public void onMuxNewChannelSlot(MuxNewChannelSlot slot);
+ }
+
+ private final static Logger LOG = Log.getLogger(MuxParser.class);
+
+ private MuxedFrame muxframe = new MuxedFrame();
+ private MuxParser.Listener events;
+ private long channelId;
+
+ public MuxParser.Listener getEvents()
+ {
+ return events;
+ }
+
+ /**
+ * Parse the raw {@link WebSocketFrame} payload data for various Mux frames.
+ *
+ * @param frame
+ * the WebSocketFrame to parse for mux payload
+ */
+ public synchronized void parse(Frame frame)
+ {
+ if (events == null)
+ {
+ throw new RuntimeException("No " + MuxParser.Listener.class + " specified");
+ }
+
+ if (!frame.hasPayload())
+ {
+ LOG.debug("No payload data, skipping");
+ return; // nothing to parse
+ }
+
+ if (frame.getOpCode() != OpCode.BINARY)
+ {
+ LOG.debug("Not a binary opcode (base frame), skipping");
+ return; // not a binary opcode
+ }
+
+ LOG.debug("Parsing Mux Payload of {}",frame);
+
+ try
+ {
+ ByteBuffer buffer = frame.getPayload().slice();
+
+ if (buffer.remaining() <= 0)
+ {
+ return;
+ }
+
+ if (frame.getOpCode() == OpCode.CONTINUATION)
+ {
+ muxframe.reset();
+ muxframe.setFin(frame.isFin());
+ muxframe.setFin(frame.isRsv1());
+ muxframe.setFin(frame.isRsv2());
+ muxframe.setFin(frame.isRsv3());
+ muxframe.setIsContinuation();
+ parseDataFramePayload(buffer);
+ }
+ else
+ {
+ // new frame
+ channelId = readChannelId(buffer);
+ if (channelId == 0)
+ {
+ parseControlBlocks(buffer);
+ }
+ else
+ {
+ parseDataFrame(buffer);
+ }
+ }
+ }
+ catch (MuxException e)
+ {
+ events.onMuxException(e);
+ }
+ catch (Throwable t)
+ {
+ events.onMuxException(new MuxException(t));
+ }
+ }
+
+ private void parseControlBlocks(ByteBuffer buffer)
+ {
+ // process the remaining buffer here.
+ while (buffer.remaining() > 0)
+ {
+ byte b = buffer.get();
+ byte opc = (byte)((byte)(b >> 5) & 0xFF);
+ b = (byte)(b & 0x1F);
+
+ try {
+ switch (opc)
+ {
+ case MuxOp.ADD_CHANNEL_REQUEST:
+ {
+ MuxAddChannelRequest op = new MuxAddChannelRequest();
+ op.setRsv((byte)((b & 0x1C) >> 2));
+ op.setEncoding((byte)(b & 0x03));
+ op.setChannelId(readChannelId(buffer));
+ long handshakeSize = read139EncodedSize(buffer);
+ op.setHandshake(readBlock(buffer,handshakeSize));
+ events.onMuxAddChannelRequest(op);
+ break;
+ }
+ case MuxOp.ADD_CHANNEL_RESPONSE:
+ {
+ MuxAddChannelResponse op = new MuxAddChannelResponse();
+ op.setFailed((b & 0x10) != 0);
+ op.setRsv((byte)((byte)(b & 0x0C) >> 2));
+ op.setEncoding((byte)(b & 0x03));
+ op.setChannelId(readChannelId(buffer));
+ long handshakeSize = read139EncodedSize(buffer);
+ op.setHandshake(readBlock(buffer,handshakeSize));
+ events.onMuxAddChannelResponse(op);
+ break;
+ }
+ case MuxOp.DROP_CHANNEL:
+ {
+ int rsv = (b & 0x1F);
+ long channelId = readChannelId(buffer);
+ long reasonSize = read139EncodedSize(buffer);
+ ByteBuffer reasonBuf = readBlock(buffer,reasonSize);
+ MuxDropChannel op = MuxDropChannel.parse(channelId,reasonBuf);
+ op.setRsv(rsv);
+ events.onMuxDropChannel(op);
+ break;
+ }
+ case MuxOp.FLOW_CONTROL:
+ {
+ MuxFlowControl op = new MuxFlowControl();
+ op.setRsv((byte)(b & 0x1F));
+ op.setChannelId(readChannelId(buffer));
+ op.setSendQuotaSize(read139EncodedSize(buffer));
+ events.onMuxFlowControl(op);
+ break;
+ }
+ case MuxOp.NEW_CHANNEL_SLOT:
+ {
+ MuxNewChannelSlot op = new MuxNewChannelSlot();
+ op.setRsv((byte)((b & 0x1E) >> 1));
+ op.setFallback((b & 0x01) != 0);
+ op.setNumberOfSlots(read139EncodedSize(buffer));
+ op.setInitialSendQuota(read139EncodedSize(buffer));
+ events.onMuxNewChannelSlot(op);
+ break;
+ }
+ default:
+ {
+ String err = String.format("Unknown Mux Control Code OPC [0x%X]",opc);
+ throw new MuxException(err);
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ LOG.warn(t);
+ throw new MuxException(t);
+ }
+ }
+ }
+
+ private void parseDataFrame(ByteBuffer buffer)
+ {
+ byte b = buffer.get();
+ boolean fin = ((b & 0x80) != 0);
+ boolean rsv1 = ((b & 0x40) != 0);
+ boolean rsv2 = ((b & 0x20) != 0);
+ boolean rsv3 = ((b & 0x10) != 0);
+ byte opcode = (byte)(b & 0x0F);
+
+ if (opcode == OpCode.CONTINUATION)
+ {
+ muxframe.setIsContinuation();
+ }
+ else
+ {
+ muxframe.reset();
+ muxframe.setOp(opcode);
+ }
+
+ muxframe.setChannelId(channelId);
+ muxframe.setFin(fin);
+ muxframe.setRsv1(rsv1);
+ muxframe.setRsv2(rsv2);
+ muxframe.setRsv3(rsv3);
+
+ parseDataFramePayload(buffer);
+ }
+
+ private void parseDataFramePayload(ByteBuffer buffer)
+ {
+ int capacity = buffer.remaining();
+ ByteBuffer payload = ByteBuffer.allocate(capacity);
+ payload.put(buffer);
+ BufferUtil.flipToFlush(payload,0);
+ muxframe.setPayload(payload);
+ try
+ {
+ LOG.debug("notifyFrame() - {}",muxframe);
+ events.onMuxedFrame(muxframe);
+ }
+ catch (Throwable t)
+ {
+ LOG.warn(t);
+ }
+ }
+
+ /**
+ * Per section <a href="https://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing#section-9.1">9.1. Number Encoding in Multiplex Control
+ * Blocks</a>, read the 1/3/9 byte length using <a href="https://tools.ietf.org/html/rfc6455#section-5.2">Section 5.2 of RFC 6455</a>.
+ *
+ * @param buffer
+ * the buffer to read from
+ * @return the decoded size
+ * @throws MuxException
+ * when the encoding does not make sense per the spec, or it is a value above {@link Long#MAX_VALUE}
+ */
+ public long read139EncodedSize(ByteBuffer buffer)
+ {
+ long ret = -1;
+ long minValue = 0x00; // used to validate minimum # of bytes (per spec)
+ int cursor = 0;
+
+ byte b = buffer.get();
+ ret = (b & 0x7F);
+
+ if (ret == 0x7F)
+ {
+ // 9 byte length
+ ret = 0;
+ minValue = 0xFF_FF;
+ cursor = 8;
+ }
+ else if (ret == 0x7E)
+ {
+ // 3 byte length
+ ret = 0;
+ minValue = 0x7F;
+ cursor = 2;
+ }
+ else
+ {
+ // 1 byte length
+ // no validation of minimum bytes needed here
+ return ret;
+ }
+
+ // parse multi-byte length
+ while (cursor > 0)
+ {
+ ret = ret << 8;
+ b = buffer.get();
+ ret |= (b & 0xFF);
+ --cursor;
+ }
+
+ // validate minimum value per spec.
+ if (ret <= minValue)
+ {
+ String err = String.format("Invalid 1/3/9 length 0x%X (minimum value for chosen encoding is 0x%X)",ret,minValue);
+ throw new MuxException(err);
+ }
+
+ return ret;
+ }
+
+ private ByteBuffer readBlock(ByteBuffer buffer, long size)
+ {
+ if (size == 0)
+ {
+ return null;
+ }
+
+ if (size > buffer.remaining())
+ {
+ String err = String.format("Truncated data, expected %,d byte(s), but only %,d byte(s) remain",size,buffer.remaining());
+ throw new MuxException(err);
+ }
+
+ if (size > Integer.MAX_VALUE)
+ {
+ String err = String.format("[Int-Sane!] Buffer size %,d is too large to be supported (max allowed is %,d)",size,Integer.MAX_VALUE);
+ throw new MuxException(err);
+ }
+
+ ByteBuffer ret = ByteBuffer.allocate((int)size);
+ BufferUtil.put(buffer,ret);
+ BufferUtil.flipToFlush(ret,0);
+ return ret;
+ }
+
+ /**
+ * Read Channel ID using <a href="https://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing#section-7">Section 7. Framing</a> techniques
+ *
+ * @param buffer
+ * the buffer to parse from.
+ * @return the channel Id
+ * @throws MuxException
+ * when the encoding does not make sense per the spec.
+ */
+ public long readChannelId(ByteBuffer buffer)
+ {
+ long id = -1;
+ long minValue = 0x00; // used to validate minimum # of bytes (per spec)
+ byte b = buffer.get();
+ int cursor = -1;
+ if ((b & 0x80) == 0)
+ {
+ // 7 bit channel id
+ // no validation of minimum bytes needed here
+ return (b & 0x7F);
+ }
+ else if ((b & 0x40) == 0)
+ {
+ // 14 bit channel id
+ id = (b & 0x3F);
+ minValue = 0x7F;
+ cursor = 1;
+ }
+ else if ((b & 0x20) == 0)
+ {
+ // 21 bit channel id
+ id = (b & 0x1F);
+ minValue = 0x3F_FF;
+ cursor = 2;
+ }
+ else
+ {
+ // 29 bit channel id
+ id = (b & 0x1F);
+ minValue = 0x1F_FF_FF;
+ cursor = 3;
+ }
+
+ while (cursor > 0)
+ {
+ id = id << 8;
+ b = buffer.get();
+ id |= (b & 0xFF);
+ --cursor;
+ }
+
+ // validate minimum value per spec.
+ if (id <= minValue)
+ {
+ String err = String.format("Invalid Channel ID 0x%X (minimum value for chosen encoding is 0x%X)",id,minValue);
+ throw new MuxException(err);
+ }
+
+ return id;
+ }
+
+ public void setEvents(MuxParser.Listener events)
+ {
+ this.events = events;
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxPhysicalConnectionException.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxPhysicalConnectionException.java
new file mode 100644
index 0000000..d808dfe
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxPhysicalConnectionException.java
@@ -0,0 +1,44 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import org.eclipse.jetty.websocket.mux.op.MuxDropChannel;
+
+public class MuxPhysicalConnectionException extends MuxException
+{
+ private static final long serialVersionUID = 1L;
+ private MuxDropChannel drop;
+
+ public MuxPhysicalConnectionException(MuxDropChannel.Reason code, String phrase)
+ {
+ super(phrase);
+ drop = new MuxDropChannel(0,code,phrase);
+ }
+
+ public MuxPhysicalConnectionException(MuxDropChannel.Reason code, String phrase, Throwable t)
+ {
+ super(phrase,t);
+ drop = new MuxDropChannel(0,code,phrase);
+ }
+
+ public MuxDropChannel getMuxDropChannel()
+ {
+ return drop;
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxRequest.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxRequest.java
new file mode 100644
index 0000000..deb623f
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxRequest.java
@@ -0,0 +1,63 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+
+public class MuxRequest extends UpgradeRequest
+{
+ public static final String HEADER_VALUE_DELIM="\"\\\n\r\t\f\b%+ ;=";
+
+ public static UpgradeRequest merge(UpgradeRequest baseReq, UpgradeRequest deltaReq)
+ {
+ MuxRequest req = new MuxRequest(baseReq);
+
+ // TODO: finish
+
+ return req;
+ }
+
+ private static String overlay(String val, String defVal)
+ {
+ if (val == null)
+ {
+ return defVal;
+ }
+ return val;
+ }
+
+ public static UpgradeRequest parse(ByteBuffer handshake)
+ {
+ MuxRequest req = new MuxRequest();
+ // TODO Auto-generated method stub
+ return req;
+ }
+
+ public MuxRequest()
+ {
+ super();
+ }
+
+ public MuxRequest(UpgradeRequest copy)
+ {
+ super();
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxResponse.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxResponse.java
new file mode 100644
index 0000000..cd2eaf6
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxResponse.java
@@ -0,0 +1,26 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+
+public class MuxResponse extends UpgradeResponse
+{
+
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxedFrame.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxedFrame.java
new file mode 100644
index 0000000..64142b4
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxedFrame.java
@@ -0,0 +1,78 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
+
+public class MuxedFrame extends DataFrame
+{
+ private long channelId = -1;
+
+ public MuxedFrame()
+ {
+ super(OpCode.BINARY);
+ }
+
+ public MuxedFrame(MuxedFrame frame)
+ {
+ super(frame);
+ this.channelId = frame.channelId;
+ }
+
+ public long getChannelId()
+ {
+ return channelId;
+ }
+
+ @Override
+ public void reset()
+ {
+ super.reset();
+ this.channelId = -1;
+ }
+
+ public void setChannelId(long channelId)
+ {
+ this.channelId = channelId;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder b = new StringBuilder();
+ b.append(OpCode.name(getOpCode()));
+ b.append('[');
+ b.append("channel=").append(channelId);
+ b.append(",len=").append(getPayloadLength());
+ b.append(",fin=").append(isFin());
+ b.append(",rsv=");
+ b.append(isRsv1()?'1':'.');
+ b.append(isRsv2()?'1':'.');
+ b.append(isRsv3()?'1':'.');
+ b.append(']');
+ return b.toString();
+ }
+
+ public void setOp(byte opcode)
+ {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/Muxer.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/Muxer.java
new file mode 100644
index 0000000..fb79f8d
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/Muxer.java
@@ -0,0 +1,439 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.frames.ControlFrame;
+import org.eclipse.jetty.websocket.mux.add.MuxAddClient;
+import org.eclipse.jetty.websocket.mux.add.MuxAddServer;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelRequest;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse;
+import org.eclipse.jetty.websocket.mux.op.MuxDropChannel;
+import org.eclipse.jetty.websocket.mux.op.MuxFlowControl;
+import org.eclipse.jetty.websocket.mux.op.MuxNewChannelSlot;
+
+/**
+ * Muxer responsible for managing sub-channels.
+ * <p>
+ * Maintains a 1 (incoming and outgoing mux encapsulated frames) to many (per-channel incoming/outgoing standard websocket frames) relationship, along with
+ * routing of {@link MuxControlBlock} events.
+ * <p>
+ * Control Channel events (channel ID == 0) are handled by the Muxer.
+ */
+public class Muxer implements IncomingFrames, MuxParser.Listener
+{
+ private static final int CONTROL_CHANNEL_ID = 0;
+
+ private static final Logger LOG = Log.getLogger(Muxer.class);
+
+ /**
+ * Map of sub-channels, key is the channel Id.
+ */
+ private Map<Long, MuxChannel> channels = new HashMap<Long, MuxChannel>();
+
+ private final WebSocketPolicy policy;
+ private final LogicalConnection physicalConnection;
+ private InetSocketAddress remoteAddress;
+ /** Parsing frames destined for sub-channels */
+ private MuxParser parser;
+ /** Generating frames destined for physical connection */
+ private MuxGenerator generator;
+ private MuxAddServer addServer;
+ private MuxAddClient addClient;
+ /** The original request headers, used for delta encoded AddChannelRequest blocks */
+ private UpgradeRequest physicalRequestHeaders;
+ /** The original response headers, used for delta encoded AddChannelResponse blocks */
+ private UpgradeResponse physicalResponseHeaders;
+
+ public Muxer(final LogicalConnection connection)
+ {
+ this.physicalConnection = connection;
+ this.policy = connection.getPolicy().clonePolicy();
+ this.parser = new MuxParser();
+ this.parser.setEvents(this);
+ this.generator = new MuxGenerator();
+ }
+
+ public MuxAddClient getAddClient()
+ {
+ return addClient;
+ }
+
+ public MuxAddServer getAddServer()
+ {
+ return addServer;
+ }
+
+ public MuxChannel getChannel(long channelId, boolean create)
+ {
+ if (channelId == CONTROL_CHANNEL_ID)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Invalid Channel ID");
+ }
+
+ MuxChannel channel = channels.get(channelId);
+ if (channel == null)
+ {
+ if (create)
+ {
+ channel = new MuxChannel(channelId,this);
+ channels.put(channelId,channel);
+ }
+ else
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Unknown Channel ID");
+ }
+ }
+ return channel;
+ }
+
+ public WebSocketPolicy getPolicy()
+ {
+ return policy;
+ }
+
+ /**
+ * Get the remote address of the physical connection.
+ *
+ * @return the remote address of the physical connection
+ */
+ public InetSocketAddress getRemoteAddress()
+ {
+ return this.remoteAddress;
+ }
+
+ /**
+ * Incoming parser errors
+ */
+ @Override
+ public void incomingError(Throwable e)
+ {
+ MuxDropChannel.Reason reason = MuxDropChannel.Reason.PHYSICAL_CONNECTION_FAILED;
+ String phrase = String.format("%s: %s", e.getClass().getName(), e.getMessage());
+ mustFailPhysicalConnection(new MuxPhysicalConnectionException(reason,phrase));
+ }
+
+ /**
+ * Incoming mux encapsulated frames.
+ */
+ @Override
+ public void incomingFrame(Frame frame)
+ {
+ parser.parse(frame);
+ }
+
+ /**
+ * Is the muxer and the physical connection still open?
+ *
+ * @return true if open
+ */
+ public boolean isOpen()
+ {
+ return physicalConnection.isOpen();
+ }
+
+ public String mergeHeaders(List<String> physicalHeaders, String deltaHeaders)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /**
+ * Per spec, the physical connection must be failed.
+ * <p>
+ * <a href="https://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-08#section-18">Section 18. Fail the Physical Connection.</a>
+ *
+ * <blockquote> To _Fail the Physical Connection_, an endpoint MUST send a DropChannel multiplex control block with objective channel ID of 0 and drop
+ * reason code in the range of 2000-2999, and then _Fail the WebSocket Connection_ on the physical connection with status code of 1011. </blockquote>
+ */
+ private void mustFailPhysicalConnection(MuxPhysicalConnectionException muxe)
+ {
+ // TODO: stop muxer from receiving incoming sub-channel traffic.
+
+ MuxDropChannel drop = muxe.getMuxDropChannel();
+ LOG.warn(muxe);
+ try
+ {
+ generator.generate(null,drop);
+ }
+ catch (IOException ioe)
+ {
+ LOG.warn("Unable to send mux DropChannel",ioe);
+ }
+
+ String reason = "Mux[MUST FAIL]" + drop.getPhrase();
+ reason = StringUtil.truncate(reason,ControlFrame.MAX_CONTROL_PAYLOAD);
+ this.physicalConnection.close(StatusCode.SERVER_ERROR,reason);
+
+ // TODO: trigger abnormal close for all sub-channels.
+ }
+
+ /**
+ * Incoming mux control block, destined for the control channel (id 0)
+ */
+ @Override
+ public void onMuxAddChannelRequest(MuxAddChannelRequest request)
+ {
+ if (policy.getBehavior() == WebSocketBehavior.CLIENT)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"AddChannelRequest not allowed per spec");
+ }
+
+ if (request.getRsv() != 0)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_REQUEST_ENCODING,"RSV Not allowed to be set");
+ }
+
+ // Pre-allocate channel.
+ long channelId = request.getChannelId();
+ MuxChannel channel = getChannel(channelId, true);
+
+ // submit to upgrade handshake process
+ try
+ {
+ switch (request.getEncoding())
+ {
+ case MuxAddChannelRequest.IDENTITY_ENCODING:
+ {
+ UpgradeRequest idenReq = MuxRequest.parse(request.getHandshake());
+ addServer.handshake(this,channel,idenReq);
+ break;
+ }
+ case MuxAddChannelRequest.DELTA_ENCODING:
+ {
+ UpgradeRequest baseReq = addServer.getPhysicalHandshakeRequest();
+ UpgradeRequest deltaReq = MuxRequest.parse(request.getHandshake());
+ UpgradeRequest mergedReq = MuxRequest.merge(baseReq,deltaReq);
+
+ addServer.handshake(this,channel,mergedReq);
+ break;
+ }
+ default:
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.BAD_REQUEST,"Unrecognized request encoding");
+ }
+ }
+ }
+ catch (MuxPhysicalConnectionException e)
+ {
+ throw e;
+ }
+ catch (Throwable t)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.BAD_REQUEST,"Unable to parse request",t);
+ }
+ }
+
+ /**
+ * Incoming mux control block, destined for the control channel (id 0)
+ */
+ @Override
+ public void onMuxAddChannelResponse(MuxAddChannelResponse response)
+ {
+ if (policy.getBehavior() == WebSocketBehavior.SERVER)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"AddChannelResponse not allowed per spec");
+ }
+
+ if (response.getRsv() != 0)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_RESPONSE_ENCODING,"RSV Not allowed to be set");
+ }
+
+ // Process channel
+ long channelId = response.getChannelId();
+ MuxChannel channel = getChannel(channelId,false);
+
+ // Process Response headers
+ try
+ {
+ // Parse Response
+
+ // TODO: Sec-WebSocket-Accept header
+ // TODO: Sec-WebSocket-Extensions header
+ // TODO: Setup extensions
+ // TODO: Setup sessions
+
+ // Trigger channel open
+ channel.onOpen();
+ }
+ catch (Throwable t)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.BAD_RESPONSE,"Unable to parse response",t);
+ }
+ }
+
+ /**
+ * Incoming mux control block, destined for the control channel (id 0)
+ */
+ @Override
+ public void onMuxDropChannel(MuxDropChannel drop)
+ {
+ // Process channel
+ long channelId = drop.getChannelId();
+ MuxChannel channel = getChannel(channelId,false);
+
+ String reason = "Mux " + drop.toString();
+ reason = StringUtil.truncate(reason,(ControlFrame.MAX_CONTROL_PAYLOAD - 2));
+ channel.close(StatusCode.PROTOCOL,reason);
+ // TODO: set channel to inactive?
+ }
+
+ /**
+ * Incoming mux-unwrapped frames, destined for a sub-channel
+ */
+ @Override
+ public void onMuxedFrame(MuxedFrame frame)
+ {
+ MuxChannel subchannel = channels.get(frame.getChannelId());
+ subchannel.incomingFrame(frame);
+ }
+
+ @Override
+ public void onMuxException(MuxException e)
+ {
+ if (e instanceof MuxPhysicalConnectionException)
+ {
+ mustFailPhysicalConnection((MuxPhysicalConnectionException)e);
+ }
+
+ LOG.warn(e);
+ // TODO: handle other (non physical) mux exceptions how?
+ }
+
+ /**
+ * Incoming mux control block, destined for the control channel (id 0)
+ */
+ @Override
+ public void onMuxFlowControl(MuxFlowControl flow)
+ {
+ if (flow.getSendQuotaSize() > 0x7F_FF_FF_FF_FF_FF_FF_FFL)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.SEND_QUOTA_OVERFLOW,"Send Quota Overflow");
+ }
+
+ // Process channel
+ long channelId = flow.getChannelId();
+ MuxChannel channel = getChannel(channelId,false);
+
+ // TODO: set channel quota
+ }
+
+ /**
+ * Incoming mux control block, destined for the control channel (id 0)
+ */
+ @Override
+ public void onMuxNewChannelSlot(MuxNewChannelSlot slot)
+ {
+ if (policy.getBehavior() == WebSocketBehavior.SERVER)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"NewChannelSlot not allowed per spec");
+ }
+
+ if (slot.isFallback())
+ {
+ if (slot.getNumberOfSlots() == 0)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Cannot have 0 number of slots during fallback");
+ }
+ if (slot.getInitialSendQuota() == 0)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Cannot have 0 initial send quota during fallback");
+ }
+ }
+
+ // TODO: handle channel slot
+ }
+
+ /**
+ * Outgoing frame, without mux encapsulated payload.
+ */
+ public void output(long channelId, Frame frame, WriteCallback callback)
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("output({}, {})",channelId,frame,callback);
+ }
+ generator.generate(channelId,frame,callback);
+ }
+
+ /**
+ * Write an OP out the physical connection.
+ *
+ * @param op
+ * the mux operation to write
+ * @throws IOException
+ */
+ public void output(MuxControlBlock op) throws IOException
+ {
+ generator.generate(null,op);
+ }
+
+ public void setAddClient(MuxAddClient addClient)
+ {
+ this.addClient = addClient;
+ }
+
+ public void setAddServer(MuxAddServer addServer)
+ {
+ this.addServer = addServer;
+ }
+
+ public void setOutgoingFramesHandler(OutgoingFrames outgoing)
+ {
+ this.generator.setOutgoing(outgoing);
+ }
+
+ /**
+ * Set the remote address of the physical connection.
+ * <p>
+ * This address made available to sub-channels.
+ *
+ * @param remoteAddress
+ * the remote address
+ */
+ public void setRemoteAddress(InetSocketAddress remoteAddress)
+ {
+ this.remoteAddress = remoteAddress;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("Muxer[subChannels.size=%d]",channels.size());
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/MuxAddClient.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/MuxAddClient.java
new file mode 100644
index 0000000..6fa1d35
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/MuxAddClient.java
@@ -0,0 +1,30 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.add;
+
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse;
+
+/**
+ * Interface for Mux Client to handle receiving a AddChannelResponse
+ */
+public interface MuxAddClient
+{
+ WebSocketSession createSession(MuxAddChannelResponse response);
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/MuxAddServer.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/MuxAddServer.java
new file mode 100644
index 0000000..66fb7ab
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/MuxAddServer.java
@@ -0,0 +1,52 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.add;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.mux.MuxChannel;
+import org.eclipse.jetty.websocket.mux.MuxException;
+import org.eclipse.jetty.websocket.mux.Muxer;
+
+/**
+ * Server interface, for dealing with incoming AddChannelRequest / AddChannelResponse flows.
+ */
+public interface MuxAddServer
+{
+ public UpgradeRequest getPhysicalHandshakeRequest();
+
+ public UpgradeResponse getPhysicalHandshakeResponse();
+
+ /**
+ * Perform the handshake.
+ *
+ * @param channel
+ * the channel to attach the {@link WebSocketSession} to.
+ * @param requestHandshake
+ * the request handshake (request headers)
+ * @throws MuxException
+ * if unable to handshake
+ * @throws IOException
+ * if unable to parse request headers
+ */
+ void handshake(Muxer muxer, MuxChannel channel, UpgradeRequest request) throws MuxException, IOException;
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/package-info.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/package-info.java
new file mode 100644
index 0000000..1834e97
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/package-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+/**
+ * Jetty WebSocket Common : MUX Extension Add Channel Handling [<em>Unstable Early Draft</em>]
+ */
+package org.eclipse.jetty.websocket.mux.add;
+
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/MuxClientAddHandler.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/MuxClientAddHandler.java
new file mode 100644
index 0000000..daa0ac3
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/MuxClientAddHandler.java
@@ -0,0 +1,33 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.client;
+
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.mux.add.MuxAddClient;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse;
+
+public class MuxClientAddHandler implements MuxAddClient
+{
+ @Override
+ public WebSocketSession createSession(MuxAddChannelResponse response)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/MuxClientExtension.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/MuxClientExtension.java
new file mode 100644
index 0000000..fd03392
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/MuxClientExtension.java
@@ -0,0 +1,31 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.client;
+
+import org.eclipse.jetty.websocket.mux.AbstractMuxExtension;
+import org.eclipse.jetty.websocket.mux.Muxer;
+
+public class MuxClientExtension extends AbstractMuxExtension
+{
+ @Override
+ public void configureMuxer(Muxer muxer)
+ {
+ muxer.setAddClient(new MuxClientAddHandler());
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/package-info.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/package-info.java
new file mode 100644
index 0000000..e8d7942
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/package-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+/**
+ * Jetty WebSocket Client : MUX Extension [<em>Unstable Early Draft</em>]
+ */
+package org.eclipse.jetty.websocket.mux.client;
+
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxAddChannelRequest.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxAddChannelRequest.java
new file mode 100644
index 0000000..013bc65
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxAddChannelRequest.java
@@ -0,0 +1,114 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.op;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.mux.MuxControlBlock;
+import org.eclipse.jetty.websocket.mux.MuxOp;
+
+public class MuxAddChannelRequest implements MuxControlBlock
+{
+ public static final byte IDENTITY_ENCODING = (byte)0x00;
+ public static final byte DELTA_ENCODING = (byte)0x01;
+
+ private long channelId = -1;
+ private byte encoding;
+ private ByteBuffer handshake;
+ private byte rsv;
+
+ public long getChannelId()
+ {
+ return channelId;
+ }
+
+ public byte getEncoding()
+ {
+ return encoding;
+ }
+
+ public ByteBuffer getHandshake()
+ {
+ return handshake;
+ }
+
+ public long getHandshakeSize()
+ {
+ if (handshake == null)
+ {
+ return 0;
+ }
+ return handshake.remaining();
+ }
+
+ @Override
+ public int getOpCode()
+ {
+ return MuxOp.ADD_CHANNEL_REQUEST;
+ }
+
+ public byte getRsv()
+ {
+ return rsv;
+ }
+
+ public boolean isDeltaEncoded()
+ {
+ return (encoding == DELTA_ENCODING);
+ }
+
+ public boolean isIdentityEncoded()
+ {
+ return (encoding == IDENTITY_ENCODING);
+ }
+
+ public void setChannelId(long channelId)
+ {
+ this.channelId = channelId;
+ }
+
+ public void setEncoding(byte enc)
+ {
+ this.encoding = enc;
+ }
+
+ public void setHandshake(ByteBuffer handshake)
+ {
+ if (handshake == null)
+ {
+ this.handshake = null;
+ }
+ else
+ {
+ this.handshake = handshake.slice();
+ }
+ }
+
+ public void setHandshake(String rawstring)
+ {
+ setHandshake(BufferUtil.toBuffer(rawstring, StandardCharsets.UTF_8));
+ }
+
+ public void setRsv(byte rsv)
+ {
+ this.rsv = rsv;
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxAddChannelResponse.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxAddChannelResponse.java
new file mode 100644
index 0000000..fad6df3
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxAddChannelResponse.java
@@ -0,0 +1,125 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.op;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.mux.MuxControlBlock;
+import org.eclipse.jetty.websocket.mux.MuxOp;
+
+public class MuxAddChannelResponse implements MuxControlBlock
+{
+ public static final byte IDENTITY_ENCODING = (byte)0x00;
+ public static final byte DELTA_ENCODING = (byte)0x01;
+
+ private long channelId;
+ private byte encoding;
+ private byte rsv;
+ private boolean failed = false;
+ private ByteBuffer handshake;
+
+ public long getChannelId()
+ {
+ return channelId;
+ }
+
+ public byte getEncoding()
+ {
+ return encoding;
+ }
+
+ public ByteBuffer getHandshake()
+ {
+ return handshake;
+ }
+
+ public long getHandshakeSize()
+ {
+ if (handshake == null)
+ {
+ return 0;
+ }
+ return handshake.remaining();
+ }
+
+ @Override
+ public int getOpCode()
+ {
+ return MuxOp.ADD_CHANNEL_RESPONSE;
+ }
+
+ public byte getRsv()
+ {
+ return rsv;
+ }
+
+ public boolean isDeltaEncoded()
+ {
+ return (encoding == DELTA_ENCODING);
+ }
+
+ public boolean isFailed()
+ {
+ return failed;
+ }
+
+ public boolean isIdentityEncoded()
+ {
+ return (encoding == IDENTITY_ENCODING);
+ }
+
+ public void setChannelId(long channelId)
+ {
+ this.channelId = channelId;
+ }
+
+ public void setEncoding(byte enc)
+ {
+ this.encoding = enc;
+ }
+
+ public void setFailed(boolean failed)
+ {
+ this.failed = failed;
+ }
+
+ public void setHandshake(ByteBuffer handshake)
+ {
+ if (handshake == null)
+ {
+ this.handshake = null;
+ }
+ else
+ {
+ this.handshake = handshake.slice();
+ }
+ }
+
+ public void setHandshake(String responseHandshake)
+ {
+ setHandshake(BufferUtil.toBuffer(responseHandshake, StandardCharsets.UTF_8));
+ }
+
+ public void setRsv(byte rsv)
+ {
+ this.rsv = rsv;
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxDropChannel.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxDropChannel.java
new file mode 100644
index 0000000..edd186f
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxDropChannel.java
@@ -0,0 +1,183 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.op;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.websocket.mux.MuxControlBlock;
+import org.eclipse.jetty.websocket.mux.MuxOp;
+
+public class MuxDropChannel implements MuxControlBlock
+{
+ /**
+ * Outlined in <a href="https://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-05#section-9.4.1">Section 9.4.1. Drop Reason Codes</a>
+ */
+ public static enum Reason
+ {
+ // Normal Close : (1000-1999)
+ NORMAL_CLOSURE(1000),
+
+ // Failures in Physical Connection : (2000-2999)
+ PHYSICAL_CONNECTION_FAILED(2000),
+ INVALID_ENCAPSULATING_MESSAGE(2001),
+ CHANNEL_ID_TRUNCATED(2002),
+ ENCAPSULATED_FRAME_TRUNCATED(2003),
+ UNKNOWN_MUX_CONTROL_OPC(2004),
+ UNKNOWN_MUX_CONTROL_BLOCK(2005),
+ CHANNEL_ALREADY_EXISTS(2006),
+ NEW_CHANNEL_SLOT_VIOLATION(2007),
+ NEW_CHANNEL_SLOT_OVERFLOW(2008),
+ BAD_REQUEST(2009),
+ UNKNOWN_REQUEST_ENCODING(2010),
+ BAD_RESPONSE(2011),
+ UNKNOWN_RESPONSE_ENCODING(2012),
+
+ // Failures in Logical Connections : (3000-3999)
+ LOGICAL_CHANNEL_FAILED(3000),
+ SEND_QUOTA_VIOLATION(3005),
+ SEND_QUOTA_OVERFLOW(3006),
+ IDLE_TIMEOUT(3007),
+ DROP_CHANNEL_ACK(3008),
+
+ // Other Peer Actions : (4000-4999)
+ USE_ANOTHER_PHYSICAL_CONNECTION(4001),
+ BUSY(4002);
+
+ private static final Map<Integer, Reason> codeMap;
+
+ static
+ {
+ codeMap = new HashMap<>();
+ for (Reason r : values())
+ {
+ codeMap.put(r.getValue(),r);
+ }
+ }
+
+ public static Reason valueOf(int code)
+ {
+ return codeMap.get(code);
+ }
+
+ private final int code;
+
+ private Reason(int code)
+ {
+ this.code = code;
+ }
+
+ public int getValue()
+ {
+ return code;
+ }
+ }
+
+ public static MuxDropChannel parse(long channelId, ByteBuffer payload)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ private final long channelId;
+ private final Reason code;
+ private String phrase;
+ private int rsv;
+
+ /**
+ * Normal Drop. no reason Phrase.
+ *
+ * @param channelId
+ * the logical channel Id to perform drop against.
+ */
+ public MuxDropChannel(long channelId)
+ {
+ this(channelId,Reason.NORMAL_CLOSURE,null);
+ }
+
+ /**
+ * Drop with reason code and optional phrase
+ *
+ * @param channelId
+ * the logical channel Id to perform drop against.
+ * @param code
+ * reason code
+ * @param phrase
+ * optional human readable phrase
+ */
+ public MuxDropChannel(long channelId, int code, String phrase)
+ {
+ this(channelId, Reason.valueOf(code), phrase);
+ }
+
+ /**
+ * Drop with reason code and optional phrase
+ *
+ * @param channelId
+ * the logical channel Id to perform drop against.
+ * @param code
+ * reason code
+ * @param phrase
+ * optional human readable phrase
+ */
+ public MuxDropChannel(long channelId, Reason code, String phrase)
+ {
+ this.channelId = channelId;
+ this.code = code;
+ this.phrase = phrase;
+ }
+
+ public ByteBuffer asReasonBuffer()
+ {
+ // TODO: convert to reason buffer
+ return null;
+ }
+
+ public long getChannelId()
+ {
+ return channelId;
+ }
+
+ public Reason getCode()
+ {
+ return code;
+ }
+
+ @Override
+ public int getOpCode()
+ {
+ return MuxOp.DROP_CHANNEL;
+ }
+
+ public String getPhrase()
+ {
+ return phrase;
+ }
+
+ public int getRsv()
+ {
+ return rsv;
+ }
+
+ public void setRsv(int rsv)
+ {
+ this.rsv = rsv;
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxFlowControl.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxFlowControl.java
new file mode 100644
index 0000000..c238c6f
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxFlowControl.java
@@ -0,0 +1,65 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.op;
+
+import org.eclipse.jetty.websocket.mux.MuxControlBlock;
+import org.eclipse.jetty.websocket.mux.MuxOp;
+
+public class MuxFlowControl implements MuxControlBlock
+{
+ private long channelId;
+ private byte rsv;
+ private long sendQuotaSize;
+
+ public long getChannelId()
+ {
+ return channelId;
+ }
+
+ @Override
+ public int getOpCode()
+ {
+ return MuxOp.FLOW_CONTROL;
+ }
+
+ public byte getRsv()
+ {
+ return rsv;
+ }
+
+ public long getSendQuotaSize()
+ {
+ return sendQuotaSize;
+ }
+
+ public void setChannelId(long channelId)
+ {
+ this.channelId = channelId;
+ }
+
+ public void setRsv(byte rsv)
+ {
+ this.rsv = rsv;
+ }
+
+ public void setSendQuotaSize(long sendQuotaSize)
+ {
+ this.sendQuotaSize = sendQuotaSize;
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxNewChannelSlot.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxNewChannelSlot.java
new file mode 100644
index 0000000..4fae241
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxNewChannelSlot.java
@@ -0,0 +1,76 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.op;
+
+import org.eclipse.jetty.websocket.mux.MuxControlBlock;
+import org.eclipse.jetty.websocket.mux.MuxOp;
+
+public class MuxNewChannelSlot implements MuxControlBlock
+{
+ private boolean fallback;
+ private long initialSendQuota;
+ private long numberOfSlots;
+ private byte rsv;
+
+ public long getInitialSendQuota()
+ {
+ return initialSendQuota;
+ }
+
+ public long getNumberOfSlots()
+ {
+ return numberOfSlots;
+ }
+
+ @Override
+ public int getOpCode()
+ {
+ return MuxOp.NEW_CHANNEL_SLOT;
+ }
+
+ public byte getRsv()
+ {
+ return rsv;
+ }
+
+ public boolean isFallback()
+ {
+ return fallback;
+ }
+
+ public void setFallback(boolean fallback)
+ {
+ this.fallback = fallback;
+ }
+
+ public void setInitialSendQuota(long initialSendQuota)
+ {
+ this.initialSendQuota = initialSendQuota;
+ }
+
+ public void setNumberOfSlots(long numberOfSlots)
+ {
+ this.numberOfSlots = numberOfSlots;
+ }
+
+ public void setRsv(byte rsv)
+ {
+ this.rsv = rsv;
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/package-info.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/package-info.java
new file mode 100644
index 0000000..a01e182
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/package-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+/**
+ * Jetty WebSocket Common : MUX Extension OpCode Handling [<em>Unstable Early Draft</em>]
+ */
+package org.eclipse.jetty.websocket.mux.op;
+
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/package-info.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/package-info.java
new file mode 100644
index 0000000..b025466
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/package-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+/**
+ * Jetty WebSocket Common : MUX Extension Core [<em>Unstable Early Draft</em>]
+ */
+package org.eclipse.jetty.websocket.mux;
+
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/EmptyHttpInput.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/EmptyHttpInput.java
new file mode 100644
index 0000000..b453278
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/EmptyHttpInput.java
@@ -0,0 +1,41 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.server;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.server.ByteBufferQueuedHttpInput;
+
+/**
+ * HttpInput for Empty Http body sections.
+ */
+public class EmptyHttpInput extends ByteBufferQueuedHttpInput
+{
+ @Override
+ protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
+ {
+ return 0;
+ }
+
+ @Override
+ protected int remaining(ByteBuffer item)
+ {
+ return 0;
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/HttpChannelOverMux.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/HttpChannelOverMux.java
new file mode 100644
index 0000000..5234550
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/HttpChannelOverMux.java
@@ -0,0 +1,40 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.server;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpInput;
+import org.eclipse.jetty.server.HttpTransport;
+
+/**
+ * Process incoming AddChannelRequest headers within the existing Jetty framework. Benefiting from Server container knowledge and various webapp configuration
+ * knowledge.
+ */
+public class HttpChannelOverMux extends HttpChannel<ByteBuffer>
+{
+ public HttpChannelOverMux(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input)
+ {
+ super(connector,configuration,endPoint,transport,input);
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/HttpTransportOverMux.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/HttpTransportOverMux.java
new file mode 100644
index 0000000..20d43a0
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/HttpTransportOverMux.java
@@ -0,0 +1,74 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.server;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.server.HttpTransport;
+import org.eclipse.jetty.util.BlockingCallback;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.mux.MuxChannel;
+import org.eclipse.jetty.websocket.mux.Muxer;
+
+/**
+ * Take {@link ResponseInfo} objects and convert to bytes for response.
+ */
+public class HttpTransportOverMux implements HttpTransport
+{
+ private static final Logger LOG = Log.getLogger(HttpTransportOverMux.class);
+ private final BlockingCallback streamBlocker = new BlockingCallback();
+
+ public HttpTransportOverMux(Muxer muxer, MuxChannel channel)
+ {
+ // TODO Auto-generated constructor stub
+ }
+
+ @Override
+ public void completed()
+ {
+ LOG.debug("completed");
+ }
+
+
+ @Override
+ public void send(ResponseInfo info, ByteBuffer responseBodyContent, boolean lastContent, Callback callback)
+ {
+ if (lastContent == false)
+ {
+ // throw error
+ }
+
+ if (info.getContentLength() > 0)
+ {
+ // throw error
+ }
+
+ // prepare the AddChannelResponse
+ // TODO: look at HttpSender in jetty-client for generator loop logic
+ }
+
+ @Override
+ public void send(ByteBuffer responseBodyContent, boolean lastContent, Callback callback)
+ {
+ send(null,responseBodyContent, lastContent, callback);
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/MuxAddHandler.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/MuxAddHandler.java
new file mode 100644
index 0000000..926da83
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/MuxAddHandler.java
@@ -0,0 +1,112 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.server;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.mux.MuxChannel;
+import org.eclipse.jetty.websocket.mux.MuxException;
+import org.eclipse.jetty.websocket.mux.Muxer;
+import org.eclipse.jetty.websocket.mux.add.MuxAddServer;
+
+/**
+ * Handler for incoming MuxAddChannel requests.
+ */
+public class MuxAddHandler implements MuxAddServer
+{
+ /** Represents physical connector */
+ private Connector connector;
+
+ /** Used for local address */
+ private EndPoint endPoint;
+
+ /** The original request handshake */
+ private UpgradeRequest baseHandshakeRequest;
+
+ /** The original request handshake */
+ private UpgradeResponse baseHandshakeResponse;
+
+ private int maximumHeaderSize = 32 * 1024;
+
+ @Override
+ public UpgradeRequest getPhysicalHandshakeRequest()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public UpgradeResponse getPhysicalHandshakeResponse()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /**
+ * An incoming MuxAddChannel request.
+ *
+ * @param muxer the muxer handling this
+ * @param channel the
+ * channel this request should be bound to
+ * @param request
+ * the incoming request headers (complete and merged if delta encoded)
+ */
+ @Override
+ public void handshake(Muxer muxer, MuxChannel channel, UpgradeRequest request) throws MuxException, IOException
+ {
+ // Need to call into HttpChannel to get the websocket properly setup.
+ HttpTransportOverMux transport = new HttpTransportOverMux(muxer,channel);
+ EmptyHttpInput input = new EmptyHttpInput();
+ HttpConfiguration configuration = new HttpConfiguration();
+
+ HttpChannelOverMux httpChannel = new HttpChannelOverMux(//
+ connector,configuration,endPoint,transport,input);
+
+ HttpMethod method = HttpMethod.fromString(request.getMethod());
+ HttpVersion version = HttpVersion.fromString(request.getHttpVersion());
+ httpChannel.startRequest(method,request.getMethod(),BufferUtil.toBuffer(request.getRequestURI().toASCIIString()),version);
+
+ for (String headerName : request.getHeaders().keySet())
+ {
+ HttpHeader header = HttpHeader.CACHE.getBest(headerName.getBytes(),0,headerName.length());
+ for (String value : request.getHeaders().get(headerName))
+ {
+ httpChannel.parsedHeader(new HttpField(header,value));
+ }
+ }
+
+ httpChannel.headerComplete();
+ httpChannel.messageComplete();
+ httpChannel.run(); // calls into server for appropriate resource
+
+ // TODO: what's in request handshake is not enough to process the request.
+ // like a partial http request. (consider this a AddChannelRequest failure)
+ throw new MuxException("Not a valid request");
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/MuxServerExtension.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/MuxServerExtension.java
new file mode 100644
index 0000000..61562b2
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/MuxServerExtension.java
@@ -0,0 +1,31 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.server;
+
+import org.eclipse.jetty.websocket.mux.AbstractMuxExtension;
+import org.eclipse.jetty.websocket.mux.Muxer;
+
+public class MuxServerExtension extends AbstractMuxExtension
+{
+ @Override
+ public void configureMuxer(Muxer muxer)
+ {
+ muxer.setAddServer(new MuxAddHandler());
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/package-info.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/package-info.java
new file mode 100644
index 0000000..5e44702
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/package-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+/**
+ * Jetty WebSocket Server : MUX Extension [<em>Unstable Early Draft</em>]
+ */
+package org.eclipse.jetty.websocket.mux.server;
+
diff --git a/jetty-websocket/websocket-mux-extension/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.client.ClientExtension b/jetty-websocket/websocket-mux-extension/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.client.ClientExtension
new file mode 100644
index 0000000..8c39ac7
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.client.ClientExtension
@@ -0,0 +1 @@
+org.eclipse.jetty.websocket.mux.client.MuxClientExtension
\ No newline at end of file
diff --git a/jetty-websocket/websocket-mux-extension/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.server.ServerExtension b/jetty-websocket/websocket-mux-extension/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.server.ServerExtension
new file mode 100644
index 0000000..05c9974
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.server.ServerExtension
@@ -0,0 +1 @@
+org.eclipse.jetty.websocket.mux.client.MuxServerExtension
\ No newline at end of file
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/examples/echo/AdapterEchoSocket.java b/jetty-websocket/websocket-mux-extension/src/test/java/examples/echo/AdapterEchoSocket.java
new file mode 100644
index 0000000..ad6b3cf
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/examples/echo/AdapterEchoSocket.java
@@ -0,0 +1,47 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package examples.echo;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+
+/**
+ * Example EchoSocket using Adapter.
+ */
+public class AdapterEchoSocket extends WebSocketAdapter
+{
+ @Override
+ public void onWebSocketText(String message)
+ {
+ if (isConnected())
+ {
+ try
+ {
+ System.out.printf("Echoing back message [%s]%n",message);
+ // echo the message back
+ getRemote().sendString(message);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace(System.err);
+ }
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxDecoder.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxDecoder.java
new file mode 100644
index 0000000..3cbdcec
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxDecoder.java
@@ -0,0 +1,45 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+
+/**
+ * Helpful utility class to parse arbitrary mux events from a physical connection's OutgoingFrames.
+ *
+ * @see MuxEncoder
+ */
+public class MuxDecoder extends MuxEventCapture implements OutgoingFrames
+{
+ private MuxParser parser;
+
+ public MuxDecoder()
+ {
+ parser = new MuxParser();
+ parser.setEvents(this);
+ }
+
+ @Override
+ public void outgoingFrame(Frame frame, WriteCallback callback)
+ {
+ parser.parse(frame);
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxEncoder.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxEncoder.java
new file mode 100644
index 0000000..e175d58
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxEncoder.java
@@ -0,0 +1,64 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.io.FramePipes;
+
+/**
+ * Helpful utility class to send arbitrary mux events into a physical connection's IncomingFrames.
+ *
+ * @see MuxDecoder
+ */
+public class MuxEncoder
+{
+ public static MuxEncoder toIncoming(IncomingFrames incoming)
+ {
+ return new MuxEncoder(FramePipes.to(incoming));
+ }
+
+ public static MuxEncoder toOutgoing(OutgoingFrames outgoing)
+ {
+ return new MuxEncoder(outgoing);
+ }
+
+ private MuxGenerator generator;
+
+ private MuxEncoder(OutgoingFrames outgoing)
+ {
+ this.generator = new MuxGenerator();
+ this.generator.setOutgoing(outgoing);
+ }
+
+ public void frame(long channelId, WebSocketFrame frame) throws IOException
+ {
+ this.generator.generate(channelId,frame,null);
+ }
+
+ public void op(MuxControlBlock op) throws IOException
+ {
+ WriteCallback callback = null;
+ this.generator.generate(callback,op);
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxEventCapture.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxEventCapture.java
new file mode 100644
index 0000000..f0f80b9
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxEventCapture.java
@@ -0,0 +1,137 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import static org.hamcrest.Matchers.is;
+
+import java.util.LinkedList;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelRequest;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse;
+import org.eclipse.jetty.websocket.mux.op.MuxDropChannel;
+import org.eclipse.jetty.websocket.mux.op.MuxFlowControl;
+import org.eclipse.jetty.websocket.mux.op.MuxNewChannelSlot;
+import org.junit.Assert;
+
+public class MuxEventCapture implements MuxParser.Listener
+{
+ private static final Logger LOG = Log.getLogger(MuxEventCapture.class);
+
+ private LinkedList<MuxedFrame> frames = new LinkedList<>();
+ private LinkedList<MuxControlBlock> ops = new LinkedList<>();
+ private LinkedList<MuxException> errors = new LinkedList<>();
+
+ public void assertFrameCount(int expected)
+ {
+ Assert.assertThat("Frame Count",frames.size(), is(expected));
+ }
+
+ public void assertHasFrame(byte opcode, long channelId, int expectedCount)
+ {
+ int actualCount = 0;
+
+ for (MuxedFrame frame : frames)
+ {
+ if (frame.getChannelId() == channelId)
+ {
+ if (frame.getOpCode() == opcode)
+ {
+ actualCount++;
+ }
+ }
+ }
+
+ Assert.assertThat("Expected Count of " + OpCode.name(opcode) + " frames on Channel ID " + channelId,actualCount,is(expectedCount));
+ }
+
+ public void assertHasOp(byte opCode, int expectedCount)
+ {
+ int actualCount = 0;
+ for (MuxControlBlock block : ops)
+ {
+ if (block.getOpCode() == opCode)
+ {
+ actualCount++;
+ }
+ }
+ Assert.assertThat("Op[" + opCode + "] count",actualCount,is(expectedCount));
+ }
+
+ public LinkedList<MuxedFrame> getFrames()
+ {
+ return frames;
+ }
+
+ public LinkedList<MuxControlBlock> getOps()
+ {
+ return ops;
+ }
+
+ @Override
+ public void onMuxAddChannelRequest(MuxAddChannelRequest request)
+ {
+ ops.add(request);
+ }
+
+ @Override
+ public void onMuxAddChannelResponse(MuxAddChannelResponse response)
+ {
+ ops.add(response);
+ }
+
+ @Override
+ public void onMuxDropChannel(MuxDropChannel drop)
+ {
+ ops.add(drop);
+ }
+
+ @Override
+ public void onMuxedFrame(MuxedFrame frame)
+ {
+ frames.add(new MuxedFrame(frame));
+ }
+
+ @Override
+ public void onMuxException(MuxException e)
+ {
+ LOG.debug(e);
+ errors.add(e);
+ }
+
+ @Override
+ public void onMuxFlowControl(MuxFlowControl flow)
+ {
+ ops.add(flow);
+ }
+
+ @Override
+ public void onMuxNewChannelSlot(MuxNewChannelSlot slot)
+ {
+ ops.add(slot);
+ }
+
+ public void reset()
+ {
+ frames.clear();
+ ops.clear();
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxGeneratorWrite139SizeTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxGeneratorWrite139SizeTest.java
new file mode 100644
index 0000000..1d3c732
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxGeneratorWrite139SizeTest.java
@@ -0,0 +1,97 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MuxGeneratorWrite139SizeTest
+{
+ private static MuxGenerator generator = new MuxGenerator();
+
+ @Parameters
+ public static Collection<Object[]> data()
+ {
+ // Various good 1/3/9 encodings
+ List<Object[]> data = new ArrayList<>();
+
+ // @formatter:off
+ // - 1 byte tests
+ data.add(new Object[]{ 0L, "00"});
+ data.add(new Object[]{ 1L, "01"});
+ data.add(new Object[]{ 2L, "02"});
+ data.add(new Object[]{ 55L, "37"});
+ data.add(new Object[]{125L, "7D"});
+
+ // - 3 byte tests
+ data.add(new Object[]{0x00_80L, "7E0080"});
+ data.add(new Object[]{0x00_ABL, "7E00AB"});
+ data.add(new Object[]{0x00_FFL, "7E00FF"});
+ data.add(new Object[]{0x3F_FFL, "7E3FFF"});
+
+ // - 9 byte tests
+ data.add(new Object[]{0x00_00_01_FF_FFL, "7F000000000001FFFF"});
+ data.add(new Object[]{0x00_00_FF_FF_FFL, "7F0000000000FFFFFF"});
+ data.add(new Object[]{0x00_FF_FF_FF_FFL, "7F00000000FFFFFFFF"});
+ data.add(new Object[]{0xFF_FF_FF_FF_FFL, "7F000000FFFFFFFFFF"});
+ // @formatter:on
+
+ return data;
+ }
+
+ @Rule
+ public TestName testname = new TestName();
+
+ private long value;
+ private String expectedHex;
+
+ public MuxGeneratorWrite139SizeTest(long value, String expectedHex)
+ {
+ this.value = value;
+ this.expectedHex = expectedHex;
+ }
+
+ @Test
+ public void testWrite139Size()
+ {
+ System.err.printf("Running %s.%s - value: %,d%n",this.getClass().getName(),testname.getMethodName(),value);
+ ByteBuffer bbuf = ByteBuffer.allocate(10);
+ generator.write139Size(bbuf,value);
+ BufferUtil.flipToFlush(bbuf,0);
+ byte actual[] = BufferUtil.toArray(bbuf);
+ String actualHex = TypeUtil.toHexString(actual).toUpperCase(Locale.ENGLISH);
+ Assert.assertThat("1/3/9 encoded size of [" + value + "]",actualHex,is(expectedHex));
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxGeneratorWriteChannelIdTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxGeneratorWriteChannelIdTest.java
new file mode 100644
index 0000000..7214b08
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxGeneratorWriteChannelIdTest.java
@@ -0,0 +1,101 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests of valid ChannelID generation
+ */
+@RunWith(Parameterized.class)
+public class MuxGeneratorWriteChannelIdTest
+{
+ private static MuxGenerator generator = new MuxGenerator();
+
+ @Parameters
+ public static Collection<Object[]> data()
+ {
+ // Various good Channel IDs
+ List<Object[]> data = new ArrayList<>();
+
+ // @formatter:off
+ // - 1 byte tests
+ data.add(new Object[]{ 0L, "00"});
+ data.add(new Object[]{ 1L, "01"});
+ data.add(new Object[]{ 2L, "02"});
+ data.add(new Object[]{ 55L, "37"});
+ data.add(new Object[]{127L, "7F"});
+
+ // - 2 byte tests
+ data.add(new Object[]{0x00_80L, "8080"});
+ data.add(new Object[]{0x00_FFL, "80FF"});
+ data.add(new Object[]{0x3F_FFL, "BFFF"});
+
+ // - 3 byte tests
+ data.add(new Object[]{0x00_FF_FFL, "C0FFFF"});
+ data.add(new Object[]{0x1F_FF_FFL, "DFFFFF"});
+
+ // - 3 byte tests
+ data.add(new Object[]{0x00_FF_FF_FFL, "E0FFFFFF"});
+ data.add(new Object[]{0x1F_FF_FF_FFL, "FFFFFFFF"});
+
+ // @formatter:on
+ return data;
+ }
+
+ @Rule
+ public TestName testname = new TestName();
+
+ private long channelId;
+ private String expectedHex;
+
+ public MuxGeneratorWriteChannelIdTest(long channelId, String expectedHex)
+ {
+ this.channelId = channelId;
+ this.expectedHex = expectedHex;
+ }
+
+ @Test
+ public void testReadChannelId()
+ {
+ System.err.printf("Running %s.%s - channelId: %,d%n",this.getClass().getName(),testname.getMethodName(),channelId);
+ ByteBuffer bbuf = ByteBuffer.allocate(10);
+ generator.writeChannelId(bbuf,channelId);
+ BufferUtil.flipToFlush(bbuf,0);
+ byte actual[] = BufferUtil.toArray(bbuf);
+ String actualHex = TypeUtil.toHexString(actual).toUpperCase(Locale.ENGLISH);
+ Assert.assertThat("Channel ID [" + channelId + "]",actualHex,is(expectedHex));
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRFCTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRFCTest.java
new file mode 100644
index 0000000..2dec52a
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRFCTest.java
@@ -0,0 +1,244 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import static org.hamcrest.Matchers.is;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
+import org.eclipse.jetty.websocket.mux.helper.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.mux.helper.UnitParser;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class MuxParserRFCTest
+{
+ public static class DummyMuxExtension extends AbstractMuxExtension
+ {
+ @Override
+ public void configureMuxer(Muxer muxer)
+ {
+ /* nothing to do */
+ }
+ }
+
+ private LinkedList<WebSocketFrame> asFrames(byte[] buf)
+ {
+ IncomingFramesCapture capture = new IncomingFramesCapture();
+ WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+ Parser parser = new UnitParser(policy);
+ parser.setIncomingFramesHandler(capture);
+ List<? extends AbstractExtension> muxList = Collections.singletonList(new DummyMuxExtension());
+ parser.configureFromExtensions(muxList);
+ ByteBuffer bbuf = ByteBuffer.wrap(buf);
+ parser.parse(bbuf);
+
+ return capture.getFrames();
+ }
+
+ private boolean isHexOnly(String part)
+ {
+ Pattern bytePat = Pattern.compile("(\\s*0x[0-9A-Fa-f]{2}+){1,}+");
+ Matcher mat = bytePat.matcher(part);
+ return mat.matches();
+ }
+
+ private MuxEventCapture parseMuxFrames(LinkedList<WebSocketFrame> frames)
+ {
+ MuxParser parser = new MuxParser();
+ MuxEventCapture capture = new MuxEventCapture();
+ parser.setEvents(capture);
+ for(Frame frame: frames) {
+ parser.parse(frame);
+ }
+ return capture;
+ }
+
+ @Test
+ public void testIsHexOnly()
+ {
+ Assert.assertTrue(isHexOnly("0x00"));
+ Assert.assertTrue(isHexOnly("0x00 0xaF"));
+ Assert.assertFalse(isHexOnly("Hello World"));
+ }
+
+ @Test
+ @Ignore
+ public void testRFCExample1() throws IOException
+ {
+ // Create RFC detailed frames
+ byte buf[] = toByteArray("0x82 0x0d 0x01 0x81","Hello world");
+ LinkedList<WebSocketFrame> frames = asFrames(buf);
+ Assert.assertThat("Frame count",frames.size(),is(1));
+
+ // Have mux parse frames
+ MuxEventCapture capture = parseMuxFrames(frames);
+ capture.assertFrameCount(1);
+
+ MuxedFrame mux;
+
+ mux = capture.getFrames().pop();
+ String prefix = "MuxFrame[0]";
+ Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L));
+ Assert.assertThat(prefix + ".fin",mux.isFin(),is(true));
+ Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false));
+ Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false));
+ Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false));
+ Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false));
+ Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT));
+
+ String payload = mux.getPayloadAsUTF8();
+ Assert.assertThat(prefix + ".payload/text",payload,is("Hello world"));
+ }
+
+ @Test
+ @Ignore
+ public void testRFCExample2() throws IOException
+ {
+ // Create RFC detailed frames
+ byte buf[] = toByteArray("0x02 0x07 0x01 0x81","Hello","0x80 0x06"," world");
+ LinkedList<WebSocketFrame> frames = asFrames(buf);
+ Assert.assertThat("Frame count",frames.size(),is(2));
+
+ // Have mux parse frames
+ MuxEventCapture capture = parseMuxFrames(frames);
+ capture.assertFrameCount(2);
+
+ MuxedFrame mux;
+
+ // Text Frame
+ mux = capture.getFrames().get(0);
+ String prefix = "MuxFrame[0]";
+ Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L));
+ // (BUG IN DRAFT) Assert.assertThat(prefix + ".fin",mux.isFin(),is(false));
+ Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false));
+ Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false));
+ Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false));
+ Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false));
+ Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT));
+
+ String payload = mux.getPayloadAsUTF8();
+ Assert.assertThat(prefix + ".payload/text",payload,is("Hello"));
+
+ // Continuation Frame
+ mux = capture.getFrames().get(1);
+ prefix = "MuxFrame[1]";
+ // (BUG IN DRAFT) Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L));
+ // (BUG IN DRAFT) Assert.assertThat(prefix + ".fin",mux.isFin(),is(true));
+ Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false));
+ Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false));
+ Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false));
+ Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false));
+ // (BUG IN DRAFT) Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.BINARY));
+
+ payload = mux.getPayloadAsUTF8();
+ Assert.assertThat(prefix + ".payload/text",payload,is(" world"));
+ }
+
+ @Test
+ @Ignore
+ public void testRFCExample3() throws IOException
+ {
+ // Create RFC detailed frames
+ byte buf[] = toByteArray("0x82 0x07 0x01 0x01","Hello","0x82 0x05 0x02 0x81","bye","0x82 0x08 0x01 0x80"," world");
+ LinkedList<WebSocketFrame> frames = asFrames(buf);
+ Assert.assertThat("Frame count",frames.size(),is(3));
+
+ // Have mux parse frames
+ MuxEventCapture capture = parseMuxFrames(frames);
+ capture.assertFrameCount(3);
+
+ MuxedFrame mux;
+
+ // Text Frame (Message 1)
+ mux = capture.getFrames().pop();
+ String prefix = "MuxFrame[0]";
+ Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L));
+ Assert.assertThat(prefix + ".fin",mux.isFin(),is(false));
+ Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false));
+ Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false));
+ Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false));
+ Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false));
+ Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT));
+
+ String payload = mux.getPayloadAsUTF8();
+ Assert.assertThat(prefix + ".payload/text",payload,is("Hello"));
+
+ // Text Frame (Message 2)
+ mux = capture.getFrames().pop();
+ prefix = "MuxFrame[1]";
+ Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(2L));
+ Assert.assertThat(prefix + ".fin",mux.isFin(),is(true));
+ Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false));
+ Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false));
+ Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false));
+ Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false));
+ Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT));
+
+ payload = mux.getPayloadAsUTF8();
+ Assert.assertThat(prefix + ".payload/text",payload,is("bye"));
+
+ // Continuation Frame (Message 1)
+ mux = capture.getFrames().pop();
+ prefix = "MuxFrame[2]";
+ Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L));
+ Assert.assertThat(prefix + ".fin",mux.isFin(),is(true));
+ Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false));
+ Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false));
+ Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false));
+ Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false));
+ Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT));
+
+ payload = mux.getPayloadAsUTF8();
+ Assert.assertThat(prefix + ".payload/text",payload,is(" world"));
+ }
+
+ private byte[] toByteArray(String... parts) throws IOException
+ {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ for(String part: parts) {
+ if (isHexOnly(part))
+ {
+ String hexonly = part.replaceAll("\\s*0x","");
+ out.write(TypeUtil.fromHexString(hexonly));
+ }
+ else
+ {
+ out.write(part.getBytes(StandardCharsets.UTF_8));
+ }
+ }
+ return out.toByteArray();
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRead139Size_BadEncodingTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRead139Size_BadEncodingTest.java
new file mode 100644
index 0000000..0ea5d4f
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRead139Size_BadEncodingTest.java
@@ -0,0 +1,104 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import static org.hamcrest.Matchers.containsString;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests for bad 1/3/9 size encoding.
+ */
+@RunWith(Parameterized.class)
+public class MuxParserRead139Size_BadEncodingTest
+{
+ private static MuxParser parser = new MuxParser();
+
+ @Parameters
+ public static Collection<Object[]> data()
+ {
+ // Various bad 1/3/9 encodings
+ // Violating "minimal number of bytes necessary" rule.
+ List<Object[]> data = new ArrayList<>();
+
+ // @formatter:off
+ // - 1 byte tests
+ // all known 1 byte tests are valid
+
+ // - 3 byte tests
+ data.add(new Object[]{"7E0000"});
+ data.add(new Object[]{"7E0001"});
+ data.add(new Object[]{"7E0012"});
+ data.add(new Object[]{"7E0059"});
+ // extra bytes (not related to 1/3/9 size)
+ data.add(new Object[]{"7E0012345678"});
+
+ // - 9 byte tests
+ data.add(new Object[]{"7F0000000000000000"});
+ data.add(new Object[]{"7F0000000000000001"});
+ data.add(new Object[]{"7F0000000000000012"});
+ data.add(new Object[]{"7F0000000000001234"});
+ data.add(new Object[]{"7F000000000000FFFF"});
+
+ // @formatter:on
+ return data;
+ }
+
+ @Rule
+ public TestName testname = new TestName();
+
+ private String rawhex;
+ private byte buf[];
+
+ public MuxParserRead139Size_BadEncodingTest(String rawhex)
+ {
+ this.rawhex = rawhex;
+ this.buf = TypeUtil.fromHexString(rawhex);
+ }
+
+ @Test
+ public void testRead139EncodedSize()
+ {
+ System.err.printf("Running %s.%s - hex: %s%n",this.getClass().getName(),testname.getMethodName(),rawhex);
+ ByteBuffer bbuf = ByteBuffer.wrap(buf);
+ try
+ {
+ parser.read139EncodedSize(bbuf);
+ // unexpected path
+ Assert.fail("Should have failed with an invalid parse");
+ }
+ catch (MuxException e)
+ {
+ // expected path
+ Assert.assertThat(e.getMessage(),containsString("Invalid 1/3/9 length"));
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRead139Size_GoodTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRead139Size_GoodTest.java
new file mode 100644
index 0000000..abe5712
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRead139Size_GoodTest.java
@@ -0,0 +1,99 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MuxParserRead139Size_GoodTest
+{
+ private static MuxParser parser = new MuxParser();
+
+ @Parameters
+ public static Collection<Object[]> data()
+ {
+ // Various good 1/3/9 encodings
+ List<Object[]> data = new ArrayList<>();
+
+ // @formatter:off
+ // - 1 byte tests
+ data.add(new Object[]{"00", 0L});
+ data.add(new Object[]{"01", 1L});
+ data.add(new Object[]{"02", 2L});
+ data.add(new Object[]{"37", 55L});
+ data.add(new Object[]{"7D", 125L});
+ // extra bytes (not related to 1/3/9 size)
+ data.add(new Object[]{"37FF", 55L});
+ data.add(new Object[]{"0123456789", 0x01L});
+
+ // - 3 byte tests
+ data.add(new Object[]{"7E0080", 0x00_80L});
+ data.add(new Object[]{"7E00AB", 0x00_ABL});
+ data.add(new Object[]{"7E00FF", 0x00_FFL});
+ data.add(new Object[]{"7E3FFF", 0x3F_FFL});
+ // extra bytes (not related to 1/3/9 size)
+ data.add(new Object[]{"7E0123456789", 0x01_23L});
+
+ // - 9 byte tests
+ data.add(new Object[]{"7F000000000001FFFF", 0x00_00_01_FF_FFL});
+ data.add(new Object[]{"7F0000000000FFFFFF", 0x00_00_FF_FF_FFL});
+ data.add(new Object[]{"7F00000000FFFFFFFF", 0x00_FF_FF_FF_FFL});
+ data.add(new Object[]{"7F000000FFFFFFFFFF", 0xFF_FF_FF_FF_FFL});
+
+ // @formatter:on
+ return data;
+ }
+
+ @Rule
+ public TestName testname = new TestName();
+
+ private String rawhex;
+ private byte buf[];
+ private long expected;
+
+ public MuxParserRead139Size_GoodTest(String rawhex, long expected)
+ {
+ this.rawhex = rawhex;
+ this.buf = TypeUtil.fromHexString(rawhex);
+ this.expected = expected;
+ }
+
+ @Test
+ public void testRead139EncodedSize()
+ {
+ System.err.printf("Running %s.%s - hex: %s%n",this.getClass().getName(),testname.getMethodName(),rawhex);
+ ByteBuffer bbuf = ByteBuffer.wrap(buf);
+ long actual = parser.read139EncodedSize(bbuf);
+ Assert.assertThat("1/3/9 size from buffer [" + rawhex + "]",actual,is(expected));
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserReadChannelId_BadEncodingTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserReadChannelId_BadEncodingTest.java
new file mode 100644
index 0000000..f8ac4fd
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserReadChannelId_BadEncodingTest.java
@@ -0,0 +1,106 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import static org.hamcrest.Matchers.containsString;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests of Invalid ChannelID parsing
+ */
+@RunWith(Parameterized.class)
+public class MuxParserReadChannelId_BadEncodingTest
+{
+ private static MuxParser parser = new MuxParser();
+
+ @Parameters
+ public static Collection<Object[]> data()
+ {
+ // Various Invalid Encoded Channel IDs.
+ // Violating "minimal number of bytes necessary" rule.
+ List<Object[]> data = new ArrayList<>();
+
+ // @formatter:off
+ // - 1 byte tests
+ // all known 1 byte tests are valid
+
+ // - 2 byte tests
+ data.add(new Object[]{"8000"});
+ data.add(new Object[]{"8001"});
+ data.add(new Object[]{"807F"});
+ // extra bytes (not related to channelId)
+ data.add(new Object[]{"8023456789"});
+
+ // - 3 byte tests
+ data.add(new Object[]{"C00000"});
+ data.add(new Object[]{"C01234"});
+ data.add(new Object[]{"C03FFF"});
+
+ // - 3 byte tests
+ data.add(new Object[]{"E0000000"});
+ data.add(new Object[]{"E0000001"});
+ data.add(new Object[]{"E01FFFFF"});
+
+ // @formatter:on
+ return data;
+ }
+
+ @Rule
+ public TestName testname = new TestName();
+
+ private String rawhex;
+ private byte buf[];
+
+ public MuxParserReadChannelId_BadEncodingTest(String rawhex)
+ {
+ this.rawhex = rawhex;
+ this.buf = TypeUtil.fromHexString(rawhex);
+ }
+
+ @Test
+ public void testBadEncoding()
+ {
+ System.err.printf("Running %s.%s - hex: %s%n",this.getClass().getName(),testname.getMethodName(),rawhex);
+ ByteBuffer bbuf = ByteBuffer.wrap(buf);
+ try
+ {
+ parser.readChannelId(bbuf);
+ // unexpected path
+ Assert.fail("Should have failed with an invalid parse");
+ }
+ catch (MuxException e)
+ {
+ // expected path
+ Assert.assertThat(e.getMessage(),containsString("Invalid Channel ID"));
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserReadChannelId_GoodTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserReadChannelId_GoodTest.java
new file mode 100644
index 0000000..558e68a
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserReadChannelId_GoodTest.java
@@ -0,0 +1,106 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests of valid ChannelID parsing
+ */
+@RunWith(Parameterized.class)
+public class MuxParserReadChannelId_GoodTest
+{
+ private static MuxParser parser = new MuxParser();
+
+ @Parameters
+ public static Collection<Object[]> data()
+ {
+ // Various good Channel IDs
+ List<Object[]> data = new ArrayList<>();
+
+ // @formatter:off
+ // - 1 byte tests
+ data.add(new Object[]{"00", 0L});
+ data.add(new Object[]{"01", 1L});
+ data.add(new Object[]{"02", 2L});
+ data.add(new Object[]{"7F", 127L});
+ // extra bytes (not related to channelId)
+ data.add(new Object[]{"37FF", 55L});
+ data.add(new Object[]{"0123456789", 0x01L});
+
+ // - 2 byte tests
+ data.add(new Object[]{"8080", 0x00_80L});
+ data.add(new Object[]{"80FF", 0x00_FFL});
+ data.add(new Object[]{"BFFF", 0x3F_FFL});
+ // extra bytes (not related to channelId)
+ data.add(new Object[]{"8123456789", 0x01_23L});
+
+ // - 3 byte tests
+ data.add(new Object[]{"C0FFFF", 0x00_FF_FFL});
+ data.add(new Object[]{"DFFFFF", 0x1F_FF_FFL});
+ // extra bytes (not related to channelId)
+ data.add(new Object[]{"C123456789", 0x01_23_45L});
+
+ // - 3 byte tests
+ data.add(new Object[]{"E0FFFFFF", 0x00_FF_FF_FFL});
+ data.add(new Object[]{"FFFFFFFF", 0x1F_FF_FF_FFL});
+ // extra bytes (not related to channelId)
+ data.add(new Object[]{"E123456789", 0x01_23_45_67L});
+
+ // @formatter:on
+ return data;
+ }
+
+ @Rule
+ public TestName testname = new TestName();
+
+ private String rawhex;
+ private byte buf[];
+ private long expected;
+
+ public MuxParserReadChannelId_GoodTest(String rawhex, long expected)
+ {
+ this.rawhex = rawhex;
+ this.buf = TypeUtil.fromHexString(rawhex);
+ this.expected = expected;
+ }
+
+ @Test
+ public void testReadChannelId()
+ {
+ System.err.printf("Running %s.%s - hex: %s%n",this.getClass().getName(),testname.getMethodName(),rawhex);
+ ByteBuffer bbuf = ByteBuffer.wrap(buf);
+ long actual = parser.readChannelId(bbuf);
+ Assert.assertThat("Channel ID from buffer [" + rawhex + "]",actual,is(expected));
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/DummyMuxAddClient.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/DummyMuxAddClient.java
new file mode 100644
index 0000000..156f22d
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/DummyMuxAddClient.java
@@ -0,0 +1,32 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.add;
+
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse;
+
+public class DummyMuxAddClient implements MuxAddClient
+{
+ @Override
+ public WebSocketSession createSession(MuxAddChannelResponse response)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/DummyMuxAddServer.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/DummyMuxAddServer.java
new file mode 100644
index 0000000..19f8fb2
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/DummyMuxAddServer.java
@@ -0,0 +1,98 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.add;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
+import org.eclipse.jetty.websocket.mux.MuxChannel;
+import org.eclipse.jetty.websocket.mux.MuxException;
+import org.eclipse.jetty.websocket.mux.Muxer;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse;
+
+import examples.echo.AdapterEchoSocket;
+
+/**
+ * Dummy impl of MuxAddServer
+ */
+public class DummyMuxAddServer implements MuxAddServer
+{
+ @SuppressWarnings("unused")
+ private static final Logger LOG = Log.getLogger(DummyMuxAddServer.class);
+ private AdapterEchoSocket echo;
+ private WebSocketPolicy policy;
+ private EventDriverFactory eventDriverFactory;
+
+ public DummyMuxAddServer()
+ {
+ this.policy = WebSocketPolicy.newServerPolicy();
+ this.eventDriverFactory = new EventDriverFactory(policy);
+ this.echo = new AdapterEchoSocket();
+ }
+
+ @Override
+ public UpgradeRequest getPhysicalHandshakeRequest()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public UpgradeResponse getPhysicalHandshakeResponse()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void handshake(Muxer muxer, MuxChannel channel, UpgradeRequest request) throws MuxException, IOException
+ {
+ StringBuilder response = new StringBuilder();
+ response.append("HTTP/1.1 101 Switching Protocols\r\n");
+ response.append("Connection: upgrade\r\n");
+ // not meaningful (per Draft 08) hresp.append("Upgrade: websocket\r\n");
+ // not meaningful (per Draft 08) hresp.append("Sec-WebSocket-Accept: Kgo85/8KVE8YPONSeyhgL3GwqhI=\r\n");
+ response.append("\r\n");
+
+ EventDriver websocket = this.eventDriverFactory.wrap(echo);
+ WebSocketSession session = new WebSocketSession(request.getRequestURI(),websocket,channel);
+ UpgradeResponse uresponse = new UpgradeResponse();
+ uresponse.setAcceptedSubProtocol("echo");
+ session.setUpgradeResponse(uresponse);
+ channel.setSession(session);
+ channel.setSubProtocol("echo");
+ channel.onOpen();
+ session.open();
+
+ MuxAddChannelResponse addChannelResponse = new MuxAddChannelResponse();
+ addChannelResponse.setChannelId(channel.getChannelId());
+ addChannelResponse.setEncoding(MuxAddChannelResponse.IDENTITY_ENCODING);
+ addChannelResponse.setFailed(false);
+ addChannelResponse.setHandshake(response.toString());
+
+ muxer.output(addChannelResponse);
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/MuxerAddClientTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/MuxerAddClientTest.java
new file mode 100644
index 0000000..80d922c
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/MuxerAddClientTest.java
@@ -0,0 +1,105 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.add;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.mux.MuxChannel;
+import org.eclipse.jetty.websocket.mux.MuxDecoder;
+import org.eclipse.jetty.websocket.mux.MuxEncoder;
+import org.eclipse.jetty.websocket.mux.MuxOp;
+import org.eclipse.jetty.websocket.mux.Muxer;
+import org.eclipse.jetty.websocket.mux.helper.LocalWebSocketConnection;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelRequest;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class MuxerAddClientTest
+{
+ @Rule
+ public TestName testname = new TestName();
+
+ @Test
+ @Ignore("Interrim, not functional yet")
+ public void testAddChannel_Client() throws Exception
+ {
+ // Client side physical socket
+ LocalWebSocketConnection physical = new LocalWebSocketConnection(testname);
+ physical.setPolicy(WebSocketPolicy.newClientPolicy());
+ physical.open();
+
+ // Server Reader
+ MuxDecoder serverRead = new MuxDecoder();
+
+ // Client side Muxer
+ Muxer muxer = new Muxer(physical);
+ DummyMuxAddClient addClient = new DummyMuxAddClient();
+ muxer.setAddClient(addClient);
+ muxer.setOutgoingFramesHandler(serverRead);
+
+ // Server Writer
+ MuxEncoder serverWrite = MuxEncoder.toIncoming(physical);
+
+ // Build AddChannelRequest handshake data
+ StringBuilder request = new StringBuilder();
+ request.append("GET /echo HTTP/1.1\r\n");
+ request.append("Host: localhost\r\n");
+ request.append("Upgrade: websocket\r\n");
+ request.append("Connection: Upgrade\r\n");
+ request.append("Sec-WebSocket-Key: ZDTIRU5vU9xOfkg8JAgN3A==\r\n");
+ request.append("Sec-WebSocket-Version: 13\r\n");
+ request.append("\r\n");
+
+ // Build AddChannelRequest
+ long channelId = 1L;
+ MuxAddChannelRequest req = new MuxAddChannelRequest();
+ req.setChannelId(channelId);
+ req.setEncoding((byte)0);
+ req.setHandshake(request.toString());
+
+ // Have client sent AddChannelRequest
+ MuxChannel channel = muxer.getChannel(channelId,true);
+ MuxEncoder clientWrite = MuxEncoder.toOutgoing(channel);
+ clientWrite.op(req);
+
+ // Have server read request
+ serverRead.assertHasOp(MuxOp.ADD_CHANNEL_REQUEST,1);
+
+ // prepare AddChannelResponse
+ StringBuilder response = new StringBuilder();
+ response.append("HTTP/1.1 101 Switching Protocols\r\n");
+ response.append("Upgrade: websocket\r\n");
+ response.append("Connection: upgrade\r\n");
+ response.append("Sec-WebSocket-Accept: Kgo85/8KVE8YPONSeyhgL3GwqhI=\r\n");
+ response.append("\r\n");
+
+ MuxAddChannelResponse resp = new MuxAddChannelResponse();
+ resp.setChannelId(channelId);
+ resp.setFailed(false);
+ resp.setEncoding((byte)0);
+ resp.setHandshake(resp.toString());
+
+ // Server writes add channel response
+ serverWrite.op(resp);
+
+ // TODO: handle the upgrade on client side.
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/MuxerAddServerTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/MuxerAddServerTest.java
new file mode 100644
index 0000000..e8b93d4
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/MuxerAddServerTest.java
@@ -0,0 +1,106 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.add;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.mux.MuxDecoder;
+import org.eclipse.jetty.websocket.mux.MuxEncoder;
+import org.eclipse.jetty.websocket.mux.MuxOp;
+import org.eclipse.jetty.websocket.mux.Muxer;
+import org.eclipse.jetty.websocket.mux.helper.LocalWebSocketConnection;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelRequest;
+import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class MuxerAddServerTest
+{
+ @Rule
+ public TestName testname = new TestName();
+
+ @Test
+ @Ignore("Interrim, not functional yet")
+ public void testAddChannel_Server() throws Exception
+ {
+ // Server side physical connection
+ LocalWebSocketConnection physical = new LocalWebSocketConnection(testname);
+ physical.setPolicy(WebSocketPolicy.newServerPolicy());
+ physical.open();
+
+ // Client reader
+ MuxDecoder clientRead = new MuxDecoder();
+
+ // Build up server side muxer.
+ Muxer muxer = new Muxer(physical);
+ DummyMuxAddServer addServer = new DummyMuxAddServer();
+ muxer.setAddServer(addServer);
+ muxer.setOutgoingFramesHandler(clientRead);
+
+ // Wire up physical connection to forward incoming frames to muxer
+ physical.setNextIncomingFrames(muxer);
+
+ // Client simulator
+ // Can inject mux encapsulated frames into physical connection as if from
+ // physical connection.
+ MuxEncoder clientWrite = MuxEncoder.toIncoming(physical);
+
+ // Build AddChannelRequest handshake data
+ StringBuilder request = new StringBuilder();
+ request.append("GET /echo HTTP/1.1\r\n");
+ request.append("Host: localhost\r\n");
+ request.append("Upgrade: websocket\r\n");
+ request.append("Connection: Upgrade\r\n");
+ request.append("Sec-WebSocket-Key: ZDTIRU5vU9xOfkg8JAgN3A==\r\n");
+ request.append("Sec-WebSocket-Version: 13\r\n");
+ request.append("\r\n");
+
+ // Build AddChannelRequest
+ MuxAddChannelRequest req = new MuxAddChannelRequest();
+ req.setChannelId(1);
+ req.setEncoding((byte)0);
+ req.setHandshake(request.toString());
+
+ // Have client sent AddChannelRequest
+ clientWrite.op(req);
+
+ // Make sure client got AddChannelResponse
+ clientRead.assertHasOp(MuxOp.ADD_CHANNEL_RESPONSE,1);
+ MuxAddChannelResponse response = (MuxAddChannelResponse)clientRead.getOps().pop();
+ Assert.assertThat("AddChannelResponse.channelId",response.getChannelId(),is(1L));
+ Assert.assertThat("AddChannelResponse.failed",response.isFailed(),is(false));
+ Assert.assertThat("AddChannelResponse.handshake",response.getHandshake(),notNullValue());
+ Assert.assertThat("AddChannelResponse.handshakeSize",response.getHandshakeSize(),is(57L));
+
+ clientRead.reset();
+
+ // Send simple echo request
+ clientWrite.frame(1,new TextFrame().setPayload("Hello World"));
+
+ // Test for echo response (is there a user echo websocket connected to the sub-channel?)
+ clientRead.assertHasFrame(OpCode.TEXT,1L,1);
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/IncomingFramesCapture.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/IncomingFramesCapture.java
new file mode 100644
index 0000000..7ea90be
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/IncomingFramesCapture.java
@@ -0,0 +1,143 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.helper;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+
+import java.util.LinkedList;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.junit.Assert;
+
+public class IncomingFramesCapture implements IncomingFrames
+{
+ private static final Logger LOG = Log.getLogger(IncomingFramesCapture.class);
+
+ private LinkedList<WebSocketFrame> frames = new LinkedList<>();
+ private LinkedList<Throwable> errors = new LinkedList<>();
+
+ public void assertErrorCount(int expectedCount)
+ {
+ Assert.assertThat("Captured error count",errors.size(),is(expectedCount));
+ }
+
+ public void assertFrameCount(int expectedCount)
+ {
+ Assert.assertThat("Captured frame count",frames.size(),is(expectedCount));
+ }
+
+ public void assertHasErrors(Class<? extends WebSocketException> errorType, int expectedCount)
+ {
+ Assert.assertThat(errorType.getSimpleName(),getErrorCount(errorType),is(expectedCount));
+ }
+
+ public void assertHasFrame(byte op)
+ {
+ Assert.assertThat(OpCode.name(op),getFrameCount(op),greaterThanOrEqualTo(1));
+ }
+
+ public void assertHasFrame(byte op, int expectedCount)
+ {
+ Assert.assertThat(OpCode.name(op),getFrameCount(op),is(expectedCount));
+ }
+
+ public void assertHasNoFrames()
+ {
+ Assert.assertThat("Has no frames",frames.size(),is(0));
+ }
+
+ public void assertNoErrors()
+ {
+ Assert.assertThat("Has no errors",errors.size(),is(0));
+ }
+
+ public void dump()
+ {
+ System.err.printf("Captured %d incoming frames%n",frames.size());
+ for (int i = 0; i < frames.size(); i++)
+ {
+ Frame frame = frames.get(i);
+ System.err.printf("[%3d] %s%n",i,frame);
+ System.err.printf(" %s%n",BufferUtil.toDetailString(frame.getPayload()));
+ }
+ }
+
+ public int getErrorCount(Class<? extends WebSocketException> errorType)
+ {
+ int count = 0;
+ for (Throwable error : errors)
+ {
+ if (errorType.isInstance(error))
+ {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ public LinkedList<Throwable> getErrors()
+ {
+ return errors;
+ }
+
+ public int getFrameCount(byte op)
+ {
+ int count = 0;
+ for (WebSocketFrame frame : frames)
+ {
+ if (frame.getOpCode() == op)
+ {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ public LinkedList<WebSocketFrame> getFrames()
+ {
+ return frames;
+ }
+
+ @Override
+ public void incomingError(Throwable e)
+ {
+ LOG.debug(e);
+ errors.add(e);
+ }
+
+ @Override
+ public void incomingFrame(Frame frame)
+ {
+ WebSocketFrame copy = WebSocketFrame.copy(frame);
+ frames.add(copy);
+ }
+
+ public int size()
+ {
+ return frames.size();
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/LocalWebSocketConnection.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/LocalWebSocketConnection.java
new file mode 100644
index 0000000..cd86267
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/LocalWebSocketConnection.java
@@ -0,0 +1,250 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.helper;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ExecutorThreadPool;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.SuspendToken;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.ConnectionState;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.io.IOState;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
+import org.junit.rules.TestName;
+
+public class LocalWebSocketConnection implements LogicalConnection, IncomingFrames, ConnectionStateListener
+{
+ private static final Logger LOG = Log.getLogger(LocalWebSocketConnection.class);
+ private final String id;
+ private final ByteBufferPool bufferPool;
+ private final Executor executor;
+ private WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
+ private IncomingFrames incoming;
+ private IOState ioState = new IOState();
+
+ public LocalWebSocketConnection()
+ {
+ this("anon");
+ }
+
+ public LocalWebSocketConnection(String id)
+ {
+ this.id = id;
+ this.bufferPool = new MappedByteBufferPool();
+ this.executor = new ExecutorThreadPool();
+ this.ioState.addListener(this);
+ }
+
+ public LocalWebSocketConnection(TestName testname)
+ {
+ this(testname.getMethodName());
+ }
+
+ @Override
+ public Executor getExecutor()
+ {
+ return executor;
+ }
+
+ @Override
+ public void close()
+ {
+ close(StatusCode.NORMAL,null);
+ }
+
+ @Override
+ public void close(int statusCode, String reason)
+ {
+ LOG.debug("close({}, {})",statusCode,reason);
+ CloseInfo close = new CloseInfo(statusCode,reason);
+ ioState.onCloseLocal(close);
+ }
+
+ public void connect()
+ {
+ LOG.debug("connect()");
+ ioState.onConnected();
+ }
+
+ @Override
+ public void disconnect()
+ {
+ LOG.debug("disconnect()");
+ }
+
+ @Override
+ public ByteBufferPool getBufferPool()
+ {
+ return this.bufferPool;
+ }
+
+ @Override
+ public long getIdleTimeout()
+ {
+ return 0;
+ }
+
+ public IncomingFrames getIncoming()
+ {
+ return incoming;
+ }
+
+ @Override
+ public IOState getIOState()
+ {
+ return ioState;
+ }
+
+ @Override
+ public InetSocketAddress getLocalAddress()
+ {
+ return null;
+ }
+
+ @Override
+ public long getMaxIdleTimeout()
+ {
+ return 0;
+ }
+
+ @Override
+ public WebSocketPolicy getPolicy()
+ {
+ return policy;
+ }
+
+ @Override
+ public InetSocketAddress getRemoteAddress()
+ {
+ return null;
+ }
+
+ @Override
+ public WebSocketSession getSession()
+ {
+ return null;
+ }
+
+ @Override
+ public void incomingError(Throwable e)
+ {
+ incoming.incomingError(e);
+ }
+
+ @Override
+ public void incomingFrame(Frame frame)
+ {
+ incoming.incomingFrame(frame);
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return getIOState().isOpen();
+ }
+
+ @Override
+ public boolean isReading()
+ {
+ return false;
+ }
+
+ @Override
+ public void onConnectionStateChange(ConnectionState state)
+ {
+ LOG.debug("Connection State Change: {}",state);
+ switch (state)
+ {
+ case CLOSED:
+ this.disconnect();
+ break;
+ case CLOSING:
+ if (ioState.wasRemoteCloseInitiated())
+ {
+ // send response close frame
+ CloseInfo close = ioState.getCloseInfo();
+ LOG.debug("write close frame: {}",close);
+ ioState.onCloseLocal(close);
+ }
+ default:
+ break;
+ }
+ }
+
+ public void open()
+ {
+ LOG.debug("open()");
+ ioState.onOpened();
+ }
+
+ @Override
+ public void outgoingFrame(Frame frame, WriteCallback callback)
+ {
+ }
+
+ @Override
+ public void resume()
+ {
+ }
+
+ @Override
+ public void setMaxIdleTimeout(long ms)
+ {
+ }
+
+ @Override
+ public void setNextIncomingFrames(IncomingFrames incoming)
+ {
+ this.incoming = incoming;
+ }
+
+ public void setPolicy(WebSocketPolicy policy)
+ {
+ this.policy = policy;
+ }
+
+ @Override
+ public void setSession(WebSocketSession session)
+ {
+ }
+
+ @Override
+ public SuspendToken suspend()
+ {
+ return null;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[%s]",LocalWebSocketConnection.class.getSimpleName(),id);
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/LocalWebSocketSession.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/LocalWebSocketSession.java
new file mode 100644
index 0000000..bc8ede1
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/LocalWebSocketSession.java
@@ -0,0 +1,50 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.helper;
+
+import java.net.URI;
+
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.junit.rules.TestName;
+
+public class LocalWebSocketSession extends WebSocketSession
+{
+ private String id;
+ private OutgoingFramesCapture outgoingCapture;
+
+ public LocalWebSocketSession(TestName testname, EventDriver driver)
+ {
+ super(URI.create("ws://localhost/LocalWebSocketSesssion/" + testname.getMethodName()),driver,new LocalWebSocketConnection(testname));
+ this.id = testname.getMethodName();
+ outgoingCapture = new OutgoingFramesCapture();
+ setOutgoingHandler(outgoingCapture);
+ }
+
+ public OutgoingFramesCapture getOutgoingCapture()
+ {
+ return outgoingCapture;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[%s]",LocalWebSocketSession.class.getSimpleName(),id);
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/OutgoingFramesCapture.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/OutgoingFramesCapture.java
new file mode 100644
index 0000000..7022d00
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/OutgoingFramesCapture.java
@@ -0,0 +1,97 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.helper;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+
+import java.util.LinkedList;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.junit.Assert;
+
+public class OutgoingFramesCapture implements OutgoingFrames
+{
+ private LinkedList<WebSocketFrame> frames = new LinkedList<>();
+
+ public void assertFrameCount(int expectedCount)
+ {
+ Assert.assertThat("Captured frame count",frames.size(),is(expectedCount));
+ }
+
+ public void assertHasFrame(byte op)
+ {
+ Assert.assertThat(OpCode.name(op),getFrameCount(op),greaterThanOrEqualTo(1));
+ }
+
+ public void assertHasFrame(byte op, int expectedCount)
+ {
+ Assert.assertThat(OpCode.name(op),getFrameCount(op),is(expectedCount));
+ }
+
+ public void assertHasNoFrames()
+ {
+ Assert.assertThat("Has no frames",frames.size(),is(0));
+ }
+
+ public void dump()
+ {
+ System.out.printf("Captured %d outgoing writes%n",frames.size());
+ for (int i = 0; i < frames.size(); i++)
+ {
+ Frame frame = frames.get(i);
+ System.out.printf("[%3d] %s%n",i,frame);
+ System.out.printf(" %s%n",BufferUtil.toDetailString(frame.getPayload()));
+ }
+ }
+
+ public int getFrameCount(byte op)
+ {
+ int count = 0;
+ for (WebSocketFrame frame : frames)
+ {
+ if (frame.getOpCode() == op)
+ {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ public LinkedList<WebSocketFrame> getFrames()
+ {
+ return frames;
+ }
+
+ @Override
+ public void outgoingFrame(Frame frame, WriteCallback callback)
+ {
+ WebSocketFrame copy = WebSocketFrame.copy(frame);
+ frames.add(copy);
+ if (callback != null)
+ {
+ callback.writeSuccess();
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/UnitParser.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/UnitParser.java
new file mode 100644
index 0000000..7976515
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/UnitParser.java
@@ -0,0 +1,78 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.mux.helper;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.Parser;
+
+public class UnitParser extends Parser
+{
+ public UnitParser()
+ {
+ this(WebSocketPolicy.newServerPolicy());
+ }
+
+ public UnitParser(ByteBufferPool bufferPool, WebSocketPolicy policy)
+ {
+ super(policy,bufferPool);
+ }
+
+ public UnitParser(WebSocketPolicy policy)
+ {
+ this(new MappedByteBufferPool(),policy);
+ }
+
+ private void parsePartial(ByteBuffer buf, int numBytes)
+ {
+ int len = Math.min(numBytes,buf.remaining());
+ byte arr[] = new byte[len];
+ buf.get(arr,0,len);
+ this.parse(ByteBuffer.wrap(arr));
+ }
+
+ /**
+ * Parse a buffer, but do so in a quiet fashion, squelching stacktraces if encountered.
+ * <p>
+ * Use if you know the parse will cause an exception and just don't wnat to make the test console all noisy.
+ */
+ public void parseQuietly(ByteBuffer buf)
+ {
+ try (StacklessLogging supress = new StacklessLogging(Parser.class))
+ {
+ parse(buf);
+ }
+ catch (Exception ignore)
+ {
+ /* ignore */
+ }
+ }
+
+ public void parseSlowly(ByteBuffer buf, int segmentSize)
+ {
+ while (buf.remaining() > 0)
+ {
+ parsePartial(buf,segmentSize);
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-mux-extension/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-mux-extension/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..4d43210
--- /dev/null
+++ b/jetty-websocket/websocket-mux-extension/src/test/resources/jetty-logging.properties
@@ -0,0 +1,7 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.websocket.LEVEL=WARN
+# org.eclipse.jetty.websocket.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.protocol.Parser.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.protocol.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.io.payload.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.core.extensions.compress.LEVEL=DEBUG
diff --git a/jetty-websocket/websocket-server/pom.xml b/jetty-websocket/websocket-server/pom.xml
index f349460..b9d94e0 100644
--- a/jetty-websocket/websocket-server/pom.xml
+++ b/jetty-websocket/websocket-server/pom.xml
@@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -48,8 +48,13 @@
<version>${project.version}</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlet</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
@@ -64,12 +69,6 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-servlet</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java
new file mode 100644
index 0000000..580116d
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java
@@ -0,0 +1,33 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+/**
+ * Common interface for MappedWebSocketCreator
+ */
+public interface MappedWebSocketCreator
+{
+ public void addMapping(PathSpec spec, WebSocketCreator creator);
+
+ public PathMappings<WebSocketCreator> getMappings();
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandshake.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandshake.java
index e7f6fb6..3622e72 100644
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandshake.java
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandshake.java
@@ -30,7 +30,6 @@
*
* @param request
* @param response
- * @param acceptedSubProtocol
*/
public void doHandshakeResponse(ServletUpgradeRequest request, ServletUpgradeResponse response) throws IOException;
}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java
index f899941..3879335 100644
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java
@@ -27,7 +27,6 @@
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
-import org.eclipse.jetty.websocket.common.ConnectionState;
import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
public class WebSocketServerConnection extends AbstractWebSocketConnection
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java
index fbbb0b5..adc173d 100644
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java
@@ -19,10 +19,12 @@
package org.eclipse.jetty.websocket.server;
import java.io.IOException;
+import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
@@ -42,13 +44,15 @@
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.SessionFactory;
import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.WebSocketSessionFactory;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
@@ -64,7 +68,6 @@
public class WebSocketServerFactory extends ContainerLifeCycle implements WebSocketCreator, WebSocketServletFactory
{
private static final Logger LOG = Log.getLogger(WebSocketServerFactory.class);
-
private static final ThreadLocal<UpgradeContext> ACTIVE_CONTEXT = new ThreadLocal<>();
public static UpgradeContext getActiveUpgradeContext()
@@ -82,15 +85,16 @@
handshakes.put(HandshakeRFC6455.VERSION,new HandshakeRFC6455());
}
- private final Queue<WebSocketSession> sessions = new ConcurrentLinkedQueue<>();
/**
* Have the factory maintain 1 and only 1 scheduler. All connections share this scheduler.
*/
private final Scheduler scheduler = new ScheduledExecutorScheduler();
+ private final Queue<WebSocketSession> sessions = new ConcurrentLinkedQueue<>();
private final String supportedVersions;
- private final WebSocketPolicy basePolicy;
+ private final WebSocketPolicy defaultPolicy;
private final EventDriverFactory eventDriverFactory;
private final WebSocketExtensionFactory extensionFactory;
+ private List<SessionFactory> sessionFactories;
private WebSocketCreator creator;
private List<Class<?>> registeredSocketClasses;
@@ -111,9 +115,11 @@
this.registeredSocketClasses = new ArrayList<>();
- this.basePolicy = policy;
- this.eventDriverFactory = new EventDriverFactory(basePolicy);
- this.extensionFactory = new WebSocketExtensionFactory(basePolicy,bufferPool);
+ this.defaultPolicy = policy;
+ this.eventDriverFactory = new EventDriverFactory(defaultPolicy);
+ this.extensionFactory = new WebSocketExtensionFactory(defaultPolicy,bufferPool);
+ this.sessionFactories = new ArrayList<>();
+ this.sessionFactories.add(new WebSocketSessionFactory());
this.creator = this;
// Create supportedVersions
@@ -138,16 +144,16 @@
@Override
public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException
{
+ return acceptWebSocket(getCreator(),request,response);
+ }
+
+ @Override
+ public boolean acceptWebSocket(WebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
try
{
- // TODO: use ServletUpgradeRequest in Jetty 9.1
- @SuppressWarnings("deprecation")
- ServletWebSocketRequest sockreq = new ServletWebSocketRequest(request);
- // TODO: use ServletUpgradeResponse in Jetty 9.1
- @SuppressWarnings("deprecation")
- ServletWebSocketResponse sockresp = new ServletWebSocketResponse(response);
-
- WebSocketCreator creator = getCreator();
+ ServletUpgradeRequest sockreq = new ServletUpgradeRequest(request);
+ ServletUpgradeResponse sockresp = new ServletUpgradeResponse(response);
UpgradeContext context = getActiveUpgradeContext();
if (context == null)
@@ -155,6 +161,7 @@
context = new UpgradeContext();
setActiveUpgradeContext(context);
}
+
context.setRequest(sockreq);
context.setResponse(sockresp);
@@ -183,6 +190,15 @@
}
}
+ public void addSessionFactory(SessionFactory sessionFactory)
+ {
+ if (sessionFactories.contains(sessionFactory))
+ {
+ return;
+ }
+ this.sessionFactories.add(sessionFactory);
+ }
+
@Override
public void cleanup()
{
@@ -200,14 +216,7 @@
{
for (WebSocketSession session : sessions)
{
- try
- {
- session.close();
- }
- catch (IOException e)
- {
- LOG.warn("CloseAllConnections Close failure",e);
- }
+ session.close();
}
sessions.clear();
}
@@ -218,11 +227,36 @@
return new WebSocketServerFactory(policy);
}
+ private WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection)
+ {
+ if (websocket == null)
+ {
+ throw new InvalidWebSocketException("Unable to create Session from null websocket");
+ }
+
+ for (SessionFactory impl : sessionFactories)
+ {
+ if (impl.supports(websocket))
+ {
+ try
+ {
+ return impl.createSession(requestURI,websocket,connection);
+ }
+ catch (Throwable e)
+ {
+ throw new InvalidWebSocketException("Unable to create Session",e);
+ }
+ }
+ }
+
+ throw new InvalidWebSocketException("Unable to create Session: unrecognized internal EventDriver type: " + websocket.getClass().getName());
+ }
+
/**
* Default Creator logic
*/
@Override
- public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
+ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
if (registeredSocketClasses.size() < 1)
{
@@ -258,6 +292,11 @@
return this.creator;
}
+ public EventDriverFactory getEventDriverFactory()
+ {
+ return eventDriverFactory;
+ }
+
@Override
public ExtensionFactory getExtensionFactory()
{
@@ -267,34 +306,65 @@
@Override
public WebSocketPolicy getPolicy()
{
- return basePolicy;
+ return defaultPolicy;
}
@Override
public void init() throws Exception
{
- start();
+ start(); // start lifecycle
}
@Override
public boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response)
{
+ if (!"GET".equalsIgnoreCase(request.getMethod()))
+ {
+ // not a "GET" request (not a websocket upgrade)
+ return false;
+ }
+
+ String connection = request.getHeader("connection");
+ if (connection == null)
+ {
+ // no "Connection: upgrade" header present.
+ return false;
+ }
+
+ // Test for "Upgrade" token
+ boolean foundUpgradeToken = false;
+ Iterator<String> iter = QuoteUtil.splitAt(connection,",");
+ while (iter.hasNext())
+ {
+ String token = iter.next();
+ if ("upgrade".equalsIgnoreCase(token))
+ {
+ foundUpgradeToken = true;
+ break;
+ }
+ }
+
+ if (!foundUpgradeToken)
+ {
+ return false;
+ }
+
String upgrade = request.getHeader("Upgrade");
if (upgrade == null)
{
- // Quietly fail
+ // no "Upgrade: websocket" header present.
return false;
}
if (!"websocket".equalsIgnoreCase(upgrade))
{
- LOG.warn("Not a 'Upgrade: WebSocket' (was [Upgrade: " + upgrade + "])");
+ LOG.debug("Not a 'Upgrade: WebSocket' (was [Upgrade: " + upgrade + "])");
return false;
}
if (!"HTTP/1.1".equals(request.getProtocol()))
{
- LOG.warn("Not a 'HTTP/1.1' request (was [" + request.getProtocol() + "])");
+ LOG.debug("Not a 'HTTP/1.1' request (was [" + request.getProtocol() + "])");
return false;
}
@@ -320,11 +390,6 @@
return protocols;
}
- /*
- * (non-Javadoc)
- *
- * @see org.eclipse.jetty.websocket.server.WebSocketServletFactory#register(java.lang.Class)
- */
@Override
public void register(Class<?> websocketPojo)
{
@@ -403,7 +468,18 @@
// Initialize / Negotiate Extensions
ExtensionStack extensionStack = new ExtensionStack(getExtensionFactory());
- extensionStack.negotiate(request.getExtensions());
+ // The JSR allows for the extensions to be pre-negotiated, filtered, etc...
+ // Usually from a Configurator.
+ if (response.isExtensionsNegotiated())
+ {
+ // Use pre-negotiated extension list from response
+ extensionStack.negotiate(response.getExtensions());
+ }
+ else
+ {
+ // Use raw extension list from request
+ extensionStack.negotiate(request.getExtensions());
+ }
// Create connection
UpgradeContext context = getActiveUpgradeContext();
@@ -426,9 +502,10 @@
}
// Setup Session
- WebSocketSession session = new WebSocketSession(request.getRequestURI(),driver,connection);
- session.setPolicy(getPolicy().clonePolicy());
+ WebSocketSession session = createSession(request.getRequestURI(),driver,connection);
+ session.setPolicy(driver.getPolicy());
session.setUpgradeRequest(request);
+ // set true negotiated extension list back to response
response.setExtensions(extensionStack.getNegotiatedExtensions());
session.setUpgradeResponse(response);
connection.setSession(session);
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java
new file mode 100644
index 0000000..b1bef23
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java
@@ -0,0 +1,236 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+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.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+/**
+ * Inline Servlet Filter to capture WebSocket upgrade requests and perform path mappings to {@link WebSocketCreator} objects.
+ */
+@ManagedObject("WebSocket Upgrade Filter")
+public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter, MappedWebSocketCreator, Dumpable
+{
+ private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
+
+ public static WebSocketUpgradeFilter configureContext(ServletContextHandler context)
+ {
+ WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+
+ WebSocketUpgradeFilter filter = new WebSocketUpgradeFilter(policy);
+ FilterHolder fholder = new FilterHolder(filter);
+ fholder.setName("Jetty_WebSocketUpgradeFilter");
+ fholder.setDisplayName("WebSocket Upgrade Filter");
+ String pathSpec = "/*";
+ context.addFilter(fholder,pathSpec,EnumSet.of(DispatcherType.REQUEST));
+ LOG.debug("Adding {} mapped to {} to {}",filter,pathSpec,context);
+
+ // Store reference to the WebSocketUpgradeFilter
+ context.setAttribute(WebSocketUpgradeFilter.class.getName(),filter);
+
+ return filter;
+ }
+
+ private final WebSocketServerFactory factory;
+ private final PathMappings<WebSocketCreator> pathmap = new PathMappings<>();
+
+ public WebSocketUpgradeFilter(WebSocketPolicy policy)
+ {
+ factory = new WebSocketServerFactory(policy);
+ addBean(factory,true);
+ }
+
+ @Override
+ public void addMapping(PathSpec spec, WebSocketCreator creator)
+ {
+ pathmap.put(spec,creator);
+ }
+
+ @Override
+ public void destroy()
+ {
+ factory.cleanup();
+ pathmap.reset();
+ super.destroy();
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
+ {
+ if (factory == null)
+ {
+ // no factory, cannot operate
+ LOG.debug("WebSocketUpgradeFilter is not operational - no WebSocketServletFactory configured");
+ chain.doFilter(request,response);
+ return;
+ }
+
+ if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse))
+ {
+ HttpServletRequest httpreq = (HttpServletRequest)request;
+ HttpServletResponse httpresp = (HttpServletResponse)response;
+
+ // Since this is a filter, we need to be smart about determining the target path
+ String contextPath = httpreq.getContextPath();
+ String target = httpreq.getRequestURI();
+ if (target.startsWith(contextPath))
+ {
+ target = target.substring(contextPath.length());
+ }
+
+ if (factory.isUpgradeRequest(httpreq,httpresp))
+ {
+ LOG.debug("target = [{}]",target);
+
+ MappedResource<WebSocketCreator> resource = pathmap.getMatch(target);
+ if (resource == null)
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("WebSocket Upgrade on {} has no associated endpoint",target);
+ LOG.debug("PathMappings: {}",pathmap.dump());
+ }
+ // no match.
+ chain.doFilter(request,response);
+ return;
+ }
+ LOG.debug("WebSocket Upgrade detected on {} for endpoint {}",target,resource);
+
+ WebSocketCreator creator = resource.getResource();
+
+ // Store PathSpec resource mapping as request attribute
+ httpreq.setAttribute(PathSpec.class.getName(),resource.getPathSpec());
+
+ // We have an upgrade request
+ if (factory.acceptWebSocket(creator,httpreq,httpresp))
+ {
+ // We have a socket instance created
+ return;
+ }
+
+ // If we reach this point, it means we had an incoming request to upgrade
+ // but it was either not a proper websocket upgrade, or it was possibly rejected
+ // due to incoming request constraints (controlled by WebSocketCreator)
+ if (response.isCommitted())
+ {
+ // not much we can do at this point.
+ return;
+ }
+ }
+ }
+
+ // not an Upgrade request
+ chain.doFilter(request,response);
+ }
+
+ @Override
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ out.append(indent).append(" +- pathmap=").append(pathmap.toString()).append("\n");
+ pathmap.dump(out,indent + " ");
+ }
+
+ public WebSocketServerFactory getFactory()
+ {
+ return factory;
+ }
+
+ @ManagedAttribute(value = "mappings", readonly = true)
+ @Override
+ public PathMappings<WebSocketCreator> getMappings()
+ {
+ return pathmap;
+ }
+
+ @Override
+ public void init(FilterConfig config) throws ServletException
+ {
+ try
+ {
+ WebSocketPolicy policy = factory.getPolicy();
+
+ String max = config.getInitParameter("maxIdleTime");
+ if (max != null)
+ {
+ policy.setIdleTimeout(Long.parseLong(max));
+ }
+
+ max = config.getInitParameter("maxTextMessageSize");
+ if (max != null)
+ {
+ policy.setMaxTextMessageSize(Integer.parseInt(max));
+ }
+
+ max = config.getInitParameter("maxBinaryMessageSize");
+ if (max != null)
+ {
+ policy.setMaxBinaryMessageSize(Integer.parseInt(max));
+ }
+
+ max = config.getInitParameter("inputBufferSize");
+ if (max != null)
+ {
+ policy.setInputBufferSize(Integer.parseInt(max));
+ }
+
+ factory.start();
+ }
+ catch (Exception x)
+ {
+ throw new ServletException(x);
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[factory=%s,pathmap=%s]",this.getClass().getSimpleName(),factory,pathmap);
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java
new file mode 100644
index 0000000..8599968
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java
@@ -0,0 +1,92 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+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.HandlerWrapper;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+public class WebSocketUpgradeHandlerWrapper extends HandlerWrapper implements MappedWebSocketCreator
+{
+ private PathMappings<WebSocketCreator> pathmap = new PathMappings<>();
+ private final WebSocketServerFactory factory;
+
+ public WebSocketUpgradeHandlerWrapper()
+ {
+ factory = new WebSocketServerFactory();
+ }
+
+ @Override
+ public void addMapping(PathSpec spec, WebSocketCreator creator)
+ {
+ pathmap.put(spec,creator);
+ }
+
+ @Override
+ public PathMappings<WebSocketCreator> getMappings()
+ {
+ return pathmap;
+ }
+
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ if (factory.isUpgradeRequest(request,response))
+ {
+ MappedResource<WebSocketCreator> resource = pathmap.getMatch(target);
+ if (resource == null)
+ {
+ // no match.
+ response.sendError(HttpServletResponse.SC_NOT_FOUND,"No websocket endpoint matching path: " + target);
+ return;
+ }
+
+ WebSocketCreator creator = resource.getResource();
+
+ // Store PathSpec resource mapping as request attribute
+ request.setAttribute(PathSpec.class.getName(),resource);
+
+ // We have an upgrade request
+ if (factory.acceptWebSocket(creator,request,response))
+ {
+ // We have a socket instance created
+ return;
+ }
+
+ // If we reach this point, it means we had an incoming request to upgrade
+ // but it was either not a proper websocket upgrade, or it was possibly rejected
+ // due to incoming request constraints (controlled by WebSocketCreator)
+ if (response.isCommitted())
+ {
+ // not much we can do at this point.
+ return;
+ }
+ }
+ super.handle(target,baseRequest,request,response);
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/EmptyHttpInput.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/EmptyHttpInput.java
deleted file mode 100644
index 3084ac4..0000000
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/EmptyHttpInput.java
+++ /dev/null
@@ -1,47 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.server.mux;
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.server.HttpInput;
-
-/**
- * HttpInput for Empty Http body sections.
- */
-public class EmptyHttpInput extends HttpInput<ByteBuffer>
-{
- @Override
- protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
- {
- return 0;
- }
-
- @Override
- protected void onContentConsumed(ByteBuffer item)
- {
- // do nothing
- }
-
- @Override
- protected int remaining(ByteBuffer item)
- {
- return 0;
- }
-}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/HttpChannelOverMux.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/HttpChannelOverMux.java
deleted file mode 100644
index a3c953f..0000000
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/HttpChannelOverMux.java
+++ /dev/null
@@ -1,40 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.server.mux;
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.HttpChannel;
-import org.eclipse.jetty.server.HttpConfiguration;
-import org.eclipse.jetty.server.HttpInput;
-import org.eclipse.jetty.server.HttpTransport;
-
-/**
- * Process incoming AddChannelRequest headers within the existing Jetty framework. Benefiting from Server container knowledge and various webapp configuration
- * knowledge.
- */
-public class HttpChannelOverMux extends HttpChannel<ByteBuffer>
-{
- public HttpChannelOverMux(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input)
- {
- super(connector,configuration,endPoint,transport,input);
- }
-}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/HttpTransportOverMux.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/HttpTransportOverMux.java
deleted file mode 100644
index cc442b1..0000000
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/HttpTransportOverMux.java
+++ /dev/null
@@ -1,85 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.server.mux;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.server.HttpTransport;
-import org.eclipse.jetty.util.BlockingCallback;
-import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxChannel;
-import org.eclipse.jetty.websocket.common.extensions.mux.Muxer;
-
-/**
- * Take {@link ResponseInfo} objects and convert to bytes for response.
- */
-public class HttpTransportOverMux implements HttpTransport
-{
- private static final Logger LOG = Log.getLogger(HttpTransportOverMux.class);
- private final BlockingCallback streamBlocker = new BlockingCallback();
-
- public HttpTransportOverMux(Muxer muxer, MuxChannel channel)
- {
- // TODO Auto-generated constructor stub
- }
-
- @Override
- public void completed()
- {
- LOG.debug("completed");
- }
-
- /**
- * Process ResponseInfo object into AddChannelResponse
- */
- @Override
- public void send(ResponseInfo info, ByteBuffer responseBodyContent, boolean lastContent) throws IOException
- {
- send(info,responseBodyContent,lastContent,streamBlocker);
- streamBlocker.block();
- }
-
- @Override
- public void send(ResponseInfo info, ByteBuffer responseBodyContent, boolean lastContent, Callback callback)
- {
- if (lastContent == false)
- {
- // throw error
- }
-
- if (info.getContentLength() > 0)
- {
- // throw error
- }
-
- // prepare the AddChannelResponse
- // TODO: look at HttpSender in jetty-client for generator loop logic
- }
-
- @Override
- public void send(ByteBuffer responseBodyContent, boolean lastContent, Callback callback)
- {
- send(null,responseBodyContent, lastContent, callback);
- }
-}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxAddHandler.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxAddHandler.java
deleted file mode 100644
index 4f46b9d..0000000
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxAddHandler.java
+++ /dev/null
@@ -1,112 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.server.mux;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.http.HttpField;
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.HttpMethod;
-import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.HttpConfiguration;
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxChannel;
-import org.eclipse.jetty.websocket.common.extensions.mux.MuxException;
-import org.eclipse.jetty.websocket.common.extensions.mux.Muxer;
-import org.eclipse.jetty.websocket.common.extensions.mux.add.MuxAddServer;
-
-/**
- * Handler for incoming MuxAddChannel requests.
- */
-public class MuxAddHandler implements MuxAddServer
-{
- /** Represents physical connector */
- private Connector connector;
-
- /** Used for local address */
- private EndPoint endPoint;
-
- /** The original request handshake */
- private UpgradeRequest baseHandshakeRequest;
-
- /** The original request handshake */
- private UpgradeResponse baseHandshakeResponse;
-
- private int maximumHeaderSize = 32 * 1024;
-
- @Override
- public UpgradeRequest getPhysicalHandshakeRequest()
- {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public UpgradeResponse getPhysicalHandshakeResponse()
- {
- // TODO Auto-generated method stub
- return null;
- }
-
- /**
- * An incoming MuxAddChannel request.
- *
- * @param the
- * channel this request should be bound to
- * @param request
- * the incoming request headers (complete and merged if delta encoded)
- * @return the outgoing response headers
- */
- @Override
- public void handshake(Muxer muxer, MuxChannel channel, UpgradeRequest request) throws MuxException, IOException
- {
- // Need to call into HttpChannel to get the websocket properly setup.
- HttpTransportOverMux transport = new HttpTransportOverMux(muxer,channel);
- EmptyHttpInput input = new EmptyHttpInput();
- HttpConfiguration configuration = new HttpConfiguration();
-
- HttpChannelOverMux httpChannel = new HttpChannelOverMux(//
- connector,configuration,endPoint,transport,input);
-
- HttpMethod method = HttpMethod.fromString(request.getMethod());
- HttpVersion version = HttpVersion.fromString(request.getHttpVersion());
- httpChannel.startRequest(method,request.getMethod(),BufferUtil.toBuffer(request.getRequestURI().toASCIIString()),version);
-
- for (String headerName : request.getHeaders().keySet())
- {
- HttpHeader header = HttpHeader.CACHE.getBest(headerName.getBytes(),0,headerName.length());
- for (String value : request.getHeaders().get(headerName))
- {
- httpChannel.parsedHeader(new HttpField(header,value));
- }
- }
-
- httpChannel.headerComplete();
- httpChannel.messageComplete();
- httpChannel.run(); // calls into server for appropriate resource
-
- // TODO: what's in request handshake is not enough to process the request.
- // like a partial http request. (consider this a AddChannelRequest failure)
- throw new MuxException("Not a valid request");
- }
-}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxServerExtension.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxServerExtension.java
deleted file mode 100644
index 1aa510d..0000000
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxServerExtension.java
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.server.mux;
-
-import org.eclipse.jetty.websocket.common.extensions.mux.AbstractMuxExtension;
-import org.eclipse.jetty.websocket.common.extensions.mux.Muxer;
-
-public class MuxServerExtension extends AbstractMuxExtension
-{
- @Override
- public void configureMuxer(Muxer muxer)
- {
- muxer.setAddServer(new MuxAddHandler());
- }
-}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/package-info.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/package-info.java
deleted file mode 100644
index 3436788..0000000
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-/**
- * Jetty WebSocket Server : MUX Extension [<em>Unstable Early Draft</em>]
- */
-package org.eclipse.jetty.websocket.server.mux;
-
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java
new file mode 100644
index 0000000..31785ad
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java
@@ -0,0 +1,189 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+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.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
+
+/**
+ * Path Mappings of PathSpec to Resource.
+ * <p>
+ * Sorted into search order upon entry into the Set
+ *
+ * @param <E>
+ */
+@ManagedObject("Path Mappings")
+public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
+{
+ @ManagedObject("Mapped Resource")
+ public static class MappedResource<E> implements Comparable<MappedResource<E>>
+ {
+ private final PathSpec pathSpec;
+ private final E resource;
+
+ public MappedResource(PathSpec pathSpec, E resource)
+ {
+ this.pathSpec = pathSpec;
+ this.resource = resource;
+ }
+
+ /**
+ * Comparison is based solely on the pathSpec
+ */
+ @Override
+ public int compareTo(MappedResource<E> other)
+ {
+ return this.pathSpec.compareTo(other.pathSpec);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ MappedResource<?> other = (MappedResource<?>)obj;
+ if (pathSpec == null)
+ {
+ if (other.pathSpec != null)
+ {
+ return false;
+ }
+ }
+ else if (!pathSpec.equals(other.pathSpec))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @ManagedAttribute(value = "path spec", readonly = true)
+ public PathSpec getPathSpec()
+ {
+ return pathSpec;
+ }
+
+ @ManagedAttribute(value = "resource", readonly = true)
+ public E getResource()
+ {
+ return resource;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((pathSpec == null)?0:pathSpec.hashCode());
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("MappedResource[pathSpec=%s,resource=%s]",pathSpec,resource);
+ }
+ }
+
+ private static final Logger LOG = Log.getLogger(PathMappings.class);
+ private List<MappedResource<E>> mappings = new ArrayList<MappedResource<E>>();
+ private MappedResource<E> defaultResource = null;
+
+ @Override
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ ContainerLifeCycle.dump(out,indent,mappings);
+ }
+
+ @ManagedAttribute(value = "mappings", readonly = true)
+ public List<MappedResource<E>> getMappings()
+ {
+ return mappings;
+ }
+
+ public void reset()
+ {
+ mappings.clear();
+ }
+
+ public MappedResource<E> getMatch(String path)
+ {
+ int len = mappings.size();
+ for (int i = 0; i < len; i++)
+ {
+ MappedResource<E> mr = mappings.get(i);
+ if (mr.getPathSpec().matches(path))
+ {
+ return mr;
+ }
+ }
+ return defaultResource;
+ }
+
+ @Override
+ public Iterator<MappedResource<E>> iterator()
+ {
+ return mappings.iterator();
+ }
+
+ public void put(PathSpec pathSpec, E resource)
+ {
+ MappedResource<E> entry = new MappedResource<>(pathSpec,resource);
+ if (pathSpec.group == PathSpecGroup.DEFAULT)
+ {
+ defaultResource = entry;
+ }
+ // TODO: warning on replacement of existing mapping?
+ mappings.add(entry);
+ LOG.debug("Added {} to {}",entry,this);
+ Collections.sort(mappings);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[size=%d]",this.getClass().getSimpleName(),mappings.size());
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpec.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpec.java
new file mode 100644
index 0000000..945b1aa
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpec.java
@@ -0,0 +1,167 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+/**
+ * The base PathSpec, what all other path specs are based on
+ */
+public abstract class PathSpec implements Comparable<PathSpec>
+{
+ protected String pathSpec;
+ protected PathSpecGroup group;
+ protected int pathDepth;
+ protected int specLength;
+
+ @Override
+ public int compareTo(PathSpec other)
+ {
+ // Grouping (increasing)
+ int diff = this.group.ordinal() - other.group.ordinal();
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ // Spec Length (decreasing)
+ diff = other.specLength - this.specLength;
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ // Path Spec Name (alphabetical)
+ return this.pathSpec.compareTo(other.pathSpec);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ PathSpec other = (PathSpec)obj;
+ if (pathSpec == null)
+ {
+ if (other.pathSpec != null)
+ {
+ return false;
+ }
+ }
+ else if (!pathSpec.equals(other.pathSpec))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public PathSpecGroup getGroup()
+ {
+ return group;
+ }
+
+ /**
+ * Get the number of path elements that this path spec declares.
+ * <p>
+ * This is used to determine longest match logic.
+ *
+ * @return the depth of the path segments that this spec declares
+ */
+ public int getPathDepth()
+ {
+ return pathDepth;
+ }
+
+ /**
+ * Return the portion of the path that is after the path spec.
+ *
+ * @param path
+ * the path to match against
+ * @return the path info portion of the string
+ */
+ public abstract String getPathInfo(String path);
+
+ /**
+ * Return the portion of the path that matches a path spec.
+ *
+ * @param path
+ * the path to match against
+ * @return the match, or null if no match at all
+ */
+ public abstract String getPathMatch(String path);
+
+ /**
+ * The as-provided path spec.
+ *
+ * @return the as-provided path spec
+ */
+ public String getPathSpec()
+ {
+ return pathSpec;
+ }
+
+ /**
+ * Get the relative path.
+ *
+ * @param base
+ * the base the path is relative to
+ * @param path
+ * the additional path
+ * @return the base plus path with pathSpec portion removed
+ */
+ public abstract String getRelativePath(String base, String path);
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((pathSpec == null)?0:pathSpec.hashCode());
+ return result;
+ }
+
+ /**
+ * Test to see if the provided path matches this path spec
+ *
+ * @param path
+ * the path to test
+ * @return true if the path matches this path spec, false otherwise
+ */
+ public abstract boolean matches(String path);
+
+ @Override
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder();
+ str.append(this.getClass().getSimpleName()).append("[\"");
+ str.append(pathSpec);
+ str.append("\",pathDepth=").append(pathDepth);
+ str.append(",group=").append(group);
+ str.append("]");
+ return str.toString();
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpecGroup.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpecGroup.java
new file mode 100644
index 0000000..2ea700e
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpecGroup.java
@@ -0,0 +1,82 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+/**
+ * Types of path spec groups.
+ * <p>
+ * This is used to facilitate proper pathspec search order.
+ * <p>
+ * Search Order: {@link PathSpecGroup#ordinal()} [increasin], {@link PathSpec#specLength} [decreasing], {@link PathSpec#pathSpec} [natural sort order]
+ */
+public enum PathSpecGroup
+{
+ // NOTE: Order of enums determines order of Groups.
+
+ /**
+ * For exactly defined path specs, no glob.
+ */
+ EXACT,
+ /**
+ * For path specs that have a hardcoded prefix and suffix with wildcard glob in the middle.
+ *
+ * <pre>
+ * "^/downloads/[^/]*.zip$" - regex spec
+ * "/a/{var}/c" - websocket spec
+ * </pre>
+ *
+ * Note: there is no known servlet spec variant of this kind of path spec
+ */
+ MIDDLE_GLOB,
+ /**
+ * For path specs that have a hardcoded prefix and a trailing wildcard glob.
+ * <p>
+ *
+ * <pre>
+ * "/downloads/*" - servlet spec
+ * "/api/*" - servlet spec
+ * "^/rest/.*$" - regex spec
+ * "/bookings/{guest-id}" - websocket spec
+ * "/rewards/{vip-level}" - websocket spec
+ * </pre>
+ */
+ PREFIX_GLOB,
+ /**
+ * For path specs that have a wildcard glob with a hardcoded suffix
+ *
+ * <pre>
+ * "*.do" - servlet spec
+ * "*.css" - servlet spec
+ * "^.*\.zip$" - regex spec
+ * </pre>
+ *
+ * Note: there is no known websocket spec variant of this kind of path spec
+ */
+ SUFFIX_GLOB,
+ /**
+ * The default spec for accessing the Root and/or Default behavior.
+ *
+ * <pre>
+ * "/" - servlet spec (Default Servlet)
+ * "/" - websocket spec (Root Context)
+ * "^/$" - regex spec (Root Context)
+ * </pre>
+ */
+ DEFAULT;
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpec.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpec.java
new file mode 100644
index 0000000..040b611
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpec.java
@@ -0,0 +1,176 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class RegexPathSpec extends PathSpec
+{
+ protected Pattern pattern;
+
+ protected RegexPathSpec()
+ {
+ super();
+ }
+
+ public RegexPathSpec(String regex)
+ {
+ super.pathSpec = regex;
+ boolean inGrouping = false;
+ this.pathDepth = 0;
+ this.specLength = pathSpec.length();
+ // build up a simple signature we can use to identify the grouping
+ StringBuilder signature = new StringBuilder();
+ for (char c : pathSpec.toCharArray())
+ {
+ switch (c)
+ {
+ case '[':
+ inGrouping = true;
+ break;
+ case ']':
+ inGrouping = false;
+ signature.append('g'); // glob
+ break;
+ case '*':
+ signature.append('g'); // glob
+ break;
+ case '/':
+ if (!inGrouping)
+ {
+ this.pathDepth++;
+ }
+ break;
+ default:
+ if (!inGrouping)
+ {
+ if (Character.isLetterOrDigit(c))
+ {
+ signature.append('l'); // literal (exact)
+ }
+ }
+ break;
+ }
+ }
+ this.pattern = Pattern.compile(pathSpec);
+
+ // Figure out the grouping based on the signature
+ String sig = signature.toString();
+
+ if (Pattern.matches("^l*$",sig))
+ {
+ this.group = PathSpecGroup.EXACT;
+ }
+ else if (Pattern.matches("^l*g+",sig))
+ {
+ this.group = PathSpecGroup.PREFIX_GLOB;
+ }
+ else if (Pattern.matches("^g+l+$",sig))
+ {
+ this.group = PathSpecGroup.SUFFIX_GLOB;
+ }
+ else
+ {
+ this.group = PathSpecGroup.MIDDLE_GLOB;
+ }
+ }
+
+ public Matcher getMatcher(String path)
+ {
+ return this.pattern.matcher(path);
+ }
+
+ @Override
+ public String getPathInfo(String path)
+ {
+ // Path Info only valid for PREFIX_GLOB types
+ if (group == PathSpecGroup.PREFIX_GLOB)
+ {
+ Matcher matcher = getMatcher(path);
+ if (matcher.matches())
+ {
+ if (matcher.groupCount() >= 1)
+ {
+ String pathInfo = matcher.group(1);
+ if ("".equals(pathInfo))
+ {
+ return "/";
+ }
+ else
+ {
+ return pathInfo;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getPathMatch(String path)
+ {
+ Matcher matcher = getMatcher(path);
+ if (matcher.matches())
+ {
+ if (matcher.groupCount() >= 1)
+ {
+ int idx = matcher.start(1);
+ if (idx > 0)
+ {
+ if (path.charAt(idx - 1) == '/')
+ {
+ idx--;
+ }
+ return path.substring(0,idx);
+ }
+ }
+ return path;
+ }
+ return null;
+ }
+
+ public Pattern getPattern()
+ {
+ return this.pattern;
+ }
+
+ @Override
+ public String getRelativePath(String base, String path)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public boolean matches(final String path)
+ {
+ int idx = path.indexOf('?');
+ if (idx >= 0)
+ {
+ // match only non-query part
+ return getMatcher(path.substring(0,idx)).matches();
+ }
+ else
+ {
+ // match entire path
+ return getMatcher(path).matches();
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpec.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpec.java
new file mode 100644
index 0000000..a6feb8f
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpec.java
@@ -0,0 +1,291 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import org.eclipse.jetty.util.URIUtil;
+
+public class ServletPathSpec extends PathSpec
+{
+ public static final String PATH_SPEC_SEPARATORS = ":,";
+
+ /**
+ * Get multi-path spec splits.
+ *
+ * @param servletPathSpec
+ * the path spec that might contain multiple declared path specs
+ * @return the individual path specs found.
+ */
+ public static ServletPathSpec[] getMultiPathSpecs(String servletPathSpec)
+ {
+ String pathSpecs[] = servletPathSpec.split(PATH_SPEC_SEPARATORS);
+ int len = pathSpecs.length;
+ ServletPathSpec sps[] = new ServletPathSpec[len];
+ for (int i = 0; i < len; i++)
+ {
+ sps[i] = new ServletPathSpec(pathSpecs[i]);
+ }
+ return sps;
+ }
+
+ public ServletPathSpec(String servletPathSpec)
+ {
+ super();
+ assertValidServletPathSpec(servletPathSpec);
+
+ // The Path Spec for Default Servlet
+ if ((servletPathSpec == null) || (servletPathSpec.length() == 0) || "/".equals(servletPathSpec))
+ {
+ super.pathSpec = "/";
+ super.pathDepth = -1; // force this to be last in sort order
+ this.specLength = 1;
+ this.group = PathSpecGroup.DEFAULT;
+ return;
+ }
+
+ this.specLength = servletPathSpec.length();
+ super.pathDepth = 0;
+ char lastChar = servletPathSpec.charAt(specLength - 1);
+ // prefix based
+ if ((servletPathSpec.charAt(0) == '/') && (specLength > 1) && (lastChar == '*'))
+ {
+ this.group = PathSpecGroup.PREFIX_GLOB;
+ }
+ // suffix based
+ else if (servletPathSpec.charAt(0) == '*')
+ {
+ this.group = PathSpecGroup.SUFFIX_GLOB;
+ }
+ else
+ {
+ this.group = PathSpecGroup.EXACT;
+ }
+
+ for (int i = 0; i < specLength; i++)
+ {
+ int cp = servletPathSpec.codePointAt(i);
+ if (cp < 128)
+ {
+ char c = (char)cp;
+ switch (c)
+ {
+ case '/':
+ super.pathDepth++;
+ break;
+ }
+ }
+ }
+
+ super.pathSpec = servletPathSpec;
+ }
+
+ private void assertValidServletPathSpec(String servletPathSpec)
+ {
+ if ((servletPathSpec == null) || servletPathSpec.equals(""))
+ {
+ return; // empty path spec
+ }
+
+ // Ensure we don't have path spec separators here in our single path spec.
+ for (char c : PATH_SPEC_SEPARATORS.toCharArray())
+ {
+ if (servletPathSpec.indexOf(c) >= 0)
+ {
+ throw new IllegalArgumentException("Servlet Spec 12.2 violation: encountered Path Spec Separator [" + PATH_SPEC_SEPARATORS
+ + "] within specified path spec. did you forget to split this path spec up?");
+ }
+ }
+
+ int len = servletPathSpec.length();
+ // path spec must either start with '/' or '*.'
+ if (servletPathSpec.charAt(0) == '/')
+ {
+ // Prefix Based
+ if (len == 1)
+ {
+ return; // simple '/' path spec
+ }
+ int idx = servletPathSpec.indexOf('*');
+ if (idx < 0)
+ {
+ return; // no hit on glob '*'
+ }
+ // only allowed to have '*' at the end of the path spec
+ if (idx != (len - 1))
+ {
+ throw new IllegalArgumentException("Servlet Spec 12.2 violation: glob '*' can only exist at end of prefix based matches");
+ }
+ }
+ else if (servletPathSpec.startsWith("*."))
+ {
+ // Suffix Based
+ int idx = servletPathSpec.indexOf('/');
+ // cannot have path separator
+ if (idx >= 0)
+ {
+ throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have path separators");
+ }
+
+ idx = servletPathSpec.indexOf('*',2);
+ // only allowed to have 1 glob '*', at the start of the path spec
+ if (idx >= 1)
+ {
+ throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have multiple glob '*'");
+ }
+ }
+ else
+ {
+ throw new IllegalArgumentException("Servlet Spec 12.2 violation: path spec must start with \"/\" or \"*.\"");
+ }
+ }
+
+ @Override
+ public String getPathInfo(String path)
+ {
+ // Path Info only valid for PREFIX_GLOB types
+ if (group == PathSpecGroup.PREFIX_GLOB)
+ {
+ if (path.length() == (specLength - 2))
+ {
+ return null;
+ }
+ return path.substring(specLength - 2);
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getPathMatch(String path)
+ {
+ switch (group)
+ {
+ case EXACT:
+ if (pathSpec.equals(path))
+ {
+ return path;
+ }
+ else
+ {
+ return null;
+ }
+ case PREFIX_GLOB:
+ if (isWildcardMatch(path))
+ {
+ return path.substring(0,specLength - 2);
+ }
+ else
+ {
+ return null;
+ }
+ case SUFFIX_GLOB:
+ if (path.regionMatches(path.length() - (specLength - 1),pathSpec,1,specLength - 1))
+ {
+ return path;
+ }
+ else
+ {
+ return null;
+ }
+ case DEFAULT:
+ return path;
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public String getRelativePath(String base, String path)
+ {
+ String info = getPathInfo(path);
+ if (info == null)
+ {
+ info = path;
+ }
+
+ if (info.startsWith("./"))
+ {
+ info = info.substring(2);
+ }
+ if (base.endsWith(URIUtil.SLASH))
+ {
+ if (info.startsWith(URIUtil.SLASH))
+ {
+ path = base + info.substring(1);
+ }
+ else
+ {
+ path = base + info;
+ }
+ }
+ else if (info.startsWith(URIUtil.SLASH))
+ {
+ path = base + info;
+ }
+ else
+ {
+ path = base + URIUtil.SLASH + info;
+ }
+ return path;
+ }
+
+ private boolean isExactMatch(String path)
+ {
+ if (group == PathSpecGroup.EXACT)
+ {
+ if (pathSpec.equals(path))
+ {
+ return true;
+ }
+ return (path.charAt(path.length() - 1) == '/') && (path.equals(pathSpec + '/'));
+ }
+ return false;
+ }
+
+ private boolean isWildcardMatch(String path)
+ {
+ // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
+ int cpl = specLength - 2;
+ if ((group == PathSpecGroup.PREFIX_GLOB) && (path.regionMatches(0,pathSpec,0,cpl)))
+ {
+ if ((path.length() == cpl) || ('/' == path.charAt(cpl)))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean matches(String path)
+ {
+ switch (group)
+ {
+ case EXACT:
+ return isExactMatch(path);
+ case PREFIX_GLOB:
+ return isWildcardMatch(path);
+ case SUFFIX_GLOB:
+ return path.regionMatches((path.length() - specLength) + 1,pathSpec,1,specLength - 1);
+ case DEFAULT:
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java
index e2f8d92..fd44ddc 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java
@@ -18,25 +18,24 @@
package org.eclipse.jetty.websocket.server;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.io.IOException;
import java.net.URI;
+import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.websocket.api.StatusCode;
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.examples.echo.BigEchoSocket;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
-import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.junit.AfterClass;
import org.junit.Assert;
@@ -61,14 +60,7 @@
@Override
public void configure(WebSocketServletFactory factory)
{
- factory.setCreator(new WebSocketCreator()
- {
- @Override
- public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
- {
- return new BigEchoSocket();
- }
- });
+ factory.register(BigEchoSocket.class);
}
};
@@ -103,7 +95,7 @@
// Generate text frame
String msg = "this is an echo ... cho ... ho ... o";
- client.write(WebSocketFrame.text(msg));
+ client.write(new TextFrame().setPayload(msg));
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
@@ -116,7 +108,7 @@
}
}
- @Test
+ @Test(timeout=4000)
public void testEchoTooBig() throws IOException, Exception
{
BlockheadClient client = new BlockheadClient(serverUri);
@@ -130,7 +122,7 @@
// Generate text frame
byte buf[] = new byte[90*1024]; // buffer bigger than maxMessageSize
Arrays.fill(buf,(byte)'x');
- client.write(WebSocketFrame.text().setPayload(buf));
+ client.write(new TextFrame().setPayload(ByteBuffer.wrap(buf)));
// Read frame (hopefully close frame saying its too large)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ByteBufferAssert.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ByteBufferAssert.java
index 84e174b..83da73b 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ByteBufferAssert.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ByteBufferAssert.java
@@ -18,23 +18,35 @@
package org.eclipse.jetty.websocket.server;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.server.helper.Hex;
import org.junit.Assert;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.nullValue;
-
public class ByteBufferAssert
{
public static void assertEquals(String message, byte[] expected, byte[] actual)
{
- Assert.assertThat(message + " byte[].length",actual.length,is(expected.length));
- int len = expected.length;
- for (int i = 0; i < len; i++)
+ if (expected.length <= 200)
{
- Assert.assertThat(message + " byte[" + i + "]",actual[i],is(expected[i]));
+ // Simple comparison (useful for clear error messages)
+ String actualHex = Hex.asHex(actual);
+ String expectedHex = Hex.asHex(expected);
+ Assert.assertThat(message + " bytes", actualHex, is(expectedHex));
+ }
+ else
+ {
+ // Big byte buffers are checked byte for byte
+ Assert.assertThat(message + " byte[].length",actual.length,is(expected.length));
+ int len = expected.length;
+ for (int i = 0; i < len; i++)
+ {
+ Assert.assertThat(message + " byte[" + i + "]",actual[i],is(expected[i]));
+ }
}
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java
index 68bc1b1..3d59fb7 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java
@@ -18,11 +18,13 @@
package org.eclipse.jetty.websocket.server;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
@@ -66,7 +68,7 @@
// Generate text frame
String msg = "this is an echo ... cho ... ho ... o";
- client.write(WebSocketFrame.text(msg));
+ client.write(new TextFrame().setPayload(msg));
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FirefoxTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FirefoxTest.java
new file mode 100644
index 0000000..9ffb435
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FirefoxTest.java
@@ -0,0 +1,78 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.is;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
+import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class FirefoxTest
+{
+ private static SimpleServletServer server;
+
+ @BeforeClass
+ public static void startServer() throws Exception
+ {
+ server = new SimpleServletServer(new MyEchoServlet());
+ server.start();
+ }
+
+ @AfterClass
+ public static void stopServer()
+ {
+ server.stop();
+ }
+
+ @Test
+ public void testConnectionKeepAlive() throws Exception
+ {
+ BlockheadClient client = new BlockheadClient(server.getServerUri());
+ try
+ {
+ // Odd Connection Header value seen in Firefox
+ client.setConnectionValue("keep-alive, Upgrade");
+ client.connect();
+ client.sendStandardRequest();
+ client.expectUpgradeResponse();
+
+ // Generate text frame
+ String msg = "this is an echo ... cho ... ho ... o";
+ client.write(new TextFrame().setPayload(msg));
+
+ // Read frame (hopefully text frame)
+ IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
+ WebSocketFrame tf = capture.getFrames().poll();
+ Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
+ }
+ finally
+ {
+ client.close();
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java
index b84ee24..e770d08 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java
@@ -18,11 +18,13 @@
package org.eclipse.jetty.websocket.server;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.helper.EchoServlet;
@@ -86,7 +88,7 @@
Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("fragment"));
String msg = "Sent as a long message that should be split";
- client.write(WebSocketFrame.text(msg));
+ client.write(new TextFrame().setPayload(msg));
String parts[] = split(msg,fragSize);
IncomingFramesCapture capture = client.readFrames(parts.length,TimeUnit.MILLISECONDS,1000);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java
index 78a6be2..2e92820 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java
@@ -18,11 +18,13 @@
package org.eclipse.jetty.websocket.server;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.helper.EchoServlet;
@@ -72,7 +74,7 @@
String msg = "Hello";
// Client sends first message
- client.write(WebSocketFrame.text(msg));
+ client.write(new TextFrame().setPayload(msg));
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000);
WebSocketFrame frame = capture.getFrames().poll();
@@ -81,7 +83,7 @@
// Client sends second message
client.clearCaptured();
msg = "There";
- client.write(WebSocketFrame.text(msg));
+ client.write(new TextFrame().setPayload(msg));
capture = client.readFrames(1,TimeUnit.SECONDS,1);
frame = capture.getFrames().poll();
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java
index 47fa7ca..d316227 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java
@@ -18,11 +18,13 @@
package org.eclipse.jetty.websocket.server;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.helper.EchoServlet;
@@ -70,7 +72,7 @@
Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("identity"));
- client.write(WebSocketFrame.text("Hello"));
+ client.write(new TextFrame().setPayload("Hello"));
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000);
WebSocketFrame frame = capture.getFrames().poll();
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java
index 97e34c6..22152c4 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java
@@ -18,22 +18,14 @@
package org.eclipse.jetty.websocket.server;
-import static org.hamcrest.Matchers.*;
-
-import java.io.IOException;
import java.util.concurrent.TimeUnit;
-import org.eclipse.jetty.websocket.api.StatusCode;
-import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.OpCode;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
-import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.eclipse.jetty.websocket.server.helper.RFCSocket;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.junit.AfterClass;
-import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -84,17 +76,14 @@
// longer than server timeout configured in TimeoutServlet
client.sleep(TimeUnit.MILLISECONDS,1000);
- // Write to server (the server should be timed out and disconnect now)
- client.write(WebSocketFrame.text("Hello"));
+ // Write to server
+ // This action is possible, but does nothing.
+ // Server could be in a half-closed state at this point.
+ // Where the server read is closed (due to timeout), but the server write is still open.
+ // The server could not read this frame, if it is in this half closed state
+ client.write(new TextFrame().setPayload("Hello"));
- // now read 1 frame from server (should be close frame)
- IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1500);
- WebSocketFrame frame = capture.getFrames().poll();
- Assert.assertThat("Was close frame", frame.getOpCode(), is(OpCode.CLOSE));
- CloseInfo close = new CloseInfo(frame);
- Assert.assertThat("Close.code", close.getStatusCode(), is(StatusCode.SHUTDOWN));
- Assert.assertThat("Close.reason", close.getReason(), containsString("Idle Timeout"));
-
+ // Expect server to be disconnected at this point
client.expectServerDisconnect();
}
finally
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/LoadTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/LoadTest.java
index cd688c7..142edce 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/LoadTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/LoadTest.java
@@ -31,7 +31,7 @@
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
-import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@@ -68,7 +68,7 @@
@OnWebSocketMessage
public void onWebSocketText(String message)
{
- session.getRemote().sendStringByFuture(message);
+ session.getRemote().sendString(message,null);
long iter = count.incrementAndGet();
if ((iter % 100) == 0)
{
@@ -98,7 +98,7 @@
{
for (int i = 0; i < iterations; i++)
{
- client.write(WebSocketFrame.text("msg-" + i));
+ client.write(new TextFrame().setPayload("msg-" + i));
if ((i % 100) == 0)
{
LOG.info("Client Wrote {} msgs",i);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java
index b28d79f..7be26e2 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java
@@ -18,7 +18,9 @@
package org.eclipse.jetty.websocket.server;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
import java.net.HttpCookie;
import java.util.List;
@@ -28,6 +30,8 @@
import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.helper.EchoSocket;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@@ -45,7 +49,7 @@
private EchoSocket echoSocket = new EchoSocket();
@Override
- public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
+ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
this.lastRequest = req;
this.lastResponse = resp;
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java
index aafdea8..72cd507 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java
@@ -18,9 +18,8 @@
package org.eclipse.jetty.websocket.server;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -31,16 +30,16 @@
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.AbstractEventDriver;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.eclipse.jetty.websocket.server.helper.RFCSocket;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@@ -75,7 +74,6 @@
{
errors.add(cause);
}
-
}
@SuppressWarnings("serial")
@@ -88,7 +86,7 @@
}
@Override
- public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
+ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
if (req.hasSubProtocol("fastclose"))
{
@@ -116,14 +114,7 @@
public void onWebSocketConnect(Session sess)
{
LOG.debug("onWebSocketConnect({})",sess);
- try
- {
- sess.close();
- }
- catch (IOException e)
- {
- e.printStackTrace(System.err);
- }
+ sess.close();
}
}
@@ -209,7 +200,7 @@
client.setTimeout(TimeUnit.SECONDS,1);
try
{
- try (StacklessLogging scope = new StacklessLogging(EventDriver.class))
+ try (StacklessLogging scope = new StacklessLogging(AbstractEventDriver.class))
{
client.connect();
client.sendStandardRequest();
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
index 410fa82..c764f21 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
@@ -18,7 +18,8 @@
package org.eclipse.jetty.websocket.server;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java
index 3502924..d3a5d04 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.server;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.net.URI;
import java.util.concurrent.Future;
@@ -26,8 +26,6 @@
import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.toolchain.test.TestTracker;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.server.helper.CaptureSocket;
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java
index 6194090..52445e4 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java
@@ -18,15 +18,15 @@
package org.eclipse.jetty.websocket.server;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.net.URI;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
-import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.eclipse.jetty.websocket.server.helper.SessionServlet;
@@ -68,7 +68,7 @@
client.sendStandardRequest();
client.expectUpgradeResponse();
- client.write(WebSocketFrame.text("harsh-disconnect"));
+ client.write(new TextFrame().setPayload("harsh-disconnect"));
client.awaitDisconnect(1,TimeUnit.SECONDS);
}
@@ -90,10 +90,10 @@
client.expectUpgradeResponse();
// Ask the server socket for specific parameter map info
- client.write(WebSocketFrame.text("getParameterMap|snack"));
- client.write(WebSocketFrame.text("getParameterMap|amount"));
- client.write(WebSocketFrame.text("getParameterMap|brand"));
- client.write(WebSocketFrame.text("getParameterMap|cost")); // intentionall invalid
+ client.write(new TextFrame().setPayload("getParameterMap|snack"));
+ client.write(new TextFrame().setPayload("getParameterMap|amount"));
+ client.write(new TextFrame().setPayload("getParameterMap|brand"));
+ client.write(new TextFrame().setPayload("getParameterMap|cost")); // intentionall invalid
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(4,TimeUnit.MILLISECONDS,500);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java
index 215f491..5538c0d 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java
@@ -18,9 +18,8 @@
package org.eclipse.jetty.websocket.server;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
-import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@@ -38,21 +37,22 @@
import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.helper.Hex;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.eclipse.jetty.websocket.server.helper.RFCServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Test various <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> specified requirements placed on {@link WebSocketServlet}
- * <p>
- * This test serves a different purpose than than the {@link WebSocketMessageRFC6455Test}, and {@link WebSocketParserRFC6455Test} tests.
*/
@RunWith(AdvancedRunner.class)
public class WebSocketServletRFCTest
@@ -103,15 +103,15 @@
WebSocketFrame bin;
- bin = WebSocketFrame.binary(buf1).setFin(false);
+ bin = new BinaryFrame().setPayload(buf1).setFin(false);
client.write(bin); // write buf1 (fin=false)
- bin = new WebSocketFrame(OpCode.CONTINUATION).setPayload(buf2).setFin(false);
+ bin = new ContinuationFrame().setPayload(buf2).setFin(false);
client.write(bin); // write buf2 (fin=false)
- bin = new WebSocketFrame(OpCode.CONTINUATION).setPayload(buf3).setFin(true);
+ bin = new ContinuationFrame().setPayload(buf3).setFin(true);
client.write(bin); // write buf3 (fin=true)
@@ -179,7 +179,7 @@
// Generate text frame
String msg = "this is an echo ... cho ... ho ... o";
- client.write(WebSocketFrame.text(msg));
+ client.write(new TextFrame().setPayload(msg));
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
@@ -199,6 +199,9 @@
@Test
public void testInternalError() throws Exception
{
+ // Disable Long Stacks from EventDriver (we know this test will throw an exception)
+ enableStacks(EventDriver.class,false);
+
BlockheadClient client = new BlockheadClient(server.getServerUri());
try
{
@@ -209,7 +212,7 @@
try (StacklessLogging context = new StacklessLogging(EventDriver.class))
{
// Generate text frame
- client.write(WebSocketFrame.text("CRASH"));
+ client.write(new TextFrame().setPayload("CRASH"));
// Read frame (hopefully close frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
@@ -220,6 +223,8 @@
}
finally
{
+ // Reenable Long Stacks from EventDriver
+ enableStacks(EventDriver.class,true);
client.close();
}
}
@@ -253,7 +258,7 @@
// Generate text frame
String msg = "this is an echo ... cho ... ho ... o";
- client.write(WebSocketFrame.text(msg));
+ client.write(new TextFrame().setPayload(msg));
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
@@ -267,90 +272,6 @@
}
@Test
- @Ignore("Should be moved to Fuzzer")
- public void testMaxBinarySize() throws Exception
- {
- BlockheadClient client = new BlockheadClient(server.getServerUri());
- client.setProtocols("other");
- try
- {
- client.connect();
- client.sendStandardRequest();
- client.expectUpgradeResponse();
-
- // Choose a size for a single frame larger than the
- // server side policy
- int dataSize = 1024 * 100;
- byte buf[] = new byte[dataSize];
- Arrays.fill(buf,(byte)0x44);
-
- WebSocketFrame bin = WebSocketFrame.binary(buf).setFin(true);
- ByteBuffer bb = generator.generate(bin);
- try
- {
- client.writeRaw(bb);
- Assert.fail("Write should have failed due to terminated connection");
- }
- catch (SocketException e)
- {
- Assert.assertThat("Exception",e.getMessage(),containsString("Broken pipe"));
- }
-
- IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
- WebSocketFrame frame = capture.getFrames().poll();
- Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
- CloseInfo close = new CloseInfo(frame);
- Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.MESSAGE_TOO_LARGE));
- }
- finally
- {
- client.close();
- }
- }
-
- @Test
- @Ignore("Should be moved to Fuzzer")
- public void testMaxTextSize() throws Exception
- {
- BlockheadClient client = new BlockheadClient(server.getServerUri());
- client.setProtocols("other");
- try
- {
- client.connect();
- client.sendStandardRequest();
- client.expectUpgradeResponse();
-
- // Choose a size for a single frame larger than the
- // server side policy
- int dataSize = 1024 * 100;
- byte buf[] = new byte[dataSize];
- Arrays.fill(buf,(byte)'z');
-
- WebSocketFrame text = WebSocketFrame.text().setPayload(buf).setFin(true);
- ByteBuffer bb = generator.generate(text);
- try
- {
- client.writeRaw(bb);
- Assert.fail("Write should have failed due to terminated connection");
- }
- catch (SocketException e)
- {
- Assert.assertThat("Exception",e.getMessage(),containsString("Broken pipe"));
- }
-
- IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
- WebSocketFrame frame = capture.getFrames().poll();
- Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
- CloseInfo close = new CloseInfo(frame);
- Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.MESSAGE_TOO_LARGE));
- }
- finally
- {
- client.close();
- }
- }
-
- @Test
public void testTextNotUTF8() throws Exception
{
// Disable Long Stacks from Parser (we know this test will throw an exception)
@@ -367,9 +288,11 @@
byte buf[] = new byte[]
{ (byte)0xC2, (byte)0xC3 };
- WebSocketFrame txt = WebSocketFrame.text().setPayload(buf);
- ByteBuffer bb = generator.generate(txt);
- client.writeRaw(bb);
+ WebSocketFrame txt = new TextFrame().setPayload(ByteBuffer.wrap(buf));
+ txt.setMask(Hex.asByteArray("11223344"));
+ ByteBuffer bbHeader = generator.generateHeaderBytes(txt);
+ client.writeRaw(bbHeader);
+ client.writeRaw(txt.getPayload());
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
WebSocketFrame frame = capture.getFrames().poll();
@@ -414,7 +337,7 @@
// Generate text frame
String msg = "this is an echo ... cho ... ho ... o";
- client.write(WebSocketFrame.text(msg));
+ client.write(new TextFrame().setPayload(msg));
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABServlet.java
index 72f79f5..4c606d0 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABServlet.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABServlet.java
@@ -36,7 +36,8 @@
// Test cases 9.x uses BIG frame sizes, let policy handle them.
int bigFrameSize = 20 * MBYTE;
- factory.getPolicy().setMaxMessageSize(bigFrameSize);
+ factory.getPolicy().setMaxTextMessageSize(bigFrameSize);
+ factory.getPolicy().setMaxBinaryMessageSize(bigFrameSize);
factory.register(ABSocket.class);
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABSocket.java
index ecd1662..d8da5f9 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABSocket.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABSocket.java
@@ -27,6 +27,7 @@
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.common.util.TextUtil;
/**
* Simple Echo WebSocket, using async writes of echo
@@ -38,15 +39,6 @@
private Session session;
- private String abbreviate(String message)
- {
- if (message.length() > 80)
- {
- return '"' + message.substring(0,80) + "\"...";
- }
- return '"' + message + '"';
- }
-
@OnWebSocketMessage
public void onBinary(byte buf[], int offset, int len)
{
@@ -54,7 +46,7 @@
// echo the message back.
ByteBuffer data = ByteBuffer.wrap(buf,offset,len);
- this.session.getRemote().sendBytesByFuture(data);
+ this.session.getRemote().sendBytes(data,null);
}
@OnWebSocketConnect
@@ -74,14 +66,14 @@
}
else
{
- LOG.debug("onText() size={}, msg={}",message.length(),abbreviate(message));
+ LOG.debug("onText() size={}, msg={}",message.length(),TextUtil.hint(message));
}
}
try
{
// echo the message back.
- this.session.getRemote().sendStringByFuture(message);
+ this.session.getRemote().sendString(message,null);
}
catch (WebSocketException e)
{
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AbstractABCase.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AbstractABCase.java
index 0da9a58..c63ec08 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AbstractABCase.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AbstractABCase.java
@@ -22,10 +22,14 @@
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.util.log.StdErrLog;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.server.SimpleServletServer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@@ -34,9 +38,36 @@
public abstract class AbstractABCase
{
+ // Allow Fuzzer / Generator to create bad frames for testing frame validation
+ protected static class BadFrame extends WebSocketFrame
+ {
+ public BadFrame(byte opcode)
+ {
+ super(OpCode.CONTINUATION);
+ super.finRsvOp = (byte)((finRsvOp & 0xF0) | (opcode & 0x0F));
+ // NOTE: Not setting Frame.Type intentionally
+ }
+
+ @Override
+ public void assertValid()
+ {
+ }
+
+ @Override
+ public boolean isControlFrame()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isDataFrame()
+ {
+ return false;
+ }
+ }
+
protected static final byte FIN = (byte)0x80;
protected static final byte NOFIN = 0x00;
- private static final byte MASKED_BIT = (byte)0x80;
protected static final byte[] MASK =
{ 0x12, 0x34, 0x56, 0x78 };
@@ -65,6 +96,57 @@
{
server.stop();
}
+
+ /**
+ * Make a copy of a byte buffer.
+ * <p>
+ * This is important in some tests, as the underlying byte buffer contained in a Frame can be modified through
+ * masking and make it difficult to compare the results in the fuzzer.
+ *
+ * @param payload the payload to copy
+ * @return a new byte array of the payload contents
+ */
+ protected ByteBuffer copyOf(byte[] payload)
+ {
+ byte copy[] = new byte[payload.length];
+ System.arraycopy(payload,0,copy,0,payload.length);
+ return ByteBuffer.wrap(copy);
+ }
+
+ /**
+ * Make a copy of a byte buffer.
+ * <p>
+ * This is important in some tests, as the underlying byte buffer contained in a Frame can be modified through
+ * masking and make it difficult to compare the results in the fuzzer.
+ *
+ * @param payload the payload to copy
+ * @return a new byte array of the payload contents
+ */
+ protected ByteBuffer clone(ByteBuffer payload)
+ {
+ ByteBuffer copy = ByteBuffer.allocate(payload.remaining());
+ copy.put(payload.slice());
+ copy.flip();
+ return copy;
+ }
+
+ /**
+ * Make a copy of a byte buffer.
+ * <p>
+ * This is important in some tests, as the underlying byte buffer contained in a Frame can be modified through
+ * masking and make it difficult to compare the results in the fuzzer.
+ *
+ * @param payload the payload to copy
+ * @return a new byte array of the payload contents
+ */
+ protected ByteBuffer copyOf(ByteBuffer payload)
+ {
+ ByteBuffer copy = ByteBuffer.allocate(payload.remaining());
+ BufferUtil.clearToFill(copy);
+ BufferUtil.put(payload,copy);
+ BufferUtil.flipToFlush(copy,0);
+ return copy;
+ }
public static String toUtf8String(byte[] buf)
{
@@ -89,6 +171,10 @@
@Rule
public TestName testname = new TestName();
+ /**
+ * @deprecated use {@link StacklessLogging} in a try-with-resources block instead
+ */
+ @Deprecated
protected void enableStacks(Class<?> clazz, boolean enabled)
{
StdErrLog log = StdErrLog.getLogger(clazz);
@@ -105,57 +191,22 @@
return server;
}
- protected byte[] masked(final byte[] data)
+ public static byte[] masked(final byte[] data)
{
- int len = data.length;
- byte ret[] = new byte[len];
- System.arraycopy(data,0,ret,0,len);
- for (int i = 0; i < len; i++)
- {
- ret[i] ^= MASK[i % 4];
- }
- return ret;
+ return RawFrameBuilder.mask(data,MASK);
}
- private void putLength(ByteBuffer buf, int length, boolean masked)
+ public static void putLength(ByteBuffer buf, int length, boolean masked)
{
- if (length < 0)
- {
- throw new IllegalArgumentException("Length cannot be negative");
- }
- byte b = (masked?MASKED_BIT:0x00);
-
- // write the uncompressed length
- if (length > 0xFF_FF)
- {
- buf.put((byte)(b | 0x7F));
- buf.put((byte)0x00);
- buf.put((byte)0x00);
- buf.put((byte)0x00);
- buf.put((byte)0x00);
- buf.put((byte)((length >> 24) & 0xFF));
- buf.put((byte)((length >> 16) & 0xFF));
- buf.put((byte)((length >> 8) & 0xFF));
- buf.put((byte)(length & 0xFF));
- }
- else if (length >= 0x7E)
- {
- buf.put((byte)(b | 0x7E));
- buf.put((byte)(length >> 8));
- buf.put((byte)(length & 0xFF));
- }
- else
- {
- buf.put((byte)(b | length));
- }
+ RawFrameBuilder.putLength(buf,length,masked);
}
- public void putMask(ByteBuffer buf)
+ public static void putMask(ByteBuffer buf)
{
buf.put(MASK);
}
- public void putPayloadLength(ByteBuffer buf, int length)
+ public static void putPayloadLength(ByteBuffer buf, int length)
{
putLength(buf,length,true);
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java
index 82b2ce9..43399ef 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java
@@ -18,7 +18,8 @@
package org.eclipse.jetty.websocket.server.ab;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
import java.io.IOException;
import java.net.SocketException;
@@ -62,7 +63,7 @@
PER_FRAME,
SLOW
}
-
+
public static enum DisconnectMode
{
/** Disconnect occurred after a proper close handshake */
@@ -92,7 +93,8 @@
int bigMessageSize = 20 * MBYTE;
- policy.setMaxMessageSize(bigMessageSize);
+ policy.setMaxTextMessageSize(bigMessageSize);
+ policy.setMaxBinaryMessageSize(bigMessageSize);
policy.setIdleTimeout(5000);
this.client = new BlockheadClient(policy,testcase.getServer().getServerUri());
@@ -108,15 +110,14 @@
buflen += f.getPayloadLength() + Generator.OVERHEAD;
}
ByteBuffer buf = ByteBuffer.allocate(buflen);
- BufferUtil.clearToFill(buf);
// Generate frames
for (WebSocketFrame f : send)
{
setClientMask(f);
- BufferUtil.put(generator.generate(f),buf);
+ generator.generateWholeFrame(f,buf);
}
- BufferUtil.flipToFlush(buf,0);
+ buf.flip();
return buf;
}
@@ -266,19 +267,16 @@
buflen += f.getPayloadLength() + Generator.OVERHEAD;
}
ByteBuffer buf = ByteBuffer.allocate(buflen);
- BufferUtil.clearToFill(buf);
// Generate frames
for (WebSocketFrame f : send)
{
setClientMask(f);
- ByteBuffer rawbytes = generator.generate(f);
- if (LOG.isDebugEnabled())
+ buf.put(generator.generateHeaderBytes(f));
+ if (f.hasPayload())
{
- LOG.debug("frame: {}",f);
- LOG.debug("bytes: {}",BufferUtil.toDetailString(rawbytes));
+ buf.put(f.getPayload());
}
- BufferUtil.put(rawbytes,buf);
}
BufferUtil.flipToFlush(buf,0);
@@ -301,7 +299,11 @@
{
f.setMask(MASK); // make sure we have mask set
// Using lax generator, generate and send
- client.writeRaw(generator.generate(f));
+ ByteBuffer fullframe = ByteBuffer.allocate(f.getPayloadLength() + Generator.OVERHEAD);
+ BufferUtil.clearToFill(fullframe);
+ generator.generateWholeFrame(f,fullframe);
+ BufferUtil.flipToFlush(fullframe,0);
+ client.writeRaw(fullframe);
client.flush();
}
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/RawFrameBuilder.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/RawFrameBuilder.java
new file mode 100644
index 0000000..0d91d47
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/RawFrameBuilder.java
@@ -0,0 +1,110 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Assert;
+
+public class RawFrameBuilder
+{
+ public static void putOpFin(ByteBuffer buf, byte opcode, boolean fin)
+ {
+ byte b = 0x00;
+ if (fin)
+ {
+ b |= 0x80;
+ }
+ b |= opcode & 0x0F;
+ buf.put(b);
+ }
+
+ public static void putLengthAndMask(ByteBuffer buf, int length, byte mask[])
+ {
+ if (mask != null)
+ {
+ Assert.assertThat("Mask.length",mask.length,is(4));
+ putLength(buf,length,(mask != null));
+ buf.put(mask);
+ }
+ else
+ {
+ putLength(buf,length,false);
+ }
+ }
+
+ public static byte[] mask(final byte[] data, final byte mask[])
+ {
+ Assert.assertThat("Mask.length",mask.length,is(4));
+ int len = data.length;
+ byte ret[] = new byte[len];
+ System.arraycopy(data,0,ret,0,len);
+ for (int i = 0; i < len; i++)
+ {
+ ret[i] ^= mask[i % 4];
+ }
+ return ret;
+ }
+
+ public static void putLength(ByteBuffer buf, int length, boolean masked)
+ {
+ if (length < 0)
+ {
+ throw new IllegalArgumentException("Length cannot be negative");
+ }
+ byte b = (masked?(byte)0x80:0x00);
+
+ // write the uncompressed length
+ if (length > 0xFF_FF)
+ {
+ buf.put((byte)(b | 0x7F));
+ buf.put((byte)0x00);
+ buf.put((byte)0x00);
+ buf.put((byte)0x00);
+ buf.put((byte)0x00);
+ buf.put((byte)((length >> 24) & 0xFF));
+ buf.put((byte)((length >> 16) & 0xFF));
+ buf.put((byte)((length >> 8) & 0xFF));
+ buf.put((byte)(length & 0xFF));
+ }
+ else if (length >= 0x7E)
+ {
+ buf.put((byte)(b | 0x7E));
+ buf.put((byte)(length >> 8));
+ buf.put((byte)(length & 0xFF));
+ }
+ else
+ {
+ buf.put((byte)(b | length));
+ }
+ }
+
+ public static void putMask(ByteBuffer buf, byte mask[])
+ {
+ Assert.assertThat("Mask.length",mask.length,is(4));
+ buf.put(mask);
+ }
+
+ public static void putPayloadLength(ByteBuffer buf, int length)
+ {
+ putLength(buf,length,true);
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java
index a3e183c..178a78f 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java
@@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.server.ab;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -25,6 +26,8 @@
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.ab.Fuzzer.SendMode;
import org.junit.Test;
@@ -37,11 +40,11 @@
public void testCase1_1_1() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text());
+ send.add(new TextFrame());
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text());
+ expect.add(new TextFrame());
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -67,13 +70,14 @@
{
byte payload[] = new byte[125];
Arrays.fill(payload,(byte)'*');
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text().setPayload(payload));
+ send.add(new TextFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text().setPayload(payload));
+ expect.add(new TextFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -99,13 +103,14 @@
{
byte payload[] = new byte[126];
Arrays.fill(payload,(byte)'*');
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text().setPayload(payload));
+ send.add(new TextFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text().setPayload(payload));
+ expect.add(new TextFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -131,13 +136,14 @@
{
byte payload[] = new byte[127];
Arrays.fill(payload,(byte)'*');
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text().setPayload(payload));
+ send.add(new TextFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text().setPayload(payload));
+ expect.add(new TextFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -163,13 +169,14 @@
{
byte payload[] = new byte[128];
Arrays.fill(payload,(byte)'*');
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text().setPayload(payload));
+ send.add(new TextFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text().setPayload(payload));
+ expect.add(new TextFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -195,13 +202,14 @@
{
byte payload[] = new byte[65535];
Arrays.fill(payload,(byte)'*');
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text().setPayload(payload));
+ send.add(new TextFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text().setPayload(payload));
+ expect.add(new TextFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -227,13 +235,14 @@
{
byte payload[] = new byte[65536];
Arrays.fill(payload,(byte)'*');
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text().setPayload(payload));
+ send.add(new TextFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text().setPayload(payload));
+ expect.add(new TextFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -263,14 +272,15 @@
{
byte payload[] = new byte[65536];
Arrays.fill(payload,(byte)'*');
+ ByteBuffer buf = ByteBuffer.wrap(payload);
int segmentSize = 997;
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text().setPayload(payload));
+ send.add(new TextFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text().setPayload(payload));
+ expect.add(new TextFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -296,11 +306,11 @@
public void testCase1_2_1() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary());
+ send.add(new BinaryFrame());
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary());
+ expect.add(new BinaryFrame());
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -326,13 +336,14 @@
{
byte payload[] = new byte[125];
Arrays.fill(payload,(byte)0xFE);
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary().setPayload(payload));
+ send.add(new BinaryFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary().setPayload(payload));
+ expect.add(new BinaryFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -358,13 +369,14 @@
{
byte payload[] = new byte[126];
Arrays.fill(payload,(byte)0xFE);
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary().setPayload(payload));
+ send.add(new BinaryFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary().setPayload(payload));
+ expect.add(new BinaryFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -390,13 +402,14 @@
{
byte payload[] = new byte[127];
Arrays.fill(payload,(byte)0xFE);
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary().setPayload(payload));
+ send.add(new BinaryFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary().setPayload(payload));
+ expect.add(new BinaryFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -422,13 +435,14 @@
{
byte payload[] = new byte[128];
Arrays.fill(payload,(byte)0xFE);
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary().setPayload(payload));
+ send.add(new BinaryFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary().setPayload(payload));
+ expect.add(new BinaryFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -454,13 +468,14 @@
{
byte payload[] = new byte[65535];
Arrays.fill(payload,(byte)0xFE);
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary().setPayload(payload));
+ send.add(new BinaryFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary().setPayload(payload));
+ expect.add(new BinaryFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -486,13 +501,14 @@
{
byte payload[] = new byte[65536];
Arrays.fill(payload,(byte)0xFE);
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary().setPayload(payload));
+ send.add(new BinaryFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary().setPayload(payload));
+ expect.add(new BinaryFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -522,14 +538,15 @@
{
byte payload[] = new byte[65536];
Arrays.fill(payload,(byte)0xFE);
+ ByteBuffer buf = ByteBuffer.wrap(payload);
int segmentSize = 997;
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary().setPayload(payload));
+ send.add(new BinaryFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary().setPayload(payload));
+ expect.add(new BinaryFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase2.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase2.java
index 3a9f5e1..593cf8b 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase2.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase2.java
@@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.server.ab;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -30,6 +31,8 @@
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,9 +45,9 @@
@Test
public void testCase2_1() throws Exception
{
- WebSocketFrame send = WebSocketFrame.ping();
+ WebSocketFrame send = new PingFrame();
- WebSocketFrame expect = WebSocketFrame.pong();
+ WebSocketFrame expect = new PongFrame();
Fuzzer fuzzer = new Fuzzer(this);
try
@@ -79,8 +82,8 @@
for (int i = 0; i < pingCount; i++)
{
String payload = String.format("ping-%d[%X]",i,i);
- send.add(WebSocketFrame.ping().setPayload(payload));
- expect.add(WebSocketFrame.pong().setPayload(payload));
+ send.add(new PingFrame().setPayload(payload));
+ expect.add(new PongFrame().setPayload(payload));
}
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
@@ -118,8 +121,8 @@
for (int i = 0; i < pingCount; i++)
{
String payload = String.format("ping-%d[%X]",i,i);
- send.add(WebSocketFrame.ping().setPayload(payload));
- expect.add(WebSocketFrame.pong().setPayload(payload));
+ send.add(new PingFrame().setPayload(payload));
+ expect.add(new PongFrame().setPayload(payload));
}
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
@@ -148,11 +151,11 @@
byte payload[] = StringUtil.getUtf8Bytes("Hello world");
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.ping().setPayload(payload));
+ send.add(new PingFrame().setPayload(payload));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.pong().setPayload(payload));
+ expect.add(new PongFrame().setPayload(copyOf(payload)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -179,11 +182,11 @@
{ 0x00, (byte)0xFF, (byte)0xFE, (byte)0xFD, (byte)0xFC, (byte)0xFB, 0x00, (byte)0xFF };
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.ping().setPayload(payload));
+ send.add(new PingFrame().setPayload(payload));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.pong().setPayload(payload));
+ expect.add(new PongFrame().setPayload(copyOf(payload)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -210,11 +213,11 @@
Arrays.fill(payload,(byte)0xFE);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.ping().setPayload(payload));
+ send.add(new PingFrame().setPayload(payload));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.pong().setPayload(payload));
+ expect.add(new PongFrame().setPayload(copyOf(payload)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -241,10 +244,11 @@
{
byte payload[] = new byte[126]; // intentionally too big
Arrays.fill(payload,(byte)'5');
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
// trick websocket frame into making extra large payload for ping
- send.add(WebSocketFrame.binary(payload).setOpCode(OpCode.PING));
+ send.add(new BadFrame(OpCode.PING).setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL,"Test 2.5").asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
@@ -276,11 +280,11 @@
Arrays.fill(payload,(byte)'6');
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.ping().setPayload(payload));
+ send.add(new PingFrame().setPayload(payload));
send.add(new CloseInfo(StatusCode.NORMAL,"Test 2.6").asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.pong().setPayload(payload));
+ expect.add(new PongFrame().setPayload(copyOf(payload)));
expect.add(new CloseInfo(StatusCode.NORMAL,"Test 2.6").asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -305,7 +309,7 @@
public void testCase2_7() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.pong()); // unsolicited pong
+ send.add(new PongFrame()); // unsolicited pong
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
@@ -332,7 +336,7 @@
public void testCase2_8() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.pong().setPayload("unsolicited")); // unsolicited pong
+ send.add(new PongFrame().setPayload("unsolicited")); // unsolicited pong
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
@@ -359,12 +363,12 @@
public void testCase2_9() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.pong().setPayload("unsolicited")); // unsolicited pong
- send.add(WebSocketFrame.ping().setPayload("our ping")); // our ping
+ send.add(new PongFrame().setPayload("unsolicited")); // unsolicited pong
+ send.add(new PingFrame().setPayload("our ping")); // our ping
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.pong().setPayload("our ping")); // our pong
+ expect.add(new PongFrame().setPayload("our ping")); // our pong
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase3.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase3.java
index 1dc6c48..4a45b53 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase3.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase3.java
@@ -25,10 +25,10 @@
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.junit.After;
-import org.junit.Before;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,25 +38,13 @@
@RunWith(AdvancedRunner.class)
public class TestABCase3 extends AbstractABCase
{
- @After
- public void enableParserStacks()
- {
- enableStacks(Parser.class,true);
- }
-
- @Before
- public void quietParserStacks()
- {
- enableStacks(Parser.class,false);
- }
-
/**
* Send small text frame, with RSV1 == true, with no extensions defined.
*/
@Test
public void testCase3_1() throws Exception
{
- WebSocketFrame send = WebSocketFrame.text("small").setRsv1(true); // intentionally bad
+ WebSocketFrame send = new TextFrame().setPayload("small").setRsv1(true); // intentionally bad
WebSocketFrame expect = new CloseInfo(StatusCode.PROTOCOL).asFrame();
@@ -81,12 +69,12 @@
public void testCase3_2() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text("small"));
- send.add(WebSocketFrame.text("small").setRsv2(true)); // intentionally bad
- send.add(WebSocketFrame.ping().setPayload("ping"));
+ send.add(new TextFrame().setPayload("small"));
+ send.add(new TextFrame().setPayload("small").setRsv2(true)); // intentionally bad
+ send.add(new PingFrame().setPayload("ping"));
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("small")); // echo on good frame
+ expect.add(new TextFrame().setPayload("small")); // echo on good frame
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -110,12 +98,12 @@
public void testCase3_3() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text("small"));
- send.add(WebSocketFrame.text("small").setRsv1(true).setRsv2(true)); // intentionally bad
- send.add(WebSocketFrame.ping().setPayload("ping"));
+ send.add(new TextFrame().setPayload("small"));
+ send.add(new TextFrame().setPayload("small").setRsv1(true).setRsv2(true)); // intentionally bad
+ send.add(new PingFrame().setPayload("ping"));
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("small")); // echo on good frame
+ expect.add(new TextFrame().setPayload("small")); // echo on good frame
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -139,12 +127,12 @@
public void testCase3_4() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text("small"));
- send.add(WebSocketFrame.text("small").setRsv3(true)); // intentionally bad
- send.add(WebSocketFrame.ping().setPayload("ping"));
+ send.add(new TextFrame().setPayload("small"));
+ send.add(new TextFrame().setPayload("small").setRsv3(true)); // intentionally bad
+ send.add(new PingFrame().setPayload("ping"));
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("small")); // echo on good frame
+ expect.add(new TextFrame().setPayload("small")); // echo on good frame
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -172,7 +160,7 @@
Arrays.fill(payload,(byte)0xFF);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary(payload).setRsv3(true).setRsv1(true)); // intentionally bad
+ send.add(new BinaryFrame().setPayload(payload).setRsv3(true).setRsv1(true)); // intentionally bad
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
@@ -201,7 +189,7 @@
Arrays.fill(payload,(byte)0xFF);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.ping().setPayload(payload).setRsv3(true).setRsv2(true)); // intentionally bad
+ send.add(new PingFrame().setPayload(payload).setRsv3(true).setRsv2(true)); // intentionally bad
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase4.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase4.java
index 01bfd2f..ba2a85f 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase4.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase4.java
@@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.server.ab;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -25,10 +26,9 @@
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
-import org.junit.After;
-import org.junit.Before;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,29 +38,6 @@
@RunWith(AdvancedRunner.class)
public class TestABCase4 extends AbstractABCase
{
- // Allow Fuzzer / Generator to create bad frames for testing frame validation
- private static class BadFrame extends WebSocketFrame
- {
- public BadFrame(byte opcode)
- {
- super();
- super.opcode = opcode;
- // NOTE: Not setting Frame.Type intentionally
- }
- }
-
- @After
- public void enableParserStacks()
- {
- enableStacks(Parser.class,true);
- }
-
- @Before
- public void quietParserStacks()
- {
- enableStacks(Parser.class,false);
- }
-
/**
* Send opcode 3 (reserved)
*/
@@ -94,9 +71,10 @@
public void testCase4_1_2() throws Exception
{
byte payload[] = StringUtil.getUtf8Bytes("reserved payload");
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new BadFrame((byte)4).setPayload(payload)); // intentionally bad
+ send.add(new BadFrame((byte)4).setPayload(buf)); // intentionally bad
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
@@ -122,12 +100,12 @@
public void testCase4_1_3() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text("hello"));
+ send.add(new TextFrame().setPayload("hello"));
send.add(new BadFrame((byte)5)); // intentionally bad
- send.add(WebSocketFrame.ping());
+ send.add(new PingFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("hello")); // echo
+ expect.add(new TextFrame().setPayload("hello")); // echo
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -150,13 +128,15 @@
@Test
public void testCase4_1_4() throws Exception
{
+ ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad"));
+
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text("hello"));
- send.add(new BadFrame((byte)6).setPayload("bad")); // intentionally bad
- send.add(WebSocketFrame.ping());
+ send.add(new TextFrame().setPayload("hello"));
+ send.add(new BadFrame((byte)6).setPayload(buf)); // intentionally bad
+ send.add(new PingFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("hello")); // echo
+ expect.add(new TextFrame().setPayload("hello")); // echo
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -179,13 +159,15 @@
@Test
public void testCase4_1_5() throws Exception
{
+ ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad"));
+
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text("hello"));
- send.add(new BadFrame((byte)7).setPayload("bad")); // intentionally bad
- send.add(WebSocketFrame.ping());
+ send.add(new TextFrame().setPayload("hello"));
+ send.add(new BadFrame((byte)7).setPayload(buf)); // intentionally bad
+ send.add(new PingFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("hello")); // echo
+ expect.add(new TextFrame().setPayload("hello")); // echo
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -234,8 +216,10 @@
@Test
public void testCase4_2_2() throws Exception
{
+ ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad"));
+
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new BadFrame((byte)12).setPayload("bad")); // intentionally bad
+ send.add(new BadFrame((byte)12).setPayload(buf)); // intentionally bad
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
@@ -261,12 +245,12 @@
public void testCase4_2_3() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text("hello"));
+ send.add(new TextFrame().setPayload("hello"));
send.add(new BadFrame((byte)13)); // intentionally bad
- send.add(WebSocketFrame.ping());
+ send.add(new PingFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("hello")); // echo
+ expect.add(new TextFrame().setPayload("hello")); // echo
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -289,13 +273,15 @@
@Test
public void testCase4_2_4() throws Exception
{
+ ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad"));
+
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text("hello"));
- send.add(new BadFrame((byte)14).setPayload("bad")); // intentionally bad
- send.add(WebSocketFrame.ping());
+ send.add(new TextFrame().setPayload("hello"));
+ send.add(new BadFrame((byte)14).setPayload(buf)); // intentionally bad
+ send.add(new PingFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("hello")); // echo
+ expect.add(new TextFrame().setPayload("hello")); // echo
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -318,13 +304,15 @@
@Test
public void testCase4_2_5() throws Exception
{
+ ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad"));
+
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text("hello"));
- send.add(new BadFrame((byte)15).setPayload("bad")); // intentionally bad
- send.add(WebSocketFrame.ping());
+ send.add(new TextFrame().setPayload("hello"));
+ send.add(new BadFrame((byte)15).setPayload(buf)); // intentionally bad
+ send.add(new PingFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("hello")); // echo
+ expect.add(new TextFrame().setPayload("hello")); // echo
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java
index 0c3a4ee..434cc4d 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java
@@ -24,11 +24,15 @@
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,19 +48,16 @@
@Test
public void testCase5_1() throws Exception
{
- // Disable Long Stacks from Parser (we know this test will throw an exception)
- enableStacks(Parser.class,false);
-
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.PING).setPayload("hello, ").setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("world"));
+ send.add(new PingFrame().setPayload("hello, ").setFin(false));
+ send.add(new ContinuationFrame().setPayload("world"));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
@@ -66,7 +67,6 @@
finally
{
fuzzer.close();
- enableStacks(Parser.class,true);
}
}
@@ -76,19 +76,16 @@
@Test
public void testCase5_10() throws Exception
{
- // Disable Long Stacks from Parser (we know this test will throw an exception)
- enableStacks(Parser.class,false);
-
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("sorry").setFin(true));
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("hello, world"));
+ send.add(new ContinuationFrame().setPayload("sorry").setFin(true));
+ send.add(new TextFrame().setPayload("hello, world"));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME);
@@ -97,7 +94,6 @@
}
finally
{
- enableStacks(Parser.class,true);
fuzzer.close();
}
}
@@ -108,19 +104,16 @@
@Test
public void testCase5_11() throws Exception
{
- // Disable Long Stacks from Parser (we know this test will throw an exception)
- enableStacks(Parser.class,false);
-
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("sorry").setFin(true));
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("hello, world"));
+ send.add(new ContinuationFrame().setPayload("sorry").setFin(true));
+ send.add(new TextFrame().setPayload("hello, world"));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
@@ -130,7 +123,6 @@
}
finally
{
- enableStacks(Parser.class,true);
fuzzer.close();
}
}
@@ -141,19 +133,16 @@
@Test
public void testCase5_12() throws Exception
{
- // Disable Long Stacks from Parser (we know this test will throw an exception)
- enableStacks(Parser.class,false);
-
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("sorry").setFin(false));
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("hello, world"));
+ send.add(new ContinuationFrame().setPayload("sorry").setFin(false));
+ send.add(new TextFrame().setPayload("hello, world"));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
@@ -162,7 +151,6 @@
}
finally
{
- enableStacks(Parser.class,true);
fuzzer.close();
}
}
@@ -173,18 +161,16 @@
@Test
public void testCase5_13() throws Exception
{
- // Disable Long Stacks from Parser (we know this test will throw an exception)
- enableStacks(Parser.class,false);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("sorry").setFin(false));
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("hello, world"));
+ send.add(new ContinuationFrame().setPayload("sorry").setFin(false));
+ send.add(new TextFrame().setPayload("hello, world"));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME);
@@ -193,7 +179,6 @@
}
finally
{
- enableStacks(Parser.class,true);
fuzzer.close();
}
}
@@ -204,19 +189,16 @@
@Test
public void testCase5_14() throws Exception
{
- // Disable Long Stacks from Parser (we know this test will throw an exception)
- enableStacks(Parser.class,false);
-
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("sorry").setFin(false));
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("hello, world"));
+ send.add(new ContinuationFrame().setPayload("sorry").setFin(false));
+ send.add(new TextFrame().setPayload("hello, world"));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
@@ -226,7 +208,6 @@
}
finally
{
- enableStacks(Parser.class,false);
fuzzer.close();
}
}
@@ -237,22 +218,19 @@
@Test
public void testCase5_15() throws Exception
{
- // Disable Long Stacks from Parser (we know this test will throw an exception)
- enableStacks(Parser.class,false);
-
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment1").setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment2").setFin(true));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment3").setFin(false)); // bad frame
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment4").setFin(true));
+ send.add(new TextFrame().setPayload("fragment1").setFin(false));
+ send.add(new ContinuationFrame().setPayload("fragment2").setFin(true));
+ send.add(new ContinuationFrame().setPayload("fragment3").setFin(false)); // bad frame
+ send.add(new TextFrame().setPayload("fragment4").setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("fragment1fragment2"));
+ expect.add(new TextFrame().setPayload("fragment1fragment2"));
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
@@ -261,7 +239,6 @@
}
finally
{
- enableStacks(Parser.class,true);
fuzzer.close();
}
}
@@ -272,23 +249,20 @@
@Test
public void testCase5_16() throws Exception
{
- // Disable Long Stacks from Parser (we know this test will throw an exception)
- enableStacks(Parser.class,false);
-
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment1").setFin(false)); // bad frame
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment2").setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment3").setFin(true));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment4").setFin(false)); // bad frame
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment5").setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment6").setFin(true));
+ send.add(new ContinuationFrame().setPayload("fragment1").setFin(false)); // bad frame
+ send.add(new TextFrame().setPayload("fragment2").setFin(false));
+ send.add(new ContinuationFrame().setPayload("fragment3").setFin(true));
+ send.add(new ContinuationFrame().setPayload("fragment4").setFin(false)); // bad frame
+ send.add(new TextFrame().setPayload("fragment5").setFin(false));
+ send.add(new ContinuationFrame().setPayload("fragment6").setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
@@ -297,7 +271,6 @@
}
finally
{
- enableStacks(Parser.class,true);
fuzzer.close();
}
}
@@ -308,23 +281,20 @@
@Test
public void testCase5_17() throws Exception
{
- // Disable Long Stacks from Parser (we know this test will throw an exception)
- enableStacks(Parser.class,false);
-
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment1").setFin(true)); // nothing to continue
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment2").setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment3").setFin(true));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment4").setFin(true)); // nothing to continue
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment5").setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("fragment6").setFin(true));
+ send.add(new ContinuationFrame().setPayload("fragment1").setFin(true)); // nothing to continue
+ send.add(new TextFrame().setPayload("fragment2").setFin(false));
+ send.add(new ContinuationFrame().setPayload("fragment3").setFin(true));
+ send.add(new ContinuationFrame().setPayload("fragment4").setFin(true)); // nothing to continue
+ send.add(new TextFrame().setPayload("fragment5").setFin(false));
+ send.add(new ContinuationFrame().setPayload("fragment6").setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
@@ -333,7 +303,6 @@
}
finally
{
- enableStacks(Parser.class,true);
fuzzer.close();
}
}
@@ -344,19 +313,16 @@
@Test
public void testCase5_18() throws Exception
{
- // Disable Long Stacks from Parser (we know this test will throw an exception)
- enableStacks(Parser.class,false);
-
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment1").setFin(false));
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("fragment2").setFin(true)); // bad frame, must be continuation
+ send.add(new TextFrame().setPayload("fragment1").setFin(false));
+ send.add(new TextFrame().setPayload("fragment2").setFin(true)); // bad frame, must be continuation
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
@@ -365,7 +331,6 @@
}
finally
{
- enableStacks(Parser.class,true);
fuzzer.close();
}
}
@@ -379,28 +344,28 @@
{
// phase 1
List<WebSocketFrame> send1 = new ArrayList<>();
- send1.add(new WebSocketFrame(OpCode.TEXT).setPayload("f1").setFin(false));
- send1.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f2").setFin(false));
- send1.add(new WebSocketFrame(OpCode.PING).setPayload("pong-1"));
+ send1.add(new TextFrame().setPayload("f1").setFin(false));
+ send1.add(new ContinuationFrame().setPayload(",f2").setFin(false));
+ send1.add(new PingFrame().setPayload("pong-1"));
List<WebSocketFrame> expect1 = new ArrayList<>();
- expect1.add(WebSocketFrame.pong().setPayload("pong-1"));
+ expect1.add(new PongFrame().setPayload("pong-1"));
// phase 2
List<WebSocketFrame> send2 = new ArrayList<>();
- send2.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f3").setFin(false));
- send2.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f4").setFin(false));
- send2.add(new WebSocketFrame(OpCode.PING).setPayload("pong-2"));
- send2.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f5").setFin(true));
+ send2.add(new ContinuationFrame().setPayload(",f3").setFin(false));
+ send2.add(new ContinuationFrame().setPayload(",f4").setFin(false));
+ send2.add(new PingFrame().setPayload("pong-2"));
+ send2.add(new ContinuationFrame().setPayload(",f5").setFin(true));
send2.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect2 = new ArrayList<>();
- expect2.add(WebSocketFrame.pong().setPayload("pong-2"));
- expect2.add(WebSocketFrame.text("f1,f2,f3,f4,f5"));
+ expect2.add(new PongFrame().setPayload("pong-2"));
+ expect2.add(new TextFrame().setPayload("f1,f2,f3,f4,f5"));
expect2.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
@@ -428,19 +393,16 @@
@Test
public void testCase5_2() throws Exception
{
- // Disable Long Stacks from Parser (we know this test will throw an exception)
- enableStacks(Parser.class,false);
-
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.PONG).setPayload("hello, ").setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("world"));
+ send.add(new PongFrame().setPayload("hello, ").setFin(false));
+ send.add(new ContinuationFrame().setPayload("world"));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
@@ -449,7 +411,6 @@
}
finally
{
- enableStacks(Parser.class,true);
fuzzer.close();
}
}
@@ -461,27 +422,27 @@
public void testCase5_20() throws Exception
{
List<WebSocketFrame> send1 = new ArrayList<>();
- send1.add(new WebSocketFrame(OpCode.TEXT).setPayload("f1").setFin(false));
- send1.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f2").setFin(false));
- send1.add(new WebSocketFrame(OpCode.PING).setPayload("pong-1"));
+ send1.add(new TextFrame().setPayload("f1").setFin(false));
+ send1.add(new ContinuationFrame().setPayload(",f2").setFin(false));
+ send1.add(new PingFrame().setPayload("pong-1"));
List<WebSocketFrame> send2 = new ArrayList<>();
- send2.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f3").setFin(false));
- send2.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f4").setFin(false));
- send2.add(new WebSocketFrame(OpCode.PING).setPayload("pong-2"));
- send2.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f5").setFin(true));
+ send2.add(new ContinuationFrame().setPayload(",f3").setFin(false));
+ send2.add(new ContinuationFrame().setPayload(",f4").setFin(false));
+ send2.add(new PingFrame().setPayload("pong-2"));
+ send2.add(new ContinuationFrame().setPayload(",f5").setFin(true));
send2.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect1 = new ArrayList<>();
- expect1.add(WebSocketFrame.pong().setPayload("pong-1"));
+ expect1.add(new PongFrame().setPayload("pong-1"));
List<WebSocketFrame> expect2 = new ArrayList<>();
- expect2.add(WebSocketFrame.pong().setPayload("pong-2"));
- expect2.add(WebSocketFrame.text("f1,f2,f3,f4,f5"));
+ expect2.add(new PongFrame().setPayload("pong-2"));
+ expect2.add(new TextFrame().setPayload("f1,f2,f3,f4,f5"));
expect2.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME);
@@ -507,27 +468,27 @@
public void testCase5_20_slow() throws Exception
{
List<WebSocketFrame> send1 = new ArrayList<>();
- send1.add(new WebSocketFrame(OpCode.TEXT).setPayload("f1").setFin(false));
- send1.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f2").setFin(false));
- send1.add(new WebSocketFrame(OpCode.PING).setPayload("pong-1"));
+ send1.add(new TextFrame().setPayload("f1").setFin(false));
+ send1.add(new ContinuationFrame().setPayload(",f2").setFin(false));
+ send1.add(new PingFrame().setPayload("pong-1"));
List<WebSocketFrame> send2 = new ArrayList<>();
- send2.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f3").setFin(false));
- send2.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f4").setFin(false));
- send2.add(new WebSocketFrame(OpCode.PING).setPayload("pong-2"));
- send2.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(",f5").setFin(true));
+ send2.add(new ContinuationFrame().setPayload(",f3").setFin(false));
+ send2.add(new ContinuationFrame().setPayload(",f4").setFin(false));
+ send2.add(new PingFrame().setPayload("pong-2"));
+ send2.add(new ContinuationFrame().setPayload(",f5").setFin(true));
send2.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect1 = new ArrayList<>();
- expect1.add(WebSocketFrame.pong().setPayload("pong-1"));
+ expect1.add(new PongFrame().setPayload("pong-1"));
List<WebSocketFrame> expect2 = new ArrayList<>();
- expect2.add(WebSocketFrame.pong().setPayload("pong-2"));
- expect2.add(WebSocketFrame.text("f1,f2,f3,f4,f5"));
+ expect2.add(new PongFrame().setPayload("pong-2"));
+ expect2.add(new TextFrame().setPayload("f1,f2,f3,f4,f5"));
expect2.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
@@ -554,16 +515,16 @@
public void testCase5_3() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("hello, ").setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("world").setFin(true));
+ send.add(new TextFrame().setPayload("hello, ").setFin(false));
+ send.add(new ContinuationFrame().setPayload("world").setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("hello, world"));
+ expect.add(new TextFrame().setPayload("hello, world"));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
@@ -583,16 +544,16 @@
public void testCase5_4() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("hello, ").setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("world").setFin(true));
+ send.add(new TextFrame().setPayload("hello, ").setFin(false));
+ send.add(new ContinuationFrame().setPayload("world").setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("hello, world"));
+ expect.add(new TextFrame().setPayload("hello, world"));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME);
@@ -612,16 +573,16 @@
public void testCase5_5() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("hello, ").setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("world").setFin(true));
+ send.add(new TextFrame().setPayload("hello, ").setFin(false));
+ send.add(new ContinuationFrame().setPayload("world").setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("hello, world"));
+ expect.add(new TextFrame().setPayload("hello, world"));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
@@ -642,18 +603,18 @@
public void testCase5_6() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("hello, ").setFin(false));
- send.add(new WebSocketFrame(OpCode.PING).setPayload("ping"));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("world").setFin(true));
+ send.add(new TextFrame().setPayload("hello, ").setFin(false));
+ send.add(new PingFrame().setPayload("ping"));
+ send.add(new ContinuationFrame().setPayload("world").setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.pong().setPayload("ping"));
- expect.add(WebSocketFrame.text("hello, world"));
+ expect.add(new PongFrame().setPayload("ping"));
+ expect.add(new TextFrame().setPayload("hello, world"));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
@@ -673,18 +634,18 @@
public void testCase5_7() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("hello, ").setFin(false));
- send.add(new WebSocketFrame(OpCode.PING).setPayload("ping"));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("world").setFin(true));
+ send.add(new TextFrame().setPayload("hello, ").setFin(false));
+ send.add(new PingFrame().setPayload("ping"));
+ send.add(new ContinuationFrame().setPayload("world").setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.pong().setPayload("ping"));
- expect.add(WebSocketFrame.text("hello, world"));
+ expect.add(new PongFrame().setPayload("ping"));
+ expect.add(new TextFrame().setPayload("hello, world"));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME);
@@ -704,18 +665,18 @@
public void testCase5_8() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("hello, ").setFin(false));
- send.add(new WebSocketFrame(OpCode.PING).setPayload("ping"));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("world").setFin(true));
+ send.add(new TextFrame().setPayload("hello, ").setFin(false));
+ send.add(new PingFrame().setPayload("ping"));
+ send.add(new ContinuationFrame().setPayload("world").setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.pong().setPayload("ping"));
- expect.add(WebSocketFrame.text("hello, world"));
+ expect.add(new PongFrame().setPayload("ping"));
+ expect.add(new TextFrame().setPayload("hello, world"));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
@@ -735,19 +696,17 @@
@Test
public void testCase5_9() throws Exception
{
- // Disable Long Stacks from Parser (we know this test will throw an exception)
- enableStacks(Parser.class,false);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("sorry").setFin(true));
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("hello, world"));
+ send.add(new ContinuationFrame().setPayload("sorry").setFin(true));
+ send.add(new TextFrame().setPayload("hello, world"));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
@@ -756,7 +715,6 @@
}
finally
{
- enableStacks(Parser.class,true);
fuzzer.close();
}
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java
index b7aa7b3..24178f8 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java
@@ -31,9 +31,11 @@
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.helper.Hex;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,18 +52,26 @@
protected void fragmentText(List<WebSocketFrame> frames, byte msg[])
{
int len = msg.length;
- byte opcode = OpCode.TEXT;
+ boolean continuation = false;
byte mini[];
for (int i = 0; i < len; i++)
{
- WebSocketFrame frame = new WebSocketFrame(opcode);
+ DataFrame frame = null;
+ if (continuation)
+ {
+ frame = new ContinuationFrame();
+ }
+ else
+ {
+ frame = new TextFrame();
+ }
mini = new byte[1];
mini[0] = msg[i];
- frame.setPayload(mini);
+ frame.setPayload(ByteBuffer.wrap(mini));
boolean isLast = (i >= (len - 1));
frame.setFin(isLast);
frames.add(frame);
- opcode = OpCode.CONTINUATION;
+ continuation = true;
}
}
@@ -72,11 +82,11 @@
public void testCase6_1_1() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text());
+ send.add(new TextFrame());
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text());
+ expect.add(new TextFrame());
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -100,13 +110,13 @@
public void testCase6_1_2() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setFin(true));
+ send.add(new TextFrame().setFin(false));
+ send.add(new ContinuationFrame().setFin(false));
+ send.add(new ContinuationFrame().setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text());
+ expect.add(new TextFrame());
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -130,13 +140,13 @@
public void testCase6_1_3() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setFin(false).setPayload("middle"));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setFin(true));
+ send.add(new TextFrame().setFin(false));
+ send.add(new ContinuationFrame().setPayload("middle").setFin(false));
+ send.add(new ContinuationFrame().setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("middle"));
+ expect.add(new TextFrame().setPayload("middle"));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -159,16 +169,23 @@
@Test
public void testCase6_2_2() throws Exception
{
- String utf8[] =
- { "Hello-\uC2B5@\uC39F\uC3A4", "\uC3BC\uC3A0\uC3A1-UTF-8!!" };
+ String utf1 = "Hello-\uC2B5@\uC39F\uC3A4";
+ String utf2 = "\uC3BC\uC3A0\uC3A1-UTF-8!!";
+
+ ByteBuffer b1 = ByteBuffer.wrap(StringUtil.getUtf8Bytes(utf1));
+ ByteBuffer b2 = ByteBuffer.wrap(StringUtil.getUtf8Bytes(utf2));
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload(utf8[0]).setFin(false));
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload(utf8[1]).setFin(true));
+ send.add(new TextFrame().setPayload(b1).setFin(false));
+ send.add(new ContinuationFrame().setPayload(b2).setFin(true));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(new WebSocketFrame(OpCode.TEXT).setPayload(utf8[0] + utf8[1]));
+ ByteBuffer e1 = ByteBuffer.allocate(100);
+ e1.put(StringUtil.getUtf8Bytes(utf1));
+ e1.put(StringUtil.getUtf8Bytes(utf2));
+ e1.flip();
+ expect.add(new TextFrame().setPayload(e1));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -199,7 +216,7 @@
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(new WebSocketFrame(OpCode.TEXT).setPayload(msg));
+ expect.add(new TextFrame().setPayload(ByteBuffer.wrap(msg)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -229,7 +246,7 @@
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(new WebSocketFrame(OpCode.TEXT).setPayload(msg));
+ expect.add(new TextFrame().setPayload(ByteBuffer.wrap(msg)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -299,11 +316,11 @@
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
- fuzzer.send(new WebSocketFrame(OpCode.TEXT).setPayload(part1).setFin(false));
+ fuzzer.send(new TextFrame().setPayload(ByteBuffer.wrap(part1)).setFin(false));
TimeUnit.SECONDS.sleep(1);
- fuzzer.send(new WebSocketFrame(OpCode.CONTINUATION).setPayload(part2).setFin(false));
+ fuzzer.send(new ContinuationFrame().setPayload(ByteBuffer.wrap(part2)).setFin(false));
TimeUnit.SECONDS.sleep(1);
- fuzzer.send(new WebSocketFrame(OpCode.CONTINUATION).setPayload(part3).setFin(true));
+ fuzzer.send(new ContinuationFrame().setPayload(ByteBuffer.wrap(part3)).setFin(true));
fuzzer.expect(expect);
}
@@ -338,11 +355,11 @@
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
- fuzzer.send(new WebSocketFrame(OpCode.TEXT).setPayload(part1).setFin(false));
+ fuzzer.send(new TextFrame().setPayload(ByteBuffer.wrap(part1)).setFin(false));
TimeUnit.SECONDS.sleep(1);
- fuzzer.send(new WebSocketFrame(OpCode.CONTINUATION).setPayload(part2).setFin(false));
+ fuzzer.send(new ContinuationFrame().setPayload(ByteBuffer.wrap(part2)).setFin(false));
TimeUnit.SECONDS.sleep(1);
- fuzzer.send(new WebSocketFrame(OpCode.CONTINUATION).setPayload(part3).setFin(true));
+ fuzzer.send(new ContinuationFrame().setPayload(ByteBuffer.wrap(part3)).setFin(true));
fuzzer.expect(expect);
}
finally
@@ -359,7 +376,7 @@
public void testCase6_4_3() throws Exception
{
// Disable Long Stacks from Parser (we know this test will throw an exception)
- try(StacklessLogging scope = new StacklessLogging(Parser.class))
+ try (StacklessLogging scope = new StacklessLogging(Parser.class))
{
ByteBuffer payload = ByteBuffer.allocate(64);
BufferUtil.clearToFill(payload);
@@ -369,7 +386,7 @@
BufferUtil.flipToFlush(payload,0);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload(payload));
+ send.add(new TextFrame().setPayload(payload));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
@@ -395,13 +412,13 @@
part3.limit(splits[2]);
fuzzer.send(part1); // the header + good utf
- TimeUnit.SECONDS.sleep(1);
+ TimeUnit.MILLISECONDS.sleep(500);
fuzzer.send(part2); // the bad UTF
+ TimeUnit.MILLISECONDS.sleep(500);
+ fuzzer.send(part3); // the rest (shouldn't work)
fuzzer.expect(expect);
- TimeUnit.SECONDS.sleep(1);
- fuzzer.send(part3); // the rest (shouldn't work)
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.UNCLEAN);
}
finally
@@ -418,20 +435,17 @@
@Slow
public void testCase6_4_4() throws Exception
{
- // Disable Long Stacks from Parser (we know this test will throw an exception)
- enableStacks(Parser.class,false);
-
byte invalid[] = Hex.asByteArray("CEBAE1BDB9CF83CEBCCEB5F49080808080656469746564");
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload(invalid));
+ send.add(new TextFrame().setPayload(ByteBuffer.wrap(invalid)));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging scope = new StacklessLogging(Parser.class))
{
fuzzer.connect();
@@ -447,7 +461,6 @@
}
finally
{
- enableStacks(Parser.class,true);
fuzzer.close();
}
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_BadUTF.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_BadUTF.java
index 6767586..7304a6c 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_BadUTF.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_BadUTF.java
@@ -18,17 +18,19 @@
package org.eclipse.jetty.websocket.server.ab;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.helper.Hex;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -154,16 +156,15 @@
public void assertBadTextPayload() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload(invalid));
+ send.add(new TextFrame().setPayload(ByteBuffer.wrap(invalid)));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging supress = new StacklessLogging(Parser.class))
{
- enableStacks(Parser.class,false);
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
fuzzer.send(send);
@@ -173,7 +174,6 @@
finally
{
fuzzer.close();
- enableStacks(Parser.class,true);
}
}
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_GoodUTF.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_GoodUTF.java
index ecf10a2..b530191 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_GoodUTF.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_GoodUTF.java
@@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.server.ab;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -26,8 +27,8 @@
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.helper.Hex;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -118,23 +119,23 @@
return data;
}
- private final byte[] msg;
+ private final ByteBuffer msg;
public TestABCase6_GoodUTF(String testId, String hexMsg)
{
LOG.debug("Test ID: {}",testId);
- this.msg = Hex.asByteArray(hexMsg);
+ this.msg = Hex.asByteBuffer(hexMsg);
}
@Test
public void assertEchoTextMessage() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload(msg));
+ send.add(new TextFrame().setPayload(msg));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(new WebSocketFrame(OpCode.TEXT).setPayload(msg));
+ expect.add(new TextFrame().setPayload(clone(msg)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7.java
index d6e71a9..50cf3a0 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7.java
@@ -26,11 +26,16 @@
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.server.helper.Hex;
import org.junit.Rule;
import org.junit.Test;
@@ -50,11 +55,11 @@
public void testCase7_1_1() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text("Hello World"));
+ send.add(new TextFrame().setPayload("Hello World"));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text("Hello World"));
+ expect.add(new TextFrame().setPayload("Hello World"));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -107,7 +112,7 @@
{
List<WebSocketFrame> send = new ArrayList<>();
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
- send.add(WebSocketFrame.ping().setPayload("out of band ping"));
+ send.add(new PingFrame().setPayload("out of band ping"));
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
@@ -135,7 +140,7 @@
{
List<WebSocketFrame> send = new ArrayList<>();
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
- send.add(WebSocketFrame.text("out of band text"));
+ send.add(new TextFrame().setPayload("out of band text"));
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
@@ -162,9 +167,9 @@
public void testCase7_1_5() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload("an").setFin(false));
+ send.add(new TextFrame().setPayload("an").setFin(false));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
- send.add(new WebSocketFrame(OpCode.CONTINUATION).setPayload("ticipation").setFin(true));
+ send.add(new ContinuationFrame().setPayload("ticipation").setFin(true));
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
@@ -192,14 +197,15 @@
{
byte msg[] = new byte[256 * 1024];
Arrays.fill(msg,(byte)'*');
+ ByteBuffer buf = ByteBuffer.wrap(msg);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.TEXT).setPayload(msg));
+ send.add(new TextFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
- send.add(new WebSocketFrame(OpCode.PING).setPayload("out of band"));
+ send.add(new PingFrame().setPayload("out of band"));
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(new WebSocketFrame(OpCode.TEXT).setPayload(msg));
+ expect.add(new TextFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -224,10 +230,10 @@
public void testCase7_3_1() throws Exception
{
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CLOSE));
+ send.add(new CloseFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(new WebSocketFrame(OpCode.CLOSE));
+ expect.add(new CloseFrame());
Fuzzer fuzzer = new Fuzzer(this);
try
@@ -252,17 +258,17 @@
{
byte payload[] = new byte[]
{ 0x00 };
+ ByteBuffer buf = ByteBuffer.wrap(payload);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CLOSE).setPayload(payload));
+ send.add(new CloseFrame().setPayload(buf));
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging scope = new StacklessLogging(Parser.class))
{
- enableStacks(Parser.class,false);
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
fuzzer.send(send);
@@ -271,7 +277,6 @@
}
finally
{
- enableStacks(Parser.class,true);
fuzzer.close();
}
}
@@ -362,47 +367,6 @@
}
/**
- * close with invalid payload (124 byte reason) (exceeds total allowed control frame payload bytes)
- */
- @Test
- public void testCase7_3_6() throws Exception
- {
- ByteBuffer payload = ByteBuffer.allocate(256);
- BufferUtil.clearToFill(payload);
- payload.put((byte)0xE8);
- payload.put((byte)0x03);
- byte reason[] = new byte[124]; // too big
- Arrays.fill(reason,(byte)'!');
- payload.put(reason);
- BufferUtil.flipToFlush(payload,0);
-
- List<WebSocketFrame> send = new ArrayList<>();
- WebSocketFrame close = new WebSocketFrame();
- close.setPayload(payload);
- close.setOpCode(OpCode.CLOSE); // set opcode after payload (to prevent early bad payload detection)
- send.add(close);
-
- List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
-
- Fuzzer fuzzer = new Fuzzer(this);
- try
- {
- enableStacks(Parser.class,false);
- fuzzer.connect();
- fuzzer.setSendMode(Fuzzer.SendMode.BULK);
- fuzzer.send(send);
- fuzzer.expect(expect);
- fuzzer.expectNoMoreFrames();
- }
- finally
- {
- enableStacks(Parser.class,true);
- fuzzer.close();
- }
- }
-
- /**
* close with invalid UTF8 in payload
*/
@Test
@@ -417,18 +381,16 @@
BufferUtil.flipToFlush(payload,0);
List<WebSocketFrame> send = new ArrayList<>();
- WebSocketFrame close = new WebSocketFrame(); // anonymous (no opcode) intentionally
+ WebSocketFrame close = new BadFrame(OpCode.CLOSE);
close.setPayload(payload); // intentionally bad payload
- close.setOpCode(OpCode.CLOSE); // set opcode after payload (to prevent early bad payload detection)
send.add(close);
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
- try
+ try(StacklessLogging scope = new StacklessLogging(Parser.class))
{
- enableStacks(Parser.class,false);
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
fuzzer.send(send);
@@ -437,7 +399,6 @@
}
finally
{
- enableStacks(Parser.class,true);
fuzzer.close();
}
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_BadStatusCodes.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_BadStatusCodes.java
index 727be4d..6af0d0d 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_BadStatusCodes.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_BadStatusCodes.java
@@ -29,8 +29,8 @@
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
-import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -92,7 +92,7 @@
BufferUtil.flipToFlush(payload,0);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CLOSE).setPayload(payload.slice()));
+ send.add(new CloseFrame().setPayload(payload.slice()));
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
@@ -125,7 +125,7 @@
BufferUtil.flipToFlush(payload,0);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CLOSE).setPayload(payload.slice()));
+ send.add(new CloseFrame().setPayload(payload.slice()));
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_GoodStatusCodes.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_GoodStatusCodes.java
index 7343c57..768388c 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_GoodStatusCodes.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_GoodStatusCodes.java
@@ -27,8 +27,8 @@
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -87,10 +87,10 @@
BufferUtil.flipToFlush(payload,0);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CLOSE).setPayload(payload.slice()));
+ send.add(new CloseFrame().setPayload(payload.slice()));
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(new WebSocketFrame(OpCode.CLOSE).setPayload(payload.slice()));
+ expect.add(new CloseFrame().setPayload(clone(payload)));
Fuzzer fuzzer = new Fuzzer(this);
try
@@ -114,16 +114,15 @@
public void testStatusCodeWithReason() throws Exception
{
ByteBuffer payload = ByteBuffer.allocate(256);
- BufferUtil.clearToFill(payload);
payload.putChar((char)statusCode);
payload.put(StringUtil.getBytes("Reason"));
- BufferUtil.flipToFlush(payload,0);
+ payload.flip();
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(OpCode.CLOSE).setPayload(payload.slice()));
+ send.add(new CloseFrame().setPayload(payload.slice()));
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(new WebSocketFrame(OpCode.CLOSE).setPayload(payload.slice()));
+ expect.add(new CloseFrame().setPayload(clone(payload)));
Fuzzer fuzzer = new Fuzzer(this);
try
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java
index e94e256..8be2b8c 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java
@@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.server.ab;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -30,6 +31,10 @@
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,6 +47,21 @@
private static final int KBYTE = 1024;
private static final int MBYTE = KBYTE * KBYTE;
+ private DataFrame toDataFrame(byte op)
+ {
+ switch (op)
+ {
+ case OpCode.BINARY:
+ return new BinaryFrame();
+ case OpCode.TEXT:
+ return new TextFrame();
+ case OpCode.CONTINUATION:
+ return new ContinuationFrame();
+ default:
+ throw new IllegalArgumentException("Not a data frame: " + op);
+ }
+ }
+
private void assertMultiFrameEcho(byte opcode, int overallMsgSize, int fragmentSize) throws Exception
{
byte msg[] = new byte[overallMsgSize];
@@ -52,6 +72,8 @@
int remaining = msg.length;
int offset = 0;
boolean fin;
+ ByteBuffer buf;
+ ;
byte op = opcode;
while (remaining > 0)
{
@@ -60,14 +82,17 @@
System.arraycopy(msg,offset,frag,0,len);
remaining -= len;
fin = (remaining <= 0);
- send.add(new WebSocketFrame(op).setPayload(frag).setFin(fin));
+ buf = ByteBuffer.wrap(frag);
+
+ send.add(toDataFrame(op).setPayload(buf).setFin(fin));
+
offset += len;
op = OpCode.CONTINUATION;
}
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(new WebSocketFrame(opcode).setPayload(msg));
+ expect.add(toDataFrame(opcode).setPayload(copyOf(msg)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -88,13 +113,14 @@
{
byte msg[] = new byte[overallMsgSize];
Arrays.fill(msg,(byte)'M');
+ ByteBuffer buf = ByteBuffer.wrap(msg);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(new WebSocketFrame(opcode).setPayload(msg));
+ send.add(toDataFrame(opcode).setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(new WebSocketFrame(opcode).setPayload(msg));
+ expect.add(toDataFrame(opcode).setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -123,11 +149,11 @@
String msg = StringUtil.toUTF8String(utf,0,utf.length);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text(msg));
+ send.add(new TextFrame().setPayload(msg));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text(msg));
+ expect.add(new TextFrame().setPayload(msg));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -152,13 +178,14 @@
{
byte utf[] = new byte[256 * KBYTE];
Arrays.fill(utf,(byte)'y');
+ ByteBuffer buf = ByteBuffer.wrap(utf);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text().setPayload(utf));
+ send.add(new TextFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text().setPayload(utf));
+ expect.add(new TextFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -184,13 +211,14 @@
{
byte utf[] = new byte[1 * MBYTE];
Arrays.fill(utf,(byte)'y');
+ ByteBuffer buf = ByteBuffer.wrap(utf);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text().setPayload(utf));
+ send.add(new TextFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text().setPayload(utf));
+ expect.add(new TextFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -216,13 +244,14 @@
{
byte utf[] = new byte[4 * MBYTE];
Arrays.fill(utf,(byte)'y');
+ ByteBuffer buf = ByteBuffer.wrap(utf);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text().setPayload(utf));
+ send.add(new TextFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text().setPayload(utf));
+ expect.add(new TextFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -248,13 +277,14 @@
{
byte utf[] = new byte[8 * MBYTE];
Arrays.fill(utf,(byte)'y');
+ ByteBuffer buf = ByteBuffer.wrap(utf);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text().setPayload(utf));
+ send.add(new TextFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text().setPayload(utf));
+ expect.add(new TextFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -280,13 +310,14 @@
{
byte utf[] = new byte[16 * MBYTE];
Arrays.fill(utf,(byte)'y');
+ ByteBuffer buf = ByteBuffer.wrap(utf);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.text().setPayload(utf));
+ send.add(new TextFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.text().setPayload(utf));
+ expect.add(new TextFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -313,11 +344,11 @@
Arrays.fill(data,(byte)0x21);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary(data));
+ send.add(new BinaryFrame().setPayload(data));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary(data));
+ expect.add(new BinaryFrame().setPayload(copyOf(data)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -342,13 +373,14 @@
{
byte data[] = new byte[256 * KBYTE];
Arrays.fill(data,(byte)0x22);
+ ByteBuffer buf = ByteBuffer.wrap(data);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary().setPayload(data));
+ send.add(new BinaryFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary().setPayload(data));
+ expect.add(new BinaryFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -374,13 +406,14 @@
{
byte data[] = new byte[1 * MBYTE];
Arrays.fill(data,(byte)0x23);
+ ByteBuffer buf = ByteBuffer.wrap(data);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary().setPayload(data));
+ send.add(new BinaryFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary().setPayload(data));
+ expect.add(new BinaryFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -406,13 +439,14 @@
{
byte data[] = new byte[4 * MBYTE];
Arrays.fill(data,(byte)0x24);
+ ByteBuffer buf = ByteBuffer.wrap(data);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary().setPayload(data));
+ send.add(new BinaryFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary().setPayload(data));
+ expect.add(new BinaryFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -438,13 +472,14 @@
{
byte data[] = new byte[8 * MBYTE];
Arrays.fill(data,(byte)0x25);
+ ByteBuffer buf = ByteBuffer.wrap(data);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary().setPayload(data));
+ send.add(new BinaryFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary().setPayload(data));
+ expect.add(new BinaryFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
@@ -470,13 +505,14 @@
{
byte data[] = new byte[16 * MBYTE];
Arrays.fill(data,(byte)0x26);
+ ByteBuffer buf = ByteBuffer.wrap(data);
List<WebSocketFrame> send = new ArrayList<>();
- send.add(WebSocketFrame.binary().setPayload(data));
+ send.add(new BinaryFrame().setPayload(buf));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
- expect.add(WebSocketFrame.binary().setPayload(data));
+ expect.add(new BinaryFrame().setPayload(clone(buf)));
expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
index 601919a..cad7409 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
@@ -18,8 +18,11 @@
package org.eclipse.jetty.websocket.server.blockhead;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
@@ -50,7 +53,6 @@
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
@@ -85,7 +87,7 @@
* with regards to basic IO behavior, a write should work as expected, a read should work as expected, but <u>what</u> byte it sends or reads is not within its
* scope.
*/
-public class BlockheadClient implements IncomingFrames, OutgoingFrames, ConnectionStateListener
+public class BlockheadClient implements IncomingFrames, OutgoingFrames, ConnectionStateListener, Closeable
{
private static final String REQUEST_HASH_KEY = "dGhlIHNhbXBsZSBub25jZQ==";
private static final int BUFFER_SIZE = 8192;
@@ -117,6 +119,7 @@
private IOState ioState;
private CountDownLatch disconnectedLatch = new CountDownLatch(1);
private ByteBuffer remainingBuffer;
+ private String connectionValue = "Upgrade";
public BlockheadClient(URI destWebsocketURI) throws URISyntaxException
{
@@ -234,6 +237,36 @@
}
}
+ public void expectServerDisconnect()
+ {
+ if (eof)
+ {
+ return;
+ }
+
+ try
+ {
+ int len = in.read();
+ if (len == (-1))
+ {
+ // we are disconnected
+ eof = true;
+ return;
+ }
+
+ Assert.assertThat("Expecting no data and proper socket disconnect (issued from server)",len,is(-1));
+ }
+ catch (SocketTimeoutException e)
+ {
+ LOG.warn(e);
+ Assert.fail("Expected a server initiated disconnect, instead the read timed out");
+ }
+ catch (IOException e)
+ {
+ // acceptable path
+ }
+ }
+
public HttpResponse expectUpgradeResponse() throws IOException
{
HttpResponse response = readResponseHeader();
@@ -295,6 +328,11 @@
out.flush();
}
+ public String getConnectionValue()
+ {
+ return connectionValue;
+ }
+
private List<ExtensionConfig> getExtensionConfigs(HttpResponse response)
{
List<ExtensionConfig> configs = new ArrayList<>();
@@ -376,7 +414,7 @@
* Errors received (after extensions)
*/
@Override
- public void incomingError(WebSocketException e)
+ public void incomingError(Throwable e)
{
incomingFrames.incomingError(e);
}
@@ -394,13 +432,13 @@
LOG.info("Client parsed {} frames",count);
}
- if (frame.getType() == Frame.Type.CLOSE)
+ if (frame.getOpCode() == OpCode.CLOSE)
{
CloseInfo close = new CloseInfo(frame);
ioState.onCloseRemote(close);
}
- WebSocketFrame copy = new WebSocketFrame(frame);
+ WebSocketFrame copy = WebSocketFrame.copy(frame);
incomingFrames.incomingFrame(copy);
}
@@ -434,14 +472,15 @@
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
{
- ByteBuffer buf = generator.generate(frame);
+ ByteBuffer headerBuf = generator.generateHeaderBytes(frame);
if (LOG.isDebugEnabled())
{
- LOG.debug("writing out: {}",BufferUtil.toDetailString(buf));
+ LOG.debug("writing out: {}",BufferUtil.toDetailString(headerBuf));
}
try
{
- BufferUtil.writeTo(buf,out);
+ BufferUtil.writeTo(headerBuf,out);
+ BufferUtil.writeTo(frame.getPayload(),out);
out.flush();
if (callback != null)
{
@@ -457,45 +496,15 @@
}
finally
{
- bufferPool.release(buf);
+ bufferPool.release(headerBuf);
}
- if (frame.getType().getOpCode() == OpCode.CLOSE)
+ if (frame.getOpCode() == OpCode.CLOSE)
{
disconnect();
}
}
- public void expectServerDisconnect()
- {
- if (eof)
- {
- return;
- }
-
- try
- {
- int len = in.read();
- if (len == (-1))
- {
- // we are disconnected
- eof = true;
- return;
- }
-
- Assert.assertThat("Expecting no data and proper socket disconnect (issued from server)",len,is(-1));
- }
- catch (SocketTimeoutException e)
- {
- LOG.warn(e);
- Assert.fail("Expected a server initiated disconnect, instead the read timed out");
- }
- catch (IOException e)
- {
- // acceptable path
- }
- }
-
public int read(ByteBuffer buf) throws IOException
{
if (eof)
@@ -508,7 +517,7 @@
return BufferUtil.put(remainingBuffer,buf);
}
- int len = 0;
+ int len = -1;
int b;
while ((in.available() > 0) && (buf.remaining() > 0))
{
@@ -521,6 +530,7 @@
buf.put((byte)b);
len++;
}
+
return len;
}
@@ -608,7 +618,7 @@
req.append("GET ").append(getRequestPath()).append(" HTTP/1.1\r\n");
req.append("Host: ").append(getRequestHost()).append("\r\n");
req.append("Upgrade: websocket\r\n");
- req.append("Connection: Upgrade\r\n");
+ req.append("Connection: ").append(connectionValue).append("\r\n");
for (String header : headers)
{
req.append(header);
@@ -629,6 +639,11 @@
writeRaw(req.toString());
}
+ public void setConnectionValue(String connectionValue)
+ {
+ this.connectionValue = connectionValue;
+ }
+
public void setDebug(boolean flag)
{
this.debug = flag;
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClientConstructionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClientConstructionTest.java
index 5a38491..4f8c3f5 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClientConstructionTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClientConstructionTest.java
@@ -18,7 +18,7 @@
package org.eclipse.jetty.websocket.server.blockhead;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import java.net.URI;
import java.net.URISyntaxException;
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java
index 59d44eb..c183fb9 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java
@@ -19,9 +19,8 @@
package org.eclipse.jetty.websocket.server.blockhead;
import java.nio.ByteBuffer;
-import java.util.HashMap;
-import java.util.Locale;
import java.util.Map;
+import java.util.TreeMap;
import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener;
@@ -29,13 +28,13 @@
{
private int statusCode;
private String statusReason;
- private Map<String, String> headers = new HashMap<>();
+ private Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private ByteBuffer remainingBuffer;
@Override
public void addHeader(String name, String value)
{
- headers.put(name.toLowerCase(Locale.ENGLISH),value);
+ headers.put(name,value);
}
public String getExtensionsHeader()
@@ -45,7 +44,7 @@
public String getHeader(String name)
{
- return headers.get(name.toLowerCase(Locale.ENGLISH));
+ return headers.get(name);
}
public ByteBuffer getRemainingBuffer()
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java
index d618681..ccbd426 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java
@@ -23,11 +23,9 @@
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
-import org.eclipse.jetty.websocket.common.extensions.compress.FrameCompressionExtension;
-import org.eclipse.jetty.websocket.common.extensions.compress.MessageCompressionExtension;
import org.eclipse.jetty.websocket.server.WebSocketHandler;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@@ -69,7 +67,7 @@
private Server server;
@Override
- public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
+ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
LOG.debug("Creating BrowserSocket");
@@ -84,6 +82,8 @@
String ua = req.getHeader("User-Agent");
String rexts = req.getHeader("Sec-WebSocket-Extensions");
+ LOG.debug("User-Agent: {}", ua);
+ LOG.debug("Sec-WebSocket-Extensions: {}", rexts);
BrowserSocket socket = new BrowserSocket(ua,rexts);
return socket;
}
@@ -110,15 +110,11 @@
{
LOG.debug("Configuring WebSocketServerFactory ...");
- // Setup some extensions we want to test against
- factory.getExtensionFactory().register("x-webkit-deflate-frame",FrameCompressionExtension.class);
- factory.getExtensionFactory().register("permessage-compress",MessageCompressionExtension.class);
-
// Setup the desired Socket to use for all incoming upgrade requests
factory.setCreator(BrowserDebugTool.this);
// Set the timeout
- factory.getPolicy().setIdleTimeout(2000);
+ factory.getPolicy().setIdleTimeout(20000);
}
};
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java
index 726ad52..a9a4011 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java
@@ -67,7 +67,7 @@
randomText[i] = letters[rand.nextInt(lettersLen)];
}
msg = String.format("ManyThreads [%s]",String.valueOf(randomText));
- remote.sendStringByFuture(msg);
+ remote.sendString(msg,null);
}
}
}
@@ -219,7 +219,7 @@
}
// Async write
- remote.sendStringByFuture(message);
+ remote.sendString(message,null);
}
private void writeMessage(String format, Object... args)
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/MyCustomCreationServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/MyCustomCreationServlet.java
index 5a2d1e8..9a78bba 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/MyCustomCreationServlet.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/MyCustomCreationServlet.java
@@ -20,9 +20,9 @@
import java.io.IOException;
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.server.examples.echo.BigEchoSocket;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@@ -33,7 +33,7 @@
public static class MyCustomCreator implements WebSocketCreator
{
@Override
- public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
+ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
String query = req.getQueryString();
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/BigEchoSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/BigEchoSocket.java
index da5aa8b..ab9f8c0 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/BigEchoSocket.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/BigEchoSocket.java
@@ -29,7 +29,7 @@
/**
* Example Socket for echoing back Big data using the Annotation techniques along with stateless techniques.
*/
-@WebSocket(maxMessageSize = 64 * 1024)
+@WebSocket(maxTextMessageSize = 64 * 1024, maxBinaryMessageSize = 64 * 1024)
public class BigEchoSocket
{
private static final Logger LOG = Log.getLogger(BigEchoSocket.class);
@@ -42,7 +42,7 @@
LOG.warn("Session is closed");
return;
}
- session.getRemote().sendBytesByFuture(ByteBuffer.wrap(buf,offset,length));
+ session.getRemote().sendBytes(ByteBuffer.wrap(buf,offset,length),null);
}
@OnWebSocketMessage
@@ -53,6 +53,6 @@
LOG.warn("Session is closed");
return;
}
- session.getRemote().sendStringByFuture(message);
+ session.getRemote().sendString(message,null);
}
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoBroadcastSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoBroadcastSocket.java
index 6cbe5d4..ab9a09c 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoBroadcastSocket.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoBroadcastSocket.java
@@ -40,7 +40,7 @@
ByteBuffer data = ByteBuffer.wrap(buf,offset,len);
for (EchoBroadcastSocket sock : BROADCAST)
{
- sock.session.getRemote().sendBytesByFuture(data.slice());
+ sock.session.getRemote().sendBytes(data.slice(),null);
}
}
@@ -62,7 +62,7 @@
{
for (EchoBroadcastSocket sock : BROADCAST)
{
- sock.session.getRemote().sendStringByFuture(text);
+ sock.session.getRemote().sendString(text,null);
}
}
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoCreator.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoCreator.java
index 8b21bf2..c477517 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoCreator.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoCreator.java
@@ -18,8 +18,8 @@
package org.eclipse.jetty.websocket.server.examples.echo;
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
/**
@@ -32,7 +32,7 @@
private LogSocket logSocket = new LogSocket();
@Override
- public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
+ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
for (String protocol : req.getSubProtocols())
{
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoFragmentSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoFragmentSocket.java
index 1a5bdae..59f1da7 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoFragmentSocket.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoFragmentSocket.java
@@ -37,8 +37,9 @@
@OnWebSocketFrame
public void onFrame(Session session, Frame frame)
{
- if (frame.getType().isData())
+ if (!frame.getType().isData())
{
+ // Don't process non-data frames
return;
}
@@ -58,13 +59,13 @@
switch (frame.getType())
{
case BINARY:
- remote.sendBytesByFuture(buf1);
- remote.sendBytesByFuture(buf2);
+ remote.sendBytes(buf1,null);
+ remote.sendBytes(buf2,null);
break;
case TEXT:
// NOTE: This impl is not smart enough to split on a UTF8 boundary
- remote.sendStringByFuture(BufferUtil.toUTF8String(buf1));
- remote.sendStringByFuture(BufferUtil.toUTF8String(buf2));
+ remote.sendString(BufferUtil.toUTF8String(buf1),null);
+ remote.sendString(BufferUtil.toUTF8String(buf2),null);
break;
default:
throw new IOException("Unexpected frame type: " + frame.getType());
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/CaptureSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/CaptureSocket.java
index a1b597a..b983ba9 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/CaptureSocket.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/CaptureSocket.java
@@ -18,8 +18,6 @@
package org.eclipse.jetty.websocket.server.helper;
-import java.io.IOException;
-import java.sql.Connection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -44,14 +42,7 @@
public void close()
{
- try
- {
- getSession().close();
- }
- catch (IOException ignore)
- {
- /* ignore */
- }
+ getSession().close();
}
@Override
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoServlet.java
index 7cbbd2e..66e2352 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoServlet.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoServlet.java
@@ -18,8 +18,8 @@
package org.eclipse.jetty.websocket.server.helper;
-import org.eclipse.jetty.websocket.common.extensions.compress.FrameCompressionExtension;
-import org.eclipse.jetty.websocket.common.extensions.compress.MessageCompressionExtension;
+import org.eclipse.jetty.websocket.common.extensions.compress.DeflateFrameExtension;
+import org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@@ -33,8 +33,8 @@
public void configure(WebSocketServletFactory factory)
{
// Setup some extensions we want to test against
- factory.getExtensionFactory().register("x-webkit-deflate-frame",FrameCompressionExtension.class);
- factory.getExtensionFactory().register("permessage-compress",MessageCompressionExtension.class);
+ factory.getExtensionFactory().register("x-webkit-deflate-frame",DeflateFrameExtension.class);
+ factory.getExtensionFactory().register("permessage-compress",PerMessageDeflateExtension.class);
// Setup the desired Socket to use for all incoming upgrade requests
factory.register(EchoSocket.class);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoSocket.java
index 89d7e0e..d5910e4 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoSocket.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoSocket.java
@@ -44,7 +44,7 @@
// echo the message back.
ByteBuffer data = ByteBuffer.wrap(buf,offset,len);
- this.session.getRemote().sendBytesByFuture(data);
+ this.session.getRemote().sendBytes(data,null);
}
@OnWebSocketConnect
@@ -59,6 +59,6 @@
LOG.debug("onText({})",message);
// echo the message back.
- this.session.getRemote().sendStringByFuture(message);
+ this.session.getRemote().sendString(message,null);
}
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/Hex.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/Hex.java
index 1f0129a..f691855 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/Hex.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/Hex.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.websocket.server.helper;
+import java.nio.ByteBuffer;
+
public final class Hex
{
private static final char[] hexcodes = "0123456789ABCDEF".toCharArray();
@@ -51,6 +53,11 @@
return buf;
}
+
+ public static ByteBuffer asByteBuffer(String hstr)
+ {
+ return ByteBuffer.wrap(asByteArray(hstr));
+ }
public static String asHex(byte buf[])
{
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java
index 82a4723..85ac5ca 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java
@@ -18,7 +18,8 @@
package org.eclipse.jetty.websocket.server.helper;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
import java.util.Queue;
@@ -37,7 +38,7 @@
{
private static final Logger LOG = Log.getLogger(IncomingFramesCapture.class);
private EventQueue<WebSocketFrame> frames = new EventQueue<>();
- private EventQueue<WebSocketException> errors = new EventQueue<>();
+ private EventQueue<Throwable> errors = new EventQueue<>();
public void assertErrorCount(int expectedCount)
{
@@ -86,14 +87,14 @@
for (Frame frame : frames)
{
System.err.printf("[%3d] %s%n",i++,frame);
- System.err.printf(" %s%n",BufferUtil.toDetailString(frame.getPayload()));
+ System.err.printf(" payload: %s%n",BufferUtil.toDetailString(frame.getPayload()));
}
}
- public int getErrorCount(Class<? extends WebSocketException> errorType)
+ public int getErrorCount(Class<? extends Throwable> errorType)
{
int count = 0;
- for (WebSocketException error : errors)
+ for (Throwable error : errors)
{
if (errorType.isInstance(error))
{
@@ -103,7 +104,7 @@
return count;
}
- public Queue<WebSocketException> getErrors()
+ public Queue<Throwable> getErrors()
{
return errors;
}
@@ -127,7 +128,7 @@
}
@Override
- public void incomingError(WebSocketException e)
+ public void incomingError(Throwable e)
{
LOG.debug(e);
errors.add(e);
@@ -136,7 +137,7 @@
@Override
public void incomingFrame(Frame frame)
{
- WebSocketFrame copy = new WebSocketFrame(frame);
+ WebSocketFrame copy = WebSocketFrame.copy(frame);
frames.add(copy);
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/OutgoingFramesCapture.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/OutgoingFramesCapture.java
index 78da0e4..4d1ee09 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/OutgoingFramesCapture.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/OutgoingFramesCapture.java
@@ -18,7 +18,8 @@
package org.eclipse.jetty.websocket.server.helper;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
import java.util.LinkedList;
@@ -93,7 +94,7 @@
@Override
public void outgoingFrame(Frame frame, WriteCallback callback)
{
- WebSocketFrame copy = new WebSocketFrame(frame);
+ WebSocketFrame copy = WebSocketFrame.copy(frame);
frames.add(copy);
if (callback != null)
{
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCSocket.java
index 9d03d04..4adbe3f 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCSocket.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCSocket.java
@@ -41,7 +41,7 @@
// echo the message back.
ByteBuffer data = ByteBuffer.wrap(buf,offset,len);
- this.session.getRemote().sendBytesByFuture(data);
+ this.session.getRemote().sendBytes(data,null);
}
@OnWebSocketConnect
@@ -62,6 +62,6 @@
}
// echo the message back.
- this.session.getRemote().sendStringByFuture(message);
+ this.session.getRemote().sendString(message,null);
}
}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SafariD00.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SafariD00.java
index faced9e..a2fed3d 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SafariD00.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SafariD00.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.websocket.server.helper;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -35,8 +38,6 @@
import org.eclipse.jetty.util.TypeUtil;
import org.junit.Assert;
-import static org.hamcrest.Matchers.is;
-
public class SafariD00
{
private URI uri;
@@ -114,6 +115,7 @@
{
line = br.readLine();
// System.out.printf("RESP: %s%n",line);
+ Assert.assertThat(line, notNullValue());
if (line.length() == 0)
{
foundEnd = true;
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SessionSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SessionSocket.java
index 9c00b8a..8de3013 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SessionSocket.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SessionSocket.java
@@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.server.helper;
+import java.util.List;
import java.util.Map;
import org.eclipse.jetty.util.log.Log;
@@ -52,15 +53,15 @@
{
if (message.startsWith("getParameterMap"))
{
- Map<String, String[]> parameterMap = session.getUpgradeRequest().getParameterMap();
+ Map<String, List<String>> parameterMap = session.getUpgradeRequest().getParameterMap();
int idx = message.indexOf('|');
String key = message.substring(idx + 1);
- String values[] = parameterMap.get(key);
+ List<String> values = parameterMap.get(key);
if (values == null)
{
- session.getRemote().sendStringByFuture("<null>");
+ session.getRemote().sendString("<null>",null);
return;
}
@@ -77,21 +78,21 @@
delim = true;
}
valueStr.append(']');
- session.getRemote().sendStringByFuture(valueStr.toString());
+ session.getRemote().sendString(valueStr.toString(),null);
return;
}
if ("session.isSecure".equals(message))
{
String issecure = String.format("session.isSecure=%b",session.isSecure());
- session.getRemote().sendStringByFuture(issecure);
+ session.getRemote().sendString(issecure,null);
return;
}
if ("session.upgradeRequest.requestURI".equals(message))
{
String response = String.format("session.upgradeRequest.requestURI=%s",session.getUpgradeRequest().getRequestURI().toASCIIString());
- session.getRemote().sendStringByFuture(response);
+ session.getRemote().sendString(response,null);
return;
}
@@ -102,7 +103,7 @@
}
// echo the message back.
- this.session.getRemote().sendStringByFuture(message);
+ this.session.getRemote().sendString(message,null);
}
catch (Throwable t)
{
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsBenchmarkTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsBenchmarkTest.java
new file mode 100644
index 0000000..a9d62d2
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsBenchmarkTest.java
@@ -0,0 +1,224 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.annotation.Stress;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AdvancedRunner.class)
+public class PathMappingsBenchmarkTest
+{
+ public static abstract class AbstractPathMapThread extends Thread
+ {
+ private int iterations;
+ private CyclicBarrier barrier;
+ private long success;
+ private long error;
+
+ public AbstractPathMapThread(int iterations, CyclicBarrier barrier)
+ {
+ this.iterations = iterations;
+ this.barrier = barrier;
+ }
+
+ public abstract String getMatchedResource(String path);
+
+ @Override
+ public void run()
+ {
+ int llen = LOOKUPS.length;
+ String path;
+ String expectedResource;
+ String matchedResource;
+ await(barrier);
+ for (int iter = 0; iter < iterations; iter++)
+ {
+ for (int li = 0; li < llen; li++)
+ {
+ path = LOOKUPS[li][0];
+ expectedResource = LOOKUPS[li][1];
+ matchedResource = getMatchedResource(path);
+ if (matchedResource.equals(expectedResource))
+ {
+ success++;
+ }
+ else
+ {
+ error++;
+ }
+ }
+ }
+ await(barrier);
+ }
+ }
+
+ public static class PathMapMatchThread extends AbstractPathMapThread
+ {
+ private PathMap<String> pathmap;
+
+ public PathMapMatchThread(PathMap<String> pathmap, int iters, CyclicBarrier barrier)
+ {
+ super(iters,barrier);
+ this.pathmap = pathmap;
+ }
+
+ @Override
+ public String getMatchedResource(String path)
+ {
+ return pathmap.getMatch(path).getValue();
+ }
+ }
+
+ public static class PathMatchThread extends AbstractPathMapThread
+ {
+ private PathMappings<String> pathmap;
+
+ public PathMatchThread(PathMappings<String> pathmap, int iters, CyclicBarrier barrier)
+ {
+ super(iters,barrier);
+ this.pathmap = pathmap;
+ }
+
+ @Override
+ public String getMatchedResource(String path)
+ {
+ return pathmap.getMatch(path).getResource();
+ }
+ }
+
+ private static final Logger LOG = Log.getLogger(PathMappingsBenchmarkTest.class);
+ private static final String[][] LOOKUPS;
+ private int runs = 20;
+ private int threads = 200;
+ private int iters = 10000;
+
+ static
+ {
+ LOOKUPS = new String[][]
+ {
+ // @formatter:off
+ { "/abs/path", "path" },
+ { "/abs/path/longer","longpath" },
+ { "/abs/path/foo","default" },
+ { "/main.css","default" },
+ { "/downloads/script.gz","gzipped" },
+ { "/downloads/distribution.tar.gz","tarball" },
+ { "/downloads/readme.txt","default" },
+ { "/downloads/logs.tgz","default" },
+ { "/animal/horse/mustang","animals" },
+ { "/animal/bird/eagle/bald","birds" },
+ { "/animal/fish/shark/hammerhead","fishes" },
+ { "/animal/insect/ladybug","animals" },
+ // @formatter:on
+ };
+ }
+
+ private static void await(CyclicBarrier barrier)
+ {
+ try
+ {
+ barrier.await();
+ }
+ catch (Exception x)
+ {
+ throw new RuntimeException(x);
+ }
+ }
+
+ @Stress("High CPU")
+ @Test
+ public void testServletPathMap()
+ {
+ // Setup (old) PathMap
+
+ PathMap<String> p = new PathMap<>();
+
+ p.put("/abs/path","path");
+ p.put("/abs/path/longer","longpath");
+ p.put("/animal/bird/*","birds");
+ p.put("/animal/fish/*","fishes");
+ p.put("/animal/*","animals");
+ p.put("*.tar.gz","tarball");
+ p.put("*.gz","gzipped");
+ p.put("/","default");
+
+ final CyclicBarrier barrier = new CyclicBarrier(threads + 1);
+
+ for (int r = 0; r < runs; r++)
+ {
+ for (int t = 0; t < threads; t++)
+ {
+ PathMapMatchThread thread = new PathMapMatchThread(p,iters,barrier);
+ thread.start();
+ }
+ await(barrier);
+ long begin = System.nanoTime();
+ await(barrier);
+ long end = System.nanoTime();
+ long elapsed = TimeUnit.NANOSECONDS.toMillis(end - begin);
+ int totalMatches = threads * iters * LOOKUPS.length;
+ LOG.info("jetty-http/PathMap (Servlet only) threads:{}/iters:{}/total-matches:{} => {} ms",threads,iters,totalMatches,elapsed);
+ }
+ }
+
+ @Stress("High CPU")
+ @Test
+ public void testServletPathMappings()
+ {
+ // Setup (new) PathMappings
+
+ PathMappings<String> p = new PathMappings<>();
+
+ p.put(new ServletPathSpec("/abs/path"),"path");
+ p.put(new ServletPathSpec("/abs/path/longer"),"longpath");
+ p.put(new ServletPathSpec("/animal/bird/*"),"birds");
+ p.put(new ServletPathSpec("/animal/fish/*"),"fishes");
+ p.put(new ServletPathSpec("/animal/*"),"animals");
+ p.put(new ServletPathSpec("*.tar.gz"),"tarball");
+ p.put(new ServletPathSpec("*.gz"),"gzipped");
+ p.put(new ServletPathSpec("/"),"default");
+
+ final CyclicBarrier barrier = new CyclicBarrier(threads + 1);
+
+ for (int r = 0; r < runs; r++)
+ {
+ for (int t = 0; t < threads; t++)
+ {
+ PathMatchThread thread = new PathMatchThread(p,iters,barrier);
+ thread.start();
+ }
+ await(barrier);
+ long begin = System.nanoTime();
+ await(barrier);
+ long end = System.nanoTime();
+ long elapsed = TimeUnit.NANOSECONDS.toMillis(end - begin);
+ int totalMatches = threads * iters * LOOKUPS.length;
+ LOG.info("jetty-websocket/PathMappings (Servlet only) threads:{}/iters:{}/total-matches:{} => {} ms",threads,iters,totalMatches,elapsed);
+
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsTest.java
new file mode 100644
index 0000000..eb7bccd
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsTest.java
@@ -0,0 +1,119 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import static org.hamcrest.Matchers.notNullValue;
+
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PathMappingsTest
+{
+ private void assertMatch(PathMappings<String> pathmap, String path, String expectedValue)
+ {
+ String msg = String.format(".getMatch(\"%s\")",path);
+ MappedResource<String> match = pathmap.getMatch(path);
+ Assert.assertThat(msg,match,notNullValue());
+ String actualMatch = match.getResource();
+ Assert.assertEquals(msg,expectedValue,actualMatch);
+ }
+
+ public void dumpMappings(PathMappings<String> p)
+ {
+ for (MappedResource<String> res : p)
+ {
+ System.out.printf(" %s%n",res);
+ }
+ }
+
+ /**
+ * Test the match order rules with a mixed Servlet and WebSocket path specs
+ * <p>
+ * <ul>
+ * <li>Exact match</li>
+ * <li>Longest prefix match</li>
+ * <li>Longest suffix match</li>
+ * </ul>
+ */
+ @Test
+ public void testMixedMatchOrder()
+ {
+ PathMappings<String> p = new PathMappings<>();
+
+ p.put(new ServletPathSpec("/"),"default");
+ p.put(new ServletPathSpec("/animal/bird/*"),"birds");
+ p.put(new ServletPathSpec("/animal/fish/*"),"fishes");
+ p.put(new ServletPathSpec("/animal/*"),"animals");
+ p.put(new RegexPathSpec("^/animal/.*/chat$"),"animalChat");
+ p.put(new RegexPathSpec("^/animal/.*/cam$"),"animalCam");
+ p.put(new RegexPathSpec("^/entrance/cam$"),"entranceCam");
+
+ // dumpMappings(p);
+
+ assertMatch(p,"/animal/bird/eagle","birds");
+ assertMatch(p,"/animal/fish/bass/sea","fishes");
+ assertMatch(p,"/animal/peccary/javalina/evolution","animals");
+ assertMatch(p,"/","default");
+ assertMatch(p,"/animal/bird/eagle/chat","animalChat");
+ assertMatch(p,"/animal/bird/penguin/chat","animalChat");
+ assertMatch(p,"/animal/fish/trout/cam","animalCam");
+ assertMatch(p,"/entrance/cam","entranceCam");
+ }
+
+ /**
+ * Test the match order rules imposed by the Servlet API.
+ * <p>
+ * <ul>
+ * <li>Exact match</li>
+ * <li>Longest prefix match</li>
+ * <li>Longest suffix match</li>
+ * <li>default</li>
+ * </ul>
+ */
+ @Test
+ public void testServletMatchOrder()
+ {
+ PathMappings<String> p = new PathMappings<>();
+
+ p.put(new ServletPathSpec("/abs/path"),"path");
+ p.put(new ServletPathSpec("/abs/path/longer"),"longpath");
+ p.put(new ServletPathSpec("/animal/bird/*"),"birds");
+ p.put(new ServletPathSpec("/animal/fish/*"),"fishes");
+ p.put(new ServletPathSpec("/animal/*"),"animals");
+ p.put(new ServletPathSpec("*.tar.gz"),"tarball");
+ p.put(new ServletPathSpec("*.gz"),"gzipped");
+ p.put(new ServletPathSpec("/"),"default");
+
+ // dumpMappings(p);
+
+ assertMatch(p,"/abs/path","path");
+ assertMatch(p,"/abs/path/longer","longpath");
+ assertMatch(p,"/abs/path/foo","default");
+ assertMatch(p,"/main.css","default");
+ assertMatch(p,"/downloads/script.gz","gzipped");
+ assertMatch(p,"/downloads/distribution.tar.gz","tarball");
+ assertMatch(p,"/downloads/readme.txt","default");
+ assertMatch(p,"/downloads/logs.tgz","default");
+ assertMatch(p,"/animal/horse/mustang","animals");
+ assertMatch(p,"/animal/bird/eagle/bald","birds");
+ assertMatch(p,"/animal/fish/shark/hammerhead","fishes");
+ assertMatch(p,"/animal/insect/ladybug","animals");
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpecTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpecTest.java
new file mode 100644
index 0000000..d1e0da2
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpecTest.java
@@ -0,0 +1,135 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+
+public class RegexPathSpecTest
+{
+ public static void assertMatches(PathSpec spec, String path)
+ {
+ String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+ assertThat(msg,spec.matches(path),is(true));
+ }
+
+ public static void assertNotMatches(PathSpec spec, String path)
+ {
+ String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+ assertThat(msg,spec.matches(path),is(false));
+ }
+
+ @Test
+ public void testExactSpec()
+ {
+ RegexPathSpec spec = new RegexPathSpec("^/a$");
+ assertEquals("Spec.pathSpec","^/a$",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.EXACT,spec.group);
+
+ assertMatches(spec,"/a");
+
+ assertNotMatches(spec,"/aa");
+ assertNotMatches(spec,"/a/");
+ }
+
+ @Test
+ public void testMiddleSpec()
+ {
+ RegexPathSpec spec = new RegexPathSpec("^/rest/([^/]*)/list$");
+ assertEquals("Spec.pathSpec","^/rest/([^/]*)/list$",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/rest/([^/]*)/list$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group);
+
+ assertMatches(spec,"/rest/api/list");
+ assertMatches(spec,"/rest/1.0/list");
+ assertMatches(spec,"/rest/2.0/list");
+ assertMatches(spec,"/rest/accounts/list");
+
+ assertNotMatches(spec,"/a");
+ assertNotMatches(spec,"/aa");
+ assertNotMatches(spec,"/aa/bb");
+ assertNotMatches(spec,"/rest/admin/delete");
+ assertNotMatches(spec,"/rest/list");
+ }
+
+ @Test
+ public void testMiddleSpecNoGrouping()
+ {
+ RegexPathSpec spec = new RegexPathSpec("^/rest/[^/]+/list$");
+ assertEquals("Spec.pathSpec","^/rest/[^/]+/list$",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/rest/[^/]+/list$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group);
+
+ assertMatches(spec,"/rest/api/list");
+ assertMatches(spec,"/rest/1.0/list");
+ assertMatches(spec,"/rest/2.0/list");
+ assertMatches(spec,"/rest/accounts/list");
+
+ assertNotMatches(spec,"/a");
+ assertNotMatches(spec,"/aa");
+ assertNotMatches(spec,"/aa/bb");
+ assertNotMatches(spec,"/rest/admin/delete");
+ assertNotMatches(spec,"/rest/list");
+ }
+
+ @Test
+ public void testPrefixSpec()
+ {
+ RegexPathSpec spec = new RegexPathSpec("^/a/(.*)$");
+ assertEquals("Spec.pathSpec","^/a/(.*)$",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a/(.*)$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.group);
+
+ assertMatches(spec,"/a/");
+ assertMatches(spec,"/a/b");
+ assertMatches(spec,"/a/b/c/d/e");
+
+ assertNotMatches(spec,"/a");
+ assertNotMatches(spec,"/aa");
+ assertNotMatches(spec,"/aa/bb");
+ }
+
+ @Test
+ public void testSuffixSpec()
+ {
+ RegexPathSpec spec = new RegexPathSpec("^(.*).do$");
+ assertEquals("Spec.pathSpec","^(.*).do$",spec.getPathSpec());
+ assertEquals("Spec.pattern","^(.*).do$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",0,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.group);
+
+ assertMatches(spec,"/a.do");
+ assertMatches(spec,"/a/b/c.do");
+ assertMatches(spec,"/abcde.do");
+ assertMatches(spec,"/abc/efg.do");
+
+ assertNotMatches(spec,"/a");
+ assertNotMatches(spec,"/aa");
+ assertNotMatches(spec,"/aa/bb");
+ assertNotMatches(spec,"/aa/bb.do/more");
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpecTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpecTest.java
new file mode 100644
index 0000000..9f45c37
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpecTest.java
@@ -0,0 +1,188 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+public class ServletPathSpecTest
+{
+ private void assertBadServletPathSpec(String pathSpec)
+ {
+ try
+ {
+ new ServletPathSpec(pathSpec);
+ fail("Expected IllegalArgumentException for a bad servlet pathspec on: " + pathSpec);
+ }
+ catch (IllegalArgumentException e)
+ {
+ // expected path
+ System.out.println(e);
+ }
+ }
+
+ private void assertMatches(ServletPathSpec spec, String path)
+ {
+ String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+ assertThat(msg,spec.matches(path),is(true));
+ }
+
+ private void assertNotMatches(ServletPathSpec spec, String path)
+ {
+ String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+ assertThat(msg,spec.matches(path),is(false));
+ }
+
+ @Test
+ public void testBadServletPathSpecA()
+ {
+ assertBadServletPathSpec("foo");
+ }
+
+ @Test
+ public void testBadServletPathSpecB()
+ {
+ assertBadServletPathSpec("/foo/*.do");
+ }
+
+ @Test
+ public void testBadServletPathSpecC()
+ {
+ assertBadServletPathSpec("foo/*.do");
+ }
+
+ @Test
+ public void testBadServletPathSpecD()
+ {
+ assertBadServletPathSpec("foo/*.*do");
+ }
+
+ @Test
+ public void testBadServletPathSpecE()
+ {
+ assertBadServletPathSpec("*do");
+ }
+
+ @Test
+ public void testDefaultPathSpec()
+ {
+ ServletPathSpec spec = new ServletPathSpec("/");
+ assertEquals("Spec.pathSpec","/",spec.getPathSpec());
+ assertEquals("Spec.pathDepth",-1,spec.getPathDepth());
+ }
+
+ @Test
+ public void testEmptyPathSpec()
+ {
+ ServletPathSpec spec = new ServletPathSpec("");
+ assertEquals("Spec.pathSpec","/",spec.getPathSpec());
+ assertEquals("Spec.pathDepth",-1,spec.getPathDepth());
+ }
+
+ @Test
+ public void testExactPathSpec()
+ {
+ ServletPathSpec spec = new ServletPathSpec("/abs/path");
+ assertEquals("Spec.pathSpec","/abs/path",spec.getPathSpec());
+ assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+
+ assertMatches(spec,"/abs/path");
+ assertMatches(spec,"/abs/path/");
+
+ assertNotMatches(spec,"/abs/path/more");
+ assertNotMatches(spec,"/foo");
+ assertNotMatches(spec,"/foo/abs/path");
+ assertNotMatches(spec,"/foo/abs/path/");
+ }
+
+ @Test
+ public void testGetPathInfo()
+ {
+ assertEquals("pathInfo exact",null,new ServletPathSpec("/Foo/bar").getPathInfo("/Foo/bar"));
+ assertEquals("pathInfo prefix","/bar",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/bar"));
+ assertEquals("pathInfo prefix","/*",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/*"));
+ assertEquals("pathInfo prefix","/",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/"));
+ assertEquals("pathInfo prefix",null,new ServletPathSpec("/Foo/*").getPathInfo("/Foo"));
+ assertEquals("pathInfo suffix",null,new ServletPathSpec("*.ext").getPathInfo("/Foo/bar.ext"));
+ assertEquals("pathInfo default",null,new ServletPathSpec("/").getPathInfo("/Foo/bar.ext"));
+
+ assertEquals("pathInfo default","/xxx/zzz",new ServletPathSpec("/*").getPathInfo("/xxx/zzz"));
+ }
+
+ @Test
+ public void testNullPathSpec()
+ {
+ ServletPathSpec spec = new ServletPathSpec(null);
+ assertEquals("Spec.pathSpec","/",spec.getPathSpec());
+ assertEquals("Spec.pathDepth",-1,spec.getPathDepth());
+ }
+
+ @Test
+ public void testPathMatch()
+ {
+ assertEquals("pathMatch exact","/Foo/bar",new ServletPathSpec("/Foo/bar").getPathMatch("/Foo/bar"));
+ assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo/bar"));
+ assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo/"));
+ assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo"));
+ assertEquals("pathMatch suffix","/Foo/bar.ext",new ServletPathSpec("*.ext").getPathMatch("/Foo/bar.ext"));
+ assertEquals("pathMatch default","/Foo/bar.ext",new ServletPathSpec("/").getPathMatch("/Foo/bar.ext"));
+
+ assertEquals("pathMatch default","",new ServletPathSpec("/*").getPathMatch("/xxx/zzz"));
+ }
+
+ @Test
+ public void testPrefixPathSpec()
+ {
+ ServletPathSpec spec = new ServletPathSpec("/downloads/*");
+ assertEquals("Spec.pathSpec","/downloads/*",spec.getPathSpec());
+ assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+
+ assertMatches(spec,"/downloads/logo.jpg");
+ assertMatches(spec,"/downloads/distribution.tar.gz");
+ assertMatches(spec,"/downloads/distribution.tgz");
+ assertMatches(spec,"/downloads/distribution.zip");
+
+ assertMatches(spec,"/downloads");
+
+ assertEquals("Spec.pathInfo","/",spec.getPathInfo("/downloads/"));
+ assertEquals("Spec.pathInfo","/distribution.zip",spec.getPathInfo("/downloads/distribution.zip"));
+ assertEquals("Spec.pathInfo","/dist/9.0/distribution.tar.gz",spec.getPathInfo("/downloads/dist/9.0/distribution.tar.gz"));
+ }
+
+ @Test
+ public void testSuffixPathSpec()
+ {
+ ServletPathSpec spec = new ServletPathSpec("*.gz");
+ assertEquals("Spec.pathSpec","*.gz",spec.getPathSpec());
+ assertEquals("Spec.pathDepth",0,spec.getPathDepth());
+
+ assertMatches(spec,"/downloads/distribution.tar.gz");
+ assertMatches(spec,"/downloads/jetty.log.gz");
+
+ assertNotMatches(spec,"/downloads/distribution.zip");
+ assertNotMatches(spec,"/downloads/distribution.tgz");
+ assertNotMatches(spec,"/abs/path");
+
+ assertEquals("Spec.pathInfo",null,spec.getPathInfo("/downloads/distribution.tar.gz"));
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html
index 576f3e3..ee9ef00 100644
--- a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html
+++ b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html
@@ -16,6 +16,7 @@
<input id="manythreads" class="button" type="submit" name="many" value="manythreads" disabled="disabled"/>
<input id="hello" class="button" type="submit" name="hello" value="hello" disabled="disabled"/>
<input id="there" class="button" type="submit" name="there" value="there" disabled="disabled"/>
+ <input id="json" class="button" type="submit" name="json" value="json" disabled="disabled"/>
</div>
<script type="text/javascript">
$("connect").onclick = function(event) { wstool.connect(); return false; }
@@ -26,6 +27,11 @@
$("manythreads").onclick = function(event) {wstool.write("manythreads:20,25,60"); return false; }
$("hello").onclick = function(event) {wstool.write("Hello"); return false; }
$("there").onclick = function(event) {wstool.write("There"); return false; }
+ $("json").onclick = function(event) {wstool.write("[{\"channel\":\"/meta/subscribe\",\"subscription\":\"/chat/demo\",\"id\":\"2\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+ + " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/meta/subscribe\",\"subscription\":\"/members/demo\",\"id\":\"3\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+ + " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/chat/demo\",\"data\":{\"user\":\"ch\",\"membership\":\"join\",\"chat\":\"ch"
+ + " has joined\"},\"id\":\"4\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+ + " 12 Sep 2013 19:42:30 GMT\"}]"); return false; }
</script>
</body>
</html>
\ No newline at end of file
diff --git a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js
index 1860d17..1bc5fe7 100644
--- a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js
+++ b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js
@@ -76,6 +76,7 @@
$('manythreads').disabled = !enabled;
$('hello').disabled = !enabled;
$('there').disabled = !enabled;
+ $('json').disabled = !enabled;
},
_onopen : function() {
diff --git a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
index 6d5c863..f5ce469 100644
--- a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
+++ b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
@@ -1,6 +1,7 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.LEVEL=WARN
+# org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG
# org.eclipse.jetty.websocket.LEVEL=DEBUG
# org.eclipse.jetty.websocket.LEVEL=WARN
# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG
diff --git a/jetty-websocket/websocket-servlet/pom.xml b/jetty-websocket/websocket-servlet/pom.xml
index a206f41..a4fdc86 100644
--- a/jetty-websocket/websocket-servlet/pom.xml
+++ b/jetty-websocket/websocket-servlet/pom.xml
@@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -21,10 +21,17 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </dependency>
+
+<!--
+ <dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.servlet</artifactId>
<scope>provided</scope>
</dependency>
+-->
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java
index fd739e0..36bd6eb 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java
@@ -57,7 +57,15 @@
setHttpVersion(request.getProtocol());
// Copy parameters
- super.setParameterMap(request.getParameterMap());
+ Map<String, List<String>> pmap = new HashMap<>();
+ if (request.getParameterMap() != null)
+ {
+ for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet())
+ {
+ pmap.put(entry.getKey(),Arrays.asList(entry.getValue()));
+ }
+ }
+ super.setParameterMap(pmap);
// Copy Cookies
Cookie rcookies[] = request.getCookies();
@@ -78,12 +86,7 @@
while (headerNames.hasMoreElements())
{
String name = headerNames.nextElement();
- Enumeration<String> valuesEnum = request.getHeaders(name);
- List<String> values = new ArrayList<>();
- while (valuesEnum.hasMoreElements())
- {
- values.add(valuesEnum.nextElement());
- }
+ List<String> values = Collections.list(request.getHeaders(name));
setHeader(name,values);
}
@@ -266,4 +269,26 @@
this.req.setAttribute(name,o);
}
+ public Object getServletAttribute(String name)
+ {
+ return req.getAttribute(name);
+ }
+
+ public boolean isUserInRole(String role)
+ {
+ return req.isUserInRole(role);
+ }
+
+ public String getRequestPath()
+ {
+ // Since this can be called from a filter, we need to be smart about determining the target request path
+ String contextPath = req.getContextPath();
+ String requestPath = req.getRequestURI();
+ if (requestPath.startsWith(contextPath))
+ {
+ requestPath = requestPath.substring(contextPath.length());
+ }
+
+ return requestPath;
+ }
}
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java
index c7b50dc..eaf4324 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java
@@ -19,10 +19,12 @@
package org.eclipse.jetty.websocket.servlet;
import java.io.IOException;
+import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
/**
* Servlet Specific UpgradeResponse implementation.
@@ -30,6 +32,8 @@
public class ServletUpgradeResponse extends UpgradeResponse
{
private HttpServletResponse resp;
+ private boolean extensionsNegotiated = false;
+ private boolean subprotocolNegotiated = false;
public ServletUpgradeResponse(HttpServletResponse resp)
{
@@ -60,6 +64,16 @@
return this.resp.isCommitted();
}
+ public boolean isExtensionsNegotiated()
+ {
+ return extensionsNegotiated;
+ }
+
+ public boolean isSubprotocolNegotiated()
+ {
+ return subprotocolNegotiated;
+ }
+
public void sendError(int statusCode, String message) throws IOException
{
setSuccess(false);
@@ -74,6 +88,20 @@
}
@Override
+ public void setAcceptedSubProtocol(String protocol)
+ {
+ super.setAcceptedSubProtocol(protocol);
+ subprotocolNegotiated = true;
+ }
+
+ @Override
+ public void setExtensions(List<ExtensionConfig> extensions)
+ {
+ super.setExtensions(extensions);
+ extensionsNegotiated = true;
+ }
+
+ @Override
public void setHeader(String name, String value)
{
this.resp.setHeader(name,value);
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketCreator.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketCreator.java
index aa3d162..790a4f1 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketCreator.java
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketCreator.java
@@ -18,10 +18,6 @@
package org.eclipse.jetty.websocket.servlet;
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
-import org.eclipse.jetty.websocket.api.extensions.Extension;
-
/**
* Abstract WebSocket creator interface.
* <p>
@@ -35,11 +31,6 @@
{
/**
* Create a websocket from the incoming request.
- * <p>
- * Note: if you have Servlet specific information you need to access from the UpgradeRequest, cast the {@link UpgradeRequest} to
- * {@link ServletUpgradeRequest} for this extra information.
- * <p>
- * Future versions of this interface will change to use the Servlet specific Upgrade Request and Response parameters.
*
* @param req
* the request details
@@ -47,5 +38,5 @@
* the response details
* @return a websocket object to use, or null if no websocket should be created from this request.
*/
- Object createWebSocket(UpgradeRequest req, UpgradeResponse resp);
+ Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp);
}
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java
index 609a812..7a9eeb1 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java
@@ -19,8 +19,6 @@
package org.eclipse.jetty.websocket.servlet;
import java.io.IOException;
-import java.util.Iterator;
-import java.util.ServiceLoader;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@@ -34,7 +32,7 @@
/**
* Abstract Servlet used to bridge the Servlet API to the WebSocket API.
* <p>
- * To use this servlet, you will be required to register your websockets with the {@link WebSocketServerFactory} so that it can create your websockets under the
+ * To use this servlet, you will be required to register your websockets with the {@link WebSocketServletFactory} so that it can create your websockets under the
* appropriate conditions.
* <p>
* The most basic implementation would be as follows.
@@ -58,7 +56,7 @@
* }
* </pre>
*
- * Note: that only request that conforms to a "WebSocket: Upgrade" handshake request will trigger the {@link WebSocketServerFactory} handling of creating
+ * Note: that only request that conforms to a "WebSocket: Upgrade" handshake request will trigger the {@link WebSocketServletFactory} handling of creating
* WebSockets.<br>
* All other requests are treated as normal servlet requests.
*
@@ -71,8 +69,11 @@
* <dt>maxIdleTime</dt>
* <dd>set the time in ms that a websocket may be idle before closing<br>
*
- * <dt>maxMessagesSize</dt>
- * <dd>set the size in bytes that a websocket may be accept before closing<br>
+ * <dt>maxTextMessageSize</dt>
+ * <dd>set the size in UTF-8 bytes that a websocket may be accept as a Text Message before closing<br>
+ *
+ * <dt>maxBinaryMessageSize</dt>
+ * <dd>set the size in bytes that a websocket may be accept as a Binary Message before closing<br>
*
* <dt>inputBufferSize</dt>
* <dd>set the size in bytes of the buffer used to read raw bytes from the network layer<br>
@@ -107,10 +108,16 @@
policy.setIdleTimeout(Long.parseLong(max));
}
- max = getInitParameter("maxMessageSize");
+ max = getInitParameter("maxTextMessageSize");
if (max != null)
{
- policy.setMaxMessageSize(Long.parseLong(max));
+ policy.setMaxTextMessageSize(Integer.parseInt(max));
+ }
+
+ max = getInitParameter("maxBinaryMessageSize");
+ if (max != null)
+ {
+ policy.setMaxBinaryMessageSize(Integer.parseInt(max));
}
max = getInitParameter("inputBufferSize");
@@ -119,24 +126,7 @@
policy.setInputBufferSize(Integer.parseInt(max));
}
- WebSocketServletFactory baseFactory;
- Iterator<WebSocketServletFactory> factories = ServiceLoader.load(WebSocketServletFactory.class).iterator();
-
- if (factories.hasNext())
- {
- baseFactory = factories.next();
- }
- else
- {
- // Load the default class if ServiceLoader mechanism isn't valid in this environment. (such as OSGi)
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
- @SuppressWarnings("unchecked")
- Class<WebSocketServletFactory> wssf = (Class<WebSocketServletFactory>)loader
- .loadClass("org.eclipse.jetty.websocket.server.WebSocketServerFactory");
- baseFactory = wssf.newInstance();
- }
-
- factory = baseFactory.createFactory(policy);
+ factory = WebSocketServletFactory.Loader.create(policy);
configure(factory);
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java
index 34fbf4d..1f02d69 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java
@@ -19,6 +19,8 @@
package org.eclipse.jetty.websocket.servlet;
import java.io.IOException;
+import java.util.Iterator;
+import java.util.ServiceLoader;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -32,8 +34,47 @@
*/
public interface WebSocketServletFactory
{
+ public static class Loader
+ {
+ private static WebSocketServletFactory INSTANCE;
+
+ public static WebSocketServletFactory create(WebSocketPolicy policy) throws ClassNotFoundException, InstantiationException, IllegalAccessException
+ {
+ return load(policy).createFactory(policy);
+ }
+
+ public static WebSocketServletFactory load(WebSocketPolicy policy) throws ClassNotFoundException, InstantiationException, IllegalAccessException
+ {
+ if (INSTANCE != null)
+ {
+ return INSTANCE;
+ }
+ WebSocketServletFactory baseFactory;
+ Iterator<WebSocketServletFactory> factories = ServiceLoader.load(WebSocketServletFactory.class).iterator();
+
+ if (factories.hasNext())
+ {
+ baseFactory = factories.next();
+ }
+ else
+ {
+ // Load the default class if ServiceLoader mechanism isn't valid in this environment. (such as OSGi)
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ @SuppressWarnings("unchecked")
+ Class<WebSocketServletFactory> wssf = (Class<WebSocketServletFactory>)loader
+ .loadClass("org.eclipse.jetty.websocket.server.WebSocketServerFactory");
+ baseFactory = wssf.newInstance();
+ }
+
+ INSTANCE = baseFactory;
+ return INSTANCE;
+ }
+ }
+
public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException;
+ public boolean acceptWebSocket(WebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException;
+
public void cleanup();
public WebSocketServletFactory createFactory(WebSocketPolicy policy);
diff --git a/jetty-websocket/websocket-servlet/src/test/java/examples/MyAdvancedEchoCreator.java b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAdvancedEchoCreator.java
index 98789a1..18db717 100644
--- a/jetty-websocket/websocket-servlet/src/test/java/examples/MyAdvancedEchoCreator.java
+++ b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAdvancedEchoCreator.java
@@ -18,8 +18,8 @@
package examples;
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
public class MyAdvancedEchoCreator implements WebSocketCreator
@@ -35,7 +35,7 @@
}
@Override
- public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
+ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
for (String subprotocol : req.getSubProtocols())
{
diff --git a/jetty-websocket/websocket-servlet/src/test/java/examples/MyAuthedCreator.java b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAuthedCreator.java
new file mode 100644
index 0000000..28522e7
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAuthedCreator.java
@@ -0,0 +1,60 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package examples;
+
+import java.io.IOException;
+import java.security.Principal;
+
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+public class MyAuthedCreator implements WebSocketCreator
+{
+ @Override
+ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+ {
+ try
+ {
+ // Is Authenticated?
+ Principal principal = req.getPrincipal();
+ if (principal == null)
+ {
+ resp.sendForbidden("Not authenticated yet");
+ return null;
+ }
+
+ // Is Authorized?
+ if (!req.isUserInRole("websocket"))
+ {
+ resp.sendForbidden("Not authenticated yet");
+ return null;
+ }
+
+ // Return websocket
+ return new MyEchoSocket();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace(System.err);
+ }
+ // no websocket
+ return null;
+ }
+}
diff --git a/jetty-websocket/websocket-servlet/src/test/java/examples/MyAuthedServlet.java b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAuthedServlet.java
new file mode 100644
index 0000000..49421ca
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAuthedServlet.java
@@ -0,0 +1,32 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package examples;
+
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+@SuppressWarnings("serial")
+public class MyAuthedServlet extends WebSocketServlet
+{
+ @Override
+ public void configure(WebSocketServletFactory factory)
+ {
+ factory.setCreator(new MyAuthedCreator());
+ }
+}
diff --git a/jetty-websocket/websocket-servlet/src/test/java/examples/MyBinaryEchoSocket.java b/jetty-websocket/websocket-servlet/src/test/java/examples/MyBinaryEchoSocket.java
index 15aeaa9..eeb300e 100644
--- a/jetty-websocket/websocket-servlet/src/test/java/examples/MyBinaryEchoSocket.java
+++ b/jetty-websocket/websocket-servlet/src/test/java/examples/MyBinaryEchoSocket.java
@@ -34,6 +34,6 @@
public void onWebSocketText(Session session, byte buf[], int offset, int len)
{
// Echo message back, asynchronously
- session.getRemote().sendBytesByFuture(ByteBuffer.wrap(buf,offset,len));
+ session.getRemote().sendBytes(ByteBuffer.wrap(buf,offset,len),null);
}
}
diff --git a/jetty-websocket/websocket-servlet/src/test/java/examples/MyEchoSocket.java b/jetty-websocket/websocket-servlet/src/test/java/examples/MyEchoSocket.java
index cb3e016..b9d317f 100644
--- a/jetty-websocket/websocket-servlet/src/test/java/examples/MyEchoSocket.java
+++ b/jetty-websocket/websocket-servlet/src/test/java/examples/MyEchoSocket.java
@@ -32,6 +32,6 @@
public void onWebSocketText(Session session, String message)
{
// Echo message back, asynchronously
- session.getRemote().sendStringByFuture(message);
+ session.getRemote().sendString(message,null);
}
}
diff --git a/jetty-xml/pom.xml b/jetty-xml/pom.xml
index fb94098..c7cd454 100644
--- a/jetty-xml/pom.xml
+++ b/jetty-xml/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-xml</artifactId>
diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java
index a6b93fd..3874da8 100644
--- a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java
+++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java
@@ -95,9 +95,9 @@
private synchronized static XmlParser initParser()
{
XmlParser parser = new XmlParser();
- URL config60 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd",true);
- URL config76 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_7_6.dtd",true);
- URL config90 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_9_0.dtd",true);
+ URL config60 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd");
+ URL config76 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_7_6.dtd");
+ URL config90 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_9_0.dtd");
parser.redirectEntity("configure.dtd",config90);
parser.redirectEntity("configure_1_0.dtd",config60);
parser.redirectEntity("configure_1_1.dtd",config60);
@@ -361,7 +361,7 @@
if (className == null)
return null;
- return Loader.loadClass(XmlConfiguration.class,className,true);
+ return Loader.loadClass(XmlConfiguration.class,className);
}
/**
@@ -859,7 +859,7 @@
aClass = InetAddress.class;
break;
default:
- aClass = Loader.loadClass(XmlConfiguration.class, type, true);
+ aClass = Loader.loadClass(XmlConfiguration.class, type);
break;
}
}
diff --git a/pom.xml b/pom.xml
index 52950ab..32bfb85 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,13 +6,12 @@
<version>20</version>
</parent>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
<name>Jetty :: Project</name>
<url>http://www.eclipse.org/jetty</url>
<packaging>pom</packaging>
<properties>
<jetty.url>http://www.eclipse.org/jetty</jetty.url>
- <orbit-servlet-api-version>3.0.0.v201112011016</orbit-servlet-api-version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<build-support-version>1.1</build-support-version>
<slf4j-version>1.6.1</slf4j-version>
@@ -139,37 +138,6 @@
</configuration>
</execution>
<execution>
- <id>enforce-orbit-deps</id>
- <goals>
- <goal>enforce</goal>
- </goals>
- <configuration>
- <rules>
- <!-- Banned Dependencies (should use Orbit based versions now) -->
- <bannedDependencies>
- <excludes>
- <exclude>javax.servlet</exclude>
- <exclude>javax.servlet.jsp</exclude>
- <exclude>org.apache.geronimo.specs</exclude>
- <exclude>javax.mail</exclude>
- <exclude>javax.activation</exclude>
- </excludes>
- <!-- allowed combinations -->
- <includes>
- <include>org.apache.geronimo.specs:geronimo-atinject_1.0_spec:jar:*</include>
- <include>javax.net.websocket:*:*:*</include>
- <include>javax.websocket:*:*:*</include>
- <include>javax.servlet:*:*:*:provided</include>
- <include>javax.servlet.jsp:*:*:*:provided</include>
- </includes>
- <searchTransitive>true</searchTransitive>
- <message>This dependency is banned, use the ORBIT provided dependency instead.</message>
- </bannedDependencies>
- </rules>
- <fail>true</fail>
- </configuration>
- </execution>
- <execution>
<id>ban-junit-dep.jar</id>
<goals>
<goal>enforce</goal>
@@ -348,17 +316,34 @@
<docfilessubdirs>true</docfilessubdirs>
<detectLinks>false</detectLinks>
<detectJavaApiLink>true</detectJavaApiLink>
- <excludePackageNames>org.slf4j.*;org.mortbay.*</excludePackageNames>
+ <excludePackageNames>com.acme.*;org.slf4j.*;org.mortbay.*</excludePackageNames>
<links>
- <link>http://download.eclipse.org/jetty/stable-7/apidocs/</link>
+ <link>http://docs.oracle.com/javase/7/docs/api/</link>
+ <link>http://docs.oracle.com/javaee/7/api/</link>
+ <link>http://download.eclipse.org/jetty/stable-9/apidocs/</link>
+ <link>http://junit.sourceforge.net/javadoc/</link>
</links>
<tags>
<tag>
<name>org.apache.xbean.XBean</name>
- <placement>a</placement>
- <head>Apache XBean:</head>
+ <placement>X</placement>
+ <head />
</tag>
</tags>
+ <header>
+ <![CDATA[
+ <script type="text/javascript">
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-1149868-7']);
+ _gaq.push(['_trackPageview']);
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+ </script>
+ ]]>
+ </header>
</configuration>
</plugin>
<plugin>
@@ -457,83 +442,147 @@
<!-- modules that need fixed and added back, or simply dropped and not maintained
<module>jetty-rhttp</module>
-->
- <module>jetty-overlay-deployer</module>
+ <!--<module>jetty-overlay-deployer</module>-->
</modules>
<dependencyManagement>
<dependencies>
- <!-- Orbit Deps -->
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
- <version>3.0.0.v201112011016</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.annotation</artifactId>
- <version>1.1.0.v201108011116</version>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>3.1.0</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>org.objectweb.asm</artifactId>
- <version>3.1.0.v200803061910</version>
+ <groupId>javax.websocket</groupId>
+ <artifactId>javax.websocket-api</artifactId>
+ <version>1.0</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.activation</artifactId>
- <version>1.1.0.v201105071233</version>
+ <groupId>javax.websocket</groupId>
+ <artifactId>javax.websocket-client-api</artifactId>
+ <version>1.0</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.mail.glassfish</artifactId>
- <version>1.4.1.v201005082020</version>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ <version>1.2</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.transaction</artifactId>
- <version>1.1.1.v201105210645</version>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm</artifactId>
+ <version>4.1</version>
</dependency>
<dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm-commons</artifactId>
+ <version>4.1</version>
+ </dependency>
+
+ <dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.security.auth.message</artifactId>
<version>1.0.0.v201108011116</version>
</dependency>
- <!--
+
+ <!-- JSP Deps -->
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-schemas</artifactId>
+ <version>3.1.M0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.servlet.jsp</groupId>
+ <artifactId>javax.servlet.jsp-api</artifactId>
+ <version>2.3.1</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.web</groupId>
+ <artifactId>javax.servlet.jsp</artifactId>
+ <version>2.3.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-jsp-jdt</artifactId>
+ <version>2.3.3</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>javax.el</groupId>
+ <artifactId>javax.el-api</artifactId>
+ <version>3.0.0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish</groupId>
+ <artifactId>javax.el</artifactId>
+ <version>3.0.0</version>
+ </dependency>
+
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet.jsp</artifactId>
- <version>2.1.0.v201105211820</version>
+ <artifactId>org.eclipse.jdt.core</artifactId>
+ <version>3.8.2.v20130121</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.eclipse.jetty.orbit</groupId>
+ <artifactId>javax.servlet</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
+
+ <!-- JSTL Impl -->
+ <dependency>
+ <groupId>org.eclipse.jetty.orbit</groupId>
+ <artifactId>org.apache.taglibs.standard.glassfish</artifactId>
+ <version>1.2.0.v201112081803</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.eclipse.jetty.orbit</groupId>
+ <artifactId>javax.servlet</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.servlet.jsp.jstl</artifactId>
<version>1.2.0.v201105211821</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.eclipse.jetty.orbit</groupId>
+ <artifactId>javax.servlet</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.eclipse.jetty.orbit</groupId>
+ <artifactId>javax.servlet.jsp</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
+
+
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.el</artifactId>
- <version>2.1.0.v201105211819</version>
+ <artifactId>javax.activation</artifactId>
+ <version>1.1.0.v201105071233</version>
+ <scope>provided</scope>
</dependency>
+
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>com.sun.el</artifactId>
- <version>1.0.0.v201105211818</version>
+ <artifactId>javax.mail.glassfish</artifactId>
+ <version>1.4.1.v201005082020</version>
</dependency>
+
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>org.apache.jasper.glassfish</artifactId>
- <version>2.1.0.v201110031002</version>
+ <groupId>javax.transaction</groupId>
+ <artifactId>javax.transaction-api</artifactId>
+ <version>1.2</version>
+ <scope>provided</scope>
</dependency>
- <dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>org.apache.taglibs.standard.glassfish</artifactId>
- <version>1.2.0.v201112081803</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>org.eclipse.jdt.core</artifactId>
- <version>3.7.1</version>
- </dependency>
- -->
+
<!-- Old Deps -->
<dependency>
@@ -607,12 +656,14 @@
> mvn -N site:sshdeploy (for ssh users w/passphrase and ssh-agent)
-->
<profiles>
+<!--
<profile>
<id>eclipse-release</id>
<modules>
<module>aggregates/jetty-all</module>
</modules>
</profile>
+-->
<profile>
<id>ci</id>
<modules>
diff --git a/tests/pom.xml b/tests/pom.xml
index e49ead0..2821697 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>org.eclipse.jetty.tests</groupId>
diff --git a/tests/test-continuation/pom.xml b/tests/test-continuation/pom.xml
index 8fd773b..5213cbc 100644
--- a/tests/test-continuation/pom.xml
+++ b/tests/test-continuation/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>tests-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationBase.java b/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationBase.java
index f40991d..13586d0 100644
--- a/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationBase.java
+++ b/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationBase.java
@@ -503,7 +503,6 @@
public void onTimeout(Continuation continuation)
{
((HttpServletResponse)continuation.getServletResponse()).addHeader("history","onTimeout");
- // continuation.resume();
}
};
diff --git a/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java b/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java
index 8b1b3f5..db222b4 100644
--- a/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java
+++ b/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java
@@ -23,8 +23,6 @@
import java.util.ArrayList;
import java.util.List;
-import org.junit.Assert;
-
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.RequestLog;
@@ -39,6 +37,7 @@
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
diff --git a/tests/test-loginservice/pom.xml b/tests/test-loginservice/pom.xml
index 952da14..8f1c6c5 100644
--- a/tests/test-loginservice/pom.xml
+++ b/tests/test-loginservice/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>tests-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<artifactId>test-loginservice</artifactId>
<name>Jetty Tests :: Login Service</name>
diff --git a/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java b/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java
index 794ebcc..602b9df 100644
--- a/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java
+++ b/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -32,6 +35,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
+
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
@@ -69,9 +73,6 @@
import org.junit.Ignore;
import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
public class JdbcLoginServiceTest
{
private static String _content =
@@ -155,7 +156,6 @@
security.setConstraintMappings(Collections.singletonList(mapping), knownRoles);
security.setAuthenticator(new BasicAuthenticator());
security.setLoginService(loginService);
- security.setStrict(false);
ServletContextHandler root = new ServletContextHandler();
root.setContextPath("/");
diff --git a/tests/test-sessions/pom.xml b/tests/test-sessions/pom.xml
index 987030d..59b2d9e 100644
--- a/tests/test-sessions/pom.xml
+++ b/tests/test-sessions/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>tests-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<artifactId>test-sessions-parent</artifactId>
<name>Jetty Tests :: Sessions :: Parent</name>
diff --git a/tests/test-sessions/test-hash-sessions/pom.xml b/tests/test-sessions/test-hash-sessions/pom.xml
index 03046cd..6090a5f 100644
--- a/tests/test-sessions/test-hash-sessions/pom.xml
+++ b/tests/test-sessions/test-hash-sessions/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-sessions-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<artifactId>test-hash-sessions</artifactId>
<name>Jetty Tests :: Sessions :: Hash</name>
diff --git a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/SessionRenewTest.java b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/SessionRenewTest.java
index 9d30514..dbf7ac0 100644
--- a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/SessionRenewTest.java
+++ b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/SessionRenewTest.java
@@ -21,7 +21,6 @@
import java.io.File;
import org.eclipse.jetty.server.SessionManager;
-import org.junit.Assert;
import org.junit.Test;
public class SessionRenewTest extends AbstractSessionRenewTest
diff --git a/tests/test-sessions/test-jdbc-sessions/pom.xml b/tests/test-sessions/test-jdbc-sessions/pom.xml
index 5acf96b..28e3bf0 100644
--- a/tests/test-sessions/test-jdbc-sessions/pom.xml
+++ b/tests/test-sessions/test-jdbc-sessions/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-sessions-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<artifactId>test-jdbc-sessions</artifactId>
<name>Jetty Tests :: Sessions :: JDBC</name>
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java
index b43f828..2fd52d9 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java
@@ -22,7 +22,6 @@
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
-import java.sql.Statement;
import java.util.HashSet;
import java.util.Set;
@@ -74,7 +73,7 @@
static int __workers=0;
/**
- * @see org.eclipse.jetty.server.session.AbstractTestServer#newSessionIdManager()
+ * @see org.eclipse.jetty.server.session.AbstractTestServer#newSessionIdManager(String)
*/
@Override
public SessionIdManager newSessionIdManager(String config)
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReloadedSessionMissingClassTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReloadedSessionMissingClassTest.java
index c34bb6b..65a5a57 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReloadedSessionMissingClassTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReloadedSessionMissingClassTest.java
@@ -18,7 +18,9 @@
package org.eclipse.jetty.server.session;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileWriter;
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionExpiryTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionExpiryTest.java
index 1045872..f78ac69 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionExpiryTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionExpiryTest.java
@@ -25,7 +25,6 @@
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
-import java.util.Set;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSessionEvent;
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/WebAppObjectInSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/WebAppObjectInSessionTest.java
index 7591fd0..67358c9 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/WebAppObjectInSessionTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/WebAppObjectInSessionTest.java
@@ -19,9 +19,9 @@
package org.eclipse.jetty.server.session;
import java.sql.DriverManager;
-import org.eclipse.jetty.util.resource.Resource;
import java.sql.SQLException;
+import org.eclipse.jetty.util.resource.Resource;
import org.junit.After;
import org.junit.Test;
diff --git a/tests/test-sessions/test-mongodb-sessions/pom.xml b/tests/test-sessions/test-mongodb-sessions/pom.xml
index 42b3296..19a8594 100644
--- a/tests/test-sessions/test-mongodb-sessions/pom.xml
+++ b/tests/test-sessions/test-mongodb-sessions/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-sessions-parent</artifactId>
- <version>9.0.0-SNAPSHOT</version>
+ <version>9.1.0-SNAPSHOT</version>
</parent>
<artifactId>test-mongodb-sessions</artifactId>
<name>Jetty Tests :: Sessions :: Mongo</name>
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeInvalidSessionTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeInvalidSessionTest.java
index 10f246b..76c9335 100644
--- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeInvalidSessionTest.java
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeInvalidSessionTest.java
@@ -31,9 +31,9 @@
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
-import org.eclipse.jetty.client.ContentExchange;
import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.junit.Test;
@@ -88,41 +88,30 @@
try
{
HttpClient client = new HttpClient();
- client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
client.start();
try
{
//Create a session
- ContentExchange exchange = new ContentExchange(true);
- exchange.setMethod(HttpMethods.GET);
- exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=create");
- client.send(exchange);
- exchange.waitForDone();
- assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
- String sessionCookie = exchange.getResponseFields().getStringField("Set-Cookie");
+ ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create");
+ assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+ String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
assertTrue(sessionCookie != null);
// Mangle the cookie, replacing Path with $Path, etc.
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
//make a request to invalidate the session
- exchange = new ContentExchange(true);
- exchange.setMethod(HttpMethods.GET);
- exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=invalidate");
- exchange.getRequestFields().add("Cookie", sessionCookie);
- client.send(exchange);
- exchange.waitForDone();
- assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+ Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=invalidate");
+ request.header("Cookie", sessionCookie);
+ response = request.send();
+ assertEquals(HttpServletResponse.SC_OK,response.getStatus());
Thread.currentThread().sleep(3*purgeDelay); //sleep long enough for purger to have run
- //make a request using previous session to test if its still there
- exchange = new ContentExchange(true);
- exchange.setMethod(HttpMethods.GET);
- exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=test");
- exchange.getRequestFields().add("Cookie", sessionCookie);
- client.send(exchange);
- exchange.waitForDone();
- assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+ //make a request using previous session to test if its still there
+ request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=test");
+ request.header("Cookie", sessionCookie);
+ response = request.send();
+ assertEquals(HttpServletResponse.SC_OK,response.getStatus());
}
finally
{
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeValidSessionTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeValidSessionTest.java
index 41ed3d2..a16b1bd 100644
--- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeValidSessionTest.java
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeValidSessionTest.java
@@ -31,9 +31,10 @@
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
-import org.eclipse.jetty.client.ContentExchange;
+
import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.junit.Test;
@@ -90,18 +91,13 @@
try
{
HttpClient client = new HttpClient();
- client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
client.start();
try
{
//Create a session
- ContentExchange exchange = new ContentExchange(true);
- exchange.setMethod(HttpMethods.GET);
- exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=create");
- client.send(exchange);
- exchange.waitForDone();
- assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
- String sessionCookie = exchange.getResponseFields().getStringField("Set-Cookie");
+ ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create");
+ assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+ String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
assertTrue(sessionCookie != null);
// Mangle the cookie, replacing Path with $Path, etc.
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
@@ -110,13 +106,10 @@
Thread.currentThread().sleep(3*purgeDelay); //sleep long enough for purger to have run
//make a request using previous session to test if its still there
- exchange = new ContentExchange(true);
- exchange.setMethod(HttpMethods.GET);
- exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=test");
- exchange.getRequestFields().add("Cookie", sessionCookie);
- client.send(exchange);
- exchange.waitForDone();
- assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+ Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=test");
+ request.header("Cookie", sessionCookie);
+ ContentResponse response2 = request.send();
+ assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
}
finally
{
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/ServerCrossContextSessionTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/ServerCrossContextSessionTest.java
index 4d9bd4e..ffa089f 100644
--- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/ServerCrossContextSessionTest.java
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/ServerCrossContextSessionTest.java
@@ -23,6 +23,7 @@
import org.junit.Ignore;
import org.junit.Test;
+
public class ServerCrossContextSessionTest extends AbstractServerCrossContextSessionTest
{
public AbstractTestServer createServer(int port)
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionSavingValueTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionSavingValueTest.java
index e42fdf7..0ece861 100644
--- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionSavingValueTest.java
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionSavingValueTest.java
@@ -108,9 +108,8 @@
{ "0", "null" };
// Perform one request to server1 to create a session
- Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=init");
- Future<ContentResponse> future = request.send();
- ContentResponse response = future.get();
+ ContentResponse response = client.GET("http://localhost:" + port1 + contextPath + servletMapping + "?action=init");
+
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
String[] sessionTestResponse = response.getContentAsString().split("/");
@@ -134,8 +133,7 @@
{
Request request2 = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping);
request2.header("Cookie",sessionCookie);
- Future<ContentResponse> future2 = request2.send();
- ContentResponse response2 = future2.get();
+ ContentResponse response2 = request2.send();
assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
diff --git a/tests/test-sessions/test-sessions-common/pom.xml b/tests/test-sessions/test-sessions-common/pom.xml
index 1861b5a..45e1211 100644
--- a/tests/test-sessions/test-sessions-common/pom.xml
+++ b/tests/test-sessions/test-sessions-common/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-sessions-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<artifactId>test-sessions-common</artifactId>
<name>Jetty Tests :: Sessions :: Common</name>
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractProxySerializationTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractProxySerializationTest.java
index e7fe83f..8175a53 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractProxySerializationTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractProxySerializationTest.java
@@ -22,31 +22,21 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertNotNull;
import java.io.File;
import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.InputStream;
-import java.lang.reflect.Proxy;
import java.net.URL;
import java.net.URLClassLoader;
-import java.util.jar.JarFile;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.resource.JarResource;
import org.junit.Test;
/**
@@ -63,7 +53,7 @@
/**
- * @param sec mseconds to sleep
+ * @param msec milliseconds to sleep
*/
public void pause(int msec)
{
@@ -119,10 +109,6 @@
//stop the context to be sure the sesssion will be passivated
context.stop();
- //after a stop some of the volatile info is lost, so reinstate it
- context.setClassLoader(loader);
- context.addServlet("TestServlet", servletMapping);
-
//restart the context
context.start();
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionRenewTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionRenewTest.java
index 070ddf1..cc5bb44 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionRenewTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionRenewTest.java
@@ -32,11 +32,13 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.webapp.WebAppContext;
public abstract class AbstractSessionRenewTest
@@ -49,8 +51,11 @@
String servletMapping = "/server";
int scavengePeriod = 3;
AbstractTestServer server = createServer(0, 1, scavengePeriod);
- ServletContextHandler context = server.addContext(contextPath);
+ WebAppContext context = server.addWebAppContext(".", contextPath);
context.addServlet(TestServlet.class, servletMapping);
+ TestHttpSessionIdListener testListener = new TestHttpSessionIdListener();
+ context.addEventListener(testListener);
+
HttpClient client = new HttpClient();
@@ -67,6 +72,7 @@
String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
assertTrue(sessionCookie != null);
+ assertFalse(testListener.isCalled());
//make a request to change the sessionid
Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=renew");
@@ -76,6 +82,7 @@
String renewSessionCookie = renewResponse.getHeaders().getStringField("Set-Cookie");
assertNotNull(renewSessionCookie);
assertNotSame(sessionCookie, renewSessionCookie);
+ assertTrue(testListener.isCalled());
}
finally
{
@@ -84,9 +91,30 @@
}
}
+
+
+ public static class TestHttpSessionIdListener implements HttpSessionIdListener
+ {
+ boolean called = false;
+
+ @Override
+ public void sessionIdChanged(HttpSessionEvent event, String oldSessionId)
+ {
+ assertNotNull(event.getSession());
+ assertNotSame(oldSessionId, event.getSession().getId());
+ called = true;
+ }
+
+ public boolean isCalled()
+ {
+ return called;
+ }
+ }
+
public static class TestServlet extends HttpServlet
{
+
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
diff --git a/tests/test-webapps/pom.xml b/tests/test-webapps/pom.xml
index 89fc78d..6f7becf 100644
--- a/tests/test-webapps/pom.xml
+++ b/tests/test-webapps/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>tests-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>test-webapps-parent</artifactId>
diff --git a/tests/test-webapps/test-jaas-webapp/pom.xml b/tests/test-webapps/test-jaas-webapp/pom.xml
index b02d595..ff3a066 100644
--- a/tests/test-webapps/test-jaas-webapp/pom.xml
+++ b/tests/test-webapps/test-jaas-webapp/pom.xml
@@ -4,7 +4,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-webapps-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<artifactId>test-jaas-webapp</artifactId>
<name>Jetty Tests :: WebApp :: JAAS</name>
@@ -34,7 +34,7 @@
<!-- Mandatory. This system property tells JAAS where to find the login module configuration file -->
<systemProperty>
<name>java.security.auth.login.config</name>
- <value>${basedir}/src/main/config/webapps.demo/test-jaas.d/login.conf</value>
+ <value>${basedir}/src/main/config/demo-base/etc/login.conf</value>
</systemProperty>
</systemProperties>
<webAppConfig>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/etc/login.conf b/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/etc/login.conf
new file mode 100644
index 0000000..a97b0ed
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/etc/login.conf
@@ -0,0 +1,5 @@
+xyz {
+org.eclipse.jetty.jaas.spi.PropertyFileLoginModule required
+debug="true"
+file="${jetty.base}/etc/login.properties";
+};
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.properties b/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/etc/login.properties
similarity index 100%
rename from tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.properties
rename to tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/etc/login.properties
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.xml b/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/webapps/test-jaas.xml
similarity index 100%
rename from tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.xml
rename to tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/webapps/test-jaas.xml
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.conf b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.conf
deleted file mode 100644
index 0978de6..0000000
--- a/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-xyz {
-org.eclipse.jetty.jaas.spi.PropertyFileLoginModule required
-debug="true"
-file="${jetty.home}/webapps.demo/test-jaas.d/login.properties";
-};
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html b/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html
index d9f637b..a4a5ff9 100644
--- a/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html
@@ -7,43 +7,36 @@
</HEAD>
<BODY>
<A HREF="http://www.eclipse.org/jetty"><IMG SRC="images/jetty_banner.gif"></A>
-
-<p> </p>
- <a href="http://localhost:8080/">Home</a>
-<center>
+ <br/>
+ <b><a href="http://localhost:8080/">Demo Home</a></b>
<hr/>
- <span style="color:red; font-variant:small-caps; font-weight:bold">Test Web Application Only - Do NOT Deploy in Production</span>
-</center>
+ <center><span style="color:red; font-variant:small-caps; font-weight:bold">Test Web Application Only - Do NOT Deploy in Production</span> </center>
<H1>JAAS Authentication and Authorization Demo </H1>
<h2>Preparation</h2>
- <p>To enable JAAS, edit your start.ini or start.d/*.ini files and add the following lines:
+ <p>To enable JAAS in a base jetty instance do:
<pre>
- OPTIONS=jaas
- jaas.login.conf=etc/login.conf
- etc/jetty-jaas.xml
+ $ cd $JETTY_BASE
+ $ java -jar $JETTY_HOME/start.jar --module-startd-ini=jaas
</pre>
</p>
- <p>For the jetty distribution demos, jaas is already enabled in the start.d/900-demo.ini file and sets the jaas.login.conf property to webapps.demo/test-jaas.d/login.conf for use with the webapps.demo/test-jaas.war web application. </p>
+ <p>This will create a $JETTY_BASE/start.d/jaas.ini file to enable and parameterise JAAS. If the --module-start-ini option instead, then the same initialisation will be appended to the
+ $JETTY_BASE/start.ini file instead. The jetty demo-base already has JAAS enabled in the start.ini file. </p>
<p>The full source of this demonstration is available <a
href="http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/tests/test-webapps/test-jaas-webapp">here</a>.</p>
<h2>Using the Demo</h2>
<P>
- Click on the following link to test JAAS <i>authentication</i> and role-based web security constraint <i>authorization</i>.
+ Click on the link below to test JAAS <i>authentication</i> and role-based web security constraint <i>authorization</i>. Use username="me" with password="me". All other usernames, passwords should result in authentication failure.
</P>
+ <big><b><A HREF="auth.html">LOGIN</A></b></big>
<p>
This demo uses a simple login module that stores its configuration in a properties file. There are other types of login module provided with the jetty distro. For full information, please refer to the <a href="http://www.eclipse.org/jetty/documentation/current/">Jetty 9 documentation</a>.
</p>
- <P>
- To authenticate successfully with this demonstration, you must use username="me" with password="me". All other usernames, passwords should result in authentication failure.
- </P>
- <A HREF="auth.html">Login</A>
- <center>
- <hr/>
- <a href="http://www.eclipse.org/jetty"><img style="border:0" src="images/small_powered_by.gif"/></a>
- </center>
+ <hr/>
+ <center> <a href="http://www.eclipse.org/jetty"><img style="border:0" src="images/small_powered_by.gif"/></a></center>
+
</BODY>
</HTML>
diff --git a/tests/test-webapps/test-jetty-webapp/pom.xml b/tests/test-webapps/test-jetty-webapp/pom.xml
index 2c420b6..173d97b 100644
--- a/tests/test-webapps/test-jetty-webapp/pom.xml
+++ b/tests/test-webapps/test-jetty-webapp/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-webapps-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -98,7 +98,7 @@
<configuration>
<instructions>
<Bundle-SymbolicName>org.eclipse.jetty.test-jetty-webapp</Bundle-SymbolicName>
- <Import-Package>javax.servlet.jsp.*;version="2.2.0",javax.servlet.*;version="[2.6,3.0)",org.eclipse.jetty.*;version="9.0",*</Import-Package>
+ <Import-Package>javax.servlet.jsp.*;version="[2.2.0, 3.0)",javax.servlet.*;version="[2.6,3.2)",org.eclipse.jetty.*;version="9.1",*</Import-Package>
<Export-Package>!com.acme*</Export-Package>
<!-- the test webapp is configured via a jetty xml file
in order to add the security handler. -->
@@ -147,7 +147,7 @@
<loginServices>
<loginService implementation="org.eclipse.jetty.security.HashLoginService">
<name>Test Realm</name>
- <config>src/main/config/etc/realm.properties</config>
+ <config>src/main/config/demo-base/etc/realm.properties</config>
</loginService>
</loginServices>
</configuration>
@@ -193,13 +193,8 @@
<version>${project.version}</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-continuation</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
@@ -254,5 +249,11 @@
<version>1.2</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>javax.websocket</groupId>
+ <artifactId>javax.websocket-api</artifactId>
+ <version>1.0</version>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/assembly/embedded-jetty-web-for-webbundle.xml b/tests/test-webapps/test-jetty-webapp/src/main/assembly/embedded-jetty-web-for-webbundle.xml
index 4ed6fbc..911b947 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/assembly/embedded-jetty-web-for-webbundle.xml
+++ b/tests/test-webapps/test-jetty-webapp/src/main/assembly/embedded-jetty-web-for-webbundle.xml
@@ -28,7 +28,7 @@
<Set name="extractWAR">true</Set>
<Set name="copyWebDir">false</Set>
<Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
- <Set name="overrideDescriptor"><SystemProperty name="jetty.home" default="."/>/contexts/test.d/override-web.xml</Set>
+ <Set name="overrideDescriptor"><SystemProperty name="jetty.base" default="."/>/etc/override-web.xml</Set>
<!-- virtual hosts
<Set name="virtualHosts">
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/assembly/web-bundle.xml b/tests/test-webapps/test-jetty-webapp/src/main/assembly/web-bundle.xml
index b80faf9..c220f51 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/assembly/web-bundle.xml
+++ b/tests/test-webapps/test-jetty-webapp/src/main/assembly/web-bundle.xml
@@ -27,7 +27,7 @@
<destName>jetty-web.xml</destName>
</file>
<file>
- <source>src/main/config/etc/realm.properties</source>
+ <source>src/main/config/demo-base/etc/realm.properties</source>
<outputDirectory>WEB-INF</outputDirectory>
<destName>realm.properties</destName>
</file>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/demo-rewrite-rules.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/demo-rewrite-rules.xml
new file mode 100644
index 0000000..35a8f87
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/demo-rewrite-rules.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the demos -->
+<!-- =============================================================== -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+ <!-- ============================================================= -->
+ <!-- Add rewrite rules -->
+ <!-- ============================================================= -->
+ <Ref refid="Rewrite">
+ <!-- Add rule to protect against IE ssl bug -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.MsieSslRule"/>
+ </Arg>
+ </Call>
+
+ <!-- protect favicon handling -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
+ <Set name="pattern">/favicon.ico</Set>
+ <Set name="name">Cache-Control</Set>
+ <Set name="value">Max-Age=3600,public</Set>
+ <Set name="terminating">true</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- redirect from the welcome page to a specific page -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
+ <Set name="pattern">/test/rewrite/</Set>
+ <Set name="replacement">/test/rewrite/info.html</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- replace the entire request URI -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
+ <Set name="pattern">/test/some/old/context</Set>
+ <Set name="replacement">/test/rewritten/newcontext</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- replace the beginning of the request URI -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
+ <Set name="pattern">/test/rewrite/for/*</Set>
+ <Set name="replacement">/test/rewritten/</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- reverse the order of the path sections -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.RewriteRegexRule">
+ <Set name="regex">(.*?)/reverse/([^/]*)/(.*)</Set>
+ <Set name="replacement">$1/reverse/$3/$2</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- add a cookie to each path visited -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.CookiePatternRule">
+ <Set name="pattern">/*</Set>
+ <Set name="name">visited</Set>
+ <Set name="value">yes</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- actual redirect, instead of internal rewrite -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.RedirectPatternRule">
+ <Set name="pattern">/test/redirect/*</Set>
+ <Set name="location">/test/redirected</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- add a response rule -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.ResponsePatternRule">
+ <Set name="pattern">/400Error</Set>
+ <Set name="code">400</Set>
+ <Set name="reason">ResponsePatternRule Demo</Set>
+ </New>
+ </Arg>
+ </Call>
+ </Ref>
+</Configure>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/etc/realm.properties b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/realm.properties
similarity index 100%
rename from tests/test-webapps/test-jetty-webapp/src/main/config/etc/realm.properties
rename to tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/realm.properties
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/test-realm.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/test-realm.xml
new file mode 100644
index 0000000..d5c776b
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/test-realm.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+ <!-- =========================================================== -->
+ <!-- Configure Authentication Login Service -->
+ <!-- Realms may be configured for the entire server here, or -->
+ <!-- they can be configured for a specific web app in a context -->
+ <!-- configuration (see $(jetty.home)/webapps/test.xml for an -->
+ <!-- example). -->
+ <!-- =========================================================== -->
+ <Call name="addBean">
+ <Arg>
+ <New class="org.eclipse.jetty.security.HashLoginService">
+ <Set name="name">Test Realm</Set>
+ <Set name="config"><Property name="demo.realm" default="etc/realm.properties"/></Set>
+ <Set name="refreshInterval">0</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <Get class="org.eclipse.jetty.util.log.Log" name="rootLogger">
+ <Call name="warn"><Arg>demo test-realm is deployed. DO NOT USE IN PRODUCTION!</Arg></Call>
+ </Get>
+</Configure>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/start.d/http.ini b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/start.d/http.ini
new file mode 100644
index 0000000..9607fd2
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/start.d/http.ini
@@ -0,0 +1,6 @@
+#
+# HTTP connector
+#
+--module=http
+jetty.port=8080
+
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/start.ini b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/start.ini
new file mode 100644
index 0000000..9a840f4
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/start.ini
@@ -0,0 +1,23 @@
+#
+# Example of providing a demo configuration, using a ${jetty.base}
+#
+# Additional ini files are in demo-base/start.d
+#
+
+# Enable security via jaas, and configure it
+--module=jaas
+jaas.login.conf=etc/login.conf
+
+# Enable rewrite examples
+--module=rewrite
+etc/demo-rewrite-rules.xml
+
+# Websocket chat examples needs websocket enabled
+# Don't start for all contexts (set to true in test.xml context)
+org.eclipse.jetty.websocket.jsr356=false
+--module=websocket
+
+# Create and configure the test realm
+etc/test-realm.xml
+demo.realm=etc/realm.properties
+
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.d/override-web.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.d/override-web.xml
new file mode 100644
index 0000000..3ff762a
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.d/override-web.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app
+ xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ version="2.5">
+
+
+<!-- This web.xml format file is an override file that is applied to the test webapp AFTER
+ it has been configured by the default descriptor and the WEB-INF/web.xml descriptor -->
+
+ <!-- Add or override context init parameter -->
+ <context-param>
+ <param-name>context-override-example</param-name>
+ <param-value>a context value</param-value>
+ </context-param>
+
+
+ <!-- Add or override servlet init parameter -->
+ <servlet>
+ <servlet-name>Dump</servlet-name>
+ <init-param>
+ <param-name>servlet-override-example</param-name>
+ <param-value>a servlet value</param-value>
+ </init-param>
+ </servlet>
+
+ <!-- Add servlet mapping -->
+ <servlet-mapping>
+ <servlet-name>Dump</servlet-name>
+ <url-pattern>*.more</url-pattern>
+ </servlet-mapping>
+
+ <!-- Reset servlet class and/or start order -->
+ <servlet>
+ <servlet-name>Session</servlet-name>
+ <servlet-class>com.acme.SessionDump</servlet-class>
+ <load-on-startup>5</load-on-startup>
+ </servlet>
+
+ <!-- Allow remote access to test webapp -->
+ <!--
+ <filter>
+ <filter-name>TestFilter</filter-name>
+ <filter-class>com.acme.TestFilter</filter-class>
+ <async-supported>true</async-supported>
+ <init-param>
+ <param-name>remote</param-name>
+ <param-value>true</param-value>
+ </init-param>
+ </filter>
+ -->
+
+</web-app>
+
+
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml
new file mode 100644
index 0000000..c4bdf98
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- ==================================================================
+Configure and deploy the test web application in $(jetty.home)/webapps/test
+
+Note. If this file did not exist or used a context path other that /test
+then the default configuration of jetty.xml would discover the test
+webapplication with a WebAppDeployer. By specifying a context in this
+directory, additional configuration may be specified and hot deployments
+detected.
+===================================================================== -->
+
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+ <!-- Required minimal context configuration : -->
+ <!-- + contextPath -->
+ <!-- + war OR resourceBase -->
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+ <Set name="contextPath">/test</Set>
+ <Set name="war"><Property name="jetty.webapps" default="."/>/test.war</Set>
+
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+ <!-- Optional context configuration -->
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+ <Set name="extractWAR">true</Set>
+ <Set name="copyWebDir">false</Set>
+ <Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
+ <Set name="overrideDescriptor"><Property name="jetty.webapps" default="."/>/test.d/override-web.xml</Set>
+
+ <!-- Enable WebSocket container -->
+ <Call name="setAttribute">
+ <Arg>org.eclipse.jetty.websocket.jsr356</Arg>
+ <Arg type="Boolean">true</Arg>
+ </Call>
+
+ <!-- Enable symlinks
+ <Call name="addAliasCheck">
+ <Arg><New class="org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker"/></Arg>
+ </Call>
+ -->
+
+ <!-- virtual hosts
+ <Set name="virtualHosts">
+ <Array type="String">
+ <Item>www.MyVirtualDomain.com</Item>
+ <Item>m.MyVirtualDomain.com</Item>
+ <Item>*.OtherVirtualDomain.com</Item>
+ <Item>@ConnectorName</Item>
+ <Item>localhost</Item>
+ <Item>127.0.0.1</Item>
+ </Array>
+ </Set>
+ -->
+
+ <!-- disable cookies
+ <Get name="sessionHandler">
+ <Get name="sessionManager">
+ <Set name="usingCookies" type="boolean">false</Set>
+ </Get>
+ </Get>
+ -->
+
+ <Get name="securityHandler">
+ <Set name="loginService">
+ <New class="org.eclipse.jetty.security.HashLoginService">
+ <Set name="name">Test Realm</Set>
+ <Set name="config"><SystemProperty name="jetty.base" default="."/>/etc/realm.properties</Set>
+ <!-- To enable reload of realm when properties change, uncomment the following lines -->
+ <!-- changing refreshInterval (in seconds) as desired -->
+ <!--
+ <Set name="refreshInterval">5</Set>
+ <Call name="start"></Call>
+ -->
+ </New>
+ </Set>
+ <Set name="authenticator">
+ <New class="org.eclipse.jetty.security.authentication.FormAuthenticator">
+ <Set name="alwaysSaveUri">true</Set>
+ </New>
+ </Set>
+ <Set name="checkWelcomeFiles">true</Set>
+ </Get>
+
+ <!-- Non standard error page mapping -->
+ <!--
+ <Get name="errorHandler">
+ <Call name="addErrorPage">
+ <Arg type="int">500</Arg>
+ <Arg type="int">599</Arg>
+ <Arg type="String">/dump/errorCodeRangeMapping</Arg>
+ </Call>
+ </Get>
+ -->
+
+ <!-- Add context specific logger
+ <Set name="handler">
+ <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler">
+ <Set name="requestLog">
+ <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
+ <Set name="filename"><Property name="jetty.logs" default="./logs"/>/test-yyyy_mm_dd.request.log</Set>
+ <Set name="filenameDateFormat">yyyy_MM_dd</Set>
+ <Set name="append">true</Set>
+ <Set name="LogTimeZone">GMT</Set>
+ </New>
+ </Set>
+ </New>
+ </Set>
+ -->
+
+</Configure>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/etc/test-realm.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/etc/test-realm.xml
deleted file mode 100644
index 5459dd5..0000000
--- a/tests/test-webapps/test-jetty-webapp/src/main/config/etc/test-realm.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-" "http://www.eclipse.org/jetty/configure_9_0.dtd">
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
- <!-- =========================================================== -->
- <!-- Configure Authentication Login Service -->
- <!-- Realms may be configured for the entire server here, or -->
- <!-- they can be configured for a specific web app in a context -->
- <!-- configuration (see $(jetty.home)/webapps/test.xml for an -->
- <!-- example). -->
- <!-- =========================================================== -->
- <Call name="addBean">
- <Arg>
- <New class="org.eclipse.jetty.security.HashLoginService">
- <Set name="name">Test Realm</Set>
- <Set name="config"><Property name="jetty.home" default="."/>/etc/realm.properties</Set>
- <Set name="refreshInterval">0</Set>
- </New>
- </Arg>
- </Call>
-
- <Get class="org.eclipse.jetty.util.log.Log" name="rootLogger">
- <Call name="warn"><Arg>test-realm is deployed. DO NOT USE IN PRODUCTION!</Arg></Call>
- </Get>
-</Configure>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.d/override-web.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.d/override-web.xml
deleted file mode 100644
index 08327c5..0000000
--- a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.d/override-web.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<web-app
- xmlns="http://java.sun.com/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
- version="2.5">
-
-
-<!-- This web.xml format file is an override file that is applied to the test webapp AFTER
- it has been configured by the default descriptor and the WEB-INF/web.xml descriptor -->
-
- <!-- Add or override context init parameter -->
- <context-param>
- <param-name>context-override-example</param-name>
- <param-value>a context value</param-value>
- </context-param>
-
-
- <!-- Add or override servlet init parameter -->
- <servlet>
- <servlet-name>Dump</servlet-name>
- <init-param>
- <param-name>servlet-override-example</param-name>
- <param-value>a servlet value</param-value>
- </init-param>
- </servlet>
-
- <!-- Add servlet mapping -->
- <servlet-mapping>
- <servlet-name>Dump</servlet-name>
- <url-pattern>*.more</url-pattern>
- </servlet-mapping>
-
- <!-- Reset servlet class and/or start order -->
- <servlet>
- <servlet-name>Session</servlet-name>
- <servlet-class>com.acme.SessionDump</servlet-class>
- <load-on-startup>5</load-on-startup>
- </servlet>
-
- <!-- Uncomment to override the setup of the test filter -->
- <!--
- <filter>
- <filter-name>TestFilter</filter-name>
- <filter-class>com.acme.TestFilter</filter-class>
- <async-support>true</async-support>
- <init-param>
- <param-name>remote</param-name>
- <param-value>false</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>TestFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- -->
-</web-app>
-
-
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.xml
deleted file mode 100644
index 8cee715..0000000
--- a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.xml
+++ /dev/null
@@ -1,106 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
-
-<!-- ==================================================================
-Configure and deploy the test web application in $(jetty.home)/webapps/test
-
-Note. If this file did not exist or used a context path other that /test
-then the default configuration of jetty.xml would discover the test
-webapplication with a WebAppDeployer. By specifying a context in this
-directory, additional configuration may be specified and hot deployments
-detected.
-===================================================================== -->
-
-<Configure class="org.eclipse.jetty.webapp.WebAppContext">
-
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <!-- Required minimal context configuration : -->
- <!-- + contextPath -->
- <!-- + war OR resourceBase -->
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <Set name="contextPath">/test</Set>
- <Set name="war"><Property name="jetty.webapps" default="."/>/test.war</Set>
-
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <!-- Optional context configuration -->
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <Set name="extractWAR">true</Set>
- <Set name="copyWebDir">false</Set>
- <Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
- <Set name="overrideDescriptor"><Property name="jetty.webapps" default="."/>/test.d/override-web.xml</Set>
-
- <!-- Enable symlinks
- <Call name="addAliasCheck">
- <Arg><New class="org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker"/></Arg>
- </Call>
- -->
-
- <!-- virtual hosts
- <Set name="virtualHosts">
- <Array type="String">
- <Item>www.MyVirtualDomain.com</Item>
- <Item>m.MyVirtualDomain.com</Item>
- <Item>*.OtherVirtualDomain.com</Item>
- <Item>@ConnectorName</Item>
- <Item>localhost</Item>
- <Item>127.0.0.1</Item>
- </Array>
- </Set>
- -->
-
- <!-- disable cookies
- <Get name="sessionHandler">
- <Get name="sessionManager">
- <Set name="usingCookies" type="boolean">false</Set>
- </Get>
- </Get>
- -->
-
- <Get name="securityHandler">
- <Set name="loginService">
- <New class="org.eclipse.jetty.security.HashLoginService">
- <Set name="name">Test Realm</Set>
- <Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/realm.properties</Set>
- <!-- To enable reload of realm when properties change, uncomment the following lines -->
- <!-- changing refreshInterval (in seconds) as desired -->
- <!--
- <Set name="refreshInterval">5</Set>
- <Call name="start"></Call>
- -->
- </New>
- </Set>
- <Set name="authenticator">
- <New class="org.eclipse.jetty.security.authentication.FormAuthenticator">
- <Set name="alwaysSaveUri">true</Set>
- </New>
- </Set>
- <Set name="checkWelcomeFiles">true</Set>
- </Get>
-
- <!-- Non standard error page mapping -->
- <!--
- <Get name="errorHandler">
- <Call name="addErrorPage">
- <Arg type="int">500</Arg>
- <Arg type="int">599</Arg>
- <Arg type="String">/dump/errorCodeRangeMapping</Arg>
- </Call>
- </Get>
- -->
-
- <!-- Add context specific logger
- <Set name="handler">
- <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler">
- <Set name="requestLog">
- <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
- <Set name="filename"><Property name="jetty.logs" default="./logs"/>/test-yyyy_mm_dd.request.log</Set>
- <Set name="filenameDateFormat">yyyy_MM_dd</Set>
- <Set name="append">true</Set>
- <Set name="LogTimeZone">GMT</Set>
- </New>
- </Set>
- </New>
- </Set>
- -->
-
-</Configure>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/ChatServlet.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/ChatServlet.java
index 81325d6..657a6ab 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/ChatServlet.java
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/ChatServlet.java
@@ -24,6 +24,7 @@
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicReference;
+
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java
index 604a5f6..b5fff80 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java
@@ -36,6 +36,9 @@
import java.util.Timer;
import java.util.TimerTask;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@@ -51,9 +54,6 @@
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.continuation.ContinuationSupport;
/**
* Dump Servlet Request.
@@ -176,59 +176,18 @@
}
}
- if (request.getAttribute("RESUME")==null && request.getParameter("resume")!=null)
+ if (request.getParameter("startAsync")!=null && request.getAttribute("ASYNC")!=Boolean.TRUE)
{
- request.setAttribute("RESUME",Boolean.TRUE);
-
- final long resume=Long.parseLong(request.getParameter("resume"));
- final Continuation continuation = ContinuationSupport.getContinuation(request);
- _timer.schedule(new TimerTask()
- {
- @Override
- public void run()
- {
- continuation.resume();
- }
- },resume);
-
- }
-
- if (request.getParameter("complete")!=null)
- {
- final long complete=Long.parseLong(request.getParameter("complete"));
- _timer.schedule(new TimerTask()
- {
- @Override
- public void run()
- {
- try
- {
- response.setContentType("text/html");
- response.getOutputStream().println("<h1>COMPLETED</h1>");
- Continuation continuation = ContinuationSupport.getContinuation(request);
- continuation.complete();
- }
- catch(Exception e)
- {
- e.printStackTrace();
- }
- }
- },complete);
- }
-
- if (request.getParameter("suspend")!=null && request.getAttribute("SUSPEND")!=Boolean.TRUE)
- {
- request.setAttribute("SUSPEND",Boolean.TRUE);
+ request.setAttribute("ASYNC",Boolean.TRUE);
try
{
- Continuation continuation = ContinuationSupport.getContinuation(request);
- continuation.setTimeout(Long.parseLong(request.getParameter("suspend")));
- continuation.suspend();
-
- continuation.addContinuationListener(new ContinuationListener()
+ final AsyncContext async=request.startAsync(request,response);
+ async.setTimeout(Long.parseLong(request.getParameter("startAsync")));
+ async.addListener(new AsyncListener()
{
+
@Override
- public void onTimeout(Continuation continuation)
+ public void onTimeout(AsyncEvent event) throws IOException
{
response.addHeader("Dump","onTimeout");
try
@@ -238,22 +197,73 @@
response.setContentType("text/plain");
response.getOutputStream().println("EXPIRED");
}
- continuation.complete();
+ async.complete();
}
catch (IOException e)
{
getServletContext().log("",e);
}
}
-
+
@Override
- public void onComplete(Continuation continuation)
+ public void onStartAsync(AsyncEvent event) throws IOException
+ {
+ response.addHeader("Dump","onStartAsync");
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+ response.addHeader("Dump","onError");
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
{
response.addHeader("Dump","onComplete");
}
});
- continuation.undispatch();
+ if (request.getParameter("dispatch")!=null)
+ {
+ request.setAttribute("RESUME",Boolean.TRUE);
+
+ final long resume=Long.parseLong(request.getParameter("dispatch"));
+ _timer.schedule(new TimerTask()
+ {
+ @Override
+ public void run()
+ {
+ async.dispatch();
+ }
+ },resume);
+ }
+
+ if (request.getParameter("complete")!=null)
+ {
+ final long complete=Long.parseLong(request.getParameter("complete"));
+ _timer.schedule(new TimerTask()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ response.setContentType("text/html");
+ response.getOutputStream().println("<h1>COMPLETED</h1>");
+ async.complete();
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ },complete);
+ }
+
+
+
+ return;
}
catch(Exception e)
{
@@ -424,48 +434,20 @@
pout.write("<h1>Dump Servlet</h1>\n");
pout.write("<table width=\"95%\">");
pout.write("<tr>\n");
- pout.write("<th align=\"right\">getMethod: </th>");
- pout.write("<td>" + notag(request.getMethod())+"</td>");
- pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getContentLength: </th>");
pout.write("<td>"+Integer.toString(request.getContentLength())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getContentType: </th>");
pout.write("<td>"+notag(request.getContentType())+"</td>");
pout.write("</tr><tr>\n");
- pout.write("<th align=\"right\">getRequestURI: </th>");
- pout.write("<td>"+notag(request.getRequestURI())+"</td>");
- pout.write("</tr><tr>\n");
- pout.write("<th align=\"right\">getRequestURL: </th>");
- pout.write("<td>"+notag(request.getRequestURL().toString())+"</td>");
- pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getContextPath: </th>");
pout.write("<td>"+request.getContextPath()+"</td>");
pout.write("</tr><tr>\n");
- pout.write("<th align=\"right\">getServletPath: </th>");
- pout.write("<td>"+notag(request.getServletPath())+"</td>");
+ pout.write("<th align=\"right\">getDispatcherType: </th>");
+ pout.write("<td>"+request.getDispatcherType()+"</td>");
pout.write("</tr><tr>\n");
- pout.write("<th align=\"right\">getPathInfo: </th>");
- pout.write("<td>"+notag(request.getPathInfo())+"</td>");
- pout.write("</tr><tr>\n");
- pout.write("<th align=\"right\">getPathTranslated: </th>");
- pout.write("<td>"+notag(request.getPathTranslated())+"</td>");
- pout.write("</tr><tr>\n");
- pout.write("<th align=\"right\">getQueryString: </th>");
- pout.write("<td>"+notag(request.getQueryString())+"</td>");
- pout.write("</tr><tr>\n");
-
- pout.write("<th align=\"right\">getProtocol: </th>");
- pout.write("<td>"+request.getProtocol()+"</td>");
- pout.write("</tr><tr>\n");
- pout.write("<th align=\"right\">getScheme: </th>");
- pout.write("<td>"+request.getScheme()+"</td>");
- pout.write("</tr><tr>\n");
- pout.write("<th align=\"right\">getServerName: </th>");
- pout.write("<td>"+notag(request.getServerName())+"</td>");
- pout.write("</tr><tr>\n");
- pout.write("<th align=\"right\">getServerPort: </th>");
- pout.write("<td>"+Integer.toString(request.getServerPort())+"</td>");
+ pout.write("<th align=\"right\">getLocale: </th>");
+ pout.write("<td>"+request.getLocale()+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getLocalName: </th>");
pout.write("<td>"+request.getLocalName()+"</td>");
@@ -476,11 +458,20 @@
pout.write("<th align=\"right\">getLocalPort: </th>");
pout.write("<td>"+Integer.toString(request.getLocalPort())+"</td>");
pout.write("</tr><tr>\n");
- pout.write("<th align=\"right\">getRemoteUser: </th>");
- pout.write("<td>"+request.getRemoteUser()+"</td>");
+ pout.write("<th align=\"right\">getMethod: </th>");
+ pout.write("<td>" + notag(request.getMethod())+"</td>");
pout.write("</tr><tr>\n");
- pout.write("<th align=\"right\">getUserPrincipal: </th>");
- pout.write("<td>"+request.getUserPrincipal()+"</td>");
+ pout.write("<th align=\"right\">getPathInfo: </th>");
+ pout.write("<td>"+notag(request.getPathInfo())+"</td>");
+ pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">getPathTranslated: </th>");
+ pout.write("<td>"+notag(request.getPathTranslated())+"</td>");
+ pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">getProtocol: </th>");
+ pout.write("<td>"+request.getProtocol()+"</td>");
+ pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">getQueryString: </th>");
+ pout.write("<td>"+notag(request.getQueryString())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getRemoteAddr: </th>");
pout.write("<td>"+request.getRemoteAddr()+"</td>");
@@ -491,23 +482,48 @@
pout.write("<th align=\"right\">getRemotePort: </th>");
pout.write("<td>"+request.getRemotePort()+"</td>");
pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">getRemoteUser: </th>");
+ pout.write("<td>"+request.getRemoteUser()+"</td>");
+ pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getRequestedSessionId: </th>");
pout.write("<td>"+request.getRequestedSessionId()+"</td>");
pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">getRequestURI: </th>");
+ pout.write("<td>"+notag(request.getRequestURI())+"</td>");
+ pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">getRequestURL: </th>");
+ pout.write("<td>"+notag(request.getRequestURL().toString())+"</td>");
+ pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">getScheme: </th>");
+ pout.write("<td>"+request.getScheme()+"</td>");
+ pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">getServerName: </th>");
+ pout.write("<td>"+notag(request.getServerName())+"</td>");
+ pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">getServletPath: </th>");
+ pout.write("<td>"+notag(request.getServletPath())+"</td>");
+ pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">getServerPort: </th>");
+ pout.write("<td>"+Integer.toString(request.getServerPort())+"</td>");
+ pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">getUserPrincipal: </th>");
+ pout.write("<td>"+request.getUserPrincipal()+"</td>");
+ pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">isAsyncStarted(): </th>");
+ pout.write("<td>"+request.isAsyncStarted()+"</td>");
+ pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">isAsyncSupported(): </th>");
+ pout.write("<td>"+request.isAsyncSupported()+"</td>");
+ pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">isSecure(): </th>");
pout.write("<td>"+request.isSecure()+"</td>");
-
+ pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">isUserInRole(admin): </th>");
+ pout.write("<td>"+request.isUserInRole("admin")+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">encodeRedirectURL(/foo?bar): </th>");
pout.write("<td>"+response.encodeRedirectURL("/foo?bar")+"</td>");
- pout.write("</tr><tr>\n");
- pout.write("<th align=\"right\">isUserInRole(admin): </th>");
- pout.write("<td>"+request.isUserInRole("admin")+"</td>");
-
- pout.write("</tr><tr>\n");
- pout.write("<th align=\"right\">getLocale: </th>");
- pout.write("<td>"+request.getLocale()+"</td>");
Enumeration<Locale> locales= request.getLocales();
while (locales.hasMoreElements())
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/JavaxWebSocketChat.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/JavaxWebSocketChat.java
new file mode 100644
index 0000000..4a4090e
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/JavaxWebSocketChat.java
@@ -0,0 +1,88 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package com.acme;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+@ServerEndpoint(value="/javax.websocket/", subprotocols={"chat"})
+public class JavaxWebSocketChat
+{
+ private static final List<JavaxWebSocketChat> members = new CopyOnWriteArrayList<>();
+
+ volatile Session session;
+ volatile RemoteEndpoint.Async remote;
+
+ @OnOpen
+ public void onOpen(Session sess)
+ {
+ this.session = sess;
+ this.remote = this.session.getAsyncRemote();
+ members.add(this);
+ }
+
+ @OnMessage
+ public void onMessage(String data)
+ {
+ if(data.contains("disconnect"))
+ {
+ try
+ {
+ session.close();
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ return;
+ }
+
+ ListIterator<JavaxWebSocketChat> iter = members.listIterator();
+ while(iter.hasNext())
+ {
+ JavaxWebSocketChat member = iter.next();
+
+ // Test if member is now disconnected
+ if(!member.session.isOpen())
+ {
+ iter.remove();
+ continue;
+ }
+
+ // Async write the message back
+ member.remote.sendText(data);
+ }
+ }
+
+ @OnClose
+ public void onClose(CloseReason reason)
+ {
+ members.remove(this);
+ }
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RewriteServlet.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RewriteServlet.java
index 1f25d31..8c72239 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RewriteServlet.java
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RewriteServlet.java
@@ -50,12 +50,16 @@
out.println("<tr><th>Rewritten request URI: </th><td>" + req.getRequestURI() + "</td></tr>");
Cookie cookie = null;
- for(Cookie c: req.getCookies())
+ Cookie[] cookies = req.getCookies();
+ if (cookies != null)
{
- if (c.getName().equals("visited"))
+ for(Cookie c: cookies)
{
- cookie = c;
- break;
+ if (c.getName().equals("visited"))
+ {
+ cookie = c;
+ break;
+ }
}
}
if (cookie!=null)
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/TestListener.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/TestListener.java
index fe69751..a99e6f7 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/TestListener.java
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/TestListener.java
@@ -126,16 +126,20 @@
//// System.err.println("Overridding web.xml constraints not possible:" +!unchanged.isEmpty());
/* For servlet 3.0 */
- FilterRegistration.Dynamic registration = sce.getServletContext().addFilter("TestFilter",TestFilter.class.getName());
+ FilterRegistration registration = sce.getServletContext().addFilter("TestFilter",TestFilter.class.getName());
if (registration != null) //otherwise defined in web.xml
{
- registration.setInitParameter("remote", "false");
- registration.setAsyncSupported(true);
- registration.addMappingForUrlPatterns(
- EnumSet.of(DispatcherType.ERROR,DispatcherType.ASYNC,DispatcherType.FORWARD,DispatcherType.INCLUDE,DispatcherType.REQUEST),
- true,
- new String[]{"/*"});
+ ((FilterRegistration.Dynamic)registration).setAsyncSupported(true);
}
+ else
+ {
+ registration=sce.getServletContext().getFilterRegistration("TestFilter");
+ }
+ registration.setInitParameter("remote", "false");
+ registration.addMappingForUrlPatterns(
+ EnumSet.of(DispatcherType.ERROR,DispatcherType.ASYNC,DispatcherType.FORWARD,DispatcherType.INCLUDE,DispatcherType.REQUEST),
+ true,
+ new String[]{"/*"});
}
@Override
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/WebSocketChatServlet.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/WebSocketChatServlet.java
index 0367146..9b990f0 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/WebSocketChatServlet.java
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/WebSocketChatServlet.java
@@ -48,12 +48,12 @@
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
-import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@@ -71,9 +71,14 @@
}
@Override
- public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
+ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
- return new ChatWebSocket();
+ if (req.hasSubProtocol("chat"))
+ {
+ resp.setAcceptedSubProtocol("chat");
+ return new ChatWebSocket();
+ }
+ return null;
}
@Override
@@ -106,14 +111,7 @@
{
if (data.contains("disconnect"))
{
- try
- {
- session.close();
- }
- catch (IOException ignore)
- {
- // ignore
- }
+ session.close();
return;
}
@@ -130,7 +128,7 @@
}
// Async write the message back.
- member.remote.sendStringByFuture(data);
+ member.remote.sendString(data,null);
}
}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml
index 51f8aa3..b27e57d 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml
@@ -2,9 +2,9 @@
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_1.xsd"
metadata-complete="false"
- version="3.0">
+ version="3.1">
<display-name>Test WebApp</display-name>
@@ -272,8 +272,6 @@
<web-resource-name>relax</web-resource-name>
<url-pattern>/dump/auth/relax/*</url-pattern>
<url-pattern>/auth/relax.txt</url-pattern>
- <http-method>GET</http-method>
- <http-method>HEAD</http-method>
</web-resource-collection>
</security-constraint>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/auth.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/auth.html
index 4b67966..3b55b76 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/webapp/auth.html
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/auth.html
@@ -15,7 +15,7 @@
<li><a href="auth2">auth2/index.html</a> - Authenticated (tests FormAuthenticator.setAlwaysSaveUri()) </li>
<li><a href="dump/auth/noaccess/info">dump/auth/noaccess/*</a> - Forbidden</li>
<li><a href="dump/auth/relax/info">dump/auth/relax/*</a> - Allowed</li>
-<li><a href="dump/auth/info">dump/auth/*</a> - Authenticated any user</li>
+<li><a href="dump/auth/info">dump/auth/*</a> - Authenticated any user with any role</li>
<li><a href="dump/auth/admin/info">dump/auth/admin/*</a> - Authenticated admin role (<a href="session/?Action=Invalidate">click</a> to invalidate session)</li>
<li><a href="dump/auth/ssl/info">dump/auth/ssl/*</a> - Confidential</li>
<li><a href="rego/info">rego/info/*</a> - Authenticated admin role from programmatic security (<a href="session/?Action=Invalidate">click</a> to invalidate session)</li>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/index.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/index.html
index c81fcb9..2e7ec1a 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/webapp/index.html
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/index.html
@@ -10,29 +10,53 @@
</style>
</HEAD>
<BODY>
-<A HREF="http://jetty.eclipse.org"><IMG SRC="jetty_banner.gif"></A>
+ <A HREF="http://www.eclipse.org/jetty"><IMG SRC="jetty_banner.gif"></A>
+ <br/>
+ <b><a href="http://localhost:8080/">Demo Home</a></b>
+ <hr/>
+ <center><span style="color:red; font-variant:small-caps; font-weight:bold">Test Web Application Only - Do NOT Deploy in Production</span> </center>
+
<h1>Welcome to Jetty 9</h1>
<p>
-This is the Test webapp for the Jetty 9 HTTP Server and Servlet Container. It is
-deployed in $JETTY_HOME/webapp.demo and configured by $JETTY_HOME/start.d/900-demo.ini
+This is the Test webapp for the Jetty 9 HTTP Server and Servlet Container.
+It is configured as a jetty base directory in $JETTY_HOME/demo_base.
+</p>
+
+<h2>Jetty Tests:</h2>
+<table border=0>
+<tr valign=top><td>
<ul>
<li>Servet: <a href="hello/">Hello World</a></li>
-<li>Dump: <a href="dump/info">Request</a>, <a href="session/">Session</a>, <a href="cookie/">Cookie</a></li>
+<li>Dump Servlets: <ul>
+ <li><a href="dump/info">Request</a></li>
+ <li><a href="session/">Session</a></li>
+ <li><a href="cookie/">Cookie</a></li>
+ </ul></li>
+<li>Comet Chat Examples:
+ <ul>
+ <li><a href="chat/">Long Polling</a></li>
+ <li><a href="ws">WebSocket (Jetty API)</a></li>
+ <li><a href="javax.websocket">WebSocket (javax.websocket)</a></li>
+ </ul>
+ </li>
<li>JSP: <a href="jsp/">examples</a></li>
-<li>Comet Chat: <a href="chat/">Long Polling</a>, <a href="ws">WebSocket</a></li>
+
+</ul></td><td><ul>
<li><a href="auth.html">Authentication</a></li>
<li><a href="dispatch">Dispatcher Servlet</a></li>
<li><a href="rewrite/">Request Rewrite Servlet</a></li>
-<li>static content:
-<a href="d.txt">tiny</a>,
-<a href="da.txt">small</a>,
-<a href="dat.txt">medium</a>,
-<a href="data.txt">large</a>,
-<a href="data.txt.gz">large gziped</a></li>
+<li>Static content: <ul>
+ <li><a href="d.txt">tiny</a></li>
+ <li><a href="da.txt">small</a></li>
+ <li><a href="dat.txt">medium</a></li>
+ <li><a href="data.txt">large</a></li>
+ <li><a href="data.txt.gz">large gziped</a></li>
+ </ul></li>
</ul>
+</td></tr></table>
-<p>
-Useful links are:<ul>
+<h2>Useful links:</h2>
+<ul>
<li><a
href="http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/tests/test-webapps/test-jetty-webapp">Source
tree of this webapp</a></li>
@@ -42,8 +66,7 @@
</ul>
</p>
-<p>
-<a href="/">MAIN MENU</a>
-</p>
+<hr/>
+<center> <a href="http://www.eclipse.org/jetty"><img style="border:0" src="small_powered_by.gif"/></a></center>
</BODY>
</HTML>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/javax.websocket/index.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/javax.websocket/index.html
new file mode 100644
index 0000000..7172f47
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/javax.websocket/index.html
@@ -0,0 +1,112 @@
+
+<html><head>
+ <title>WebSocket Chat</title>
+ <script type='text/javascript'>
+
+ if (!window.WebSocket && window.MozWebSocket)
+ window.WebSocket=window.MozWebSocket;
+ if (!window.WebSocket)
+ alert("WebSocket not supported by this browser");
+
+ function $() { return document.getElementById(arguments[0]); }
+ function $F() { return document.getElementById(arguments[0]).value; }
+
+ function getKeyCode(ev) { if (window.event) return window.event.keyCode; return ev.keyCode; }
+
+ var room = {
+ join: function(name) {
+ this._username=name;
+ var location = document.location.toString().replace('http://','ws://').replace('https://','wss://');
+ this._ws=new WebSocket(location,"chat");
+ this._ws.onopen=this._onopen;
+ this._ws.onmessage=this._onmessage;
+ this._ws.onclose=this._onclose;
+ },
+
+ _onopen: function(){
+ $('join').className='hidden';
+ $('joined').className='';
+ $('phrase').focus();
+ room._send(room._username,'has joined!');
+ },
+
+ _send: function(user,message){
+ user=user.replace(':','_');
+ if (this._ws)
+ this._ws.send(user+':'+message);
+ },
+
+ chat: function(text) {
+ if (text != null && text.length>0 )
+ room._send(room._username,text);
+ },
+
+ _onmessage: function(m) {
+ if (m.data){
+ var c=m.data.indexOf(':');
+ var from=m.data.substring(0,c).replace('<','<').replace('>','>');
+ var text=m.data.substring(c+1).replace('<','<').replace('>','>');
+
+ var chat=$('chat');
+ var spanFrom = document.createElement('span');
+ spanFrom.className='from';
+ spanFrom.innerHTML=from+': ';
+ var spanText = document.createElement('span');
+ spanText.className='text';
+ spanText.innerHTML=text;
+ var lineBreak = document.createElement('br');
+ chat.appendChild(spanFrom);
+ chat.appendChild(spanText);
+ chat.appendChild(lineBreak);
+ chat.scrollTop = chat.scrollHeight - chat.clientHeight;
+ }
+ },
+
+ _onclose: function(m) {
+ this._ws=null;
+ $('join').className='';
+ $('joined').className='hidden';
+ $('username').focus();
+ $('chat').innerHTML='';
+ }
+
+ };
+
+ </script>
+ <style type='text/css'>
+ div { border: 0px solid black; }
+ div#chat { clear: both; width: 40em; height: 20ex; overflow: auto; background-color: #f0f0f0; padding: 4px; border: 1px solid black; }
+ div#input { clear: both; width: 40em; padding: 4px; background-color: #e0e0e0; border: 1px solid black; border-top: 0px }
+ input#phrase { width:30em; background-color: #e0f0f0; }
+ input#username { width:14em; background-color: #e0f0f0; }
+ div.hidden { display: none; }
+ span.from { font-weight: bold; }
+ span.alert { font-style: italic; }
+ </style>
+</head><body>
+<div id='chat'></div>
+<div id='input'>
+ <div id='join' >
+ Username: <input id='username' type='text'/><input id='joinB' class='button' type='submit' name='join' value='Join'/>
+ </div>
+ <div id='joined' class='hidden'>
+ Chat: <input id='phrase' type='text'/>
+ <input id='sendB' class='button' type='submit' name='join' value='Send'/>
+ </div>
+</div>
+<script type='text/javascript'>
+$('username').setAttribute('autocomplete','OFF');
+$('username').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.join($F('username')); return false; } return true; } ;
+$('joinB').onclick = function(event) { room.join($F('username')); return false; };
+$('phrase').setAttribute('autocomplete','OFF');
+$('phrase').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.chat($F('phrase')); $('phrase').value=''; return false; } return true; };
+$('sendB').onclick = function(event) { room.chat($F('phrase')); $('phrase').value=''; return false; };
+</script>
+
+<p>
+This is a demonstration of the Jetty's support for javax.websocket server sockets.
+</p>
+</body></html>
+
+
+
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/remote.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/remote.html
index 6b501508..d892bed 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/webapp/remote.html
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/remote.html
@@ -21,11 +21,11 @@
is displayed because you have accessed this context from a non local IP address.
</p>
<p>
-You can disable the remote address checking by editing webapps.demo/test.d/override-web.xml, uncommenting the declaration of the TestFilter, and changing the
+You can disable the remote address checking by editing demo-base/webapps/test.d/override-web.xml, uncommenting the declaration of the TestFilter, and changing the
"remote" init parameter to "true".
</p>
<p>
-This webapp is deployed in $JETTY_HOME/webapps.demo/test.war and configured by $JETTY_HOME/webapps.demo/test.xml and $JETTY_HOME/webapps.demo/test.d/override-web.xml
+This webapp is deployed in $JETTY_HOME/demo-base/webapps/test.war and configured by $JETTY_HOME/demo-base/webapps/test.xml and $JETTY_HOME/demo-base/webapps/test.d/override-web.xml
</p>
</BODY>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/rewrite/index.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/rewrite/index.html
index fd9e22f..613e13d 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/webapp/rewrite/index.html
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/rewrite/index.html
@@ -8,6 +8,6 @@
<body>
<h1>Rewrite not enabled</h1>
<p>The rewrite handler is currently not enabled. To enable this demo, start up Jetty with:</p>
-<code>java -jar start.jar OPTIONS=rewrite etc/jetty-rewrite.xml</code>
+<code>java -jar start.jar --module=rewrite</code>
</body>
</html>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/small_powered_by.gif b/tests/test-webapps/test-jetty-webapp/src/main/webapp/small_powered_by.gif
new file mode 100644
index 0000000..c5dd443
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/small_powered_by.gif
Binary files differ
diff --git a/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/ChatServletTest.java b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/ChatServletTest.java
index eb8717f..718ab06 100644
--- a/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/ChatServletTest.java
+++ b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/ChatServletTest.java
@@ -18,7 +18,9 @@
package org.eclipse.jetty;
-import com.acme.ChatServlet;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletTester;
import org.junit.After;
@@ -27,8 +29,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
+import com.acme.ChatServlet;
@RunWith(JUnit4.class)
public class ChatServletTest
diff --git a/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java
index 0865696..beafe48 100644
--- a/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java
+++ b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java
@@ -37,7 +37,6 @@
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
@@ -46,15 +45,9 @@
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.server.session.HashSessionManager;
-import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory;
-import org.eclipse.jetty.spdy.server.SPDYServerConnectionFactory;
-import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory;
-import org.eclipse.jetty.spdy.server.http.PushStrategy;
-import org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StdErrLog;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.Ignore;
@@ -100,7 +93,7 @@
httpConnector.setIdleTimeout(30000);
server.addConnector(httpConnector);
-
+ /*
// SSL configurations
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(jetty_root + "/jetty-server/src/main/config/etc/keystore");
@@ -135,7 +128,7 @@
spdyConnector.setIdleTimeout(15000);
server.addConnector(spdyConnector);
-
+ */
// Handlers
HandlerCollection handlers = new HandlerCollection();
@@ -154,7 +147,7 @@
// Setup context
HashLoginService login = new HashLoginService();
login.setName("Test Realm");
- login.setConfig(jetty_root + "/tests/test-webapps/test-jetty-webapp/src/main/config/etc/realm.properties");
+ login.setConfig(jetty_root + "/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/realm.properties");
server.addBean(login);
File log=File.createTempFile("jetty-yyyy_mm_dd", "log");
diff --git a/tests/test-webapps/test-jndi-webapp/pom.xml b/tests/test-webapps/test-jndi-webapp/pom.xml
index 7e0ff1c..6f33c25 100644
--- a/tests/test-webapps/test-jndi-webapp/pom.xml
+++ b/tests/test-webapps/test-jndi-webapp/pom.xml
@@ -4,7 +4,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-webapps-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<artifactId>test-jndi-webapp</artifactId>
<name>Jetty Tests :: WebApp :: JNDI</name>
@@ -19,7 +19,7 @@
<skip>true</skip>
</configuration>
</plugin>
- <plugin>
+ <plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
@@ -61,36 +61,21 @@
<overWrite>true</overWrite>
<outputDirectory>${project.build.directory}/lib/jndi</outputDirectory>
</artifactItem>
+ <artifactItem>
+ <groupId>org.eclipse.jetty.orbit</groupId>
+ <artifactId>javax.mail.glassfish</artifactId>
+ <version>1.4.1.v201005082020</version>
+ <type>jar</type>
+ <includes>**</includes>
+ <overWrite>true</overWrite>
+ <outputDirectory>${project.build.directory}/lib/jndi</outputDirectory>
+ </artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
- <artifactId>maven-resources-plugin</artifactId>
- <executions>
- <execution>
- <id>copy-transaction-properties</id>
- <phase>process-resources</phase>
- <goals>
- <goal>copy-resources</goal>
- </goals>
- <configuration>
- <outputDirectory>${project.build.directory}/resources</outputDirectory>
- <resources>
- <resource>
- <directory>src/main/config/resources</directory>
- <includes>
- <include>**/transactions.properties</include>
- </includes>
- <filtering>true</filtering>
- </resource>
- </resources>
- </configuration>
- </execution>
- </executions>
- </plugin>
- <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-3</version>
@@ -134,27 +119,31 @@
<artifactId>test-mock-resources</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.orbit</groupId>
+ <artifactId>javax.mail.glassfish</artifactId>
+ <version>1.4.1.v201005082020</version>
+ </dependency>
</dependencies>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.transaction</artifactId>
- <version>1.1.1.v201105210645</version>
+ <groupId>javax.transaction</groupId>
+ <artifactId>javax.transaction-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.mail.glassfish</artifactId>
- <scope>provided</scope>
<version>1.4.1.v201005082020</version>
+ <scope>provided</scope>
</dependency>
</dependencies>
</project>
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml b/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml
index f4b5d58..7946043 100644
--- a/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml
+++ b/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml
@@ -18,14 +18,14 @@
</fileSet>
<fileSet>
<directory>target</directory>
- <outputDirectory>webapps.demo</outputDirectory>
+ <outputDirectory>demo-base/webapps</outputDirectory>
<includes>
<include>test-jndi.xml</include>
</includes>
</fileSet>
<fileSet>
<directory>target/lib/jndi</directory>
- <outputDirectory>lib/jndi.demo</outputDirectory>
+ <outputDirectory>demo-base/lib/ext</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java b/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java
index 12bc848..d9d3f6b 100644
--- a/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java
+++ b/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java
@@ -22,7 +22,6 @@
package com.acme;
import java.io.IOException;
-import java.lang.reflect.Method;
import javax.mail.Session;
import javax.naming.InitialContext;
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html b/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html
index 5382876..711ad61 100644
--- a/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html
+++ b/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html
@@ -6,34 +6,30 @@
<link rel="stylesheet" type="text/css" href="stylesheet.css"/>
</HEAD>
<BODY>
-<A HREF="http://www.eclipse.org/jetty"><IMG SRC="images/jetty_banner.gif"></A>
-
-<p> </p>
- <a href="http://localhost:8080/">Home</a>
-<center>
+ <A HREF="http://www.eclipse.org/jetty"><IMG SRC="images/jetty_banner.gif"></A>
+ <br/>
+ <b><a href="http://localhost:8080/">Demo Home</a></b>
<hr/>
- <span style="color:red; font-variant:small-caps; font-weight:bold">Test Web Application Only - Do NOT Deploy in Production</span>
-</center>
+ <center><span style="color:red; font-variant:small-caps; font-weight:bold">Test Web Application Only - Do NOT Deploy in Production</span> </center>
-<P>
<h1>JNDI Test WebApp</h1>
<p>
This example shows how to configure and lookup resources such as DataSources, a JTA transaction manager and a java.mail.Session in JNDI.
</p>
-<h2>Preparation</h2>
-<p>
-To enable JNDI edit the start.ini or start.d/*.ini files to include "OPTIONS=jndi".
-</p>
-<p>
-For the jetty distribution demos, jndi is already enabled in the start.d/900-demo.ini file and also enables the
-jndi.demo option which includes some mock resources used by the test.
-</p>
+ <h2>Preparation</h2>
+ <p>To enable JNDI in a base jetty instance do:
+ <pre>
+ $ cd $JETTY_BASE
+ $ java -jar $JETTY_HOME/start.jar --module-startd-ini=jndi
+ </pre>
+ </p>
+ <p>This will create a $JETTY_BASE/start.d/jndi.ini file to enable and parameterise JNDI. If the --module-start-ini option instead, then the same initialisation will be appended to the
+ $JETTY_BASE/start.ini file instead. The jetty demo-base already has JNDI enabled in the start.ini file and some mock resources included. </p>
<p>The full source of this demonstration is available <a href="http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/tests/test-webapps/test-jndi-webapp">here</a>.</p>
-
<h2>Execution</h2>
<p>
Click <code>Test</code> to check the runtime lookup of the JNDI resources.
@@ -43,10 +39,8 @@
</form>
-<center>
<hr/>
- <a href="http://www.eclipse.org/jetty"><img style="border:0" src="images/small_powered_by.gif"/></a>
-</center>
+ <center> <a href="http://www.eclipse.org/jetty"><img style="border:0" src="images/small_powered_by.gif"/></a> </center>
</BODY>
</HTML>
diff --git a/tests/test-webapps/test-mock-resources/pom.xml b/tests/test-webapps/test-mock-resources/pom.xml
index 8acc658..937c27e 100644
--- a/tests/test-webapps/test-mock-resources/pom.xml
+++ b/tests/test-webapps/test-mock-resources/pom.xml
@@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-webapps-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<name>Jetty Tests :: WebApp :: Mock Resources</name>
<artifactId>test-mock-resources</artifactId>
@@ -20,14 +20,26 @@
</build>
<dependencies>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.transaction</artifactId>
- <version>1.1.1.v201105210645</version>
+ <groupId>javax.transaction</groupId>
+ <artifactId>javax.transaction-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
</dependency>
+<!--
+ <dependency>
+ <groupId>javax.mail</groupId>
+ <artifactId>javax.mail-api</artifactId>
+ </dependency>
+-->
+ <dependency>
+ <groupId>org.eclipse.jetty.orbit</groupId>
+ <artifactId>javax.mail.glassfish</artifactId>
+ <version>1.4.1.v201005082020</version>
+ <scope>provided</scope>
+ </dependency>
+
</dependencies>
</project>
diff --git a/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockTransport.java b/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockTransport.java
new file mode 100644
index 0000000..adb86da
--- /dev/null
+++ b/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockTransport.java
@@ -0,0 +1,55 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package com.acme;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.URLName;
+
+/**
+ * MockTransport
+ *
+ *
+ */
+public class MockTransport extends Transport
+{
+
+ /**
+ * @param session
+ * @param urlname
+ */
+ public MockTransport(Session session, URLName urlname)
+ {
+ super(session, urlname);
+ }
+
+ /**
+ * @see javax.mail.Transport#sendMessage(javax.mail.Message, javax.mail.Address[])
+ */
+ @Override
+ public void sendMessage(Message arg0, Address[] arg1) throws MessagingException
+ {
+ System.err.println ("Sending message");
+ }
+
+}
diff --git a/tests/test-webapps/test-mock-resources/src/main/resources/META-INF/javaxmail.providers b/tests/test-webapps/test-mock-resources/src/main/resources/META-INF/javaxmail.providers
new file mode 100644
index 0000000..5ab3340
--- /dev/null
+++ b/tests/test-webapps/test-mock-resources/src/main/resources/META-INF/javaxmail.providers
@@ -0,0 +1 @@
+ protocol=smtp; type=transport; class=com.acme.MockTransport; vendor=Acme Tests;
diff --git a/tests/test-webapps/test-proxy-webapp/pom.xml b/tests/test-webapps/test-proxy-webapp/pom.xml
index e8dea5a..d219048 100644
--- a/tests/test-webapps/test-proxy-webapp/pom.xml
+++ b/tests/test-webapps/test-proxy-webapp/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-webapps-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -47,10 +47,18 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+<!--
+ <dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.servlet</artifactId>
<scope>provided</scope>
</dependency>
+-->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
diff --git a/tests/test-webapps/test-servlet-spec/pom.xml b/tests/test-webapps/test-servlet-spec/pom.xml
index bd87894..a1da84a 100644
--- a/tests/test-webapps/test-servlet-spec/pom.xml
+++ b/tests/test-webapps/test-servlet-spec/pom.xml
@@ -4,7 +4,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-webapps-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<artifactId>test-servlet-spec-parent</artifactId>
<name>Jetty Tests :: Spec Test WebApp :: Parent</name>
diff --git a/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml
index 53aa0db..bb7705a 100644
--- a/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml
+++ b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml
@@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-servlet-spec-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<artifactId>test-container-initializer</artifactId>
<packaging>jar</packaging>
@@ -20,9 +20,17 @@
</build>
<dependencies>
<dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+<!--
+ <dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.servlet</artifactId>
<scope>provided</scope>
</dependency>
+-->
</dependencies>
</project>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml
index 0ce19ed..7c6c656 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml
@@ -4,7 +4,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-servlet-spec-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<name>Jetty Tests :: Webapps :: Spec Webapp</name>
<artifactId>test-spec-webapp</artifactId>
@@ -136,9 +136,9 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
- <scope>provided</scope>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml
index 2fc52ba..f16ec5f 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml
@@ -18,14 +18,14 @@
</fileSet>
<fileSet>
<directory>target</directory>
- <outputDirectory>webapps.demo</outputDirectory>
+ <outputDirectory>demo-base/webapps</outputDirectory>
<includes>
<include>test-spec.xml</include>
</includes>
</fileSet>
<fileSet>
<directory>target/lib/jndi</directory>
- <outputDirectory>lib/jndi.demo</outputDirectory>
+ <outputDirectory>demo-base/lib/ext</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java
index b90f356..6d06c15 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java
@@ -197,18 +197,19 @@
out.println("<pre>");
out.println("initParams={@WebInitParam(name=\"fromAnnotation\", value=\"xyz\")}");
out.println("</pre>");
- out.println("<br/><b>Result: "+("xyz".equals(config.getInitParameter("fromAnnotation"))? "<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span>");
+ out.println("<p><b>Result: "+("xyz".equals(config.getInitParameter("fromAnnotation"))? "<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span></p>");
out.println("<h2>Init Params from web-fragment</h2>");
out.println("<pre>");
out.println("extra1=123, extra2=345");
out.println("</pre>");
boolean fragInitParamResult = "123".equals(config.getInitParameter("extra1")) && "345".equals(config.getInitParameter("extra2"));
- out.println("<br/><b>Result: "+(fragInitParamResult? "<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span>");
+ out.println("<p><b>Result: "+(fragInitParamResult? "<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span></p>");
__HandlesTypes = Arrays.asList( "javax.servlet.GenericServlet",
"javax.servlet.http.HttpServlet",
+ "com.acme.AsyncListenerServlet",
"com.acme.AnnotationTest",
"com.acme.RoleAnnotationTest",
"com.acme.MultiPartTest",
@@ -220,7 +221,7 @@
out.println("<pre>");
out.println("@HandlesTypes({javax.servlet.Servlet.class, Foo.class})");
out.println("</pre>");
- out.print("<br/><b>Result: ");
+ out.print("<p><b>Result: ");
List<Class> classes = (List<Class>)config.getServletContext().getAttribute("com.acme.Foo");
List<String> classNames = new ArrayList<String>();
if (classes != null)
@@ -240,19 +241,28 @@
}
else
out.print("<br/><span class=\"fail\">FAIL</span> (No such attribute com.acme.Foo)");
- out.println("</b>");
+ out.println("</b></p>");
out.println("<h2>Complete Servlet Registration</h2>");
Boolean complete = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.complete");
- out.println("<br/><b>Result: "+(complete.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b>");
+ out.println("<p><b>Result: "+(complete.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b></p>");
out.println("<h2>ServletContextListener Programmatic Registration from ServletContainerInitializer</h2>");
Boolean programmaticListener = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.listenerTest");
- out.println("<br/><b>Result: "+(programmaticListener.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b>");
+ out.println("<p><b>Result: "+(programmaticListener.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b></p>");
out.println("<h2>ServletContextListener Programmatic Registration Prevented from ServletContextListener</h2>");
Boolean programmaticListenerPrevention = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.listenerRegoTest");
- out.println("<br/><b>Result: "+(programmaticListenerPrevention.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b>");
+ out.println("<p><b>Result: "+(programmaticListenerPrevention.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b></p>");
+
+ out.println("<h2>ServletContextListener Registration Prevented from ServletContextListener</h2>");
+ Boolean webListenerPrevention = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.sclFromSclRegoTest");
+ out.println("<p><b>Result: "+(webListenerPrevention.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b></p>");
+
+ out.println("<h2>Invalid Type for Listener Detection</h2>");
+ Boolean badListener = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.invalidListenerRegoTest");
+ out.println("<p><b>Result: "+(badListener.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b></p>");
+
out.println("<h2>@PostConstruct Callback</h2>");
out.println("<pre>");
@@ -260,7 +270,7 @@
out.println("private void myPostConstructMethod ()");
out.println("{}");
out.println("</pre>");
- out.println("<br/><b>Result: "+postConstructResult+"</b>");
+ out.println("<p><b>Result: "+postConstructResult+"</b></p>");
out.println("<h2>@Resource Injection for DataSource</h2>");
@@ -271,8 +281,8 @@
out.println("myDS=ds;");
out.println("}");
out.println("</pre>");
- out.println("<br/><b>Result: "+dsResult+"</b>");
- out.println("<br/><b>JNDI Lookup Result: "+dsLookupResult+"</b>");
+ out.println("<p><b>Result: "+dsResult+"</b>");
+ out.println("<br/><b>JNDI Lookup Result: "+dsLookupResult+"</b></p>");
out.println("<h2>@Resource Injection for env-entry </h2>");
@@ -282,19 +292,20 @@
out.println("@Resource(name=\"minAmount\")");
out.println("private Double minAmount;");
out.println("</pre>");
- out.println("<br/><b>Result: "+envResult+": "+(maxAmount.compareTo(new Double(55))==0?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
+ out.println("<p><b>Result: "+envResult+": "+(maxAmount.compareTo(new Double(55))==0?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
out.println("<br/><b>JNDI Lookup Result: "+envLookupResult+"</b>");
out.println("<br/><b>Result: "+envResult2+": "+(minAmount.compareTo(new Double("0.99"))==0?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
out.println("<br/><b>JNDI Lookup Result: "+envLookupResult2+"</b>");
out.println("<br/><b>Result: "+envResult3+": "+(avgAmount.compareTo(new Double("1.25"))==0?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
- out.println("<br/><b>JNDI Lookup Result: "+envLookupResult3+"</b>");
+ out.println("<br/><b>JNDI Lookup Result: "+envLookupResult3+"</b></p>");
+
out.println("<h2>@Resource Injection for UserTransaction </h2>");
out.println("<pre>");
out.println("@Resource(mappedName=\"UserTransaction\")");
out.println("private UserTransaction myUserTransaction;");
out.println("</pre>");
- out.println("<br/><b>Result: "+txResult+"</b>");
- out.println("<br/><b>JNDI Lookup Result: "+txLookupResult+"</b>");
+ out.println("<p><b>Result: "+txResult+"</b>");
+ out.println("<br/><b>JNDI Lookup Result: "+txLookupResult+"</b></p>");
out.println("</body>");
out.println("</html>");
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AsyncListenerServlet.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AsyncListenerServlet.java
new file mode 100644
index 0000000..b828610
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AsyncListenerServlet.java
@@ -0,0 +1,127 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package com.acme;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet(urlPatterns="/asy/*", asyncSupported=true)
+public class AsyncListenerServlet extends HttpServlet
+{
+ public static class MyAsyncListener implements AsyncListener
+ {
+ @Resource(mappedName="maxAmount")
+ private Double maxAmount;
+
+ boolean postConstructCalled = false;
+ boolean resourceInjected = false;
+
+ @PostConstruct
+ public void postConstruct()
+ {
+ postConstructCalled = true;
+ resourceInjected = (maxAmount != null);
+ }
+
+ public boolean isPostConstructCalled()
+ {
+ return postConstructCalled;
+ }
+
+ public boolean isResourceInjected()
+ {
+ return resourceInjected;
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException
+ {
+ // TODO Auto-generated method stub
+
+ }
+ }
+
+
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException
+ {
+ AsyncContext asyncContext = req.startAsync();
+ MyAsyncListener listener = asyncContext.createListener(MyAsyncListener.class);
+
+ PrintWriter writer = resp.getWriter();
+ writer.println( "<html>");
+ writer.println("<HEAD><link rel=\"stylesheet\" type=\"text/css\" href=\"../stylesheet.css\"/></HEAD>");
+ writer.println( "<body>");
+ writer.println("<h1>AsyncListener</h2>");
+ writer.println("<pre>");
+ writer.println("<h2>@PostConstruct Callback</h2>");
+ writer.println("<pre>");
+ writer.println("@PostConstruct");
+ writer.println("private void postConstruct ()");
+ writer.println("{}");
+ writer.println("</pre>");
+ writer.println("<br/><b>Result: "+(listener.isPostConstructCalled()?"<span class=\"pass\">PASS</span>":"<span class=\"fail\">FAIL</span>")+"</b>");
+
+ writer.println("<h2>@Resource Injection for env-entry </h2>");
+ writer.println("<pre>");
+ writer.println("@Resource(mappedName=\"maxAmount\")");
+ writer.println("private Double maxAmount;");
+ writer.println("</pre>");
+ writer.println("<br/><b>Result: "+(listener.isResourceInjected()?" <span class=\"pass\">PASS</span>":" <span class=\"FAIL\">FAIL</span>")+"</b>");
+
+ writer.println( "</body>");
+ writer.println( "</html>");
+ writer.flush();
+ writer.close();
+
+ asyncContext.complete();
+ }
+}
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/TestListener.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/TestListener.java
index 7d7592d..8892fcb 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/TestListener.java
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/TestListener.java
@@ -17,6 +17,8 @@
//
package com.acme;
+import java.util.EventListener;
+
import javax.annotation.Resource;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
@@ -38,6 +40,26 @@
@WebListener
public class TestListener implements HttpSessionListener, HttpSessionAttributeListener, HttpSessionActivationListener, ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener
{
+ public class NaughtyServletContextListener implements ServletContextListener
+ {
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce)
+ {
+ throw new IllegalStateException("Should not call NaughtServletContextListener.contextInitialized");
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce)
+ {
+ throw new IllegalStateException("Should not call NaughtServletContextListener.contextDestroyed");
+ }
+ }
+
+ public class InvalidListener implements EventListener
+ {
+
+ }
@Resource(mappedName="maxAmount")
private Double maxAmount;
@@ -69,7 +91,36 @@
public void contextInitialized(ServletContextEvent sce)
{
- //System.err.println("contextInitialized, maxAmount injected as "+maxAmount);
+ //Can't add a ServletContextListener from a ServletContextListener even if it is declared in web.xml
+ try
+ {
+ sce.getServletContext().addListener(new NaughtyServletContextListener());
+ sce.getServletContext().setAttribute("com.acme.AnnotationTest.sclFromSclRegoTest", Boolean.FALSE);
+ }
+ catch (IllegalArgumentException e)
+ {
+ sce.getServletContext().setAttribute("com.acme.AnnotationTest.sclFromSclRegoTest", Boolean.TRUE);
+ }
+ catch (Exception e)
+ {
+ sce.getServletContext().setAttribute("com.acme.AnnotationTest.sclFromSclRegoTest", Boolean.FALSE);
+ }
+
+
+ //Can't add an EventListener not part of the specified list for addListener()
+ try
+ {
+ sce.getServletContext().addListener(new InvalidListener());
+ sce.getServletContext().setAttribute("com.acme.AnnotationTest.invalidListenerRegoTest", Boolean.FALSE);
+ }
+ catch (IllegalArgumentException e)
+ {
+ sce.getServletContext().setAttribute("com.acme.AnnotationTest.invalidListenerRegoTest", Boolean.TRUE);
+ }
+ catch (Exception e)
+ {
+ sce.getServletContext().setAttribute("com.acme.AnnotationTest.invalidListenerRegoTest", Boolean.FALSE);
+ }
}
public void contextDestroyed(ServletContextEvent sce)
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml
index aa5c782..50b5981 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml
@@ -46,7 +46,7 @@
<Set name="loginService">
<New class="org.eclipse.jetty.security.HashLoginService">
<Set name="name">Test Realm</Set>
- <Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/realm.properties</Set>
+ <Set name="config"><SystemProperty name="jetty.base" default="."/>/etc/realm.properties</Set>
</New>
</Set>
</Get>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html
index 854082f..d3ec487 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html
@@ -6,15 +6,12 @@
<link rel="stylesheet" type="text/css" href="stylesheet.css"/>
</HEAD>
<BODY >
-<A HREF="http://www.eclipse.org/jetty"><IMG SRC="images/jetty_banner.gif"></A>
-
-<p> </p>
- <a href="http://localhost:8080/">Home</a>
-<center>
+ <A HREF="http://www.eclipse.org/jetty"><IMG SRC="images/jetty_banner.gif"></A>
+ <br/>
+ <b><a href="http://localhost:8080/">Demo Home</a></b>
<hr/>
- <span style="color:red; font-variant:small-caps; font-weight:bold">Test Web Application Only - Do NOT Deploy in Production</span>
-</center>
-<P>
+ <center><span style="color:red; font-variant:small-caps; font-weight:bold">Test Web Application Only - Do NOT Deploy in Production</span> </center>
+
<h1>Servlet 3.1 Test WebApp</h1>
<p>
@@ -54,6 +51,12 @@
<input TYPE="submit" VALUE="Test Upload">
</form>
+<h3>AsyncListener Resource Injection</h3>
+<p>Click the following link to test that javax.servlet.AsyncListeners are injectable</p>
+<form action="asy/xx" method="post">
+ <button type="submit">Test AsyncListener</button>
+</form>
+
<center>
<hr/>
<a href="http://www.eclipse.org/jetty"><img style="border:0" src="images/small_powered_by.gif"/></a>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css
index 4ecc2cb..f90463d 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css
@@ -1,6 +1,6 @@
body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
-h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em;}
+h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em; margin-top:2em;}
h3 {font-size:100%; letter-spacing: 0.1em;}
span.pass { color: green; }
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css~ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css~
new file mode 100644
index 0000000..def6847
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css~
@@ -0,0 +1,7 @@
+body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
+h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
+h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em; margin-top:2em}
+h3 {font-size:100%; letter-spacing: 0.1em;}
+
+span.pass { color: green; }
+span.fail { color:red; }
diff --git a/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml
index 7802a4c..987cd9d 100644
--- a/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml
+++ b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml
@@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-servlet-spec-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<name>Jetty Tests :: WebApp :: Servlet Spec :: Fragment Jar</name>
<groupId>org.eclipse.jetty.tests</groupId>
@@ -20,9 +20,16 @@
</plugins>
</build>
<dependencies>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </dependency>
+
+<!--
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.servlet</artifactId>
</dependency>
+-->
</dependencies>
</project>
diff --git a/tests/test-webapps/test-webapp-rfc2616/pom.xml b/tests/test-webapps/test-webapp-rfc2616/pom.xml
index 99c6e78..ed7044b 100644
--- a/tests/test-webapps/test-webapp-rfc2616/pom.xml
+++ b/tests/test-webapps/test-webapp-rfc2616/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-webapps-parent</artifactId>
- <version>9.0.8-SNAPSHOT</version>
+ <version>9.1.1-SNAPSHOT</version>
</parent>
<artifactId>test-webapp-rfc2616</artifactId>
<name>Jetty Tests :: WebApp :: RFC2616</name>
@@ -62,8 +62,8 @@
<version>${project.version}</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.orbit</groupId>
- <artifactId>javax.servlet</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>