diff options
1263 files changed, 55629 insertions, 14822 deletions
diff --git a/README.txt b/README.TXT index 12c781192c..12c781192c 100644 --- a/README.txt +++ b/README.TXT diff --git a/VERSION.txt b/VERSION.txt index df9a323b12..666447222f 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,3 +1,81 @@ +jetty-9.1.0.M0 - 13 August 2013 + + 393473 Add support for JSR-356 (javax.websocket) draft + + 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 + + 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 + + 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 + + 413531 Introduce pluggable transports for HttpClient. + + 413901 isAsyncStarted remains true while original request is dispatched + + 414167 WebSocket handshake upgrade from FireFox fails due to keep-alive + + 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. + + 414913 WebSocket / Performance - reduce ByteBuffer allocation/copying during + generation/writing + + 414923 CompactPathRule needs to also compact the uri + jetty-9.0.5.v20130815 - 15 August 2013 + 414898 Only upgrade v0 to v1 cookies on dquote , ; backslash space and tab in the value diff --git a/aggregates/jetty-all/pom.xml b/aggregates/jetty-all/pom.xml index a12034f8db..46b77d0cec 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.6-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.5.v20130815</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.6-SNAPSHOT</version> + <version>${project.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-deploy</artifactId> - <version>9.0.6-SNAPSHOT</version> + <version>${project.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.eclipse.jetty.websocket</groupId> - <artifactId>websocket-server</artifactId> - <version>9.0.6-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.6-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.6-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.6-SNAPSHOT</version> + <version>${project.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-plus</artifactId> - <version>9.0.6-SNAPSHOT</version> + <version>${project.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-annotations</artifactId> - <version>9.0.6-SNAPSHOT</version> + <version>${project.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-util</artifactId> - <version>9.0.6-SNAPSHOT</version> + <version>${project.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-jaspi</artifactId> - <version>9.0.6-SNAPSHOT</version> + <version>${project.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-jndi</artifactId> - <version>9.0.6-SNAPSHOT</version> + <version>${project.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-rewrite</artifactId> - <version>9.0.6-SNAPSHOT</version> + <version>${project.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlets</artifactId> - <version>9.0.6-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 0000000000..26138eee16 --- /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 527b4aa784..aeb3267ce6 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.6-SNAPSHOT</version> + <version>9.1.0-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 98f9974bbd..7269c24777 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 @@ -25,7 +25,6 @@ import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; - import javax.servlet.AsyncContext; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -163,7 +162,7 @@ public class AsyncRestServlet extends AbstractRestServlet 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 805130697d..5f48d5999f 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.6-SNAPSHOT</version> + <version>9.1.0-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 735b799eac..a4c16ff91a 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.6-SNAPSHOT</version> + <version>9.1.0-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 42cf44411f..f1f5bee89f 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.6-SNAPSHOT</version> + <version>9.1.0-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> <modelVersion>4.0.0</modelVersion> @@ -68,6 +68,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/org/eclipse/jetty/embedded/SecuredHelloHandler.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java index 043a6087b9..aa1a4d6daa 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 @@ -54,7 +54,6 @@ public class SecuredHelloHandler security.setConstraintMappings(Collections.singletonList(mapping)); security.setAuthenticator(new BasicAuthenticator()); security.setLoginService(loginService); - security.setStrict(false); HelloHandler hh = new HelloHandler(); diff --git a/examples/pom.xml b/examples/pom.xml index a7ef50aa49..4e4535c5c2 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.6-SNAPSHOT</version> + <version>9.1.0-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 4e60b134df..43cc4584bf 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.6-SNAPSHOT</version> + <version>9.1.0-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,8 +98,8 @@ <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> 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 0000000000..65e4654127 --- /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/AnnotationConfiguration.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java index eaa7c009cb..c26901f73e 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 @@ -141,6 +141,11 @@ public class AnnotationConfiguration extends AbstractConfiguration } + public void addDiscoverableAnnotationHandler(DiscoverableAnnotationHandler handler) + { + _discoverableAnnotationHandlers.add(handler); + } + @Override public void deconfigure(WebAppContext context) throws Exception { @@ -211,7 +216,7 @@ public class AnnotationConfiguration extends AbstractConfiguration @Override public void postConfigure(WebAppContext context) throws Exception { - MultiMap map = (MultiMap)context.getAttribute(CLASS_INHERITANCE_MAP); + MultiMap<String> map = (MultiMap<String>)context.getAttribute(CLASS_INHERITANCE_MAP); if (map != null) map.clear(); @@ -287,7 +292,7 @@ public class AnnotationConfiguration extends AbstractConfiguration //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<>(); context.setAttribute(CLASS_INHERITANCE_MAP, map); _classInheritanceHandler = new ClassInheritanceHandler(map); } @@ -328,9 +333,9 @@ public class AnnotationConfiguration extends AbstractConfiguration /** * 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 orderedJars + * @param context * @param service - * @return + * @return true if excluded */ public boolean isFromExcludedJar (WebAppContext context, ServletContainerInitializer service) throws Exception @@ -369,7 +374,7 @@ public class AnnotationConfiguration extends AbstractConfiguration /** * @param context - * @return + * @return list of non-excluded {@link ServletContainerInitializer}s * @throws Exception */ public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context) @@ -525,7 +530,7 @@ public class AnnotationConfiguration extends AbstractConfiguration * * @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) 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 d0fd9546b0..b50fb45dd3 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 @@ public class AnnotationDecorator implements Decorator _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 @@ public class AnnotationDecorator implements Decorator { _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 4607f139fc..057d0ea22a 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 @@ -237,8 +237,6 @@ public class AnnotationParser /** * Get the name of the annotation processed by this handler. Can be null - * - * @return */ public String getAnnotationName(); } @@ -513,7 +511,7 @@ public class AnnotationParser * Register a handler that will be called back when the named annotation is * encountered on a class. * - * @deprecated see registerHandler(Handler) + * @deprecated see {@link #registerHandler(Handler)} * @param annotationName * @param handler */ @@ -525,9 +523,8 @@ public class AnnotationParser /** - * @deprecated + * @deprecated no replacement provided * @param annotationName - * @return */ @Deprecated public List<DiscoverableAnnotationHandler> getAnnotationHandlers(String annotationName) @@ -547,8 +544,7 @@ public class AnnotationParser } /** - * @deprecated - * @return + * @deprecated no replacement available */ @Deprecated public List<DiscoverableAnnotationHandler> getAnnotationHandlers() @@ -563,7 +559,7 @@ public class AnnotationParser } /** - * @deprecated see registerHandler(Handler) + * @deprecated see {@link #registerHandler(Handler)} * @param handler */ @Deprecated @@ -605,7 +601,6 @@ public class AnnotationParser * Remove a particular handler * * @param h - * @return */ public boolean deregisterHandler(Handler h) { @@ -625,7 +620,6 @@ public class AnnotationParser /** * True if the class has already been processed, false otherwise * @param className - * @return */ public boolean isParsed (String className) { @@ -652,7 +646,7 @@ public class AnnotationParser 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); @@ -683,7 +677,7 @@ public class AnnotationParser 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); @@ -732,7 +726,7 @@ public class AnnotationParser if ((resolver == null) || (!resolver.isExcluded(s) && (!isParsed(s) || resolver.shouldOverride(s)))) { s = s.replace('.', '/')+".class"; - URL resource = Loader.getResource(this.getClass(), s, false); + URL resource = Loader.getResource(this.getClass(), s); if (resource!= null) { Resource r = Resource.newResource(resource); 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 ca6c61be12..50892a851a 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 @@ -33,16 +33,15 @@ import org.eclipse.jetty.util.log.Logger; public class ClassInheritanceHandler implements ClassHandler { private static final Logger LOG = Log.getLogger(ClassInheritanceHandler.class); - - MultiMap _inheritanceMap; + MultiMap<String> _inheritanceMap; public ClassInheritanceHandler() { - _inheritanceMap = new MultiMap(); + _inheritanceMap = new MultiMap<>(); } - public ClassInheritanceHandler(MultiMap map) + public ClassInheritanceHandler(MultiMap<String> map) { _inheritanceMap = map; } @@ -65,12 +64,12 @@ public class ClassInheritanceHandler implements ClassHandler } } - public List getClassNamesExtendingOrImplementing (String className) + public List<String> getClassNamesExtendingOrImplementing (String className) { return _inheritanceMap.getValues(className); } - public MultiMap getMap () + public MultiMap<String> getMap () { return _inheritanceMap; } 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 2b962a5baf..5d5daea089 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 class PostConstructAnnotationHandler extends AbstractIntrospectableAnnota 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 545054255f..a536885ebd 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 class PreDestroyAnnotationHandler extends AbstractIntrospectableAnnotatio 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 875de2db2f..ce8d9a7190 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 class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH */ 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/ServletSecurityAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletSecurityAnnotationHandler.java index 41c654c1e7..00ac4b0f23 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 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno 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 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno * @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 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno /** * Get the ServletMappings for the servlet's class. * @param className - * @return */ protected List<ServletMapping> getServletMappings(String className) { @@ -163,7 +158,6 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno * 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 37a22a3081..08527c3550 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 @@ public class Util 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/WebFilterAnnotation.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotation.java index 578af77dd3..6d11ee3947 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotation.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotation.java @@ -61,7 +61,7 @@ public class WebFilterAnnotation extends DiscoveredAnnotation } /** - * @see org.eclipse.jetty.annotations.ClassAnnotation#apply() + * @see DiscoveredAnnotation#apply() */ public void apply() { 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 b82fd1104a..ad3e02ccbd 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.ServletContextListener; 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; @@ -57,7 +58,7 @@ public class WebListenerAnnotation extends DiscoveredAnnotation } /** - * @see org.eclipse.jetty.annotations.ClassAnnotation#apply() + * @see DiscoveredAnnotation#apply() */ public void apply() { @@ -78,7 +79,8 @@ public class WebListenerAnnotation extends DiscoveredAnnotation 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/WebServletAnnotation.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java index f76eadf22a..0ba7ca83e3 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java @@ -57,7 +57,7 @@ public class WebServletAnnotation extends DiscoveredAnnotation } /** - * @see org.eclipse.jetty.annotations.ClassAnnotation#apply() + * @see DiscoveredAnnotation#apply() */ public void apply() { 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 5fb97a30de..6e56a0def1 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 @@ -40,6 +40,7 @@ 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.Rule; import org.junit.Test; public class TestAnnotationParser @@ -132,7 +133,7 @@ public class TestAnnotationParser parser.registerHandler(new SampleAnnotationHandler()); - long start = System.currentTimeMillis(); + //long start = System.currentTimeMillis(); parser.parse(classNames,new ClassNameResolver() { public boolean isExcluded(String name) @@ -146,7 +147,7 @@ public class TestAnnotationParser } }); - long end = System.currentTimeMillis(); + //long end = System.currentTimeMillis(); //System.err.println("Time to parse class: " + ((end - start))); } 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 4ef220f740..d0c07aa727 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 @@ public class TestSecurityAnnotationConversions 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-ant/pom.xml b/jetty-ant/pom.xml index e72fa624e8..9f10f176a5 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.6-SNAPSHOT</version> + <version>9.1.0-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 e00479020f..01e2f9cbc9 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 @@ -432,9 +432,9 @@ public class AntWebAppContext extends WebAppContext /** - * 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 +669,6 @@ public class AntWebAppContext extends WebAppContext } } - /** - * @see WebApplicationProxy#stop() - */ public void doStop() { try 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 e6267d65c4..626ea4dfed 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 @@ public class JettyRunTask extends Task this.contextHandlers = handlers; } - /** - * @return - */ public File getTempDirectory() { return tempDirectory; @@ -152,9 +149,6 @@ public class JettyRunTask extends Task this.tempDirectory = tempDirectory; } - /** - * @return - */ public File getJettyXml() { return jettyXml; @@ -191,9 +185,6 @@ public class JettyRunTask extends Task } } - /** - * @return - */ public String getRequestLog() { if (requestLog != null) @@ -301,9 +292,6 @@ public class JettyRunTask extends Task 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 c4b906e433..432778e714 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 @@ public interface ServerProxy /** * 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-client/pom.xml b/jetty-client/pom.xml index 5b5c3491b4..1f7663d82a 100644 --- a/jetty-client/pom.xml +++ b/jetty-client/pom.xml @@ -1,138 +1,142 @@ -<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.6-SNAPSHOT</version> - </parent> +<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> + </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> + + <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> </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 0000000000..6788eacf79 --- /dev/null +++ b/jetty-client/src/main/config/modules/client.mod @@ -0,0 +1,7 @@ +# +# Client Feature +# + +[lib] +# Client jars +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 0000000000..46cf76aa77 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java @@ -0,0 +1,233 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// 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.ConnectException; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import javax.net.ssl.SSLEngine; + +import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.io.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.SslConnection; +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; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +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 + public void connect(HttpDestination destination, SocketAddress address, Promise<org.eclipse.jetty.client.api.Connection> promise) + { + SocketChannel channel = null; + try + { + channel = SocketChannel.open(); + HttpClient client = destination.getHttpClient(); + SocketAddress bindAddress = client.getBindAddress(); + if (bindAddress != null) + channel.bind(bindAddress); + configure(client, channel); + channel.configureBlocking(false); + channel.connect(address); + + ConnectionCallback callback = new ConnectionCallback(destination, promise); + selectorManager.connect(channel, callback); + } + // 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 + { + promise.failed(x); + } + } + } + + protected void configure(HttpClient client, SocketChannel channel) throws SocketException + { + channel.socket().setTcpNoDelay(client.isTCPNoDelay()); + } + + protected SelectorManager newSelectorManager(HttpClient client) + { + return new ClientSelectorManager(client, selectors); + } + + protected SslConnection createSslConnection(EndPoint endPoint, HttpDestination destination) + { + HttpClient httpClient = destination.getHttpClient(); + SslContextFactory sslContextFactory = httpClient.getSslContextFactory(); + SSLEngine engine = sslContextFactory.newSSLEngine(destination.getHost(), destination.getPort()); + engine.setUseClientMode(true); + + SslConnection sslConnection = newSslConnection(httpClient, endPoint, engine); + sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed()); + endPoint.setConnection(sslConnection); + EndPoint appEndPoint = sslConnection.getDecryptedEndPoint(); + Connection connection = newConnection(appEndPoint, destination); + appEndPoint.setConnection(connection); + + return sslConnection; + } + + protected SslConnection newSslConnection(HttpClient httpClient, EndPoint endPoint, SSLEngine engine) + { + return new SslConnection(httpClient.getByteBufferPool(), httpClient.getExecutor(), endPoint, engine); + } + + protected abstract Connection newConnection(EndPoint endPoint, HttpDestination destination); + + protected org.eclipse.jetty.client.api.Connection tunnel(EndPoint endPoint, HttpDestination destination, org.eclipse.jetty.client.api.Connection connection) + { + SslConnection sslConnection = createSslConnection(endPoint, destination); + Connection result = sslConnection.getDecryptedEndPoint().getConnection(); + selectorManager.connectionClosed((Connection)connection); + selectorManager.connectionOpened(sslConnection); + LOG.debug("Tunnelled {} over {}", connection, result); + return (org.eclipse.jetty.client.api.Connection)result; + } + + 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 Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException + { + ConnectionCallback callback = (ConnectionCallback)attachment; + HttpDestination destination = callback.destination; + + SslContextFactory sslContextFactory = client.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(endPoint, destination); + callback.succeeded((org.eclipse.jetty.client.api.Connection)sslConnection.getDecryptedEndPoint().getConnection()); + return sslConnection; + } + } + else + { + Connection connection = AbstractHttpClientTransport.this.newConnection(endPoint, destination); + callback.succeeded((org.eclipse.jetty.client.api.Connection)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<org.eclipse.jetty.client.api.Connection> + { + private final HttpDestination destination; + private final Promise<org.eclipse.jetty.client.api.Connection> promise; + + private ConnectionCallback(HttpDestination destination, Promise<org.eclipse.jetty.client.api.Connection> promise) + { + this.destination = destination; + this.promise = promise; + } + + @Override + public void succeeded(org.eclipse.jetty.client.api.Connection result) + { + promise.succeeded(result); + } + + @Override + public void failed(Throwable x) + { + promise.failed(x); + } + } +} 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 0000000000..a87c6ed4b9 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.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.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(); + } +} 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 3fe402825e..ef1fc0f6b4 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,14 +19,11 @@ 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; @@ -40,8 +37,8 @@ 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; @@ -50,16 +47,13 @@ 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; @@ -116,6 +110,7 @@ public class HttpClient extends ContainerLifeCycle private final List<Request.Listener> requestListeners = new ArrayList<>(); private final AuthenticationStore authenticationStore = new HttpAuthenticationStore(); private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet(); + private final HttpClientTransport transport; private final SslContextFactory sslContextFactory; private volatile CookieManager cookieManager; private volatile CookieStore cookieStore; @@ -123,13 +118,12 @@ public class HttpClient extends ContainerLifeCycle 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,6 +131,7 @@ public class HttpClient extends ContainerLifeCycle private volatile long idleTimeout; private volatile boolean tcpNoDelay = true; private volatile boolean dispatchIO = true; + private volatile boolean strictEventOrdering = false; private volatile ProxyConfiguration proxyConfig; private volatile HttpField encodingField; @@ -160,9 +155,20 @@ public class HttpClient extends ContainerLifeCycle */ 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 @@ public class HttpClient extends ContainerLifeCycle 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 @@ public class HttpClient extends ContainerLifeCycle super.doStart(); } - protected SelectorManager newSelectorManager() - { - return new ClientSelectorManager(getExecutor(), getScheduler()); - } - private CookieManager newCookieManager() { return new CookieManager(getCookieStore(), CookiePolicy.ACCEPT_ALL); @@ -246,10 +246,10 @@ public class HttpClient extends ContainerLifeCycle } /** - * 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() { @@ -387,9 +387,12 @@ public class HttpClient extends ContainerLifeCycle 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,7 +417,7 @@ public class HttpClient extends ContainerLifeCycle return newRequest; } - protected String address(String scheme, String host, int port) + public String address(String scheme, String host, int port) { StringBuilder result = new StringBuilder(); URIUtil.appendSchemeHostPort(result, scheme, host, port); @@ -447,7 +450,7 @@ public class HttpClient extends ContainerLifeCycle HttpDestination destination = destinations.get(address); if (destination == null) { - destination = new HttpDestination(this, scheme, host, port); + destination = transport.newHttpDestination(scheme, host, port); if (isRunning()) { HttpDestination existing = destinations.putIfAbsent(address, destination); @@ -489,28 +492,7 @@ public class HttpClient extends ContainerLifeCycle @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); - } + transport.connect(destination, socketAddress, promise); } @Override @@ -521,23 +503,6 @@ public class HttpClient extends ContainerLifeCycle }); } - 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); @@ -729,11 +694,6 @@ public class HttpClient extends ContainerLifeCycle 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 +839,41 @@ public class HttpClient extends ContainerLifeCycle } /** + * @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() @@ -916,16 +911,6 @@ public class HttpClient extends ContainerLifeCycle 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 +918,6 @@ public class HttpClient extends ContainerLifeCycle 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 0000000000..de943a61db --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClientTransport.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.client; + +import java.net.SocketAddress; + +import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.util.Promise; + +/** + * {@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 +{ + /** + * 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 scheme the destination scheme + * @param host the destination host + * @param port the destination port + * @return a new, transport-specific, {@link HttpDestination} object + */ + public HttpDestination newHttpDestination(String scheme, String host, int port); + + /** + * Establishes a physical connection to the given {@code address}. + * + * @param destination the destination + * @param address the address to connect to + * @param promise the promise to notify when the connection succeeds or fails + */ + public void connect(HttpDestination destination, SocketAddress address, Promise<Connection> promise); + + /** + * Establishes an encrypted tunnel over the given {@code connection} + * + * @param connection the connection to tunnel + * @return the tunnelled connection + */ + public Connection tunnel(Connection connection); +} 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 dfd11c25b0..e09079b1ca 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 @@ package org.eclipse.jetty.client; 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,134 +34,56 @@ 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.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) - { - 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); + protected abstract void send(HttpExchange exchange); - sender.send(exchange); - } - - private void normalizeRequest(Request request) + protected 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()); - // Make sure the path is there String path = request.getPath(); if (path.trim().length() == 0) @@ -182,7 +101,7 @@ public class HttpConnection extends AbstractConnection implements Connection if (version.getVersion() > 10) { if (!headers.containsKey(HttpHeader.HOST.asString())) - headers.put(getDestination().getHostField()); + headers.put(getHttpDestination().getHostField()); } // Add content headers @@ -202,7 +121,7 @@ public class HttpConnection extends AbstractConnection implements Connection } // 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) { @@ -218,140 +137,8 @@ public class HttpConnection extends AbstractConnection implements Connection // Authorization URI authenticationURI = destination.isProxied() ? destination.getProxyURI() : request.getURI(); - Authentication.Result authnResult = client.getAuthenticationStore().findAuthenticationResult(authenticationURI); + Authentication.Result authnResult = getHttpClient().getAuthenticationStore().findAuthenticationResult(authenticationURI); if (authnResult != null) authnResult.apply(request); - - if (!headers.containsKey(HttpHeader.ACCEPT_ENCODING.asString())) - { - HttpField acceptEncodingField = client.getAcceptEncodingField(); - if (acceptEncodingField != null) - headers.put(acceptEncodingField); - } - } - - 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()); } } 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 0000000000..989e7f22a5 --- /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/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java index 9b2caf889d..cf4c2dfe90 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 @@ -22,13 +22,10 @@ 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; @@ -48,18 +45,15 @@ 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 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; @@ -72,14 +66,7 @@ public class HttpDestination implements Destination, Closeable, Dumpable this.host = host; this.address = new Address(host, port); - 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); @@ -93,14 +80,14 @@ public class HttpDestination implements Destination, Closeable, Dumpable hostField = new HttpField(HttpHeader.HOST, host); } - protected BlockingQueue<Connection> getIdleConnections() + public HttpClient getHttpClient() { - return idleConnections; + return client; } - protected BlockingQueue<Connection> getActiveConnections() + public Queue<HttpExchange> getHttpExchanges() { - return activeConnections; + return exchanges; } public RequestNotifier getRequestNotifier() @@ -157,7 +144,7 @@ public class HttpDestination implements Destination, Closeable, Dumpable 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())) throw new IllegalArgumentException("Invalid request scheme " + request.getScheme() + " for destination " + this); @@ -182,9 +169,7 @@ public class HttpDestination implements Destination, Closeable, Dumpable { LOG.debug("Queued {}", request); requestNotifier.notifyQueued(request); - Connection connection = acquire(); - if (connection != null) - process(connection, false); + send(); } } else @@ -199,6 +184,8 @@ public class HttpDestination implements Destination, Closeable, Dumpable } } + protected abstract void send(); + public void newConnection(Promise<Connection> promise) { createConnection(new ProxyPromise(promise)); @@ -209,80 +196,24 @@ public class HttpDestination implements Destination, Closeable, Dumpable 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); - } - }); + return exchanges.remove(exchange); + } - // Try again the idle connections - return idleConnections.poll(); - } - } + public void close() + { + abort(new AsynchronousCloseException()); + LOG.debug("Closed {}", this); } - private void abort(Throwable cause) + /** + * 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 +221,11 @@ public class HttpDestination implements Destination, Closeable, Dumpable } /** - * <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(); @@ -431,8 +239,7 @@ public class HttpDestination implements Destination, Closeable, Dumpable protected void tunnelSucceeded(Connection connection, Promise<Connection> promise) { // Wrap the connection with TLS - Connection tunnel = client.tunnel(connection); - promise.succeeded(tunnel); + promise.succeeded(client.getTransport().tunnel(connection)); } protected void tunnelFailed(Connection connection, Promise<Connection> promise, Throwable failure) @@ -451,22 +258,19 @@ public class HttpDestination implements Destination, Closeable, Dumpable 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 client.address(getScheme(), getHost(), getPort()); } @Override public String toString() { - return String.format("%s(%s://%s:%d)%s", + return String.format("%s(%s)%s", HttpDestination.class.getSimpleName(), - getScheme(), - getHost(), - getPort(), + asString(), proxyAddress == null ? "" : " via " + proxyAddress.getHost() + ":" + proxyAddress.getPort()); } 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 de043779ef..b370524997 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 @@ public class HttpExchange { 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 @@ public class HttpExchange 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 @@ public class HttpExchange * 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 @@ public class HttpExchange if (!complete.compareAndSet(current, candidate)) continue; current = candidate; - modified = true; if ((code & 0b01) == 0b01) requestFailure = failure; else @@ -157,19 +168,16 @@ public class HttpExchange 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 @@ public class HttpExchange } 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 class HttpExchange public void resetResponse(boolean success) { + responseComplete.set(false); int responseSuccess = 0b1100; int responseFailure = 0b0100; int code = success ? responseSuccess : responseFailure; @@ -203,42 +212,25 @@ public class HttpExchange public void proceed(boolean proceed) { - HttpConnection connection = this.connection; - if (connection != null) - connection.proceed(proceed); - } - - public void terminateRequest() - { - terminate.countDown(); - } - - public void terminateResponse() - { - terminate.countDown(); - } - - public void awaitTermination() - { - try - { - terminate.await(); - } - catch (InterruptedException x) - { - LOG.ignore(x); - } + HttpChannel channel = this.channel.get(); + if (channel != null) + channel.proceed(this, proceed); } - @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/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java index 8bb6c6d3dc..4ee0e9c5a9 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.Enumeration; 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 - { - while (true) - { - // Connection may be closed in a parser callback - if (connection.isClosed()) - { - LOG.debug("{} closed", connection); - break; - } - else - { - int read = endPoint.fill(buffer); - LOG.debug("Read {} bytes from {}", read, connection); - 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); - } + return channel; } - private void parse(ByteBuffer buffer) + protected HttpExchange getHttpExchange() { - while (buffer.hasRemaining()) - parser.parseNext(buffer); + return channel.getHttpExchange(); } - private void fillInterested() + protected HttpDestination getHttpDestination() { - State state = this.state.get(); - if (state == State.IDLE || state == State.RECEIVE) - connection.fillInterested(); + return channel.getHttpDestination(); } - private void shutdown() + /** + * 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) { - // Shutting down the parser may invoke messageComplete() or fail() - parser.shutdownInput(); - State state = this.state.get(); - if (state == State.IDLE || state == State.RECEIVE) + 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) { - if (!fail(new EOFException())) - connection.close(); + 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); - @Override - public int getHeaderCacheSize() - { - // TODO get from configuration - return 256; + return true; } - @Override - public boolean startResponse(HttpVersion version, int status, String reason) + /** + * 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) { - if (updateState(State.IDLE, 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(); - - 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) + case BEGIN: + case HEADER: { - handlerListener = protocolHandler.getResponseListener(); - LOG.debug("Found protocol handler {}", protocolHandler); + if (updateResponseState(current, ResponseState.HEADER)) + break out; + break; + } + default: + { + return false; } - 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)) + HttpResponse response = exchange.getResponse(); + ResponseNotifier notifier = getHttpDestination().getResponseNotifier(); + boolean process = notifier.notifyHeader(exchange.getConversation().getResponseListeners(), response, field); + if (process) { - HttpExchange exchange = connection.getExchange(); - // The exchange may be null if it failed concurrently - if (exchange != null) + response.getHeaders().add(field); + HttpHeader fieldHeader = field.getHeader(); + if (fieldHeader != null) { - HttpConversation conversation = exchange.getConversation(); - HttpResponse response = exchange.getResponse(); - ResponseNotifier notifier = connection.getDestination().getResponseNotifier(); - boolean process = notifier.notifyHeader(conversation.getResponseListeners(), response, field); - if (process) + switch (fieldHeader) { - response.getHeaders().add(field); - HttpHeader fieldHeader = field.getHeader(); - if (fieldHeader != null) + case SET_COOKIE: + case SET_COOKIE2: + { + storeCookie(exchange.getRequest().getURI(), field); + break; + } + default: { - switch (fieldHeader) - { - case SET_COOKIE: - case SET_COOKIE2: - { - storeCookie(exchange.getRequest().getURI(), field); - break; - } - default: - { - break; - } - } + break; } } } } - return false; + + return true; } - private void storeCookie(URI uri, HttpField field) + protected void storeCookie(URI uri, HttpField field) { try { @@ -227,7 +197,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer> { 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 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer> } } - @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) + { + ResponseState current = responseState.get(); + switch (current) + { + case BEGIN: + case HEADER: + { + 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) { - HttpExchange exchange = connection.getExchange(); - // The exchange may be null if it failed concurrently - if (exchange != null) + for (ContentDecoder.Factory factory : getHttpDestination().getHttpClient().getContentDecoderFactories()) { - 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) + while (contentEncodings.hasMoreElements()) { - for (ContentDecoder.Factory factory : connection.getHttpClient().getContentDecoderFactories()) + 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; + } + default: + { + return false; } - - ResponseNotifier notifier = connection.getDestination().getResponseNotifier(); - notifier.notifyContent(conversation.getResponseListeners(), response, buffer); } } - 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; - - parser.reset(); - decoder = null; + // Reset to be ready for another response + reset(); - 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 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer> 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; - - while (true) - { - State current = state.get(); - if (updateState(current, State.FAILURE)) - break; - } + // Dispose to avoid further responses + dispose(); - 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()); - } - - private void failAndClose(Throwable failure) - { - 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)); + decoder = null; + responseState.set(ResponseState.IDLE); } - public void idleTimeout() + /** + * 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() { - // 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() - { - return String.format("%s@%x on %s", getClass().getSimpleName(), hashCode(), connection); - } - - private enum State + /** + * The request states {@link HttpReceiver} goes through when receiving a response. + */ + private enum ResponseState { - 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 0000000000..cd7e1863a0 --- /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 2e4cbec1c5..ee86fc2899 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 @@ -43,6 +43,7 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.client.util.PathContentProvider; +import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; @@ -66,8 +67,8 @@ public class HttpRequest implements Request 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; @@ -89,8 +90,12 @@ public class HttpRequest implements Request 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); + headers.put(client.getUserAgentField()); } @Override @@ -109,7 +114,7 @@ public class HttpRequest implements Request public Request scheme(String scheme) { this.scheme = scheme; - this.uri = buildURI(true); + this.uri = null; return this; } @@ -126,13 +131,7 @@ public class HttpRequest implements Request } @Override - public HttpMethod getMethod() - { - return HttpMethod.fromString(method); - } - - @Override - public String method() + public String getMethod() { return method; } @@ -140,8 +139,7 @@ public class HttpRequest implements Request @Override public Request method(HttpMethod method) { - this.method = method.asString(); - return this; + return method(method.asString()); } @Override @@ -174,9 +172,9 @@ public class HttpRequest implements Request params.clear(); extractParams(query); } - this.uri = buildURI(true); if (uri.isAbsolute()) this.path = buildURI(false).toString(); + this.uri = null; return this; } @@ -189,7 +187,9 @@ public class HttpRequest implements Request @Override public URI getURI() { - return uri; + if (uri != null) + return uri; + return uri = buildURI(true); } @Override @@ -201,7 +201,7 @@ public class HttpRequest implements Request @Override public Request version(HttpVersion version) { - this.version = version; + this.version = Objects.requireNonNull(version); return this; } @@ -210,7 +210,7 @@ public class HttpRequest implements Request { params.add(name, value); this.query = buildQuery(); - this.uri = buildURI(true); + this.uri = null; return this; } @@ -273,6 +273,7 @@ public class HttpRequest implements Request } @Override + @SuppressWarnings("unchecked") public <T extends RequestListener> List<T> getRequestListeners(Class<T> type) { // This method is invoked often in a request/response conversation, @@ -465,12 +466,12 @@ public class HttpRequest implements Request 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) @@ -595,6 +596,6 @@ public class HttpRequest implements Request @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 907f05d4ac..ed126f95aa 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 @@ public class HttpResponse implements Response @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 eff0576761..53e7584e64 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, idle -> sending"); + HttpContent content = this.content; + content.advance(); + sendContent(exchange, content, contentCallback); + return; + } + break; + } + case SENDING: + { + if (updateSenderState(current, SenderState.SCHEDULED)) { - LOG.debug("Deferred content available, sending"); - send(); + LOG.debug("Deferred content available, sending -> scheduled"); return; } break; } - case EXECUTE: + case EXPECTING: { - if (updateSendState(current, SendState.SCHEDULE)) + if (updateSenderState(current, SenderState.SCHEDULED)) { - LOG.debug("Deferred content available, scheduling"); + LOG.debug("Deferred content available, expecting -> scheduled"); return; } break; } - case SCHEDULE: + case WAITING: + { + LOG.debug("Deferred content available, waiting"); + return; + } + case SCHEDULED: { - LOG.debug("Deferred content available, queueing"); + LOG.debug("Deferred content available, scheduled"); return; } default: @@ -98,9 +144,6 @@ public class HttpSender implements AsyncContentProvider.Listener 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 @@ public class HttpSender implements AsyncContentProvider.Listener } 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; - - HttpClient client = connection.getHttpClient(); - ByteBufferPool bufferPool = client.getByteBufferPool(); - ByteBuffer header = null; - ByteBuffer chunk = null; - try - { - HttpExchange exchange = connection.getExchange(); - // The exchange may be null if it failed concurrently - if (exchange == null) - return; - - final Request request = exchange.getRequest(); - HttpConversation conversation = exchange.getConversation(); - HttpGenerator.RequestInfo requestInfo = null; - - // 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); - - ContentChunk contentChunk = continueContentChunk; - continueContentChunk = null; - if (contentChunk == null) - contentChunk = new ContentChunk(contentIterator); - - 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) - { - case NEED_INFO: - { - 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; - } - case NEED_HEADER: - { - header = bufferPool.acquire(client.getRequestBufferSize(), false); - break; - } - case NEED_CHUNK: - { - chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false); - break; - } - case FLUSH: - { - out: - while (true) - { - 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(); - } - } - } - - StatefulExecutorCallback callback = new StatefulExecutorCallback(client.getExecutor()) - { - @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(); - } - return; - } - default: - { - throw new IllegalStateException("Unknown result " + result); - } - } - } - } - catch (Exception x) - { - LOG.debug(x); - fail(x); - } - finally - { - releaseBuffers(bufferPool, header, chunk); - } + return request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()); } - private boolean processWrite(Request request, ByteBuffer content, boolean expecting100ContinueResponse) + protected boolean queuedToBegin(Request request) { - if (!commit(request)) + if (!updateRequestState(RequestState.QUEUED, RequestState.BEGIN)) return false; + LOG.debug("Request begin {}", request); + RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier(); + notifier.notifyBegin(request); + return true; + } - if (content != null) - { - RequestNotifier notifier = connection.getDestination().getRequestNotifier(); - notifier.notifyContent(request, content); - } - - if (expecting100ContinueResponse) - { - LOG.debug("Expecting 100 Continue for {}", request); - continueContentChunk.signal(); + 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; } - public void proceed(boolean proceed) + protected boolean someToContent(Request request, ByteBuffer content) { - ContinueContentChunk contentChunk = continueContentChunk; - if (contentChunk != null) + RequestState current = requestState.get(); + switch (current) { - if (proceed) + case COMMIT: + case CONTENT: { - // 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(); + 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; } - else + case FAILURE: { - HttpExchange exchange = connection.getExchange(); - if (exchange != null) - fail(new HttpRequestException("Expectation failed", exchange.getRequest())); + return false; } - } - } - - 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) + protected boolean someToSuccess(HttpExchange exchange) { - while (true) + RequestState current = requestState.get(); + switch (current) { - State current = state.get(); - switch (current) + case COMMIT: + case CONTENT: { - 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: + // Mark atomically the request as completed, with respect + // to concurrency between request success and request failure. + boolean completed = exchange.requestComplete(); + if (!completed) 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(); + // Reset to be ready for another request + reset(); - exchange.terminateRequest(); + // 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 - - HttpDestination destination = connection.getDestination(); - Request request = exchange.getRequest(); - destination.getRequestNotifier().notifySuccess(request); - LOG.debug("Sent {}", request); + // 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()); - Result result = completion.getReference(); - if (result != null) - { - connection.complete(exchange, !result.isFailed()); + 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); + } - HttpConversation conversation = exchange.getConversation(); - destination.getResponseNotifier().notifyComplete(conversation.getResponseListeners(), result); + return true; + } + case FAILURE: + { + return false; + } + default: + { + throw new IllegalStateException(); + } } - - return true; } - protected boolean fail(Throwable failure) + protected boolean anyToFailure(Throwable failure) { - HttpExchange exchange = connection.getExchange(); + HttpExchange exchange = getHttpExchange(); if (exchange == null) return false; - AtomicMarkableReference<Result> completion = exchange.requestComplete(failure); - if (!completion.isMarked()) + // Mark atomically the request as completed, with respect + // to concurrency between request success and request failure. + boolean completed = exchange.requestComplete(); + if (!completed) return false; - generator.abort(); - - State current; - while (true) - { - current = state.get(); - if (updateState(current, State.FAILURE)) - break; - } - - shutdownOutput(); + // Dispose to avoid further requests + RequestState requestState = dispose(); - exchange.terminateRequest(); + // Mark atomically the request as terminated and failed, + // with respect to concurrency between request and response. + Result result = exchange.terminateRequest(failure); - HttpDestination destination = connection.getDestination(); Request request = exchange.getRequest(); + LOG.debug("Request failure {} {}", exchange, failure); + HttpDestination destination = getHttpChannel().getHttpDestination(); destination.getRequestNotifier().notifyFailure(request, failure); - LOG.debug("Failed {} {}", request, failure); - Result result = completion.getReference(); - boolean notCommitted = isBeforeCommit(current); + boolean notCommitted = isBeforeCommit(requestState); if (result == null && notCommitted && request.getAbortCause() == null) { - completion = exchange.responseComplete(failure); - if (completion.isMarked()) + // Complete the response from here + if (exchange.responseComplete()) { - result = completion.getReference(); - exchange.terminateResponse(); - LOG.debug("Failed on behalf {}", exchange); + result = exchange.terminateResponse(failure); + LOG.debug("Failed response from request {}", exchange); } } if (result != null) { - connection.complete(exchange, false); - + boolean ordered = destination.getHttpClient().isStrictEventOrdering(); + if (!ordered) + channel.exchangeTerminated(result); + LOG.debug("Request/Response failed {}", request); HttpConversation conversation = exchange.getConversation(); destination.getResponseNotifier().notifyComplete(conversation.getResponseListeners(), result); + if (ordered) + channel.exchangeTerminated(result); } return true; } - private void shutdownOutput() + /** + * 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() { - connection.getEndPoint().shutdownOutput(); + content = null; + requestState.set(RequestState.QUEUED); + senderState.set(SenderState.IDLE); } - public boolean abort(Throwable cause) + protected RequestState dispose() { - State current = state.get(); - boolean abortable = isBeforeCommit(current) || - current == State.COMMIT && contentIterator.hasNext(); - return abortable && fail(cause); + while (true) + { + RequestState current = requestState.get(); + if (updateRequestState(current, RequestState.FAILURE)) + return current; + } } - private boolean isBeforeCommit(State state) + public void proceed(HttpExchange exchange, boolean proceed) { - return state == State.IDLE || state == State.BEGIN || state == State.HEADERS; + if (!expects100Continue(exchange.getRequest())) + return; + + if (proceed) + { + while (true) + { + SenderState current = senderState.get(); + switch (current) + { + case EXPECTING: + { + // 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 WAITING: + { + // 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()) + { + // There is content to send + LOG.debug("Proceed while waiting"); + sendContent(exchange, content, contentCallback); + } + else + { + // 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(); + } + } + } + } + else + { + anyToFailure(new HttpRequestException("Expectation failed", exchange.getRequest())); + } } - private void releaseBuffers(ByteBufferPool bufferPool, ByteBuffer header, ByteBuffer chunk) + public boolean abort(Throwable failure) { - if (!BufferUtil.hasContent(header)) - bufferPool.release(header); - if (!BufferUtil.hasContent(chunk)) - bufferPool.release(chunk); + RequestState current = requestState.get(); + boolean abortable = isBeforeCommit(current) || + isSending(current) && !content.isLast(); + return abortable && anyToFailure(failure); } - private boolean updateState(State from, State to) + protected boolean updateRequestState(RequestState from, RequestState 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 + switch (requestState) + { + case QUEUED: + case BEGIN: + case HEADERS: + return true; + default: + return false; + } } - private enum SendState + private boolean isSending(RequestState requestState) { - IDLE, EXECUTE, SCHEDULE + switch (requestState) + { + case COMMIT: + case CONTENT: + return true; + default: + return false; + } } - private static abstract class StatefulExecutorCallback implements Callback, Runnable + /** + * The request states {@link HttpSender} goes through when sending a request. + */ + protected enum RequestState { - private final AtomicReference<State> state = new AtomicReference<>(State.INCOMPLETE); - private final Executor executor; + /** + * 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 + } - private StatefulExecutorCallback(Executor executor) - { - this.executor = executor; - } + /** + * 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() + public void succeeded() { - State previous = state.get(); - while (true) + try { - if (state.compareAndSet(previous, State.SUCCEEDED)) - break; - previous = state.get(); + process(); + } + // Catch-all for runtime exceptions + catch (Exception x) + { + anyToFailure(x); } - if (previous == State.PENDING) - executor.execute(this); } - @Override - public final void run() + private void process() throws Exception { - onSucceeded(); - } + HttpExchange exchange = getHttpExchange(); + if (exchange == null) + return; - protected abstract void onSucceeded(); + Request request = exchange.getRequest(); + if (!headersToCommit(request)) + return; - @Override - public final void failed(final Throwable x) - { - State previous = state.get(); - while (true) + HttpContent content = HttpSender.this.content; + + if (!content.hasContent()) { - if (state.compareAndSet(previous, State.FAILED)) - break; - previous = state.get(); + // No content to send, we are done. + someToSuccess(exchange); } - if (previous == State.PENDING) + else { - executor.execute(new Runnable() + // Was any content sent while committing ? + ByteBuffer contentBuffer = content.getContent(); + if (contentBuffer != null) { - @Override - public void run() + if (!someToContent(request, contentBuffer)) + return; + } + + while (true) + { + SenderState current = senderState.get(); + switch (current) { - onFailed(x); + 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(); + } } - }); - } - else - { - onFailed(x); + } } } - protected abstract void onFailed(Throwable x); - - public boolean process() - { - return state.compareAndSet(State.INCOMPLETE, State.PENDING); - } - - public boolean isSucceeded() + @Override + public void failed(Throwable failure) { - return state.get() == State.SUCCEEDED; + anyToFailure(failure); } + } - public boolean isFailed() + private class ContentCallback extends IteratingCallback + { + @Override + protected boolean process() throws Exception { - return state.get() == State.FAILED; - } + HttpExchange exchange = getHttpExchange(); + if (exchange == null) + return false; - private enum State - { - INCOMPLETE, PENDING, SUCCEEDED, FAILED - } - } + Request request = exchange.getRequest(); + HttpContent content = HttpSender.this.content; - private class ContentChunk - { - private final boolean lastContent; - private final ByteBuffer content; + ByteBuffer contentBuffer = content.getContent(); + if (contentBuffer != null) + { + if (!someToContent(request, contentBuffer)) + return false; + } - private ContentChunk(ContentChunk chunk) - { - lastContent = chunk.lastContent; - content = chunk.content; + 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; } - private ContentChunk(Iterator<ByteBuffer> contentIterator) + @Override + protected void completed() { - lastContent = !contentIterator.hasNext(); - content = lastContent ? BufferUtil.EMPTY_BUFFER : contentIterator.next(); + // Nothing to do, since we always return false from process(). + // Termination is obtained via LastContentCallback. } - private boolean isDeferred() + @Override + public void failed(Throwable failure) { - return content == null && !lastContent; + super.failed(failure); + anyToFailure(failure); } } - private class ContinueContentChunk extends ContentChunk + private class LastContentCallback implements Callback { - private final CountDownLatch latch = new CountDownLatch(1); - - private ContinueContentChunk(ContentChunk chunk) - { - super(chunk); - } - - private void signal() + @Override + public void succeeded() { - latch.countDown(); + HttpExchange exchange = getHttpExchange(); + if (exchange == null) + return; + someToSuccess(exchange); } - private void await() + @Override + public void failed(Throwable failure) { - try - { - latch.await(); - } - catch (InterruptedException x) - { - LOG.ignore(x); - } + 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 0000000000..aa2106e892 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.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.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, String scheme, String host, int port) + { + super(client, scheme, host, port); + } + + @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 exchange {} on connection {}", 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; + } + + 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/RedirectProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java index 77e83858ee..c557457f6f 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 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements @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/api/Connection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java index 4cd716f4f4..621a3d5a58 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 @@ public interface Connection extends Closeable /** * 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/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java index 225137aa33..176a4255be 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 @@ public interface Request 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 @@ -510,7 +503,7 @@ public interface Request /** * 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 2fc140103e..6a43820739 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 @@ public interface Response /** * 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 0000000000..7f1e56aff1 --- /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 0000000000..2e491cfe4d --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.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.client.http; + +import org.eclipse.jetty.client.AbstractHttpClientTransport; +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; + +public class HttpClientTransportOverHTTP extends AbstractHttpClientTransport +{ + public HttpClientTransportOverHTTP() + { + this(1); + } + + public HttpClientTransportOverHTTP(int selectors) + { + super(selectors); + } + + @Override + public HttpDestination newHttpDestination(String scheme, String host, int port) + { + return new HttpDestinationOverHTTP(getHttpClient(), scheme, host, port); + } + + @Override + protected Connection newConnection(EndPoint endPoint, HttpDestination destination) + { + return new HttpConnectionOverHTTP(endPoint, destination); + } + + @Override + public org.eclipse.jetty.client.api.Connection tunnel(org.eclipse.jetty.client.api.Connection connection) + { + HttpConnectionOverHTTP httpConnection = (HttpConnectionOverHTTP)connection; + return tunnel(httpConnection.getEndPoint(), httpConnection.getHttpDestination(), 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 0000000000..fbbb0bed04 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.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.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().remove(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().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()); + } + + 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(); + } + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionPool.java new file mode 100644 index 0000000000..a9d9841f8f --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionPool.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.http; + +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 HttpConnectionPool implements Dumpable +{ + private static final Logger LOG = Log.getLogger(HttpConnectionPool.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 HttpConnectionPool(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/http/HttpDestinationOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java new file mode 100644 index 0000000000..e7f4f19a64 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java @@ -0,0 +1,186 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// 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.Arrays; + +import org.eclipse.jetty.client.HttpClient; +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.util.Promise; +import org.eclipse.jetty.util.component.ContainerLifeCycle; + +public class HttpDestinationOverHTTP extends HttpDestination implements Promise<Connection> +{ + private final HttpConnectionPool connectionPool; + + public HttpDestinationOverHTTP(HttpClient client, String scheme, String host, int port) + { + super(client, scheme, host, port); + this.connectionPool = newHttpConnectionPool(client); + } + + protected HttpConnectionPool newHttpConnectionPool(HttpClient client) + { + return new HttpConnectionPool(this, client.getMaxConnectionsPerDestination(), this); + } + + public HttpConnectionPool getHttpConnectionPool() + { + return connectionPool; + } + + @Override + public void succeeded(Connection connection) + { + process((HttpConnectionOverHTTP)connection, true); + } + + @Override + public void failed(final Throwable x) + { + getHttpClient().getExecutor().execute(new Runnable() + { + @Override + public void run() + { + abort(x); + } + }); + } + + protected void send() + { + HttpConnectionOverHTTP connection = acquire(); + if (connection != null) + process(connection, false); + } + + protected HttpConnectionOverHTTP acquire() + { + return (HttpConnectionOverHTTP)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 + */ + protected void process(final HttpConnectionOverHTTP 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() + { + connection.send(exchange); + } + }); + } + else + { + connection.send(exchange); + } + } + } + } + + protected void release(HttpConnectionOverHTTP 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); + remove(connection); + connection.close(); + } + } + + protected void remove(HttpConnectionOverHTTP connection) + { + connectionPool.remove(connection); + + // 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()) + { + connection = acquire(); + if (connection != null) + process(connection, 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/http/HttpReceiverOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java new file mode 100644 index 0000000000..efeb2c4e5a --- /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 0000000000..c18a92a41a --- /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 1c9b859c4e..9ef5dc80bd 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/BufferingResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java index 9591ca6836..1c5c45d5d1 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 @@ -24,17 +24,18 @@ import java.nio.charset.UnsupportedCharsetException; 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 55ac0ec5f5..f45e4d29b3 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 @@ -221,7 +221,7 @@ public class DigestAuthentication implements Authentication String A1 = user + ":" + realm + ":" + password; String hashA1 = toHexString(digester.digest(A1.getBytes(charset))); - 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(charset))); 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 eacbb09168..8fdc0ccee8 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 @@ -25,6 +25,8 @@ import java.util.NoSuchElementException; import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; /** * A {@link ContentProvider} for an {@link InputStream}. @@ -44,6 +46,8 @@ import org.eclipse.jetty.util.BufferUtil; */ public class InputStreamContentProvider implements ContentProvider { + private static final Logger LOG = Log.getLogger(InputStreamContentProvider.class); + private final InputStream stream; private final int bufferSize; @@ -88,63 +92,91 @@ public class InputStreamContentProvider implements ContentProvider @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 final byte[] bytes = new byte[bufferSize]; + private Exception failure; + private ByteBuffer buffer; + private Boolean hasNext; + + @Override + public boolean hasNext() + { + try { - try + if (hasNext != null) + return hasNext; + + int read = stream.read(bytes); + LOG.debug("Read {} bytes from {}", read, stream); + if (read > 0) { - int read = stream.read(bytes); - if (read > 0) - { - buffer = onRead(bytes, 0, read); - return true; - } - else if (read < 0) - { - return false; - } - else - { - buffer = BufferUtil.EMPTY_BUFFER; - return true; - } + buffer = onRead(bytes, 0, read); + hasNext = Boolean.TRUE; + return true; } - catch (Exception x) + else if (read < 0) { - if (failure == null) - { - failure = x; - // Signal we have more content to cause a call to - // next() which will throw NoSuchElementException. - return true; - } + hasNext = Boolean.FALSE; return false; } + else + { + buffer = BufferUtil.EMPTY_BUFFER; + hasNext = Boolean.TRUE; + return true; + } } - - @Override - public ByteBuffer next() + catch (Exception x) { - ByteBuffer result = buffer; - buffer = null; - if (failure != null) - throw (NoSuchElementException)new NoSuchElementException().initCause(failure); - if (result == null) - throw new NoSuchElementException(); - return result; + 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; + return true; + } + throw new IllegalStateException(); } + } - @Override - public void remove() - { - throw new UnsupportedOperationException(); - } - }; + @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(); + } } } 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 d59943a467..39844083d4 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 java.util.concurrent.atomic.AtomicReference; 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 @@ import org.eclipse.jetty.util.log.Logger; * 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 503ad74292..c4433abb6d 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 @@ public class ExternalSiteTest 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/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java index 23b69269f9..a2ae974d11 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 @@ -81,7 +81,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest 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 +89,6 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest securityHandler.addConstraintMapping(mapping); securityHandler.setAuthenticator(authenticator); securityHandler.setLoginService(loginService); - securityHandler.setStrict(false); securityHandler.setHandler(handler); start(securityHandler); @@ -116,7 +115,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest 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 +137,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest 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 +156,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest 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 +196,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest 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 +235,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest 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 +262,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest 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 e281ac43ff..7acbb3bcb6 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 @@ -407,7 +407,7 @@ public class HttpClientContinueTest extends AbstractHttpClientServerTest 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) @@ -564,40 +564,7 @@ public class HttpClientContinueTest extends AbstractHttpClientServerTest }); 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/HttpClientExplicitConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java index c49b37b97d..6b7acc6a19 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,9 @@ import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; +import org.eclipse.jetty.client.http.HttpConnectionPool; +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 +59,10 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe 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; + HttpConnectionPool connectionPool = httpDestination.getHttpConnectionPool(); + Assert.assertTrue(connectionPool.getActiveConnections().isEmpty()); + Assert.assertTrue(connectionPool.getIdleConnections().isEmpty()); } } @@ -84,11 +88,12 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe // 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; + HttpConnectionPool connectionPool = httpDestination.getHttpConnectionPool(); + 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 601fd1c49c..1ac4ae4a20 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; @@ -37,6 +36,9 @@ 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.client.http.HttpConnectionOverHTTP; +import org.eclipse.jetty.client.http.HttpConnectionPool; +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.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.Scheduler; import org.junit.Assert; import org.junit.Test; @@ -70,9 +73,27 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest 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 = 50_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 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest // 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 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest 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()); + HttpConnectionPool connectionPool = destination.getHttpConnectionPool(); + 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 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest 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 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest 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 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest 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 cb5f7d9b86..9f7ff33f7b 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 @@ -129,7 +129,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest 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 58e4400b92..f9985aa3f8 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,6 +22,7 @@ import java.io.IOException; 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; @@ -31,6 +32,7 @@ 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 +46,6 @@ import org.junit.Assert; 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 +123,7 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest .path("/301/localhost/done") .timeout(5, TimeUnit.SECONDS) .send(); - fail(); + Assert.fail(); } catch (ExecutionException x) { @@ -164,7 +164,7 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest .path("/303/localhost/302/localhost/done") .timeout(5, TimeUnit.SECONDS) .send(); - fail(); + Assert.fail(); } catch (ExecutionException x) { @@ -331,6 +331,43 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest 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 +381,7 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest 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 +389,12 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest 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/HttpClientSynchronizationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java index 2beca50108..0f717d830c 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 @@ public class HttpClientSynchronizationTest extends AbstractHttpClientServerTest 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 @@ public class HttpClientSynchronizationTest extends AbstractHttpClientServerTest 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 aa7db6bc72..974dad8747 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 @@ -50,6 +50,9 @@ import org.eclipse.jetty.client.api.Destination; 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.HttpConnectionPool; +import org.eclipse.jetty.client.http.HttpDestinationOverHTTP; import org.eclipse.jetty.client.util.BytesContentProvider; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpMethod; @@ -81,13 +84,14 @@ public class HttpClientTest extends AbstractHttpClientServerTest 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); + HttpConnectionPool connectionPool = destination.getHttpConnectionPool(); 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); @@ -98,8 +102,8 @@ public class HttpClientTest extends AbstractHttpClientServerTest 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()); } @@ -398,7 +402,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest } } }) - .send(new Response.Listener.Empty() + .send(new Response.Listener.Adapter() { @Override public void onSuccess(Response response) @@ -418,7 +422,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest latch.countDown(); } }) - .send(new Response.Listener.Empty() + .send(new Response.Listener.Adapter() { @Override public void onSuccess(Response response) @@ -445,7 +449,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest 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) @@ -526,7 +530,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest latch.countDown(); } }) - .send(new Response.Listener.Empty() + .send(new Response.Listener.Adapter() { @Override public void onSuccess(Response response) @@ -605,7 +609,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest }; } }) - .send(new Response.Listener.Empty() + .send(new Response.Listener.Adapter() { @Override public void onComplete(Result result) @@ -632,11 +636,11 @@ public class HttpClientTest extends AbstractHttpClientServerTest @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.getHttpConnectionPool().getActiveConnections().peek().close(); } }) - .send(new Response.Listener.Empty() + .send(new Response.Listener.Adapter() { @Override public void onComplete(Result result) 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 3297b80802..ccb48429b9 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 @@ -34,6 +34,7 @@ import org.eclipse.jetty.client.api.Destination; 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.util.BufferingResponseListener; import org.eclipse.jetty.client.util.InputStreamContentProvider; import org.eclipse.jetty.io.EndPoint; @@ -240,7 +241,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest 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) @@ -255,7 +256,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest } }; } - }; + }, sslContextFactory); client.setIdleTimeout(timeout); client.start(); 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 4d2aa2f438..9fcf5d0a98 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 @@ -33,8 +33,11 @@ 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.http.HttpConnectionPool; +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 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest 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 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest String host = "localhost"; int port = connector.getLocalPort(); - HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port); + HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port); + HttpConnectionPool connectionPool = destination.getHttpConnectionPool(); - 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 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest headersLatch.countDown(); } }) - .send(new Response.Listener.Empty() + .send(new Response.Listener.Adapter() { @Override public void onSuccess(Response response) @@ -117,17 +128,18 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest String host = "localhost"; int port = connector.getLocalPort(); - HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port); + HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port); + HttpConnectionPool connectionPool = destination.getHttpConnectionPool(); - 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 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest { failureLatch.countDown(); } - }).send(new Response.Listener.Empty() + }).send(new Response.Listener.Adapter() { @Override public void onComplete(Result result) @@ -167,18 +179,19 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest String host = "localhost"; int port = connector.getLocalPort(); - HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port); + HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port); + HttpConnectionPool connectionPool = destination.getHttpConnectionPool(); - 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 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest successLatch.countDown(); } }) - .send(new Response.Listener.Empty() + .send(new Response.Listener.Adapter() { @Override public void onSuccess(Response response) @@ -226,19 +239,20 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest String host = "localhost"; int port = connector.getLocalPort(); - HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port); + HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port); + HttpConnectionPool connectionPool = destination.getHttpConnectionPool(); - 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 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest successLatch.countDown(); } }) - .send(new Response.Listener.Empty() + .send(new Response.Listener.Adapter() { @Override public void onSuccess(Response response) @@ -298,12 +312,13 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest String host = "localhost"; int port = connector.getLocalPort(); - HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port); + HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port); + HttpConnectionPool connectionPool = destination.getHttpConnectionPool(); - 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 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest failureLatch.countDown(); } }) - .send(new Response.Listener.Empty() + .send(new Response.Listener.Adapter() { @Override public void onComplete(Result result) @@ -350,18 +365,19 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest String host = "localhost"; int port = connector.getLocalPort(); - HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port); + HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port); + HttpConnectionPool connectionPool = destination.getHttpConnectionPool(); - 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 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest String host = "localhost"; int port = connector.getLocalPort(); - HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port); + HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port); + HttpConnectionPool connectionPool = destination.getHttpConnectionPool(); - 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 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest 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 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest Assert.assertEquals(0, idleConnections.size()); Assert.assertEquals(0, activeConnections.size()); + server.stop(); } finally @@ -446,12 +465,13 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest String host = "localhost"; int port = connector.getLocalPort(); - HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port); + HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port); + HttpConnectionPool connectionPool = destination.getHttpConnectionPool(); - 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/HttpReceiverTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java index ad78b1ed96..d0cd047285 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,227 +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.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("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("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 5d1a2b1279..c131f4cb72 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 @@ -59,7 +59,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest { 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 +97,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest { 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 +137,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest { 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 e4cb6a003b..08de6e4503 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,282 +18,263 @@ package org.eclipse.jetty.client; -import java.net.URI; -import java.nio.ByteBuffer; -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("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("UTF-8")), ByteBuffer.wrap(content2.getBytes("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("UTF-8")), ByteBuffer.wrap(content2.getBytes("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("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("UTF-8")), ByteBuffer.wrap(content2.getBytes("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("UTF-8")), ByteBuffer.wrap(content2.getBytes("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/api/Usage.java b/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java index cd7d09e098..397d5bb930 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 @@ -133,7 +133,7 @@ public class Usage 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) @@ -319,7 +319,7 @@ public class Usage 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/HttpDestinationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java index 3be2eae5ae..611335f892 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpDestinationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java @@ -16,12 +16,14 @@ // ======================================================================== // -package org.eclipse.jetty.client; +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.api.Connection; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; @@ -34,9 +36,9 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; -public class HttpDestinationTest extends AbstractHttpClientServerTest +public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest { - public HttpDestinationTest(SslContextFactory sslContextFactory) + public HttpDestinationOverHTTPTest(SslContextFactory sslContextFactory) { super(sslContextFactory); } @@ -50,12 +52,12 @@ public class HttpDestinationTest extends AbstractHttpClientServerTest @Test public void test_FirstAcquire_WithEmptyQueue() throws Exception { - HttpDestination destination = new HttpDestination(client, "http", "localhost", connector.getLocalPort()); + HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(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); + connection = destination.getHttpConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS); } Assert.assertNotNull(connection); } @@ -63,7 +65,7 @@ public class HttpDestinationTest extends AbstractHttpClientServerTest @Test public void test_SecondAcquire_AfterFirstAcquire_WithEmptyQueue_ReturnsSameConnection() throws Exception { - HttpDestination destination = new HttpDestination(client, "http", "localhost", connector.getLocalPort()); + HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "http", "localhost", connector.getLocalPort()); Connection connection1 = destination.acquire(); if (connection1 == null) { @@ -71,8 +73,8 @@ public class HttpDestinationTest extends AbstractHttpClientServerTest long start = System.nanoTime(); while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5) { - connection1 = destination.getIdleConnections().peek(); TimeUnit.MILLISECONDS.sleep(50); + connection1 = destination.getHttpConnectionPool().getIdleConnections().peek(); } Assert.assertNotNull(connection1); @@ -85,10 +87,10 @@ public class HttpDestinationTest extends AbstractHttpClientServerTest public void test_SecondAcquire_ConcurrentWithFirstAcquire_WithEmptyQueue_CreatesTwoConnections() throws Exception { final CountDownLatch latch = new CountDownLatch(1); - HttpDestination destination = new HttpDestination(client, "http", "localhost", connector.getLocalPort()) + HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "http", "localhost", connector.getLocalPort()) { @Override - protected void process(Connection connection, boolean dispatch) + protected void process(HttpConnectionOverHTTP connection, boolean dispatch) { try { @@ -113,21 +115,29 @@ public class HttpDestinationTest extends AbstractHttpClientServerTest latch.countDown(); // There must be 2 idle connections - Connection connection = destination.getIdleConnections().poll(5, TimeUnit.SECONDS); + Connection connection = destination.getHttpConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS); Assert.assertNotNull(connection); - connection = destination.getIdleConnections().poll(5, TimeUnit.SECONDS); + connection = destination.getHttpConnectionPool().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); + HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "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.getHttpConnectionPool().getIdleConnections().peek(); + } Assert.assertNotNull(connection1); + // Acquire the connection to make it active + Assert.assertSame(connection1, destination.acquire()); + destination.process(connection1, false); destination.release(connection1); @@ -142,7 +152,7 @@ public class HttpDestinationTest extends AbstractHttpClientServerTest long idleTimeout = 1000; client.setIdleTimeout(idleTimeout); - HttpDestination destination = new HttpDestination(client, "http", "localhost", connector.getLocalPort()); + HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "http", "localhost", connector.getLocalPort()); Connection connection1 = destination.acquire(); if (connection1 == null) { @@ -150,14 +160,14 @@ public class HttpDestinationTest extends AbstractHttpClientServerTest long start = System.nanoTime(); while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5) { - connection1 = destination.getIdleConnections().peek(); TimeUnit.MILLISECONDS.sleep(50); + connection1 = destination.getHttpConnectionPool().getIdleConnections().peek(); } Assert.assertNotNull(connection1); TimeUnit.MILLISECONDS.sleep(2 * idleTimeout); - connection1 = destination.getIdleConnections().poll(); + connection1 = destination.getHttpConnectionPool().getIdleConnections().poll(); Assert.assertNull(connection1); } } 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 e0130ddb19..4ffab8f330 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,9 @@ package org.eclipse.jetty.client.ssl; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + import java.io.BufferedReader; import java.io.EOFException; import java.io.File; @@ -48,6 +51,7 @@ import javax.servlet.ServletOutputStream; 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; @@ -163,7 +167,7 @@ public class SslBytesServerTest extends SslBytesTest } }; - 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 @@ -387,7 +391,14 @@ public class SslBytesServerTest extends SslBytesTest // 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 @@ -675,7 +686,15 @@ public class SslBytesServerTest extends SslBytesTest // 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 @@ -730,7 +749,14 @@ public class SslBytesServerTest extends SslBytesTest // 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); @@ -798,7 +824,14 @@ public class SslBytesServerTest extends SslBytesTest // 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); @@ -850,9 +883,17 @@ public class SslBytesServerTest extends SslBytesTest // 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); @@ -902,7 +943,14 @@ public class SslBytesServerTest extends SslBytesTest // 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); @@ -1096,7 +1144,14 @@ public class SslBytesServerTest extends SslBytesTest // 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); @@ -1815,6 +1870,11 @@ public class SslBytesServerTest extends SslBytesTest // 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 b69b457d7b..4683f8b89c 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.6-SNAPSHOT</version> + <version>9.1.0-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 8c1a66e92c..d370a34f8a 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 @@ import javax.servlet.ServletResponseWrapper; /* ------------------------------------------------------------ */ /** * 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 6779aad7a0..204d680f9b 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.6-SNAPSHOT</version> + <version>9.1.0-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 1b46387213..16e5253a86 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 0000000000..84c90ab5d2 --- /dev/null +++ b/jetty-deploy/src/main/config/modules/deploy.mod @@ -0,0 +1,17 @@ +# +# Deploy Feature +# + +[depend] +webapp + +[lib] +# Deploy jars +lib/jetty-deploy-${jetty.version}.jar + +[files] +webapps/ + +[xml] +# Deploy configuration +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 fdb73aa9a8..ec94b5a390 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 @@ -267,6 +267,8 @@ public class WebAppProvider extends ScanningAppProvider XmlConfiguration xmlc = new XmlConfiguration(resource.getURL()); 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-distribution/pom.xml b/jetty-distribution/pom.xml index 8e0654ad0c..ebf739ac9b 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.6-SNAPSHOT</version> + <version>9.1.0-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.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>servlet-api-3.0.jar</destFileName> + <destFileName>jetty-schemas-3.1.jar</destFileName> </artifactItem> </artifactItems> </configuration> @@ -380,54 +393,54 @@ </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</includeGroupIds> + <includeArtifactIds>javax.annotation-api,org.objectweb.asm</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</includeGroupIds> + <includeArtifactIds>javax.servlet.jsp.jstl,org.apache.taglibs.standard.glassfish,org.eclipse.jdt.core, javax.servlet.jsp-api, javax.servlet.jsp, javax.el-api, javax.el</includeArtifactIds> <includeTypes>jar</includeTypes> <outputDirectory>${assembly-directory}/lib/jsp</outputDirectory> </configuration> @@ -439,7 +452,7 @@ <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 +462,28 @@ </executions> </plugin> <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <executions> + <execution> + <phase>process-classes</phase> + <goals> + <goal>java</goal> + </goals> + </execution> + </executions> + <configuration> + <mainClass>org.eclipse.jetty.start.Main</mainClass> + <arguments> + <argument>--debug</argument> + <argument>jetty.home=${assembly-directory}</argument> + <argument>jetty.base=${assembly-directory}</argument> + <argument>--module-start-ini=server,deploy,websocket,jsp,ext,resources</argument> + <argument>--module-ini=http</argument> + </arguments> + </configuration> + </plugin> + <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> @@ -488,8 +523,8 @@ <dependencies> <!-- Orbit Deps --> <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> @@ -504,13 +539,30 @@ <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>javax.servlet.jsp</groupId> + <artifactId>javax.servlet.jsp-api</artifactId> + </dependency> + <dependency> + <groupId>org.glassfish</groupId> + <artifactId>javax.el</artifactId> + </dependency> <!-- jetty deps --> <dependency> @@ -552,10 +604,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> @@ -582,6 +644,11 @@ </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> <version>${project.version}</version> </dependency> diff --git a/jetty-distribution/src/main/resources/README.TXT b/jetty-distribution/src/main/resources/README.TXT new file mode 100644 index 0000000000..5b476754c2 --- /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 9656bab431..0000000000 --- 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/demo-base/webapps/README.TXT b/jetty-distribution/src/main/resources/demo-base/webapps/README.TXT new file mode 100644 index 0000000000..d6fb93b899 --- /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 Binary files differindex f40c3644cc..f40c3644cc 100644 --- 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 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 Binary files differindex b949919f08..b949919f08 100644 --- 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 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 index ce03053d93..ce03053d93 100644 --- a/jetty-distribution/src/main/resources/webapps.demo/ROOT/index.html +++ b/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 index 90d281a7bf..90d281a7bf 100644 --- a/jetty-distribution/src/main/resources/webapps.demo/ROOT/jetty.css +++ b/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 index 4b176a4467..4b176a4467 100644 --- a/jetty-distribution/src/main/resources/webapps.demo/example-moved.xml +++ b/jetty-distribution/src/main/resources/demo-base/webapps/example-moved.xml diff --git a/jetty-distribution/src/main/resources/webapps.demo/javadoc.xml b/jetty-distribution/src/main/resources/demo-base/webapps/javadoc.xml index df854d27ac..df854d27ac 100644 --- a/jetty-distribution/src/main/resources/webapps.demo/javadoc.xml +++ b/jetty-distribution/src/main/resources/demo-base/webapps/javadoc.xml diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-distribution/src/main/resources/modules/.donotdelete index e69de29bb2..e69de29bb2 100644 --- a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP +++ b/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 0000000000..79be762389 --- /dev/null +++ b/jetty-distribution/src/main/resources/modules/setuid.mod @@ -0,0 +1,14 @@ +[depend] +server + +[lib] +lib/setuid/jetty-setuid-java-1.0.1.jar + +[xml] +etc/jetty-setuid.xml + +[ini-template] +# 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 ae04b10c3a..0000000000 --- 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 0000000000..4a6256c5a6 --- /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 3d7646db81..e760d7a547 100644 --- a/jetty-distribution/src/main/resources/start.ini +++ b/jetty-distribution/src/main/resources/start.ini @@ -1,237 +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 --module-start-ini=module option can be used to append +# a configuration template for a module to start.ini +# The --module-ini=module option can be used to creat +# 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 --module-ini=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 -# -#----------------------------------------------------------- -# --exec -# --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 - - -#=========================================================== -# 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 ec2bea255c..0000000000 --- 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/README.TXT b/jetty-distribution/src/main/resources/webapps/README.TXT index dc2b38915e..170137a0ec 100644 --- a/jetty-distribution/src/main/resources/webapps/README.TXT +++ b/jetty-distribution/src/main/resources/webapps/README.TXT @@ -23,3 +23,11 @@ only the XML is deployed (which may use the war in its configuration). 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 77ade960df..60544a3dbb 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.6-SNAPSHOT</version> + <version>9.1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>jetty-http-spi</artifactId> diff --git a/jetty-http/pom.xml b/jetty-http/pom.xml index 43b30561b7..3da7358126 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.6-SNAPSHOT</version> + <version>9.1.0-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/HttpContent.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java index 61775e579e..5a4cbf7770 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.io.InputStream; 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 @@ public interface HttpContent @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 @@ public interface HttpContent { 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/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 3f2c12d80a..41316562e1 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,10 +18,6 @@ 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; @@ -47,12 +43,13 @@ 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; import org.eclipse.jetty.util.log.Logger; +import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted; + /** * HTTP Fields. A collection of HTTP header and or Trailer fields. @@ -649,14 +646,15 @@ public class HttpFields implements Iterable<HttpField> * * @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 +662,15 @@ public class HttpFields implements Iterable<HttpField> * * @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; } /** 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 db4642957d..7cef1dcf53 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 @@ -92,17 +92,17 @@ public class HttpParser 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; @@ -119,6 +119,8 @@ public class HttpParser /* ------------------------------------------------------------------------------- */ private volatile State _state=State.START; + private volatile boolean _eof; + private volatile boolean _closed; private HttpMethod _method; private String _methodString; private HttpVersion _version; @@ -202,27 +204,27 @@ public class HttpParser } /* ------------------------------------------------------------------------------- */ - public State getState() + protected void setResponseStatus(int status) { - return _state; + _responseStatus=status; } /* ------------------------------------------------------------------------------- */ - public boolean inContentState() + public State getState() { - return _state.ordinal() > State.END.ordinal(); + return _state; } /* ------------------------------------------------------------------------------- */ - public boolean inHeaderState() + public boolean inContentState() { - return _state.ordinal() < State.END.ordinal(); + return _state.ordinal()>=State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal(); } /* ------------------------------------------------------------------------------- */ - public boolean isInContent() + public boolean inHeaderState() { - return _state.ordinal()>State.END.ordinal() && _state.ordinal()<State.CLOSED.ordinal(); + return _state.ordinal() < State.CONTENT.ordinal(); } /* ------------------------------------------------------------------------------- */ @@ -264,6 +266,7 @@ public class HttpParser /* ------------------------------------------------------------------------------- */ private static class BadMessage extends Error { + private static final long serialVersionUID = 1L; private final int _code; private final String _message; @@ -291,65 +294,43 @@ public class HttpParser } /* ------------------------------------------------------------------------------- */ - 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) { + if (ch!=HttpTokens.LINE_FEED) + throw new BadMessage("Bad EOL"); _cr=false; - if (ch==HttpTokens.LINE_FEED) - return ch; - - throw new BadMessage("Bad EOL"); + return ch; } - - // If it is a CR - if (ch==HttpTokens.CARRIAGE_RETURN) + + if (ch>=0 && ch<HttpTokens.SPACE) { - // Skip CR and look for a LF - if (buffer.hasRemaining()) + if (ch==HttpTokens.CARRIAGE_RETURN) { - if(_maxHeaderBytes>0 && _state.ordinal()<State.END.ordinal()) - _headerBytes++; - ch=buffer.get(); - if (ch==HttpTokens.LINE_FEED) - return ch; - - throw new BadMessage("Bad EOL"); + 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; + } } - - // Defer lookup of LF - _cr=true; - 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; } @@ -359,33 +340,33 @@ public class HttpParser */ private boolean quickStart(ByteBuffer buffer) { - // Quick start look - while (_state==State.START && buffer.hasRemaining()) + if (_requestHandler!=null) { - if (_requestHandler!=null) + _method = HttpMethod.lookAheadGet(buffer); + if (_method!=null) { - _method = HttpMethod.lookAheadGet(buffer); - if (_method!=null) - { - _methodString = _method.asString(); - buffer.position(buffer.position()+_methodString.length()+1); - setState(State.SPACE1); - return false; - } + _methodString = _method.asString(); + buffer.position(buffer.position()+_methodString.length()+1); + setState(State.SPACE1); + return false; } - else if (_responseHandler!=null) + } + else if (_responseHandler!=null) + { + _version = HttpVersion.lookAheadGet(buffer); + if (_version!=null) { - _version = HttpVersion.lookAheadGet(buffer); - if (_version!=null) - { - buffer.position(buffer.position()+_version.asString().length()+1); - setState(State.SPACE1); - return false; - } + buffer.position(buffer.position()+_version.asString().length()+1); + setState(State.SPACE1); + return false; } + } + + // Quick start look + while (_state==State.START && buffer.hasRemaining()) + { + int ch=next(buffer); - byte ch=next(buffer); - if (ch > HttpTokens.SPACE) { _string.setLength(0); @@ -393,6 +374,10 @@ public class HttpParser setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION); return false; } + else if (ch==0) + break; + else if (ch<0) + throw new BadMessage(); } return false; } @@ -420,15 +405,15 @@ public class HttpParser */ 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) { @@ -459,7 +444,7 @@ public class HttpParser _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); @@ -472,9 +457,7 @@ public class HttpParser 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) @@ -484,12 +467,12 @@ public class HttpParser 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 { @@ -528,7 +511,9 @@ public class HttpParser } } else if (ch < HttpTokens.SPACE) + { throw new BadMessage(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status"); + } break; case STATUS: @@ -542,7 +527,7 @@ public class HttpParser } 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 @@ -560,11 +545,11 @@ public class HttpParser { // 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 { @@ -594,28 +579,29 @@ public class HttpParser 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); } } } @@ -625,21 +611,21 @@ public class HttpParser { 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; @@ -652,9 +638,7 @@ public class HttpParser _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()) @@ -666,10 +650,10 @@ public class HttpParser 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(); @@ -682,7 +666,7 @@ public class HttpParser 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) @@ -690,7 +674,7 @@ public class HttpParser _string.append((char)ch); if (ch!=' '&&ch!='\t') _length=_string.length(); - } + } else throw new BadMessage(); break; @@ -701,7 +685,7 @@ public class HttpParser } } - return return_from_parse; + return handle; } private boolean handleKnownHeaders(ByteBuffer buffer) @@ -795,7 +779,10 @@ public class HttpParser case CONNECTION: // Don't cache if not persistent if (_valueString!=null && _valueString.indexOf("close")>=0) + { + _closed=true; _connectionFields=null; + } break; case AUTHORIZATION: @@ -826,17 +813,17 @@ public class HttpParser /* * 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) { @@ -884,7 +871,7 @@ public class HttpParser _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; @@ -928,23 +915,23 @@ public class HttpParser { 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; } } @@ -1128,7 +1115,7 @@ public class HttpParser } } - return return_from_parse; + return handle; } /* ------------------------------------------------------------------------------- */ @@ -1138,214 +1125,113 @@ public class HttpParser */ 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 (_state.ordinal()<State.END.ordinal()) - if (parseHeaders(buffer)) - return true; + if (!handle && _state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal()) + handle=parseLine(buffer); - // 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 HEAD response + if (_responseStatus>0 && _headResponse) + { + setState(State.END); + handle=_handler.messageComplete(); + } + else + handle=parseContent(buffer); } - - - // Handle _content - byte ch; - while (_state.ordinal() > State.END.ordinal() && buffer.hasRemaining()) + + // handle end states + if (_state==State.END) { - switch (_state) + // eat white space + while (buffer.remaining()>0 && buffer.get(buffer.position())<=HttpTokens.SPACE) + buffer.get(); + } + else if (_state==State.CLOSED) + { + if (BufferUtil.hasContent(buffer)) { - case EOF_CONTENT: - _contentChunk=buffer.asReadOnlyBuffer(); - _contentPosition += _contentChunk.remaining(); - buffer.position(buffer.position()+_contentChunk.remaining()); - if (_handler.content(_contentChunk)) - return true; - break; - - case CONTENT: + // 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); - } + } + } + + // Handle EOF + if (_eof && !buffer.hasRemaining()) + { + switch(_state) + { + case CLOSED: + break; + case START: + _handler.earlyEOF(); + setState(State.CLOSED); 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); + + case END: + setState(State.CLOSED); 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); - } + + case EOF_CONTENT: + handle=_handler.messageComplete()||handle; + setState(State.CLOSED); 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; - } + case CONTENT: + case CHUNKED_CONTENT: + case CHUNK_SIZE: + case CHUNK_PARAMS: + case CHUNK: + _handler.earlyEOF(); + setState(State.CLOSED); break; - } - case CLOSED: - { - BufferUtil.clear(buffer); - return false; - } - - default: + + 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); @@ -1356,8 +1242,8 @@ public class HttpParser { 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()) @@ -1375,85 +1261,187 @@ public class HttpParser } } - /** - * 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 CONTENT: + { + long remaining=_contentLength - _contentPosition; + if (remaining == 0) + { + setState(State.END); + if (_handler.messageComplete()) + return true; + } + else + { + _contentChunk=buffer.asReadOnlyBuffer(); - case EOF_CONTENT: - setState(State.END); - return _handler.messageComplete(); + // 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); + } - case CLOSED: - break; + _contentPosition += _contentChunk.remaining(); + buffer.position(buffer.position()+_contentChunk.remaining()); - default: - setState(State.END); - if (!_headResponse) - _handler.earlyEOF(); - return _handler.messageComplete(); - } + 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; @@ -1465,7 +1453,7 @@ public class HttpParser } /* ------------------------------------------------------------------------------- */ - private void setState(State state) + protected void setState(State state) { if (DEBUG) LOG.debug("{} --> {}",_state,state); @@ -1511,7 +1499,6 @@ public class HttpParser /* ------------------------------------------------------------ */ /** 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(); @@ -1542,7 +1529,7 @@ public class HttpParser /** * 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); 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 d4ee8d75cf..d3d77b3e42 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 @@ -31,7 +31,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.Assert; import org.junit.Before; import org.junit.Test; @@ -42,8 +41,8 @@ public class HttpParserTest { /* ------------------------------------------------------------------------------- */ /** - * Parse until {@link #END END} state. - * If the parser is already in the END state, then it is {@link #reset reset} and re-parsed. + * Parse until {@link State#END} state. + * If the parser is already in the END state, then it is {@link HttpParser#reset()} and re-parsed. * @param parser The parser to test * @throws IllegalStateException If the buffers have already been partially parsed. */ @@ -77,7 +76,7 @@ public class HttpParserTest assertEquals("POST", _methodOrVersion); assertEquals("/foo", _uriOrStatus); assertEquals("HTTP/1.0", _versionOrReason); - assertEquals(-1, _h); + assertEquals(-1, _headers); } @Test @@ -92,7 +91,7 @@ public class HttpParserTest assertEquals("GET", _methodOrVersion); assertEquals("/999", _uriOrStatus); assertEquals(null, _versionOrReason); - assertEquals(-1, _h); + assertEquals(-1, _headers); } @Test @@ -107,7 +106,7 @@ public class HttpParserTest assertEquals("POST", _methodOrVersion); assertEquals("/222", _uriOrStatus); assertEquals(null, _versionOrReason); - assertEquals(-1, _h); + assertEquals(-1, _headers); } @Test @@ -121,7 +120,7 @@ public class HttpParserTest assertEquals("POST", _methodOrVersion); assertEquals("/fo\u0690", _uriOrStatus); assertEquals("HTTP/1.0", _versionOrReason); - assertEquals(-1, _h); + assertEquals(-1, _headers); } @Test @@ -135,7 +134,7 @@ public class HttpParserTest assertEquals("POST", _methodOrVersion); assertEquals("/foo?param=\u0690", _uriOrStatus); assertEquals("HTTP/1.0", _versionOrReason); - assertEquals(-1, _h); + assertEquals(-1, _headers); } @Test @@ -149,7 +148,7 @@ public class HttpParserTest 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 @@ public class HttpParserTest 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 @@ public class HttpParserTest assertEquals("gzip, deflated", _val[8]); assertEquals("Accept", _hdr[9]); assertEquals("unknown", _val[9]); - assertEquals(9, _h); + assertEquals(9, _headers); } @Test @@ -263,7 +287,7 @@ public class HttpParserTest assertEquals("gzip, deflated", _val[8]); assertEquals("Accept", _hdr[9]); assertEquals("unknown", _val[9]); - assertEquals(9, _h); + assertEquals(9, _headers); } @@ -313,7 +337,7 @@ public class HttpParserTest assertEquals("gzip, deflated", _val[8]); assertEquals("Accept", _hdr[9]); assertEquals("unknown", _val[9]); - assertEquals(9, _h); + assertEquals(9, _headers); } @Test @@ -338,7 +362,7 @@ public class HttpParserTest 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 @@ public class HttpParserTest assertEquals("localhost", _val[0]); assertEquals("Connection", _hdr[1]); assertEquals("close", _val[1]); - assertEquals(1, _h); + assertEquals(1, _headers); } @Test @@ -422,7 +446,7 @@ public class HttpParserTest assertEquals("localhost", _val[0]); assertEquals("cOnNeCtIoN", _hdr[1]); assertEquals("ClOsE", _val[1]); - assertEquals(1, _h); + assertEquals(1, _headers); } @Test @@ -477,7 +501,7 @@ public class HttpParserTest assertEquals("value4", _val[4]); assertEquals("Server5", _hdr[5]); assertEquals("notServer", _val[5]); - assertEquals(5, _h); + assertEquals(5, _headers); } } @@ -502,13 +526,74 @@ public class HttpParserTest 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 @@ public class HttpParserTest 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 @@ public class HttpParserTest 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,16 +648,85 @@ public class HttpParserTest 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 { @@ -639,7 +793,7 @@ public class HttpParserTest init(); parser.parseNext(buffer); - parser.shutdownInput(); + parser.atEOF(); assertEquals("HTTP/1.1", _methodOrVersion); assertEquals("200", _uriOrStatus); assertEquals("Correct", _versionOrReason); @@ -692,6 +846,29 @@ public class HttpParserTest } @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 @@ public class HttpParserTest + "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 @@ public class HttpParserTest 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 @@ public class HttpParserTest _versionOrReason=null; _hdr=null; _val=null; - _h=0; + _headers=0; _headerCompleted=false; _messageCompleted=false; } @@ -1151,15 +1333,15 @@ public class HttpParserTest 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 class HttpParserTest 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 @@ public class HttpParserTest fields=new HttpFields(); _messageCompleted = false; _headerCompleted = false; + _early=false; return false; } @@ -1196,8 +1378,8 @@ public class HttpParserTest { _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 class HttpParserTest 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 @@ public class HttpParserTest @Override public void earlyEOF() { + _early=true; } @Override diff --git a/jetty-io/pom.xml b/jetty-io/pom.xml index 3b73ef6f08..6a698a7a25 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.6-SNAPSHOT</version> + <version>9.1.0-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 002472abd6..3f7cfc724a 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.Executor; 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 abstract class AbstractConnection implements Connection 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 @@ public abstract class AbstractConnection implements Connection _executor = executor; _readCallback = new ReadCallback(); _executeOnfillable=executeOnfillable; + _state.set(IDLE); } @Override @@ -95,149 +95,32 @@ public abstract class AbstractConnection implements Connection */ public void fillInterested() { - LOG.debug("fillInterested {}",this); - - loop:while(true) + LOG.debug("fillInterested {}",this); + + 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(); + if (next(state,state.fillInterested())) + break; } } - - private void unblock() + public void fillInterested(Callback callback) { - LOG.debug("unblock {}",this); + LOG.debug("fillInterested {}",this); - loop:while(true) + 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; - } + 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; } } - - /** - */ - 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 @@ public abstract class AbstractConnection implements Connection _endPoint.shutdownOutput(); } } + + if (_endPoint.isOpen()) + fillInterested(); } /** @@ -340,73 +226,308 @@ public abstract class AbstractConnection implements Connection { return String.format("%s@%x{%s}", getClass().getSimpleName(), hashCode(), _state.get()); } + + public boolean next(State state, State next) + { + 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 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(); - private enum State + public static final State FILLING_FILL_INTERESTED=new FillingFillInterestedState("FILLING_FILL_INTERESTED"); + + public class NestedState extends State { - IDLE, INTERESTED, FILLING, FILLING_INTERESTED, FILLING_BLOCKED, BLOCKED, FILLING_BLOCKED_INTERESTED, BLOCKED_INTERESTED + 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()); + } } - private class ReadCallback implements Callback, Runnable + + 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 - public void run() + void onEnter(final AbstractConnection connection) { - if (_state.compareAndSet(State.INTERESTED,State.FILLING)) + Callback callback=new Callback() { - try + @Override + public void succeeded() { - onFillable(); + 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(); } - finally + + @Override + public void failed(Throwable x) { - loop:while(true) + 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 = 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() + { + try + { + onFillable(); + } + finally + { + while(true) + { + 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 @@ public abstract class AbstractConnection implements Connection @Override public void run() { + while(true) + { + State state=_state.get(); + if (next(state,state.onFailed())) + break; + } onFillInterestedFailed(x); } }); @@ -425,7 +552,7 @@ public abstract class AbstractConnection implements Connection @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 4ec1d84459..9ec9e60afe 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 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint @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 e515e035d0..263e247a97 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 class ArrayByteBufferPool implements ByteBufferPool 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/ChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java index 95480a5b3f..166748a543 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 @@ -134,7 +134,8 @@ public class ChannelEndPoint extends AbstractEndPoint implements SocketBased 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 @@ public class ChannelEndPoint extends AbstractEndPoint implements SocketBased { throw new EofException(e); } - + if (flushed>0) notIdle(); for (ByteBuffer b : buffers) if (!BufferUtil.isEmpty(b)) return false; - + return true; } 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 1fefe75400..840e175ab2 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 @@ -71,7 +71,7 @@ public interface Connection extends AutoCloseable 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/FillInterest.java b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java index 6af94f59f9..41707b2dc3 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 @@ package org.eclipse.jetty.io; 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 @@ import org.eclipse.jetty.util.Callback; */ 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 @@ public abstract class FillInterest /** 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 @@ public abstract class FillInterest 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 bfbf39d6db..d0de44d225 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 @@ public interface NetworkTrafficListener /** * <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/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java index f8b1327725..a5757579cc 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 @@ -39,6 +39,7 @@ import java.util.concurrent.CountDownLatch; 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; @@ -309,6 +310,11 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa 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 @@ -317,13 +323,11 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa */ 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) { @@ -336,6 +340,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { super.doStart(); _selector = Selector.open(); + _state.set(State.PROCESS); } @Override @@ -357,48 +362,50 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa */ 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) @@ -418,7 +425,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa LOG.debug("Starting {} on {}", _thread, this); while (isRunning()) select(); - processChanges(); + runChanges(); } finally { @@ -437,7 +444,30 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa 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"); @@ -445,7 +475,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa 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) @@ -474,20 +504,6 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa } } - 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(); 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 8b3def6744..6d3c58736c 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 @@ abstract public class WriteFlusher * 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 @@ abstract public class WriteFlusher 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 @@ abstract public class WriteFlusher { 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 @@ abstract public class WriteFlusher 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/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index 05e00c92f5..1576dbeb5b 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 @@ -189,7 +189,7 @@ public class SslConnection extends AbstractConnection // filling. if (DEBUG) - LOG.debug("onFillable enter {}", getEndPoint()); + LOG.debug("onFillable enter {}", _decryptedEndPoint); // We have received a close handshake, close the end point to send FIN. if (_decryptedEndPoint.isInputShutdown()) @@ -210,7 +210,7 @@ public class SslConnection extends AbstractConnection } if (DEBUG) - LOG.debug("onFillable exit {}", getEndPoint()); + LOG.debug("onFillable exit {}", _decryptedEndPoint); } @Override diff --git a/jetty-jaas/pom.xml b/jetty-jaas/pom.xml index 460f25dbfe..313e2184bf 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.6-SNAPSHOT</version> + <version>9.1.0-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/modules/jaas.mod b/jetty-jaas/src/main/config/modules/jaas.mod new file mode 100644 index 0000000000..854d8963e8 --- /dev/null +++ b/jetty-jaas/src/main/config/modules/jaas.mod @@ -0,0 +1,17 @@ +# +# JAAS Feature +# + +[depend] +server + +[lib] +# JAAS jars +lib/jetty-jaas-${jetty.version}.jar + +[xml] +# JAAS configuration +etc/jetty-jaas.xml + +[ini-template] +jaas.login.conf=etc/login.conf diff --git a/jetty-jaspi/pom.xml b/jetty-jaspi/pom.xml index 9f8395d04c..71be42c1fb 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.6-SNAPSHOT</version> + <version>9.1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>jetty-jaspi</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> <Export-Package>org.eclipse.jetty.security.jaspi.*;version="${parsedVersion.osgiVersion}"</Export-Package> </instructions> </configuration> diff --git a/jetty-jmx/pom.xml b/jetty-jmx/pom.xml index b196615a7b..f048e28d18 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.6-SNAPSHOT</version> + <version>9.1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>jetty-jmx</artifactId> 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 0000000000..6ac0a2f429 --- /dev/null +++ b/jetty-jmx/src/main/config/modules/jmx.mod @@ -0,0 +1,16 @@ +# +# JMX Feature +# + +[lib] +# JMX jars (as defined in start.config) +lib/jetty-jmx-${jetty.version}.jar + +[xml] +# JMX configuration +etc/jetty-jmx.xml + +[ini-template] +# jetty.jmxrmihost=localhost +# jetty.jmxrmiport=1099 +# -Dcom.sun.management.jmxremote 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 e74baf4fa2..071b4a1d4d 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 @@ public class ObjectMBean implements DynamicMBean 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 @@ public class ObjectMBean implements DynamicMBean * 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 fe144186a7..0f088b669e 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.6-SNAPSHOT</version> + <version>9.1.0-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 0000000000..33c077ce68 --- /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 c56b694a5b..20ecfd16ea 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 @@ public class ContextFactory implements ObjectFactory * @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 @@ public class ContextFactory implements ObjectFactory /** * Find the naming Context for the given classloader * @param loader - * @return */ public Context getContextForClassLoader(ClassLoader loader) { diff --git a/jetty-jsp/pom.xml b/jetty-jsp/pom.xml index cc75fdbaac..04fa8c5d34 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.6-SNAPSHOT</version> + <version>9.1.0-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 0000000000..f85530d3c8 --- /dev/null +++ b/jetty-jsp/src/main/config/modules/jsp.mod @@ -0,0 +1,10 @@ +# +# Jetty Servlet Module +# + +[depend] +servlet + +[lib] +lib/jsp/*.jar + diff --git a/jetty-jspc-maven-plugin/pom.xml b/jetty-jspc-maven-plugin/pom.xml index 48f3d8208b..26c5aa5772 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.6-SNAPSHOT</version> + <version>9.1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>jetty-jspc-maven-plugin</artifactId> diff --git a/jetty-maven-plugin/pom.xml b/jetty-maven-plugin/pom.xml index de9a5c032b..133832c6ab 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.6-SNAPSHOT</version> + <version>9.1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>jetty-maven-plugin</artifactId> @@ -120,11 +120,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-monitor/README.txt b/jetty-monitor/README.TXT index 72c7da114f..72c7da114f 100644 --- a/jetty-monitor/README.txt +++ b/jetty-monitor/README.TXT diff --git a/jetty-monitor/pom.xml b/jetty-monitor/pom.xml index 837697ee7d..c95b52a9e2 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.6-SNAPSHOT</version> + <version>9.1.0-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 0000000000..67f006d0c4 --- /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 e94eed36c1..2071498c3d 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 ccf1e8ba31..ebfa3c9d39 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.6-SNAPSHOT</version> + <version>9.1.0-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 0000000000..a4189c945b --- /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 f8027958a8..d5ebdd2938 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 @@ public abstract class NoSqlSessionManager extends AbstractSessionManager impleme __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 a11b3578a5..d4bc575a19 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.6-SNAPSHOT</version> + <version>9.1.0-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 76b161b993..a2ad1f2830 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.6-SNAPSHOT</version> + <version>9.1.0-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 10c55dc769..8928798ecb 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.6-SNAPSHOT</version> + <version>9.1.0-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-httpservice/pom.xml b/jetty-osgi/jetty-osgi-httpservice/pom.xml index c080cf1759..8a68dd5350 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.6-SNAPSHOT</version> + <version>9.1.0-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 1f62e2bca7..dea1db4b29 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.6-SNAPSHOT</version> + <version>9.1.0-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 f50ed4d9fe..3bd94088d7 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.6-SNAPSHOT</version> + <version>9.1.0-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 50f795ab9c..48d69e6a3f 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.6-SNAPSHOT</version> + <version>9.1.0-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 a1afbe108f..b41c51405a 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.6-SNAPSHOT</version> + <version>9.1.0-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 03af2c19d2..6496b4110c 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.6-SNAPSHOT</version> + <version>9.1.0-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> <modelVersion>4.0.0</modelVersion> @@ -152,8 +152,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 --> @@ -171,6 +171,12 @@ <scope>provided</scope> </dependency> <dependency> + <groupId>org.eclipse.jetty.toolchain</groupId> + <artifactId>jetty-jsp-fragment</artifactId> + <version>2.3.2</version> + <scope>provided</scope> + </dependency> + <dependency> <groupId>org.eclipse.jetty.osgi</groupId> <artifactId>jetty-httpservice</artifactId> <version>${project.version}</version> @@ -261,6 +267,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> @@ -298,6 +310,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 b15e002f0c..d3e980d755 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 java.util.List; 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 @@ public class TestJettyOSGiBootContextAsService TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext); } + /** */ @Test @@ -133,10 +135,10 @@ public class TestJettyOSGiBootContextAsService { 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 @@ public class TestJettyOSGiBootContextAsService } 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 @@ public class TestJettyOSGiBootContextAsService 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 aaf9fd1cd5..ca99c64fda 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 @@ public class TestJettyOSGiBootCore 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 @@ public class TestJettyOSGiBootCore 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 @@ public class TestJettyOSGiBootCore { 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 a99d74452d..c17838a158 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 @@ public class TestJettyOSGiBootSpdy 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 be7cd780b4..9510412e68 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 java.util.List; 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 @@ public class TestJettyOSGiBootWebAppAsService 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 @@ public class TestJettyOSGiBootWebAppAsService options.addAll(jspDependencies()); return options.toArray(new Option[options.size()]); - } + public static List<Option> configureJettyHomeAndPort(String jettySelectorFileName) { @@ -119,16 +119,14 @@ public class TestJettyOSGiBootWebAppAsService 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 @@ public class TestJettyOSGiBootWebAppAsService @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 @@ public class TestJettyOSGiBootWebAppAsService } 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 3c23e22c77..45e80fcb55 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 java.util.List; 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 class TestJettyOSGiBootWithJsp 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 @@ public class TestJettyOSGiBootWithJsp { 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 d18914e2e6..a531f464f5 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.6-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 0000000000..5e73f6bc29 --- /dev/null +++ b/jetty-overlay-deployer/src/main/config/modules/overlay.mod @@ -0,0 +1,13 @@ +# +# Jetty Overlay module +# + +[depend] +deploy + +[lib] +lib/jetty-overlay-deployer-${jetty.version}.jar + +[xml] +# Plus requires configuration +etc/jetty-overlay.xml diff --git a/jetty-plus/pom.xml b/jetty-plus/pom.xml index eedbba5a2b..bcd6f5c84d 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.6-SNAPSHOT</version> + <version>9.1.0-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 0000000000..b781f00bf2 --- /dev/null +++ b/jetty-plus/src/main/config/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-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 9d93203c92..6ac2102db4 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 @@ -22,6 +22,7 @@ import java.util.HashSet; import java.util.Set; import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContextListener; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.webapp.WebAppContext; @@ -101,11 +102,12 @@ public class ContainerInitializer for (String s : _applicableTypeNames) classes.add(Loader.loadClass(context.getClass(), s)); } - + context.getServletContext().setExtendedListenerTypes(true); _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 de3cae74ea..ab0c9546c2 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 @@ public class RunAs } + /** + * @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 b43ca8666d..b62cf433b8 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 @@ -52,7 +52,6 @@ public class RunAsCollection } public RunAs getRunAs (Object o) - throws ServletException { if (o==null) return null; @@ -61,7 +60,6 @@ public class RunAsCollection } 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 68f50bcb3b..a61ff286a0 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,18 @@ 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 +44,7 @@ public class PlusDecorator implements Decorator _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 +64,13 @@ public class PlusDecorator implements Decorator } 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-proxy/pom.xml b/jetty-proxy/pom.xml index 4b2432a5bf..de26eee605 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.6-SNAPSHOT</version> + <version>9.1.0-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/modules/proxy.mod b/jetty-proxy/src/main/config/modules/proxy.mod new file mode 100644 index 0000000000..7873329afa --- /dev/null +++ b/jetty-proxy/src/main/config/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-proxy/src/main/java/org/eclipse/jetty/proxy/BalancerServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/BalancerServlet.java index 44a323bf2f..8e371835de 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 @@ public class BalancerServlet extends ProxyServlet 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/ProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java index 3c1c8e1c3b..a283b0d640 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 @@ -629,7 +629,7 @@ public class ProxyServlet extends HttpServlet } } - 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/ProxyTunnellingTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java index e4de3f3e1d..01fa37787d 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 @@ -375,7 +375,7 @@ public class ProxyTunnellingTest } @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/ diff --git a/jetty-rewrite/pom.xml b/jetty-rewrite/pom.xml index 1149c4bfbe..e1b5f85446 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.6-SNAPSHOT</version> + <version>9.1.0-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 0000000000..85fe5f090d --- /dev/null +++ b/jetty-rewrite/src/main/config/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-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 0000000000..25dc5c2f74 --- /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/RewriteHandler.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java index 4f15f42e20..662c0a0959 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 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; * <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 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; * </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-rhttp/README.txt b/jetty-rhttp/README.TXT index 46ca4e743e..46ca4e743e 100644 --- a/jetty-rhttp/README.txt +++ b/jetty-rhttp/README.TXT diff --git a/jetty-rhttp/jetty-rhttp-connector/pom.xml b/jetty-rhttp/jetty-rhttp-connector/pom.xml index 95b8d9c8ad..b248c48210 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 1243cd83e2..054bbd55bf 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 753cba588e..96f41be149 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.6-SNAPSHOT</version> + <version>9.1.0-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 f9eb10b684..b0f21bc16a 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.6-SNAPSHOT</version> + <version>9.1.0-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 0000000000..ba3163275f --- /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 0000000000..ab20ba85ab --- /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 2e079db390..c487319de0 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 @@ public interface Authenticator * @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 bec6764904..d5c74a6682 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 @@ public interface ConstraintAware * @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 fd9a415d66..d68a548ec2 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.HttpConfiguration; 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 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr /* ------------------------------------------------------------ */ /** * @param constraint - * @return */ public static Constraint createConstraint(Constraint constraint) { @@ -97,7 +100,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr * @param authenticate * @param roles * @param dataConstraint - * @return */ public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint) { @@ -115,7 +117,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr /** * @param name * @param element - * @return */ public static Constraint createConstraint (String name, HttpConstraintElement element) { @@ -129,7 +130,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr * @param rolesAllowed * @param permitOrDeny * @param transport - * @return */ public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport) { @@ -169,7 +169,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr /** * @param pathSpec * @param constraintMappings - * @return */ public static List<ConstraintMapping> getConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings) { @@ -194,7 +193,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr * * @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 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr 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); - - //Create a mapping for the pathSpec for the default case - ConstraintMapping defaultMapping = new ConstraintMapping(); - defaultMapping.setPathSpec(pathSpec); - defaultMapping.setConstraint(constraint); - mappings.add(defaultMapping); + 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 + 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 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr * 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 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr { _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 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr @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 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr 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 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr /* ------------------------------------------------------------ */ /** 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 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr sb.append(omissions[i]); } sb.append(OMISSION_SUFFIX); - RoleInfo ri = new RoleInfo(); mappings.put(sb.toString(), ri); configureRoleInfo(ri, mapping); @@ -573,7 +562,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr * @param mapping */ protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping) - { + { Constraint constraint = mapping.getConstraint(); boolean forbidden = constraint.isForbidden(); ri.setForbidden(forbidden); @@ -582,7 +571,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr //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 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr //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 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr * 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 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr 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 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr } } + return roleInfo; } @@ -693,7 +690,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration(); - if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral) { if (request.isSecure()) @@ -750,14 +746,35 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr 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 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr 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 dd12b1d911..52e93ba3dc 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 class DefaultUserIdentity implements UserIdentity } 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 7c99a3e65a..ed0a5f15dc 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 class HashCrossContextPsuedoSession<T> implements CrossContextPsuedoSessi 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/RoleInfo.java b/jetty-security/src/main/java/org/eclipse/jetty/security/RoleInfo.java index 3168e78fa4..5fcab95780 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.Set; 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 @@ import java.util.concurrent.CopyOnWriteArraySet; */ 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 @@ public class RoleInfo _forbidden=false; _roles.clear(); _isAnyRole=false; + _isAnyAuth=false; } } @@ -71,6 +77,7 @@ public class RoleInfo _checked = true; _userDataConstraint = null; _isAnyRole=false; + _isAnyAuth=false; _roles.clear(); } } @@ -84,10 +91,19 @@ public class RoleInfo { 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 @@ public class RoleInfo if (userDataConstraint == null) throw new NullPointerException("Null UserDataConstraint"); if (this._userDataConstraint == null) { + this._userDataConstraint = userDataConstraint; } else @@ -126,6 +143,8 @@ public class RoleInfo 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 @@ public class RoleInfo @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 df8a4eb12c..ef31416866 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 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti 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 3d8dcbafa4..58847bf911 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/DeferredAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java index b1a2c6061e..3853ebd6de 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 java.util.Locale; 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 @@ public class DeferredAuthentication implements Authentication.Deferred /* ------------------------------------------------------------ */ /** - * @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 class DeferredAuthentication implements Authentication.Deferred public void setContentLength(int len) { } + + public void setContentLengthLong(long len) + { + + } @Override public void setContentType(String type) @@ -348,6 +354,7 @@ public class DeferredAuthentication implements Authentication.Deferred return 0; } + }; /* ------------------------------------------------------------ */ @@ -355,17 +362,33 @@ public class DeferredAuthentication implements Authentication.Deferred /* ------------------------------------------------------------ */ 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/FormAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java index 71bba4abad..0de728841e 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 javax.servlet.http.HttpSession; 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; 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 class FormAuthenticator extends LoginAuthenticator 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 @@ public class FormAuthenticator extends LoginAuthenticator } 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 @@ public class FormAuthenticator extends LoginAuthenticator 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 @@ public class FormAuthenticator extends LoginAuthenticator 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 @@ public class FormAuthenticator extends LoginAuthenticator 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) - { - LOG.debug("auth rePOST {}->{}",authentication,j_uri); - StringBuffer buf = request.getRequestURL(); - if (request.getQueryString() != null) - buf.append("?").append(request.getQueryString()); + StringBuffer buf = request.getRequestURL(); + if (request.getQueryString() != null) + buf.append("?").append(request.getQueryString()); - if (j_uri.equals(buf.toString())) + 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 @@ public class FormAuthenticator extends LoginAuthenticator 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 @@ public class FormAuthenticator extends LoginAuthenticator 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 51ad8e9b9d..181c0d9090 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java @@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.Authenticator.AuthConfiguration; import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.server.Request; @@ -40,11 +41,20 @@ public abstract class LoginAuthenticator implements Authenticator 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 +68,7 @@ public abstract class LoginAuthenticator implements Authenticator return null; } - + /* ------------------------------------------------------------ */ @Override public void setConfiguration(AuthConfiguration configuration) { @@ -70,12 +80,16 @@ public abstract class LoginAuthenticator implements Authenticator 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 ab0888e6c1..dd4c31a1d2 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.HttpSessionBindingEvent; 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 class SessionAuthentication implements Authentication.User, Serializable, 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 a1480b4f0e..42ddacfe62 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; @@ -28,6 +30,7 @@ import java.io.IOException; 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; @@ -37,7 +40,12 @@ import java.util.concurrent.TimeUnit; 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; @@ -88,7 +96,8 @@ public class ConstraintTest 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"}); @@ -180,7 +189,16 @@ public class ConstraintTest 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 @@ -208,6 +226,224 @@ public class ConstraintTest 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 @@ -252,7 +488,6 @@ public class ConstraintTest _security.setAuthenticator(new BasicAuthenticator()); - _security.setStrict(false); _server.start(); String response; @@ -377,7 +612,6 @@ public class ConstraintTest DigestAuthenticator authenticator = new DigestAuthenticator(); authenticator.setMaxNonceCount(5); _security.setAuthenticator(authenticator); - _security.setStrict(false); _server.start(); String response; @@ -464,7 +698,6 @@ public class ConstraintTest public void testFormDispatch() throws Exception { _security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",true)); - _security.setStrict(false); _server.start(); String response; @@ -519,7 +752,6 @@ public class ConstraintTest public void testFormRedirect() throws Exception { _security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false)); - _security.setStrict(false); _server.start(); String response; @@ -576,7 +808,6 @@ public class ConstraintTest public void testFormPostRedirect() throws Exception { _security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false)); - _security.setStrict(false); _server.start(); String response; @@ -608,6 +839,7 @@ public class ConstraintTest "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" + @@ -646,7 +878,6 @@ public class ConstraintTest public void testFormNoCookies() throws Exception { _security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false)); - _security.setStrict(false); _server.start(); String response; @@ -719,7 +950,7 @@ public class ConstraintTest 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")); @@ -793,9 +1024,9 @@ public class ConstraintTest 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")); @@ -904,9 +1135,9 @@ public class ConstraintTest 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")); @@ -949,12 +1180,35 @@ public class ConstraintTest "\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 @@ -1033,7 +1287,6 @@ public class ConstraintTest RoleCheckHandler check=new RoleCheckHandler(); _security.setHandler(check); _security.setAuthenticator(new BasicAuthenticator()); - _security.setStrict(false); _server.start(); @@ -1065,7 +1318,6 @@ public class ConstraintTest public void testDeferredBasic() throws Exception { _security.setAuthenticator(new BasicAuthenticator()); - _security.setStrict(false); _server.start(); String response; @@ -1092,7 +1344,6 @@ public class ConstraintTest 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 1d656ae67a..ff200516ba 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 @@ public class SpecExampleConstraintTest } } + @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 @@ public class SpecExampleConstraintTest { _security.setAuthenticator(new BasicAuthenticator()); - _security.setStrict(false); _server.start(); String response; diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml index e980573cac..e3dcd9e166 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.6-SNAPSHOT</version> + <version>9.1.0-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 7ae3064a09..0881379bd1 100644 --- a/jetty-server/src/main/config/etc/jetty-http.xml +++ b/jetty-server/src/main/config/etc/jetty-http.xml @@ -32,8 +32,8 @@ </Array> </Arg> <Set name="host"><Property name="jetty.host" /></Set> - <Set name="port"><Property name="jetty.port" default="8080" /></Set> - <Set name="idleTimeout"><Property name="http.timeout" default="30000"/></Set> + <Set name="port"><Property name="jetty.port" default="80" /></Set> + <Set name="idleTimeout"><Property name="http.timeout" default="0"/></Set> </New> </Arg> </Call> diff --git a/jetty-server/src/main/config/etc/jetty-https.xml b/jetty-server/src/main/config/etc/jetty-https.xml index a6bef16ff1..5a4cf86d23 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="0"/></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 deef173687..85f6b9c684 100644 --- a/jetty-server/src/main/config/etc/jetty-ipaccess.xml +++ b/jetty-server/src/main/config/etc/jetty-ipaccess.xml @@ -15,17 +15,16 @@ <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> </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 213177731a..7b1b241b73 100644 --- a/jetty-server/src/main/config/etc/jetty-requestlog.xml +++ b/jetty-server/src/main/config/etc/jetty-requestlog.xml @@ -15,7 +15,7 @@ <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> diff --git a/jetty-server/src/main/config/etc/jetty-ssl.xml b/jetty-server/src/main/config/etc/jetty-ssl.xml index b4c3551aad..8eef03d999 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/modules/debug.mod b/jetty-server/src/main/config/modules/debug.mod new file mode 100644 index 0000000000..f740ea2c76 --- /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 0000000000..3af9897c03 --- /dev/null +++ b/jetty-server/src/main/config/modules/ext.mod @@ -0,0 +1,2 @@ +[lib] +lib/ext/*.jar 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 0000000000..7588ce291e --- /dev/null +++ b/jetty-server/src/main/config/modules/http.mod @@ -0,0 +1,13 @@ +# +# Jetty HTTP Connector +# + +[depend] +server + +[xml] +etc/jetty-http.xml + +[ini-template] +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 0000000000..c01652339b --- /dev/null +++ b/jetty-server/src/main/config/modules/https.mod @@ -0,0 +1,13 @@ +# +# Jetty HTTPS Connector +# + +[depend] +ssl + +[xml] +etc/jetty-https.xml + +[ini-template] +https.port=8443 +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 0000000000..956ea0f2e3 --- /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 0000000000..7f9dfb965c --- /dev/null +++ b/jetty-server/src/main/config/modules/jvm.mod @@ -0,0 +1,24 @@ +[ini-template] +#=========================================================== +# 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 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 0000000000..f743c642ca --- /dev/null +++ b/jetty-server/src/main/config/modules/lowresources.mod @@ -0,0 +1,17 @@ +# +# Low Resources module +# + +[depend] +server + +[xml] +etc/jetty-lowresources.xml + +[ini-template] +# 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 0000000000..39731ef4a6 --- /dev/null +++ b/jetty-server/src/main/config/modules/requestlog.mod @@ -0,0 +1,17 @@ +# +# Request Log module +# + +[depend] +server + +[xml] +etc/jetty-requestlog.xml + +[files] +logs/ + +[ini-template] +# requestlog.retain=90 +# requestlog.append=true +# requestlog.extended=true 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 0000000000..2064da718b --- /dev/null +++ b/jetty-server/src/main/config/modules/resources.mod @@ -0,0 +1,2 @@ +[lib] +resources
\ No newline at end of file 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 0000000000..74c4e53887 --- /dev/null +++ b/jetty-server/src/main/config/modules/server.mod @@ -0,0 +1,33 @@ +# +# Base server +# + +[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-continuation-${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] +# Annotations needs annotations configuration +etc/jetty.xml + +[ini-template] +threads.min=10 +threads.max=200 +threads.timeout=60000 +#jetty.host=myhost.com +jetty.dump.start=false +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 0000000000..325d5b8d33 --- /dev/null +++ b/jetty-server/src/main/config/modules/ssl.mod @@ -0,0 +1,26 @@ + +# 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] +# 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 +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 0000000000..0922469cdf --- /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 0000000000..fdc1b3c7b0 --- /dev/null +++ b/jetty-server/src/main/config/modules/xinetd.mod @@ -0,0 +1,9 @@ +# +# Stats module +# + +[depend] +server + +[xml] +etc/jetty-xinetd.xml 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 28fea907e5..0747b01c52 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 @@ -268,10 +268,13 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co 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 abstract class AbstractConnector extends ContainerLifeCycle implements Co 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 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co if (isAccepting()) LOG.warn(e); else - LOG.debug(e); + LOG.ignore(e); } } } 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 876e7aeae3..2538f41bd3 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.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import org.eclipse.jetty.server.handler.ContextHandler; + public class AsyncContextState implements AsyncContext { @@ -92,17 +94,20 @@ public class AsyncContextState implements AsyncContext @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/ByteBufferHttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java index 90d81d90e2..db5e841927 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferHttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java @@ -20,10 +20,12 @@ package org.eclipse.jetty.server; import java.nio.ByteBuffer; +import javax.servlet.ReadListener; + /** * <p>An implementation of HttpInput using {@link ByteBuffer} as items.</p> */ -public class ByteBufferHttpInput extends HttpInput<ByteBuffer> +public class ByteBufferQueuedHttpInput extends QueuedHttpInput<ByteBuffer> { @Override protected int remaining(ByteBuffer item) @@ -38,9 +40,16 @@ public class ByteBufferHttpInput extends HttpInput<ByteBuffer> 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/EncodingHttpWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/EncodingHttpWriter.java index 7725a342eb..fd8c1c5eea 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 class EncodingHttpWriter extends HttpWriter 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 608a861d7f..845cbc8110 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 @@ import org.eclipse.jetty.server.HttpConfiguration.Customizer; * 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 @@ public class ForwardedRequestCustomizer implements Customizer /* ------------------------------------------------------------ */ /** * 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 @@ public class ForwardedRequestCustomizer implements Customizer /** * @param forwardedHostHeader * The header name for forwarded hosts (default x-forwarded-host) - * @see #setForwarded(boolean) */ public void setForwardedHostHeader(String forwardedHostHeader) { @@ -100,7 +98,6 @@ public class ForwardedRequestCustomizer implements Customizer /* ------------------------------------------------------------ */ /** * @return the header name for forwarded server. - * @see #setForwarded(boolean) */ public String getForwardedServerHeader() { @@ -111,7 +108,6 @@ public class ForwardedRequestCustomizer implements Customizer /** * @param forwardedServerHeader * The header name for forwarded server (default x-forwarded-server) - * @see #setForwarded(boolean) */ public void setForwardedServerHeader(String forwardedServerHeader) { @@ -121,7 +117,6 @@ public class ForwardedRequestCustomizer implements Customizer /* ------------------------------------------------------------ */ /** * @return the forwarded for header - * @see #setForwarded(boolean) */ public String getForwardedForHeader() { @@ -132,7 +127,6 @@ public class ForwardedRequestCustomizer implements Customizer /** * @param forwardedRemoteAddressHeader * The header name for forwarded for (default x-forwarded-for) - * @see #setForwarded(boolean) */ public void setForwardedForHeader(String forwardedRemoteAddressHeader) { @@ -144,7 +138,6 @@ public class ForwardedRequestCustomizer implements Customizer * Get the forwardedProtoHeader. * * @return the forwardedProtoHeader (default X-Forwarded-For) - * @see #setForwarded(boolean) */ public String getForwardedProtoHeader() { @@ -157,7 +150,6 @@ public class ForwardedRequestCustomizer implements Customizer * * @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 7cedf160ad..bb96ef689c 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 @@ public interface Handler extends LifeCycle, Destroyable * @param target The target of the request - either a URI or a name. * @param baseRequest The original unwrapped request object. * @param request The request either as the {@link Request} - * object or a wrapper of that request. The {@link 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/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index de338c13db..932135d2a2 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 @@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; +import javax.servlet.WriteListener; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; @@ -43,7 +44,8 @@ import org.eclipse.jetty.io.ByteBufferPool; 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; @@ -105,6 +107,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable _uri = new HttpURI(URIUtil.__CHARSET); _state = new HttpChannelState(this); + input.init(_state); _request = new Request(this, input); _response = new Response(this, new HttpOutput(this)); } @@ -250,36 +253,69 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable // 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()) { 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); - - for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers()) - customizer.customize(getConnector(),_configuration,_request); - getServer().handle(this); - } - else - { - if (_request.getHttpChannelState().isExpired()) - { + 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); + 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); _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500)); _request.setAttribute(RequestDispatcher.ERROR_MESSAGE,"Async Timeout"); _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,_request.getRequestURI()); _response.setStatusWithReason(500,"Async Timeout"); + + 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) @@ -301,7 +337,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable } finally { - next = _state.unhandle(); + action = _state.unhandle(); } } @@ -309,7 +345,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable Thread.currentThread().setName(threadName); setCurrentHttpChannel(null); - if (next==Next.COMPLETE) + if (action==Action.COMPLETE) { try { @@ -327,7 +363,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable } catch(Exception e) { - LOG.warn(e); + LOG.warn("handle complete",e); } finally { @@ -336,9 +372,9 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable } } - LOG.debug("{} handle exit, result {}", this, next); + LOG.debug("{} handle exit, result {}", this, action); - return next!=Next.WAIT; + return action!=Action.WAIT; } /** @@ -576,7 +612,8 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable @Override public boolean messageComplete() { - _request.getHttpInput().shutdown(); + LOG.debug("{} messageComplete", this); + _request.getHttpInput().messageComplete(); return true; } @@ -594,17 +631,19 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable 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(e); + LOG.debug(e); } finally { - if (_state.unhandle()==Next.COMPLETE) + if (_state.unhandle()==Action.COMPLETE) _state.completed(); + else + throw new IllegalStateException(); } } @@ -727,7 +766,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable } else { - LOG.warn(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 127bafa3c1..470acc0a02 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 @@ -21,7 +21,6 @@ package org.eclipse.jetty.server; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; - import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.RequestDispatcher; @@ -62,32 +61,41 @@ public class HttpChannelState { 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 + { + 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 { - CONTINUE, // Continue handling the channel - WAIT, // Wait for further events - COMPLETE // Complete the channel + 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 @@ public class HttpChannelState { _channel=channel; _state=State.IDLE; + _async=null; _initial=true; } @@ -145,7 +154,7 @@ public class HttpChannelState { synchronized (this) { - return super.toString()+"@"+getStatusString(); + return String.format("%s@%x{s=%s i=%b a=%s}",getClass().getSimpleName(),hashCode(),_state,_initial,_async); } } @@ -153,97 +162,102 @@ public class HttpChannelState { 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) { @@ -269,39 +283,63 @@ public class HttpChannelState * @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: + case ASYNCIO: + break; + default: 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; + if (_asyncRead) + { + _state=State.ASYNCIO; + _asyncRead=false; + return Action.READ_CALLBACK; + } + + if (_asyncWrite) + { + _asyncWrite=false; + _state=State.ASYNCIO; + return Action.WRITE_CALLBACK; + } - default: - throw new IllegalStateException(this.getStatusString()); + 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; } } @@ -310,39 +348,26 @@ public class HttpChannelState boolean dispatch; synchronized (this) { + if (_async!=Async.STARTED && _async!=Async.EXPIRING) + throw new IllegalStateException("AsyncContext#dispath "+this.getStatusString()); + _async=Async.DISPATCH; + _event.setDispatchTarget(context,path); + switch(_state) { - case ASYNCSTARTED: - _state=State.REDISPATCHING; - _event.setDispatchTarget(context,path); - _dispatched=true; - return; - - case ASYNCWAIT: - dispatch=!_expired; - _state=State.REDISPATCH; - _event.setDispatchTarget(context,path); - _dispatched=true; + case DISPATCHED: + case ASYNCIO: + dispatch=false; break; - default: - throw new IllegalStateException(this.getStatusString()); + dispatch=true; + break; } } + cancelTimeout(); if (dispatch) - { - cancelTimeout(); scheduleDispatch(); - } - } - - public boolean isDispatched() - { - synchronized (this) - { - return _dispatched; - } } protected void expired() @@ -351,17 +376,11 @@ public class HttpChannelState AsyncEvent 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) @@ -378,22 +397,20 @@ public class HttpChannelState } } } - + + 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() @@ -402,30 +419,15 @@ public class HttpChannelState 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); @@ -453,30 +455,34 @@ public class HttpChannelState } } - 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() @@ -486,17 +492,19 @@ public class HttpChannelState 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; } } @@ -515,7 +523,11 @@ public class HttpChannelState protected void cancelTimeout() { - AsyncContextEvent event=_event; + final AsyncContextEvent event; + synchronized (this) + { + event=_event; + } if (event!=null) event.cancelTimeoutTask(); } @@ -524,7 +536,7 @@ public class HttpChannelState { synchronized (this) { - return _expired; + return _async==Async.EXPIRED; } } @@ -540,17 +552,7 @@ public class HttpChannelState { 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; } } @@ -573,18 +575,10 @@ public class HttpChannelState 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; } } @@ -592,19 +586,7 @@ public class HttpChannelState { 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; } } @@ -620,7 +602,12 @@ public class HttpChannelState public ContextHandler getContextHandler() { - final AsyncContextEvent event=_event; + final AsyncContextEvent event; + synchronized (this) + { + event=_event; + } + if (event!=null) { Context context=((Context)event.getServletContext()); @@ -632,8 +619,13 @@ public class HttpChannelState 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(); } @@ -652,6 +644,34 @@ public class HttpChannelState _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 d56fdd3ddc..1518b75894 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,9 +18,7 @@ package org.eclipse.jetty.server; -import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.channels.ClosedChannelException; import java.util.concurrent.RejectedExecutionException; import org.eclipse.jetty.http.HttpGenerator; @@ -39,7 +37,7 @@ 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; @@ -63,7 +61,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http private final HttpParser _parser; private volatile ByteBuffer _requestBuffer = null; private volatile ByteBuffer _chunk = null; - private BlockingCallback _readBlocker = new BlockingCallback(); private BlockingCallback _writeBlocker = new BlockingCallback(); @@ -92,9 +89,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http _connector = connector; _bufferPool = _connector.getByteBufferPool(); _generator = new HttpGenerator(_config.getSendServerVersion(),_config.getSendXPoweredBy()); - _channel = new HttpChannelOverHttp(connector, config, endPoint, this, new Input()); + _channel = new HttpChannelOverHttp(connector, config, endPoint, this, new HttpInputOverHTTP(this)); _parser = newHttpParser(); - LOG.debug("New HTTP Connection {}", this); } @@ -123,40 +119,11 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http 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 @@ -171,16 +138,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http 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()) { @@ -189,6 +147,13 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http _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> @@ -204,77 +169,53 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http 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); - // 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; - } - 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) + // fill 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; + if (filled==0) // Do a retry on fill 0 (optimization for SSL connections) + filled = getEndPoint().fill(_requestBuffer); + + // tell parser + if (filled < 0) + _parser.atEOF(); } } - else + + // Parse the buffer + if (_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer)) { - // 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; + // 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. + suspended = !_channel.handle(); } + } } catch (EofException e) @@ -290,10 +231,22 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http 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() @@ -308,32 +261,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http 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) @@ -359,11 +286,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http @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) { @@ -374,34 +296,58 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http 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); @@ -413,139 +359,34 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http getEndPoint().close(); } } + // else the parser must be closed, so seek the EOF if we are still open + else if (getEndPoint().isOpen()) + fillInterested(); } } - public ByteBuffer getRequestBuffer() - { - return _requestBuffer; - } - - private class Input extends ByteBufferHttpInput + private class HttpChannelOverHttp extends HttpChannel<ByteBuffer> { - @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() + public HttpChannelOverHttp(Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input) { - synchronized (lock()) - { - _inputEOF=true; - LOG.debug("{} shutdown", this); - } + super(connector,config,endPoint,transport,input); } @Override - protected void onAllContentConsumed() + public void earlyEOF() { - /* 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(); + // If we have no request yet, just close + if (getRequest().getMethod()==null) + close(); + else + super.earlyEOF(); } @Override - public String toString() + public boolean content(ByteBuffer item) { - return super.toString()+"{"+_channel+","+HttpConnection.this+"}"; - } - } - - private class HttpChannelOverHttp extends HttpChannel<ByteBuffer> - { - public HttpChannelOverHttp(Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input) - { - super(connector,config,endPoint,transport,input); + super.content(item); + return true; } @Override @@ -612,7 +453,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http } } - private class CommitCallback extends IteratingCallback + private class CommitCallback extends IteratingNestedCallback { final ByteBuffer _content; final boolean _lastContent; @@ -733,7 +574,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http } } - private class ContentCallback extends IteratingCallback + private class ContentCallback extends IteratingNestedCallback { final ByteBuffer _content; final boolean _lastContent; @@ -814,4 +655,5 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http } } } + } 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 bbed8bd2fa..b06612fddf 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,11 @@ 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,35 +37,82 @@ import org.eclipse.jetty.util.log.Logger; * {@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 HttpChannelState _channelState; + private Throwable _onError; + private ReadListener _listener; + private boolean _notReady; + + protected State _state = BLOCKING; + private State _eof=null; + private final Object _lock; + + 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); + _state = BLOCKING; + _eof=null; + _onError=null; + } + } - item = _inputQ.peekUnsafe(); - if (item == null) - onAllContentConsumed(); - } - _inputEOF = false; - _earlyEOF = false; + /** + * 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 @@ -81,12 +126,17 @@ public abstract class HttpInput<T> extends ServletInputStream @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); } } @@ -96,200 +146,299 @@ public abstract class HttpInput<T> extends ServletInputStream T item = null; 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(); } } + return get(item, b, off, len); } 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 abstract void blockForContent() throws IOException; - protected void blockForContent() throws IOException + protected boolean onAsyncRead() { - 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); - } - } - } + if (_listener==null) + return false; + _channelState.onReadPossible(); + return true; } /* ------------------------------------------------------------ */ - /** Called by this HttpInput to signal new content has been queued + /** Add some content to the input stream * @param item */ - protected void onContentQueued(T item) - { - lock().notify(); - } + public abstract void content(T item); + /* ------------------------------------------------------------ */ - /** Called by this HttpInput to signal all available content has been consumed + /** 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 + * from a subsequent read rather than a -1 return. */ - protected void onAllContentConsumed() + public void earlyEOF() { + synchronized (lock()) + { + if (_eof==null || !_eof.isEOF()) + { + LOG.debug("{} early EOF", this); + _eof=EARLY_EOF; + if (_listener!=null) + _channelState.onReadPossible(); + } + } } /* ------------------------------------------------------------ */ - /** Called by this HttpInput to signal it has reached EOF - */ - protected void onEOF() + public void messageComplete() { + synchronized (lock()) + { + if (_eof==null || !_eof.isEOF()) + { + LOG.debug("{} EOF", this); + _eof=EOF; + if (_listener!=null) + _channelState.onReadPossible(); + } + } } /* ------------------------------------------------------------ */ - /** Add some content to the input stream - * @param item - */ - public void content(T item) + public void consumeAll() { 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); + try + { + while (!isFinished()) + { + T item = getNextContent(); + if (item==null) + _state.waitForContent(this); + else + consume(item,remaining(item)); + } + } + catch (IOException e) + { + LOG.debug(e); + } } } - /* ------------------------------------------------------------ */ - /** 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 - * from a subsequent read rather than a -1 return. - */ - public void earlyEOF() + @Override + public boolean isFinished() { synchronized (lock()) { - _earlyEOF = true; - _inputEOF = true; - lock().notify(); - LOG.debug("{} early EOF", this); + return _state.isEOF(); } } - /* ------------------------------------------------------------ */ - public boolean isEarlyEOF() + @Override + public boolean isReady() { synchronized (lock()) { - return _earlyEOF; + 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; } } - /* ------------------------------------------------------------ */ - public void shutdown() + protected void unready() { + } + + @Override + public void setReadListener(ReadListener readListener) + { + if (readListener==null) + throw new NullPointerException("readListener==null"); synchronized (lock()) { - _inputEOF = true; - lock().notify(); - LOG.debug("{} shutdown", this); + if (_state!=BLOCKING) + throw new IllegalStateException("state="+_state); + _state=ASYNC; + _listener=readListener; + _notReady=true; + + _channelState.onReadPossible(); } } - /* ------------------------------------------------------------ */ - public boolean isShutdown() + public void failed(Throwable x) { synchronized (lock()) { - return _inputEOF; + if (_onError==null) + LOG.warn(x); + else + _onError=x; } } - /* ------------------------------------------------------------ */ - public void consumeAll() + @Override + public void run() { + final boolean available; + final boolean eof; + final Throwable x; + synchronized (lock()) { - T item = _inputQ.peekUnsafe(); - loop: while (!isShutdown() && !isEarlyEOF()) + if (!_notReady || _listener==null) + return; + + x=_onError; + T item; + try { - while (item != null) - { - _inputQ.pollUnsafe(); - onContentConsumed(item); + item = getNextContent(); + } + catch(Exception e) + { + item=null; + failed(e); + } + available= item!=null && remaining(item)>0; - item = _inputQ.peekUnsafe(); - if (item == null) - onAllContentConsumed(); - } + eof = !available && _state.isEOF(); + _notReady=!available&&!eof; + } - try - { - blockForContent(); - item = _inputQ.peekUnsafe(); - if (item==null) - break; - } - catch (IOException e) - { - LOG.warn(e); - break loop; - } - } + 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 0000000000..53f2c1287c --- /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; + +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 + */ + 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 bbea718ffa..dd0fd8228a 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,27 @@ 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 +50,34 @@ import org.eclipse.jetty.util.resource.Resource; * 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 +98,63 @@ public class HttpOutput extends ServletOutputStream 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) + state=_state.get(); + } + } + + /* 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)) { - _channel.failed(); - LOG.ignore(e); + try + { + _channel.getResponse().closeOutput(); + } + catch(IOException e) + { + LOG.debug(e); + _channel.failed(); + } + releaseBuffer(); + return; } + state=_state.get(); } - closed(); } private void releaseBuffer() @@ -138,44 +168,109 @@ public class HttpOutput extends ServletOutputStream public boolean isClosed() { - return _closed; + return _state.get()==State.CLOSED; } @Override public void flush() throws IOException { - if (isClosed()) - return; - - if (BufferUtil.hasContent(_aggregate)) - _channel.write(_aggregate, false); - else - _channel.write(BufferUtil.EMPTY_BUFFER, false); + while(true) + { + switch(_state.get()) + { + case OPEN: + if (BufferUtil.hasContent(_aggregate)) + _channel.write(_aggregate, false); + else + _channel.write(BufferUtil.EMPTY_BUFFER, false); + return; + + 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 +279,12 @@ public class HttpOutput extends ServletOutputStream 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 +293,7 @@ public class HttpOutput extends ServletOutputStream _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; @@ -212,37 +307,128 @@ public class HttpOutput extends ServletOutputStream _channel.write(BufferUtil.EMPTY_BUFFER,complete); if (complete) + { closed(); - } + } + } - @Override - public void write(int b) throws IOException + public void write(ByteBuffer buffer) throws IOException { - if (isClosed()) - throw new EOFException("Closed"); + _written+=buffer.remaining(); + boolean complete=_channel.getResponse().isAllContentWritten(_written); - // 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); + // 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); - BufferUtil.append(_aggregate, (byte)b); - _written++; + // 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 + { + _written+=1; boolean complete=_channel.getResponse().isAllContentWritten(_written); - - // Check if all written or full - if (complete || BufferUtil.isFull(_aggregate)) - { - BlockingCallback callback = _channel.getWriteBlockingCallback(); - _channel.write(_aggregate, false, callback); - callback.block(); - if (complete) - closed(); + + // Async or Blocking ? + while(true) + { + 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 { @@ -253,51 +439,6 @@ public class HttpOutput extends ServletOutputStream } /* ------------------------------------------------------------ */ - /** 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 @@ -332,7 +473,7 @@ public class HttpOutput extends ServletOutputStream new ReadableByteChannelWritingCB(in,callback).iterate(); callback.block(); } - + /* ------------------------------------------------------------ */ /** Blocking send of content. @@ -345,7 +486,7 @@ public class HttpOutput extends ServletOutputStream sendContent(content,callback); callback.block(); } - + /* ------------------------------------------------------------ */ /** Asynchronous send of content. @@ -367,13 +508,13 @@ public class HttpOutput extends ServletOutputStream 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 */ @@ -384,7 +525,7 @@ public class HttpOutput extends ServletOutputStream /* ------------------------------------------------------------ */ /** 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 */ @@ -400,23 +541,36 @@ public class HttpOutput extends ServletOutputStream */ 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) { @@ -424,7 +578,7 @@ public class HttpOutput extends ServletOutputStream sendContent(rbc,callback); return; } - + InputStream in = httpContent.getInputStream(); if ( in!=null ) { @@ -442,7 +596,8 @@ public class HttpOutput extends ServletOutputStream public void setBufferSize(int size) { - this._bufferSize = size; + _bufferSize = size; + _commitSize = size; } public void resetBuffer() @@ -450,24 +605,223 @@ public class HttpOutput extends ServletOutputStream if (BufferUtil.hasContent(_aggregate)) BufferUtil.clear(_aggregate); } - - + + @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() + */ + @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 + /** 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. + * 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. + * 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 IteratingCallback + 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); @@ -502,6 +856,7 @@ public class HttpOutput extends ServletOutputStream _buffer.position(0); _buffer.limit(len); _channel.write(_buffer,_eof,this); + return false; } @@ -519,26 +874,26 @@ public class HttpOutput extends ServletOutputStream 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()); @@ -561,10 +916,11 @@ public class HttpOutput extends ServletOutputStream _buffer.clear(); while (_buffer.hasRemaining() && !_eof) _eof = (_in.read(_buffer)) < 0; - + // write what we have _buffer.flip(); _channel.write(_buffer,_eof,this); + return false; } @@ -583,4 +939,5 @@ public class HttpOutput extends ServletOutputStream } } } + } 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 ef62bdc86f..0ab12b207e 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 @@ import org.eclipse.jetty.util.Callback; 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 83335c4dc3..0f4abd90fb 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 class Iso88591HttpWriter extends HttpWriter 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 6d161def3a..acf1cf073f 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 @@ -172,7 +172,6 @@ public class LocalConnector extends AbstractConnector 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/NetworkConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkConnector.java index d7304d8fc3..df8ce174c6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkConnector.java @@ -39,7 +39,6 @@ public interface NetworkConnector extends Connector, AutoCloseable * (for example, to stop accepting network connections).</p> * Once a connector has been closed, it cannot be opened again without first * calling {@link #stop()} and it will not be active again until a subsequent call to {@link #start()} - * @throws IOException if this connector cannot be closed */ @Override void close(); 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 0000000000..fb0c23a3fe --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/QueuedHttpInput.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.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 c6db08cc1e..2a7ba2574e 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 @@ -57,6 +57,7 @@ import javax.servlet.http.Cookie; 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; @@ -494,6 +495,16 @@ public class Request implements HttpServletRequest { return (int)_fields.getLongField(HttpHeader.CONTENT_LENGTH.toString()); } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest.getContentLengthLong() + */ + @Override + public long getContentLengthLong() + { + return _fields.getLongField(HttpHeader.CONTENT_LENGTH.toString()); + } /* ------------------------------------------------------------ */ /* @@ -532,7 +543,12 @@ public class Request implements HttpServletRequest 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; @@ -551,7 +567,11 @@ public class Request implements HttpServletRequest } } - 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(); } /* ------------------------------------------------------------ */ @@ -1607,9 +1627,7 @@ public class Request implements HttpServletRequest /* ------------------------------------------------------------ */ /* * 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) */ @@ -1618,35 +1636,11 @@ public class Request implements HttpServletRequest { 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); @@ -2194,4 +2188,32 @@ public class Request implements HttpServletRequest 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 c41ee2b03b..0d67712f3e 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 @@ -284,26 +284,9 @@ public class ResourceCache { 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 @@ public class ResourceCache try { if (_useFileMappedBuffer && resource.getFile()!=null) - return BufferUtil.toBuffer(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 BufferUtil.toMappedBuffer(resource.getFile()); - return buffer; + return BufferUtil.toBuffer(resource,true); } - catch(IOException e) + catch(IOException|IllegalArgumentException e) { LOG.warn(e); return null; 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 16c331cb94..a1228bc084 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 @@ -99,6 +99,7 @@ public class Response implements HttpServletResponse private Locale _locale; private MimeTypes.Type _mimeType; private String _characterEncoding; + private boolean _explicitEncoding; private String _contentType; private OutputType _outputType = OutputType.NONE; private ResponseWriter _writer; @@ -128,6 +129,7 @@ public class Response implements HttpServletResponse _contentLength = -1; _out.reset(); _fields.clear(); + _explicitEncoding=false; } public void setHeaders(HttpContent httpContent) @@ -173,6 +175,10 @@ public class Response implements HttpServletResponse public void included() { _include.decrementAndGet(); + if (_outputType == OutputType.WRITER) + { + _writer.reopen(); + } _out.reopen(); } @@ -457,10 +463,18 @@ public class Response implements HttpServletResponse _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; @@ -470,7 +484,15 @@ public class Response implements HttpServletResponse 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 { @@ -518,11 +540,17 @@ public class Response implements HttpServletResponse 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()) @@ -726,7 +754,7 @@ public class Response implements HttpServletResponse encoding = MimeTypes.inferCharsetFromContentType(_contentType); if (encoding == null) encoding = StringUtil.__ISO_8859_1; - setCharacterEncoding(encoding); + setCharacterEncoding(encoding,false); } if (_writer != null && _writer.isFor(encoding)) @@ -790,6 +818,8 @@ public class Response implements HttpServletResponse { case WRITER: _writer.close(); + if (!_out.isClosed()) + _out.close(); break; case STREAM: getOutputStream().close(); @@ -814,10 +844,21 @@ public class Response implements HttpServletResponse _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; @@ -825,6 +866,8 @@ public class Response implements HttpServletResponse { if (encoding == null) { + _explicitEncoding=false; + // Clear any encoding. if (_characterEncoding != null) { @@ -843,6 +886,7 @@ public class Response implements HttpServletResponse else { // No, so just add this one to the mimetype + _explicitEncoding=explicit; _characterEncoding = StringUtil.normalizeCharset(encoding); if (_contentType != null) { @@ -903,6 +947,7 @@ public class Response implements HttpServletResponse else { _characterEncoding = charset; + _explicitEncoding = true; } HttpField field = HttpField.CONTENT_TYPE.get(_contentType); @@ -1043,8 +1088,8 @@ public class Response implements HttpServletResponse 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/ServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java index 889e2ffefa..7fedc13f42 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 @@ import org.eclipse.jetty.util.thread.Scheduler; * 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> 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 b13144840a..b0c6270e96 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.Cookie; 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 @@ public class ServletRequestHttpWrapper extends ServletRequestWrapper implements } + /** + * @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 1f1ffb4f42..4c62b9e692 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 @@ import javax.servlet.http.HttpServletResponse; /* ------------------------------------------------------------ */ -/** 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/Utf8HttpWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Utf8HttpWriter.java index 251e3d0fd4..6028109f8e 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 class Utf8HttpWriter extends HttpWriter 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/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 91d66baa23..2554437302 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 @@ -104,10 +104,16 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu { 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}; - final private static String __unimplmented="Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler"; + 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); @@ -550,7 +556,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu _requestListeners.clear(); _requestAttributeListeners.clear(); _eventListeners.clear(); - + if (eventListeners!=null) for (EventListener listener : eventListeners) addEventListener(listener); @@ -628,7 +634,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu return _programmaticListeners.contains(listener); } - + /* ------------------------------------------------------------ */ /** @@ -828,7 +834,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu 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 +974,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu 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); @@ -1303,13 +1311,13 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu 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())) @@ -1720,6 +1728,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu 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() @@ -2124,6 +2134,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu try { Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className); + checkListener(clazz); addListener(clazz); } catch (ClassNotFoundException e) @@ -2137,6 +2148,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu { if (!_enabled) throw new UnsupportedOperationException(); + + checkListener(t.getClass()); + ContextHandler.this.addEventListener(t); ContextHandler.this.addProgrammaticListener(t); } @@ -2147,6 +2161,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu if (!_enabled) throw new UnsupportedOperationException(); + checkListener(listenerClass); + try { EventListener e = createListener(listenerClass); @@ -2164,18 +2180,42 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu { try { - return clazz.newInstance(); + return createInstance(clazz); } - catch (InstantiationException e) + catch (Exception e) { throw new ServletException(e); } - catch (IllegalAccessException 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++) { - throw new ServletException(e); + 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() { @@ -2213,6 +2253,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu { return _enabled; } + + + + public <T> T createInstance (Class<T> clazz) throws Exception + { + T o = clazz.newInstance(); + return o; + } } @@ -2553,6 +2601,16 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu { 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/ResourceHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java index 07b289d8d6..f5cddba4fd 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 @@ public class ResourceHandler extends HandlerWrapper 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 @@ public class ResourceHandler extends HandlerWrapper 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/StatisticsHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java index 78e8eb71ef..26a0d8dcf2 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 @@ -81,7 +81,6 @@ public class StatisticsHandler extends HandlerWrapper @Override public void onComplete(AsyncEvent event) throws IOException { - HttpChannelState state = ((AsyncContextEvent)event).getHttpChannelState(); Request request = state.getBaseRequest(); @@ -92,8 +91,7 @@ public class StatisticsHandler extends HandlerWrapper updateResponse(request); - if (!state.isDispatched()) - _asyncWaitStats.decrement(); + _asyncWaitStats.decrement(); } }; @@ -139,9 +137,7 @@ public class StatisticsHandler extends HandlerWrapper { // resumed request start = System.currentTimeMillis(); - _asyncWaitStats.decrement(); - if (state.isDispatched()) - _asyncDispatches.incrementAndGet(); + _asyncDispatches.incrementAndGet(); } try @@ -159,8 +155,10 @@ public class StatisticsHandler extends HandlerWrapper 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 db6b465c5e..96db015f21 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 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI { 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 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI @Override public long getCreationTime() throws IllegalStateException { + checkValid(); return _created; } @@ -365,6 +366,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI @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 4801d16dd4..b13419bc21 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.HttpSessionAttributeListener; 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 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement 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 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement _sessionAttributeListeners.add((HttpSessionAttributeListener)listener); if (listener instanceof HttpSessionListener) _sessionListeners.add((HttpSessionListener)listener); + if (listener instanceof HttpSessionIdListener) + _sessionIdListeners.add((HttpSessionIdListener)listener); } /* ------------------------------------------------------------ */ @@ -198,6 +202,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement { _sessionAttributeListeners.clear(); _sessionListeners.clear(); + _sessionIdListeners.clear(); } /* ------------------------------------------------------------ */ @@ -411,7 +416,6 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement /* ------------------------------------------------------------ */ /** - * @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 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement { _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 2f7e1b4f5e..65bcf1f071 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 @@ public class HashSessionManager extends AbstractSessionManager /* ------------------------------------------------------------ */ /** - * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStart() + * @see AbstractSessionManager#doStart() */ @Override public void doStart() throws Exception @@ -116,7 +116,7 @@ public class HashSessionManager extends AbstractSessionManager /* ------------------------------------------------------------ */ /** - * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStop() + * @see AbstractSessionManager#doStop() */ @Override public void doStop() throws Exception @@ -440,6 +440,8 @@ public class HashSessionManager extends AbstractSessionManager 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 99314557ee..9227b55d00 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 @@ -647,6 +647,8 @@ public class JDBCSessionManager extends AbstractSessionManager LOG.warn(e); } } + + super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId); } @@ -939,7 +941,7 @@ public class JDBCSessionManager extends AbstractSessionManager /** * 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 0c8df262b7..44de98106a 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.Cookie; 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 @@ public class SessionHandler extends ScopedHandler 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 323e16a218..7ad0282e12 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 @@ -18,9 +18,6 @@ package org.eclipse.jetty.server; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -28,11 +25,11 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.net.URISyntaxException; - import javax.servlet.ServletException; 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; @@ -41,6 +38,9 @@ import org.eclipse.jetty.util.log.StdErrLog; import org.junit.After; import org.junit.Before; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + public abstract class AbstractHttpTest { protected static Server server; @@ -57,8 +57,9 @@ public abstract class AbstractHttpTest 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/ConnectionOpenCloseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java index 51d1326fda..46aeb6c2d9 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 @@ -27,7 +27,6 @@ import java.net.Socket; 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; @@ -38,6 +37,7 @@ import org.eclipse.jetty.server.handler.AbstractHandler; 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; @@ -49,7 +49,7 @@ public class ConnectionOpenCloseTest extends AbstractHttpTest { super(HttpVersion.HTTP_1_1.asString()); } - + @Slow @Test public void testOpenClose() throws Exception @@ -59,6 +59,56 @@ public class ConnectionOpenCloseTest extends AbstractHttpTest @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); } }); @@ -67,7 +117,7 @@ public class ConnectionOpenCloseTest extends AbstractHttpTest 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) @@ -87,7 +137,7 @@ public class ConnectionOpenCloseTest extends AbstractHttpTest 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" + @@ -112,7 +162,7 @@ public class ConnectionOpenCloseTest extends AbstractHttpTest @Slow @Test - public void testSSLOpenClose() throws Exception + public void testSSLOpenRequestClose() throws Exception { SslContextFactory sslContextFactory = new SslContextFactory(); File keystore = MavenTestingUtils.getTestResourceFile("keystore"); @@ -138,7 +188,7 @@ public class ConnectionOpenCloseTest extends AbstractHttpTest 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 0f86e01f5e..8d8d6e048e 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 @@ -178,7 +178,7 @@ public class DumpHandler extends AbstractHandler } 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/HalfCloseRaceTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseRaceTest.java deleted file mode 100644 index 4d3714b997..0000000000 --- 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 0000000000..a9652f05a7 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseTest.java @@ -0,0 +1,217 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// 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.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.IO; +import org.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/HttpConnectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java index b2b56bd14d..6476fde3c4 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 @@ public class HttpConnectionTest } @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 @@ public class HttpConnectionTest } @Test - public void testUnconsumedError() throws Exception + public void testUnconsumedErrorRead() throws Exception { String response=null; String requests=null; @@ -448,7 +461,7 @@ public class HttpConnectionTest offset=0; requests= - "GET /R1?read=1&error=500 HTTP/1.1\n"+ + "GET /R1?read=1&error=599 HTTP/1.1\n"+ "Host: localhost\n"+ "Transfer-Encoding: chunked\n"+ "Content-Type: text/plain; charset=utf-8\n"+ @@ -468,7 +481,43 @@ public class HttpConnectionTest response=connector.getResponses(requests); - offset = checkContains(response,offset,"HTTP/1.1 500"); + 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 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"); 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 c64387b6c6..ce1441f0ad 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 @@ -27,11 +27,14 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; +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; @@ -119,9 +122,9 @@ public class HttpOutputTest 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 @@ -141,8 +144,32 @@ public class HttpOutputTest 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 { @@ -191,8 +218,175 @@ public class HttpOutputTest 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; + ByteBuffer _content; InputStream _contentInputStream; ReadableByteChannel _contentChannel; @@ -202,7 +396,7 @@ public class HttpOutputTest baseRequest.setHandled(true); response.setContentType("text/plain"); - HttpOutput out = (HttpOutput) response.getOutputStream(); + final HttpOutput out = (HttpOutput) response.getOutputStream(); if (_contentInputStream!=null) { @@ -218,6 +412,113 @@ public class HttpOutputTest 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) + { + 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 a549d3b819..a6055d1da8 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 @@ package org.eclipse.jetty.server; 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; @@ -38,7 +39,6 @@ import java.net.Socket; import java.net.URI; import java.net.URL; import java.util.Arrays; -import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Exchanger; import java.util.concurrent.TimeUnit; @@ -53,6 +53,7 @@ import javax.servlet.http.HttpServletResponse; 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; @@ -353,17 +354,19 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture "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")); } } @@ -453,29 +456,22 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture } } + @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 splits = bytes.length-REQUEST2_CONTENT.length()+5; + for (int i = 0; i < splits; i += 1) { - int[] points = new int[pointCount]; + int[] points = new int[]{i}; 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(); @@ -487,26 +483,27 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture // Check the response assertEquals("response for " + i + " " + message.toString(), RESPONSE2, response); + + Thread.sleep(100); } } } - + @Test - public void testRequest2Iterate() throws Exception + @Slow + public void testRequest2Sliced3() 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}; + int[] points = new int[]{i,i+1}; 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(); @@ -518,9 +515,14 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture // Check the response assertEquals("response for " + i + " " + message.toString(), RESPONSE2, response); + + Thread.sleep(100); } } } + + + @Test public void testFlush() throws Exception @@ -994,33 +996,28 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture "\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")); } } @@ -1360,7 +1357,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture } } - 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 9924928036..abfc3288e7 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 @@ -93,14 +93,14 @@ public class HttpServerTestFixture 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 +134,7 @@ public class HttpServerTestFixture 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/HttpWriterTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java index 1b30841ec6..71aa3cf693 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 @@ -43,7 +43,7 @@ public class HttpWriterTest _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 e7715bd973..02fad8285f 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 @@ -18,10 +18,6 @@ package org.eclipse.jetty.server; -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -30,6 +26,10 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + public class LocalConnectorTest { private Server _server; @@ -59,7 +59,7 @@ public class LocalConnectorTest { 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 eac10577bf..b4f38a10f2 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 @@ -192,6 +192,6 @@ public class LowResourcesMonitorTest 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 1f507272bc..f2f8de0d4f 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 @@ -18,9 +18,6 @@ package org.eclipse.jetty.server; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -30,7 +27,6 @@ import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; - import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; @@ -45,6 +41,9 @@ import org.junit.After; import org.junit.Ignore; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + @Ignore public class NetworkTrafficListenerTest { @@ -82,7 +81,7 @@ public class NetworkTrafficListenerTest 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; @@ -125,7 +124,7 @@ public class NetworkTrafficListenerTest 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) @@ -190,7 +189,7 @@ public class NetworkTrafficListenerTest 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) { @@ -257,7 +256,7 @@ public class NetworkTrafficListenerTest 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) { @@ -323,7 +322,7 @@ public class NetworkTrafficListenerTest 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) { @@ -396,7 +395,7 @@ public class NetworkTrafficListenerTest 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 a731c42114..8072d05a72 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 @@ -29,8 +29,10 @@ import java.util.ArrayList; 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; @@ -170,8 +172,8 @@ public class RequestTest String responses=_connector.getResponses(request); assertTrue(responses.startsWith("HTTP/1.1 200")); } - - + + @Test public void testMultiPart() throws Exception { @@ -180,7 +182,7 @@ public class RequestTest testTmpDir.deleteOnExit(); assertTrue(testTmpDir.mkdirs()); assertTrue(testTmpDir.list().length == 0); - + ContextHandler contextHandler = new ContextHandler(); contextHandler.setContextPath("/foo"); contextHandler.setResourceBase("."); @@ -202,12 +204,12 @@ public class RequestTest 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"+ @@ -218,7 +220,7 @@ public class RequestTest "\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"+ @@ -361,8 +363,8 @@ public class RequestTest 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"+ @@ -375,8 +377,8 @@ public class RequestTest 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"+ @@ -390,8 +392,8 @@ public class RequestTest 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"+ @@ -404,8 +406,8 @@ public class RequestTest 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"+ @@ -418,8 +420,8 @@ public class RequestTest 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"+ @@ -432,8 +434,8 @@ public class RequestTest 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"+ @@ -448,8 +450,8 @@ public class RequestTest 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"+ @@ -469,15 +471,25 @@ public class RequestTest @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; } }; @@ -485,11 +497,11 @@ public class RequestTest 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"+ @@ -497,9 +509,8 @@ public class RequestTest 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"; } } @@ -651,7 +662,7 @@ public class RequestTest response=_connector.getResponses( "GET / HTTP/1.1\n"+ "Host: whatever\n"+ - "\n", + "\n", 200, TimeUnit.MILLISECONDS ); assertTrue(response.indexOf("200")>0); @@ -1068,30 +1079,30 @@ public class RequestTest 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 245fb5cb60..4778f1a5bc 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 @@ -81,18 +81,10 @@ public class ResponseTest _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(); diff --git a/jetty-servlet/pom.xml b/jetty-servlet/pom.xml index c31f595ad3..2fb71301dc 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.6-SNAPSHOT</version> + <version>9.1.0-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 0000000000..fdb65c57a1 --- /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 4c9fe80cd3..6ed463bba7 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 @@ -66,6 +66,9 @@ public class ErrorPageErrorHandler extends ErrorHandler @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { + + + String method = request.getMethod(); if (!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) && !HttpMethod.HEAD.is(method)) { @@ -75,25 +78,26 @@ public class ErrorPageErrorHandler extends ErrorHandler if (_errorPages!=null) { String error_page= null; - Class<?> exClass= (Class<?>)request.getAttribute(Dispatcher.ERROR_EXCEPTION_TYPE); + + + Throwable th= (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION); - if (ServletException.class.equals(exClass)) + // 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(Dispatcher.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) @@ -121,7 +125,7 @@ public class ErrorPageErrorHandler extends ErrorHandler } } - //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); 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 b63e40a3c6..a4e2683664 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,9 @@ import javax.servlet.ServletContext; import javax.servlet.ServletException; import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +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; @@ -193,6 +197,16 @@ public class FilterHolder extends Holder<Filter> { 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/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java index 991ff844c6..9e259ade63 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 @@ -46,6 +46,7 @@ import javax.servlet.SessionTrackingMode; import javax.servlet.descriptor.JspConfigDescriptor; import javax.servlet.descriptor.JspPropertyGroupDescriptor; import javax.servlet.descriptor.TaglibDescriptor; +import javax.servlet.http.HttpUpgradeHandler; import org.eclipse.jetty.security.ConstraintAware; import org.eclipse.jetty.security.ConstraintMapping; @@ -274,10 +275,10 @@ public class ServletContextHandler extends ContextHandler 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 +575,14 @@ public class ServletContextHandler extends ContextHandler 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 +897,9 @@ public class ServletContextHandler extends ContextHandler { if (isStarted()) throw new IllegalStateException(); + + if (filterName == null || "".equals(filterName.trim())) + throw new IllegalStateException("Missing filter name"); if (!_enabled) throw new UnsupportedOperationException(); @@ -930,6 +934,9 @@ public class ServletContextHandler extends ContextHandler { if (isStarted()) throw new IllegalStateException(); + + if (filterName == null || "".equals(filterName.trim())) + throw new IllegalStateException("Missing filter name"); if (!_enabled) throw new UnsupportedOperationException(); @@ -966,6 +973,9 @@ public class ServletContextHandler extends ContextHandler if (isStarted()) throw new IllegalStateException(); + if (filterName == null || "".equals(filterName.trim())) + throw new IllegalStateException("Missing filter name"); + if (!_enabled) throw new UnsupportedOperationException(); @@ -1001,6 +1011,9 @@ public class ServletContextHandler extends ContextHandler if (!isStarting()) throw new IllegalStateException(); + if (servletName == null || "".equals(servletName.trim())) + throw new IllegalStateException("Missing servlet name"); + if (!_enabled) throw new UnsupportedOperationException(); @@ -1036,6 +1049,9 @@ public class ServletContextHandler extends ContextHandler if (!isStarting()) throw new IllegalStateException(); + if (servletName == null || "".equals(servletName.trim())) + throw new IllegalStateException("Missing servlet name"); + if (!_enabled) throw new UnsupportedOperationException(); @@ -1071,7 +1087,10 @@ public class ServletContextHandler extends ContextHandler { if (!isStarting()) throw new IllegalStateException(); - + + if (servletName == null || "".equals(servletName.trim())) + throw new IllegalStateException("Missing servlet name"); + if (!_enabled) throw new UnsupportedOperationException(); @@ -1115,19 +1134,10 @@ public class ServletContextHandler extends ContextHandler { 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 +1149,29 @@ public class ServletContextHandler extends ContextHandler { 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 +1302,10 @@ public class ServletContextHandler extends ContextHandler { 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 +1346,7 @@ public class ServletContextHandler extends ContextHandler */ 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 beb7bcbb35..dcff963e3c 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 @@ -25,6 +25,7 @@ import java.util.Collection; 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; @@ -109,6 +110,7 @@ public class ServletHandler extends ScopedHandler 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; @@ -278,6 +280,7 @@ public class ServletHandler extends ScopedHandler _filterPathMappings=null; _filterNameMappings=null; _servletPathMap=null; + _servletPathMappings=null; } /* ------------------------------------------------------------ */ @@ -343,31 +346,26 @@ public class ServletHandler extends ScopedHandler 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 +548,6 @@ public class ServletHandler extends ScopedHandler } else LOG.warn(th); - while (th instanceof ServletException) - { - Throwable cause=((ServletException)th).getRootCause(); - if (cause==null) - break; - th=cause; - } } else if (th instanceof EofException) { @@ -585,7 +576,7 @@ public class ServletHandler extends ScopedHandler 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) { @@ -909,9 +900,12 @@ public class ServletHandler extends ScopedHandler { 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(); @@ -1319,23 +1313,75 @@ public class ServletHandler extends ScopedHandler 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 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 c997399099..2626da7e37 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 @@ public class ServletMapping /* ------------------------------------------------------------ */ /** - * @param default1 + * @param fromDefault */ public void setDefault(boolean fromDefault) { diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java index ed54fc4eef..ad48042d02 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java @@ -41,10 +41,7 @@ import org.junit.Test; /** * This tests verifies that merging of queryStrings works when dispatching - * Requests via {@link Continuation} multiple times. - * - * @author tbecker - * + * Requests via {@link AsyncContext} multiple times. */ public class AsyncContextDispatchWithQueryStrings { 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 0000000000..fc12b245e2 --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.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.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 0000000000..c6cd0a3767 --- /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/AsyncServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java index 8d2e4534a7..d34fd46d70 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 @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; + import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; @@ -41,6 +42,7 @@ import org.eclipse.jetty.server.Response; 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; @@ -48,10 +50,12 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; +@RunWith(AdvancedRunner.class) public class AsyncServletTest { protected AsyncServlet _servlet=new AsyncServlet(); 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 f0759a7b33..74c8a049b2 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 @@ -102,8 +102,8 @@ public class ErrorPageTest 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-1")); assertThat(response,Matchers.containsString("ERROR_REQUEST_URI: /fail/exception")); } diff --git a/jetty-servlet/src/test/resources/jetty-logging.properties b/jetty-servlet/src/test/resources/jetty-logging.properties index 50680ef1dc..3e0d7b71f4 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 55810c255c..dbc1339f8e 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.6-SNAPSHOT</version> + <version>9.1.0-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 0000000000..e8724b87d7 --- /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/DataRateLimitedServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DataRateLimitedServlet.java new file mode 100644 index 0000000000..6a9855a003 --- /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 047f5992c6..9e3e4134da 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 @@ -1055,7 +1055,7 @@ public class DoSFilter implements Filter * * @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) 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 5693dd1fb6..6e15fa1728 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 @@ public class GzipFilter extends UserAgentFilter { 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/gzip/AbstractCompressedStream.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/AbstractCompressedStream.java index c27a272ee1..56590d6fee 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.io.UnsupportedEncodingException; 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 @@ public abstract class AbstractCompressedStream extends ServletOutputStream } } - /** - * @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 @@ public abstract class AbstractCompressedStream extends ServletOutputStream _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 b81a1e44de..63c6711f9b 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.assertThat; import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.Socket; import java.util.EnumSet; @@ -112,35 +114,37 @@ public abstract class AbstractDoSFilterTest 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("UTF-8")); - socket.getOutputStream().flush(); - if (i>0 && pauseBetweenLoops>0) - Thread.sleep(pauseBetweenLoops); - } - if (pauseBeforeLast>0) - Thread.sleep(pauseBeforeLast); - socket.getOutputStream().write(lastRequest.getBytes("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(),"UTF-8"); - } - else - { - response = IO.toString(socket.getInputStream(),"UTF-8"); + for (int i = loops; i-- > 0;) + { + out.write(loopRequests.getBytes("UTF-8")); + out.flush(); + if (i > 0 && pauseBetweenLoops > 0) + { + Thread.sleep(pauseBetweenLoops); + } + } + if (pauseBeforeLast > 0) + { + Thread.sleep(pauseBeforeLast); + } + out.write(lastRequest.getBytes("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,"UTF-8"); + return response; } - 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 0000000000..c3d7d24b05 --- /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-spdy/pom.xml b/jetty-spdy/pom.xml index 9adf942c29..c2d150c99c 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.6-SNAPSHOT</version> + <version>9.1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> @@ -119,7 +119,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> @@ -176,7 +178,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 d7166746cb..3f9e878e03 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.6-SNAPSHOT</version> + <version>9.1.0-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/SPDYClient.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java index c4360a2c12..80d2286b49 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 @@ -19,7 +19,6 @@ package org.eclipse.jetty.spdy.client; import java.io.IOException; -import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; @@ -28,8 +27,8 @@ import java.util.Collections; import java.util.List; 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; @@ -46,12 +45,29 @@ 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(); @@ -87,23 +103,57 @@ public class SPDYClient this.bindAddress = bindAddress; } - public Future<Session> connect(InetSocketAddress address, SessionFrameListener listener) throws IOException + /** + * 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(); + } + + /** + * 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 + */ + public void connect(SocketAddress address, SessionFrameListener listener, Promise<Session> promise) { 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); + try + { + SocketChannel channel = SocketChannel.open(); + if (bindAddress != null) + channel.bind(bindAddress); + channel.socket().setTcpNoDelay(true); + channel.configureBlocking(false); - channel.connect(address); - factory.selector.connect(channel, result); + SessionPromise result = new SessionPromise(promise, channel, this, listener); - return result; + channel.connect(address); + factory.selector.connect(channel, result); + } + catch (IOException x) + { + promise.failed(x); + } } public long getIdleTimeout() @@ -340,14 +390,31 @@ public class SPDYClient static class SessionPromise extends FuturePromise<Session> { private final SocketChannel channel; + private final Promise<Session> wrappedPromise; final SPDYClient client; final SessionFrameListener listener; - private SessionPromise(SocketChannel channel, SPDYClient client, SessionFrameListener listener) + private SessionPromise(Promise<Session> promise, SocketChannel channel, SPDYClient client, + SessionFrameListener listener) { this.channel = channel; this.client = client; this.listener = listener; + this.wrappedPromise = promise; + } + + @Override + public void succeeded(Session result) + { + wrappedPromise.succeeded(result); + super.succeeded(result); + } + + @Override + public void failed(Throwable cause) + { + wrappedPromise.failed(cause); + super.failed(cause); } @Override 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 c56210fbe2..7f67cbf67e 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 @@ -93,7 +93,8 @@ public class SPDYConnection extends AbstractConnection implements Controller, Id 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 d4c1e700b7..38154ac959 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.6-SNAPSHOT</version> + <version>9.1.0-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/StandardSession.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java index e50f98e1ab..66804fe6cd 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 @@ -489,7 +489,8 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable @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()); } @@ -541,7 +542,9 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable 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); @@ -802,7 +805,7 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable if (listener != null) { LOG.debug("Invoking callback with {} on listener {}", x, listener); - listener.onException(x); + listener.onFailure(this, x); } } catch (Exception xx) 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 e0b65c7b79..698c4bb253 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.ExecutionException; 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.FuturePromise; 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 @@ public class StandardStream implements IStream 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 @@ public class StandardStream implements IStream } @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 @@ public class StandardStream implements IStream @Override public void process(ControlFrame frame) { + notIdle(); switch (frame.getType()) { case SYN_STREAM: @@ -234,6 +252,7 @@ public class StandardStream implements IStream @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()) @@ -349,6 +368,7 @@ public class StandardStream implements IStream @Override public void push(PushInfo pushInfo, Promise<Stream> promise) { + notIdle(); if (isClosed() || isReset()) { promise.failed(new StreamException(getId(), StreamStatus.STREAM_ALREADY_CLOSED, @@ -373,6 +393,7 @@ public class StandardStream implements IStream @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 +416,7 @@ public class StandardStream implements IStream @Override public void data(DataInfo dataInfo, Callback callback) { + notIdle(); if (!canSend()) { session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new Adapter()); @@ -425,6 +447,7 @@ public class StandardStream implements IStream @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 f25dc1b476..07577dc4ed 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 @@ public interface Session * @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 @@ public interface Session * <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 @@ public interface Session /** * <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 ffff51fe50..3ad17715f3 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 @@ public interface SessionFrameListener extends EventListener * 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 @@ public interface SessionFrameListener extends EventListener } @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 abc1b88847..0bb21a20f1 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 @@ public interface Stream * * @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 @@ public interface Stream * 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 @@ public interface Stream * 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 @@ public interface Stream * 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 interface Stream */ 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 0a4248a1f9..e3d9cd8535 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 @@ public interface StreamFrameListener extends EventListener * <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 interface StreamFrameListener extends EventListener 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 interface StreamFrameListener extends EventListener 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/parser/ControlFrameParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameParser.java index a253a19eb0..c86d92169f 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 @@ public abstract class ControlFrameParser 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 @@ public abstract class ControlFrameParser 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 @@ public abstract class ControlFrameParser // 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 @@ public abstract class ControlFrameParser } 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 @@ public abstract class ControlFrameParser return false; } - private void reset() + void reset() { state = State.VERSION; cursor = 0; @@ -177,13 +200,14 @@ public abstract class ControlFrameParser 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/Parser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/Parser.java index 2bc95ac03f..341e2e3a09 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 @@ public class Parser { for (Listener listener : listeners) { - listener.onStreamException(x); + try + { + listener.onStreamException(x); + } + catch (Exception xx) + { + logger.debug("Could not notify listener " + listener, xx); + } } } @@ -130,49 +137,52 @@ public class Parser 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: - { - // 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: + switch (state) { - if (controlFrameParser.parse(buffer)) - reset(); - break; - } - case DATA_FRAME: - { - if (dataFrameParser.parse(buffer)) - reset(); - break; - } - default: - { - throw new IllegalStateException(); + 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(); + } } } + catch (StreamException x) + { + notifyStreamException(x); + } } } catch (SessionException x) { notifySessionException(x); } - catch (StreamException x) - { - notifyStreamException(x); - } catch (Throwable x) { notifySessionException(new SessionException(SessionStatus.PROTOCOL_ERROR, x)); @@ -227,5 +237,4 @@ public class Parser { 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 30deb7c7ed..644ffd8c7f 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 @@ public class SynStreamBodyParser extends ControlFrameBodyParser { // 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 @@ public class SynStreamBodyParser extends ControlFrameBodyParser 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 1d198a802a..dc8c650bf4 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.Executor; 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; @@ -47,6 +49,8 @@ import org.junit.runner.RunWith; @RunWith(AdvancedRunner.class) public class AsyncTimeoutTest { + EndPoint endPoint = new ByteArrayEndPoint(); + @Slow @Test public void testAsyncTimeoutInControlFrames() throws Exception @@ -60,7 +64,7 @@ public class AsyncTimeoutTest 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()) + endPoint, null, 1, null, generator, new FlowControlStrategy.None()) { @Override public void flush() @@ -103,7 +107,7 @@ public class AsyncTimeoutTest 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()) + endPoint, null, 1, null, generator, new FlowControlStrategy.None()) { @Override protected void write(ByteBuffer buffer, Callback callback) 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 813fb0ceb5..45e000aa27 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 @@ -30,6 +30,7 @@ import java.util.concurrent.TimeUnit; 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; @@ -75,6 +76,7 @@ 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; @RunWith(MockitoJUnitRunner.class) public class StandardSessionTest @@ -84,6 +86,10 @@ public class StandardSessionTest @Mock private Controller controller; + + @Mock + private EndPoint endPoint; + private ExecutorService threadPool; private StandardSession session; private Scheduler scheduler; @@ -97,8 +103,9 @@ public class StandardSessionTest 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, threadPool, scheduler, controller, endPoint, null, 1, null, generator, new FlowControlStrategy.None()); + when(endPoint.getIdleTimeout()).thenReturn(30000L); headers = new Fields(); } @@ -428,7 +435,7 @@ public class StandardSessionTest 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() { @@ -502,7 +509,7 @@ public class StandardSessionTest 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()); + 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 57bdd98805..7a78174d7e 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 @@ -18,16 +18,6 @@ package org.eclipse.jetty.spdy; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.argThat; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - import java.util.HashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; @@ -43,23 +33,44 @@ 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.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; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +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; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + @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 @@ public class StandardStreamTest @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 @@ public class StandardStreamTest @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 class StandardStreamTest 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/parser/BrokenFrameTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/BrokenFrameTest.java new file mode 100644 index 0000000000..ede69ca0ae --- /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 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; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +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-example-webapp/pom.xml b/jetty-spdy/spdy-example-webapp/pom.xml index 4fa35e7578..d5db877b3f 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.6-SNAPSHOT</version> + <version>9.1.0-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 0000000000..7c14d20eab --- /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.0-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 0000000000..992f2a5761 --- /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 0000000000..b2fb3a6e4f --- /dev/null +++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpClientTransportOverSPDY.java @@ -0,0 +1,96 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// 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.net.SocketAddress; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.api.Connection; +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 volatile HttpClient httpClient; + + public HttpClientTransportOverSPDY(SPDYClient client) + { + this.client = client; + } + + @Override + public void setHttpClient(HttpClient client) + { + httpClient = client; + } + + @Override + public HttpDestination newHttpDestination(String scheme, String host, int port) + { + return new HttpDestinationOverSPDY(httpClient, scheme, host, port); + } + + @Override + public void connect(final HttpDestination destination, SocketAddress address, final Promise<Connection> promise) + { + SessionFrameListener.Adapter listener = new SessionFrameListener.Adapter() + { + @Override + public void onFailure(Session session, Throwable x) + { + // TODO: is this correct ? + // TODO: if I get a stream error (e.g. invalid response headers) + // TODO: I must abort the *current* exchange, while below I will abort + // TODO: the queued exchanges only. + // TODO: The problem is that a single destination/connection multiplexes + // TODO: several exchanges, so I would need to cancel them all, + // TODO: or only the one that failed ? + destination.abort(x); + } + }; + + client.connect(address, listener, new Promise<Session>() + { + @Override + public void succeeded(Session session) + { + Connection result = new HttpConnectionOverSPDY(destination, session); + promise.succeeded(result); + } + + @Override + public void failed(Throwable x) + { + promise.failed(x); + } + } + ); + } + + @Override + public Connection tunnel(Connection connection) + { + throw new UnsupportedOperationException(); + } +} 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 0000000000..850088cc08 --- /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 0000000000..321fb9991c --- /dev/null +++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpDestinationOverSPDY.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 org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.MultiplexHttpDestination; + +public class HttpDestinationOverSPDY extends MultiplexHttpDestination<HttpConnectionOverSPDY> +{ + public HttpDestinationOverSPDY(HttpClient client, String scheme, String host, int port) + { + super(client, scheme, host, port); + } + + @Override + protected void send(HttpConnectionOverSPDY connection, HttpExchange exchange) + { + connection.send(exchange); + } +} 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 0000000000..bf726ea2e5 --- /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)).value()); + response.version(version); + String[] status = fields.get(HTTPSPDYHeader.STATUS.name(spdy)).value().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.name(); + if (HTTPSPDYHeader.from(spdy, name) != null) + continue; + // TODO: handle multiple values properly + HttpField httpField = new HttpField(name, field.value()); + 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 0000000000..9dcf17f8f1 --- /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 0000000000..644067fcff --- /dev/null +++ b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/AbstractHttpClientServerTest.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.client.http; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.NetworkConnector; +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.spdy.server.http.PushStrategy; +import org.eclipse.jetty.toolchain.test.TestTracker; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.After; +import org.junit.Rule; + +public abstract class AbstractHttpClientServerTest +{ + @Rule + public final TestTracker tracker = new TestTracker(); + + protected Server server; + protected NetworkConnector connector; + protected SPDYClient.Factory factory; + protected HttpClient client; + protected String scheme = HttpScheme.HTTP.asString(); + + public void start(Handler handler) throws Exception + { + server = new Server(); + + short version = SPDY.V3; + + HTTPSPDYServerConnectionFactory spdyConnectionFactory = new HTTPSPDYServerConnectionFactory(version, new HttpConfiguration(), new PushStrategy.None()); + connector = new ServerConnector(server, spdyConnectionFactory); + + 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)), null); + 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 0000000000..dfa5b95ad8 --- /dev/null +++ b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/EmptyServerHandler.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.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/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 0000000000..76e29eeab0 --- /dev/null +++ b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientTest.java @@ -0,0 +1,422 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// 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.junit.Assert; +import org.junit.Test; + +public class HttpClientTest extends AbstractHttpClientServerTest +{ + @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 0000000000..8163013d57 --- /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 Binary files differnew file mode 100644 index 0000000000..428ba54776 --- /dev/null +++ b/jetty-spdy/spdy-http-client-transport/src/test/resources/keystore.jks 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 Binary files differnew file mode 100644 index 0000000000..839cb8c351 --- /dev/null +++ b/jetty-spdy/spdy-http-client-transport/src/test/resources/truststore.jks diff --git a/jetty-spdy/spdy-http-common/pom.xml b/jetty-spdy/spdy-http-common/pom.xml new file mode 100644 index 0000000000..a807c8a987 --- /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.0-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-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYHeader.java b/jetty-spdy/spdy-http-common/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java index 8954fe2db1..18634d0884 100644 --- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYHeader.java +++ b/jetty-spdy/spdy-http-common/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.spdy.server.http; +package org.eclipse.jetty.spdy.http; import java.util.HashMap; import java.util.Map; diff --git a/jetty-spdy/spdy-http-server/pom.xml b/jetty-spdy/spdy-http-server/pom.xml index f9e3b04e3e..ce092ab74f 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.6-SNAPSHOT</version> + <version>9.1.0-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 973ab9ffb4..a94b1386b4 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="0"/></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 0000000000..cedc098dfe --- /dev/null +++ b/jetty-spdy/spdy-http-server/src/main/config/modules/npn.mod @@ -0,0 +1,7 @@ + +[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 +--exec 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 0000000000..67019561d7 --- /dev/null +++ b/jetty-spdy/spdy-http-server/src/main/config/modules/spdy.mod @@ -0,0 +1,15 @@ +[depend] +ssl +npn + +[lib] +lib/spdy/*.jar + +[xml] +etc/jetty-ssl.xml +etc/jetty-spdy.xml + +[ini-template] +spdy.port=8443 +spdy.timeout=30000 +#spdy.initialWindowSize=65536 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 45d867a28f..92db5aab7a 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 @@ import org.eclipse.jetty.util.log.Logger; 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 @@ public class HTTPSPDYServerConnectionFactory extends SPDYServerConnectionFactory // 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 @@ -136,7 +136,7 @@ public class HTTPSPDYServerConnectionFactory extends SPDYServerConnectionFactory @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 @@ public class HTTPSPDYServerConnectionFactory extends SPDYServerConnectionFactory @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 fe2b9ac57e..ab7f8fce41 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 @@ -33,6 +33,7 @@ import org.eclipse.jetty.server.HttpTransport; 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 +43,9 @@ public class HttpChannelOverSPDY extends HttpChannel<DataInfo> { 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,43 +61,46 @@ public class HttpChannelOverSPDY extends HttpChannel<DataInfo> return super.headerComplete(); } - private void post(Runnable task) + private void dispatch() { - synchronized (tasks) + synchronized (this) { - LOG.debug("Posting task {}", task); - tasks.offer(task); - dispatch(); + if (dispatched) + redispatch=true; + else + { + LOG.debug("Dispatch {}", this); + dispatched=true; + execute(this); + } } } - private void dispatch() + @Override + public void run() { - synchronized (tasks) + boolean execute=true; + + while(execute) { - if (dispatched) - return; - - final Runnable task = tasks.poll(); - if (task != null) + try + { + LOG.debug("Executing {}",this); + super.run(); + } + finally { - dispatched = true; - LOG.debug("Dispatching task {}", task); - execute(new Runnable() + LOG.debug("Completing {}", this); + synchronized (this) { - @Override - public void run() - { - LOG.debug("Executing task {}", task); - task.run(); - LOG.debug("Completing task {}", task); - dispatched = false; - dispatch(); - } - }); + dispatched = redispatch; + redispatch=false; + execute=dispatched; + } } } } + public void requestStart(final Fields headers, final boolean endRequest) { @@ -114,20 +118,19 @@ public class HttpChannelOverSPDY extends HttpChannel<DataInfo> 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 +150,13 @@ public class HttpChannelOverSPDY extends HttpChannel<DataInfo> 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) @@ -171,7 +174,7 @@ public class HttpChannelOverSPDY extends HttpChannel<DataInfo> HttpMethod httpMethod = HttpMethod.fromString(methodHeader.value()); HttpVersion httpVersion = HttpVersion.fromString(versionHeader.value()); - + // 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()); 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 eff52a46b8..326e6bdec3 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 @@ public class HttpInputOverSPDY extends HttpInput<DataInfo> { 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 0c352e8dd3..f05107c780 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.SPDY; 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; @@ -132,7 +132,7 @@ public class HttpTransportOverSPDY implements HttpTransport 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 @@ public class HttpTransportOverSPDY implements HttpTransport } @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); 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 c76bc888c7..b6022b5fd9 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.concurrent.atomic.AtomicLong; 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 @@ import org.eclipse.jetty.util.log.Logger; /** * <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<>(); @@ -166,7 +167,7 @@ public class ReferrerPushStrategy implements PushStrategy String origin = scheme + "://" + host; String url = requestHeaders.get(HTTPSPDYHeader.URI.name(version)).value(); 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); @@ -189,7 +190,7 @@ public class ReferrerPushStrategy implements PushStrategy 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 @@ public class ReferrerPushStrategy implements PushStrategy 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) @@ -282,7 +283,7 @@ public class ReferrerPushStrategy implements PushStrategy 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 @@ public class ReferrerPushStrategy implements PushStrategy // 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 class ReferrerPushStrategy implements PushStrategy 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 b46d11c662..94b3dd4f28 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.Stream; 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; @@ -132,7 +132,7 @@ public class HTTPProxyEngine extends ProxyEngine private void sendRequest(final Stream clientStream, Request request) { - request.send(new Response.Listener.Empty() + request.send(new Response.Listener.Adapter() { private volatile boolean committed; 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 0b980b57b0..ff116cc112 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 java.util.Set; 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; /** 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 452e32b95e..69cb23c1de 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.StreamFrameListener; 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; 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 76ca8bcc3a..2561a380a2 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.SessionStatus; 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; @@ -222,7 +221,7 @@ public class ProxyHTTPSPDYConnection extends HttpConnection implements HttpParse 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 @@ public class ProxyHTTPSPDYConnection extends HttpConnection implements HttpParse } @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)).value(); + 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)).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.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.value()); - HttpFields fields = new HttpFields(); - for (Fields.Field header : headers) - { - String name = camelize(header.name()); - fields.put(name, header.value()); - } + 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: 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()); + send(info, null, replyInfo.isClose(), new Adapter() + { + @Override + public void failed(Throwable x) + { + handler.failed(x); + } + }); - if (replyInfo.isClose()) - completed(); + if (replyInfo.isClose()) + completed(); - handler.succeeded(); - } - catch (IOException x) - { - handler.failed(x); - } + handler.succeeded(); } private String camelize(String name) @@ -305,25 +303,24 @@ public class ProxyHTTPSPDYConnection extends HttpConnection implements HttpParse } @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); + // Data buffer must be copied, as the ByteBuffer is pooled + ByteBuffer byteBuffer = dataInfo.asByteBuffer(false); - // TODO use the async send with callback! - send(null, byteBuffer, dataInfo.isClose()); + send(null, byteBuffer, dataInfo.isClose(), new Adapter() + { + @Override + public void failed(Throwable x) + { + handler.failed(x); + } + }); - if (dataInfo.isClose()) - completed(); + if (dataInfo.isClose()) + completed(); - handler.succeeded(); - } - catch (IOException x) - { - handler.failed(x); - } + handler.succeeded(); } } @@ -336,7 +333,7 @@ public class ProxyHTTPSPDYConnection extends HttpConnection implements HttpParse case HTTP_1_0: { Fields.Field keepAliveHeader = headers.get(HttpHeader.KEEP_ALIVE.asString()); - if (keepAliveHeader != null) + if(keepAliveHeader!=null) persistent = HttpHeaderValue.KEEP_ALIVE.asString().equals(keepAliveHeader.value()); if (!persistent) persistent = HttpMethod.CONNECT.is(headers.get("method").value()); @@ -347,7 +344,7 @@ public class ProxyHTTPSPDYConnection extends HttpConnection implements HttpParse case HTTP_1_1: { Fields.Field connectionHeader = headers.get(HttpHeader.CONNECTION.asString()); - if (connectionHeader != null) + if(connectionHeader != null) persistent = !HttpHeaderValue.CLOSE.asString().equals(connectionHeader.value()); else persistent = true; @@ -368,7 +365,7 @@ public class ProxyHTTPSPDYConnection extends HttpConnection implements HttpParse { 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 5fe79092b4..329ff72fe5 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.StreamFrameListener; 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 @@ public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener 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 @@ public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener 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) @@ -267,6 +273,12 @@ public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener 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 de96a4c8df..fe532865ec 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.net.InetSocketAddress; 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 @@ public abstract class AbstractHTTPSPDYTest 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 @@ public abstract class AbstractHTTPSPDYTest 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 e6816a38e5..99e9e5fcb8 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 @@ -21,7 +21,6 @@ package org.eclipse.jetty.spdy.server.http; import java.io.IOException; 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,6 +32,7 @@ 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.http.HTTPSPDYHeader; import org.eclipse.jetty.util.Fields; import org.junit.Assert; import org.junit.Test; @@ -79,7 +79,7 @@ public class ConcurrentStreamsTest extends AbstractHTTPSPDYTest 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); 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 9689a2d40d..07b7bb1d73 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 @@ -21,6 +21,8 @@ package org.eclipse.jetty.spdy.server.http; 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 +35,10 @@ 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.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; @@ -250,22 +251,29 @@ public class HttpTransportOverSPDYTest } @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 17f0e17d8d..65c19c7ed6 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 @@ -51,6 +51,7 @@ import org.eclipse.jetty.spdy.api.SessionFrameListener; 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 +82,7 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest @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()); @@ -383,7 +384,7 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest } } - 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 82537fd3ba..7cdeb25d19 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 @@ -51,6 +51,7 @@ 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.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; @@ -356,7 +357,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest outputStream.write(bytes); baseRequest.setHandled(true); } - }); + }, 30000); Session pushCacheBuildSession = startClient(version, bigResponseServerAddress, null); Fields mainResourceHeaders = createHeadersWithoutReferrer(mainResource); @@ -442,7 +443,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest baseRequest.setHandled(true); } }); - return startHTTPServer(version, gzipHandler); + return startHTTPServer(version, gzipHandler, 30000); } private Session sendMainRequestAndCSSRequest(SessionFrameListener sessionFrameListener, boolean awaitPush) throws Exception @@ -461,6 +462,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest 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() { @@ -595,7 +597,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest output.print("body { background: #FFF; }"); baseRequest.setHandled(true); } - }); + }, 30000); Session session1 = startClient(version, address, null); final CountDownLatch mainResourceLatch = new CountDownLatch(1); @@ -686,7 +688,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest } baseRequest.setHandled(true); } - }); + }, 30000); Session session1 = startClient(version, address, null); final CountDownLatch mainResourceLatch = new CountDownLatch(1); @@ -797,7 +799,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest output.print("\u0000"); baseRequest.setHandled(true); } - }); + }, 30000); Session session1 = startClient(version, address, null); final CountDownLatch mainResourceLatch = new CountDownLatch(1); @@ -917,7 +919,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest 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 +1004,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest output.print("body { background: #FFF; }"); baseRequest.setHandled(true); } - }); + }, 30000); Session session1 = startClient(version, address, null); final CountDownLatch mainResourceLatch = new CountDownLatch(1); 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 7d038bc7b0..e2e5acc1de 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 @@ -24,6 +24,7 @@ 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; 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 d861a737f5..1acf93d6a2 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 0613f92f56..734fe4ddd0 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 @@ -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.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; 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 98519952a3..9e6d454ed0 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,7 +18,6 @@ package org.eclipse.jetty.spdy.server.http; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -27,7 +26,9 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; 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; @@ -49,15 +50,18 @@ import org.eclipse.jetty.spdy.api.Stream; 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.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; @@ -66,6 +70,8 @@ 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); @@ -91,7 +97,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest 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); @@ -132,7 +138,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest 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); @@ -175,7 +181,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest 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); @@ -217,7 +223,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest 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); @@ -257,7 +263,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest 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"); @@ -306,7 +312,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest 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"); @@ -348,7 +354,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest 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"); @@ -393,7 +399,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest 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"); @@ -435,7 +441,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest output.write(data.getBytes("UTF-8")); handlerLatch.countDown(); } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -481,7 +487,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest output.write(data); handlerLatch.countDown(); } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -532,7 +538,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest output.write(data2.getBytes("UTF-8")); handlerLatch.countDown(); } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -587,7 +593,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest output.write(data); handlerLatch.countDown(); } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -643,7 +649,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest } handlerLatch.countDown(); } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -696,7 +702,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest output.write(data); handlerLatch.countDown(); } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -749,7 +755,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest output.close(); handlerLatch.countDown(); } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -807,7 +813,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest output.write(data2.getBytes("UTF-8")); handlerLatch.countDown(); } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -860,7 +866,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest httpResponse.sendRedirect(location); handlerLatch.countDown(); } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -897,7 +903,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest 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); @@ -942,7 +948,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest { 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); @@ -996,7 +1002,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest output.write(pangram2.getBytes("UTF-8")); handlerLatch.countDown(); } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -1039,31 +1045,11 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest 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() { @@ -1072,13 +1058,10 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest 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); @@ -1104,7 +1087,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest contentLength.addAndGet(dataInfo.asBytes(true).length); if (dataInfo.isClose()) { - assertEquals(length, contentLength.get()); + Assert.assertEquals(data.length, contentLength.get()); dataLatch.countDown(); } } @@ -1134,7 +1117,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest 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); @@ -1198,7 +1181,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest } }.start(); } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -1241,7 +1224,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest } } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -1286,7 +1269,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest } } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -1354,7 +1337,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest } }.start(); } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo"); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -1422,7 +1405,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest output.write(data); } } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo"); final CountDownLatch responseLatch = new CountDownLatch(2); @@ -1463,7 +1446,7 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest request.setHandled(true); latch.countDown(); } - }), null); + }, 30000), null); Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo"); final CountDownLatch responseLatch = new CountDownLatch(1); @@ -1484,4 +1467,145 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest 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/proxy/ProxyHTTPToSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPToSPDYTest.java index cb8f6a3881..fcbd90e73b 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 @@ -46,9 +46,9 @@ 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.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; 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 6d6ba31c8f..080f9aeeac 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 @@ -56,9 +56,13 @@ import org.eclipse.jetty.spdy.server.http.SPDYTestUtils; 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; @@ -71,9 +75,11 @@ 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 +95,8 @@ public class ProxySPDYToHTTPLoadTest }; private final short version; + private final String server1String = "server1"; + private final String server2String = "server2"; @Parameterized.Parameters public static Collection<Short[]> parameters() @@ -97,7 +105,8 @@ public class ProxySPDYToHTTPLoadTest } 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 +116,62 @@ public class ProxySPDYToHTTPLoadTest this.version = version; } - protected InetSocketAddress startServer(Handler handler) throws Exception + @Before + public void init() throws Exception + { + // 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 { - server = new Server(); + 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 +195,11 @@ public class ProxySPDYToHTTPLoadTest 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 +222,7 @@ public class ProxySPDYToHTTPLoadTest } 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 +231,16 @@ public class ProxySPDYToHTTPLoadTest { 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 +263,7 @@ public class ProxySPDYToHTTPLoadTest @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 +276,8 @@ public class ProxySPDYToHTTPLoadTest 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 +285,9 @@ public class ProxySPDYToHTTPLoadTest 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 556c148658..9f4d2e3524 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 @@ -52,7 +52,7 @@ import org.eclipse.jetty.spdy.api.StringDataInfo; 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; @@ -169,7 +169,7 @@ public class ProxySPDYToHTTPTest 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); @@ -202,7 +202,7 @@ public class ProxySPDYToHTTPTest 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); @@ -275,7 +275,7 @@ public class ProxySPDYToHTTPTest { resetLatch.countDown(); } - }).get(5, TimeUnit.SECONDS); + }); Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "GET", "/"); headers.put("connection", "close"); @@ -309,7 +309,7 @@ public class ProxySPDYToHTTPTest 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() @@ -335,7 +335,7 @@ public class ProxySPDYToHTTPTest 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); @@ -383,7 +383,7 @@ public class ProxySPDYToHTTPTest 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); @@ -439,7 +439,7 @@ public class ProxySPDYToHTTPTest { goAwayLatch.countDown(); } - }).get(5, TimeUnit.SECONDS); + }); Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "POST", "/"); ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true); @@ -470,7 +470,7 @@ public class ProxySPDYToHTTPTest } }), 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); @@ -510,7 +510,7 @@ public class ProxySPDYToHTTPTest { 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 dfb7f38a29..5f5ac53833 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 @@ -19,7 +19,6 @@ package org.eclipse.jetty.spdy.server.proxy; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; @@ -150,16 +149,10 @@ public class ProxySPDYToSPDYLoadTest @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 @@ public class ProxySPDYToSPDYLoadTest { 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(); @@ -271,7 +264,7 @@ public class ProxySPDYToSPDYLoadTest } @Override - public StreamFrameListener onSyn (Stream stream, SynInfo synInfo) + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) { Fields requestHeaders = synInfo.getHeaders(); Assert.assertNotNull(requestHeaders.get("via")); 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 064f556f95..0786e01bf1 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 @@ -44,9 +44,9 @@ 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.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; @@ -169,7 +169,7 @@ public class ProxySPDYToSPDYTest })); 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", "/"); @@ -215,7 +215,7 @@ public class ProxySPDYToSPDYTest { resetLatch.countDown(); } - }).get(5, TimeUnit.SECONDS); + }); Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "GET", "/"); headers.put(header, "bar"); @@ -247,7 +247,7 @@ public class ProxySPDYToSPDYTest })); 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); @@ -318,7 +318,7 @@ public class ProxySPDYToSPDYTest 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()); @@ -417,7 +417,7 @@ public class ProxySPDYToSPDYTest 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()); @@ -516,7 +516,7 @@ public class ProxySPDYToSPDYTest { pingLatch.countDown(); } - }).get(5, TimeUnit.SECONDS); + }); client.ping(new PingInfo(5, TimeUnit.SECONDS)); @@ -552,7 +552,7 @@ public class ProxySPDYToSPDYTest { 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 30da0a8474..25faad48a7 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.util.log.class=org.eclipse.jetty.util.log.StdErrLog #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 26633f5bdb..ae442793cb 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.6-SNAPSHOT</version> + <version>9.1.0-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/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 cdbe2ba6c2..829ceb9f8d 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 @@ package org.eclipse.jetty.spdy.server; 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 @@ public abstract class AbstractTest 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 708e6dc9be..0291fd3415 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 @@ public class IdleTimeoutTest extends AbstractTest 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 @@ public class IdleTimeoutTest extends AbstractTest 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 @@ public class IdleTimeoutTest extends AbstractTest 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/SettingsTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SettingsTest.java index 3f7963b074..692109a9e9 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 @@ public class SettingsTest extends AbstractTest 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/UnsupportedVersionTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/UnsupportedVersionTest.java index e3b2df033c..5849544c6a 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 java.util.concurrent.TimeUnit; 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 @@ public class UnsupportedVersionTest extends AbstractTest } @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 70fe0241f1..b285b7776f 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.6-SNAPSHOT</version> + <version>9.1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>jetty-spring</artifactId> diff --git a/jetty-start/pom.xml b/jetty-start/pom.xml index f3d5f079e3..9dfb9455c6 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.6-SNAPSHOT</version> + <version>9.1.0-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 0000000000..3cd5199601 --- /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 775936cefc..3333b52d61 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.io.PrintStream; 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); + } + + @Override + public String toString() + { + return "startJarLoader@" + Long.toHexString(hashCode()); + } + } - private final Vector<File> _elements = new Vector<File>(); + private final List<File> elements = new ArrayList<File>(); public Classpath() { @@ -44,117 +59,87 @@ public class Classpath addClasspath(initial); } - public File[] getElements() - { - return _elements.toArray(new File[_elements.size()]); - } - - public int count() - { - return _elements.size(); - } - - public boolean addComponent(String component) + public boolean addClasspath(String s) { - if ((component != null) && (component.length() > 0)) + boolean added = false; + if (s != null) { - 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) + StringTokenizer t = new StringTokenizer(s,File.pathSeparator); + while (t.hasMoreTokens()) { + added |= addComponent(t.nextToken()); } } - return false; + return added; } - public boolean addComponent(File component) + public boolean addComponent(File path) { - if (component != null) + if ((path == null) || (!path.exists())) { - try - { - if (component.exists()) - { - File key = component.getCanonicalFile(); - if (!_elements.contains(key)) - { - _elements.add(key); - return true; - } - } - } - catch (IOException e) + // 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 addClasspath(String s) + public boolean addComponent(String component) { - boolean added = false; - if (s != null) + if ((component == null) || (component.length() <= 0)) { - StringTokenizer t = new StringTokenizer(s, File.pathSeparator); - while (t.hasMoreTokens()) - { - added |= addComponent(t.nextToken()); - } + // nothing to add + return false; } - return added; + + return addComponent(new File(component)); } - public void dump(PrintStream out) + public int count() { - int i = 0; - for (File element : _elements) - { - out.printf("%2d: %s\n", i++, element.getAbsolutePath()); - } + return elements.size(); } - @Override - public String toString() + public void dump(PrintStream out) { - 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++) + int i = 0; + for (File element : elements) { - cp.append(File.pathSeparatorChar); - cp.append(((_elements.elementAt(i))).getPath()); + out.printf("%2d: %s%n",i++,element.getAbsolutePath()); } - 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 @@ public class Classpath { 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()); - } + return elements; } + public boolean 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 cpOther the other classpath to overlay + * 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 cpOther) + public void overlay(Classpath other) { - for (File otherElement : cpOther._elements) + for (File otherElement : other.elements) { - if (this._elements.contains(otherElement)) + if (this.elements.contains(otherElement)) { // Skip duplicate entries continue; } - this._elements.add(otherElement); + this.elements.add(otherElement); } } - public boolean isEmpty() + @Override + public String toString() { - return (_elements == null) || (_elements.isEmpty()); + 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 d50a4b70c1..d0a4197a99 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(String bin) + public CommandLineBuilder() { args = new ArrayList<String>(); + } + + public CommandLineBuilder(String bin) + { + this(); args.add(bin); } @@ -42,7 +118,9 @@ public class CommandLineBuilder public void addArg(String arg) { if (arg != null) + { args.add(quote(arg)); + } } /** @@ -65,7 +143,7 @@ public class CommandLineBuilder */ 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 class CommandLineBuilder public void addRawArg(String arg) { if (arg != null) + { args.add(arg); + } } public List<String> getArgs() @@ -94,42 +174,6 @@ public class CommandLineBuilder 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 9ed3b1b4b1..0000000000 --- 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 0000000000..977985ccdb --- /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 0000000000..c3b6b418a5 --- /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 266716ff84..0000000000 --- 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 858a2c09e0..51fc8dcaf0 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 @@ public class JarVersion return entry; } } - + return null; } @@ -58,11 +58,15 @@ public class JarVersion { 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 @@ public class JarVersion { 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 @@ public class JarVersion { 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 class JarVersion 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 @@ public class JarVersion 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 4a5ed83779..fd890e57a5 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,18 @@ package org.eclipse.jetty.start; +import static org.eclipse.jetty.start.UsageException.*; + 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,465 +37,264 @@ import java.net.InetAddress; 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 javax.naming.OperationNotSupportedException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; -/*-------------------------------------------*/ /** + * 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>Execution + * <ul> + * <li>--list-modules</li> + * <li>--list-classpath</li> + * <li>--list-config</li> + * <li>--version</li> + * <li>--help</li> + * <li>--dry-run</li> + * <li>--exec</li> + * <li>--stop</li> + * <li>(or normal startup)</li> + * </ul> + * </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); } } - Main() throws IOException + static void usageExit(int exit) { - _jettyHome = System.getProperty("jetty.home","."); - _jettyHome = new File(_jettyHome).getCanonicalPath(); + usageExit(null,exit); } - public List<String> expandCommandLine(String[] args) throws Exception + static void usageExit(Throwable t, int exit) { - 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 (t != null) { - 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); - } + t.printStackTrace(System.err); } - - // if no non-option inis, add the start.ini and start.d - if (!ini) - { - arguments.addAll(0,parseStartIniFiles()); - } - - return arguments; + 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); } - List<String> parseStartIniFiles() + private final BaseHome baseHome; + + Main() throws IOException { - 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; + baseHome = new BaseHome(); } - public List<String> processCommandLine(List<String> arguments) throws Exception + private void copyInThread(final InputStream in, final OutputStream out) { - // The XML Configuration Files to initialize with - List<String> xmls = new ArrayList<String>(); - - // Process the arguments - int startup = 0; - for (String arg : arguments) + new Thread(new Runnable() { - 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)) + @Override + public void run() { - 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()) + try { - // 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); + byte[] buf = new byte[1024]; + int len = in.read(buf); + while (len > 0) + { + out.write(buf,0,len); + len = in.read(buf); + } } - - if (!startLog.canWrite()) + catch (IOException e) { - // 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); + // e.printStackTrace(); } - 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; - } + }).start(); + } + + private void initFile(FileArg arg) + { + try + { + File file = baseHome.getBaseFile(arg.location); - if (arg.startsWith("-")) + StartLog.debug("Module file %s %s",file.getAbsolutePath(),(file.exists()?"[Exists!]":"")); + if (file.exists()) { - _jvmArgs.add(arg); - continue; + return; } - // Is this a Property? - if (arg.indexOf('=') >= 0) + if (arg.uri!=null) { - String[] assign = arg.split("=",2); + URL url = new URL(arg.uri); + + System.err.println("DOWNLOAD: " + url + " to " + arg.location); - switch (assign.length) + FS.ensureDirectoryExists(file.getParentFile()); + + byte[] buf = new byte[8192]; + try (InputStream in = url.openStream(); OutputStream out = new FileOutputStream(file);) { - case 2: - if ("OPTIONS".equals(assign[0])) + while (true) + { + int len = in.read(buf); + + if (len > 0) { - String opts[] = assign[1].split(","); - for (String opt : opts) - _config.addActiveOption(opt.trim()); + out.write(buf,0,len); } - else + if (len < 0) { - this._config.setProperty(assign[0],assign[1]); + break; } - break; - case 1: - this._config.setProperty(assign[0],null); - break; - default: - break; + } } - - continue; } - - // Anything else is considered an XML file. - if (xmls.contains(arg)) + else if (arg.location.endsWith("/")) { - System.out.println("WARN: Argument '" + arg + "' specified multiple times. Check start.ini?"); - System.out.println("Use \"java -jar start.jar --help\" for more information."); + System.err.println("MKDIR: " + baseHome.toShortForm(file)); + file.mkdirs(); } - xmls.add(arg); + else + 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); } - - return xmls; } - private void download(String arg) + private void dumpClasspathWithVersions(Classpath classpath) { - try + System.out.println(); + System.out.println("Jetty Server Classpath:"); + System.out.println("-----------------------"); + if (classpath.count() == 0) { - 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(); + System.out.println("No classpath entries and/or version information available show."); + return; + } - byte[] buf=new byte[8192]; - try (InputStream in = url.openStream(); OutputStream out = new FileOutputStream(file);) - { - while(true) - { - int len = in.read(buf); + 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 --module=name command line options will be reflected here."); - if (len>0) - out.write(buf,0,len); - if (len<0) - break; - } - } - } - catch(Exception e) + int i = 0; + for (File element : classpath.getElements()) { - System.err.println("ERROR: processing "+arg+"\n"+e); - e.printStackTrace(); - usageExit(EXIT_USAGE); + System.out.printf("%2d: %24s | %s\n",i++,getVersion(element),baseHome.toShortForm(element)); } } - private void usage() + public BaseHome getBaseHome() { - String usageResource = "org/eclipse/jetty/start/usage.txt"; - InputStream usageStream = getClass().getClassLoader().getResourceAsStream(usageResource); + return baseHome; + } - if (usageStream == null) + private String getVersion(File element) + { + if (element.isDirectory()) { - System.err.println("ERROR: detailed usage resource unavailable"); - usageExit(EXIT_USAGE); + return "(dir)"; } - BufferedReader buf = null; - try + if (element.isFile()) { - buf = new BufferedReader(new InputStreamReader(usageStream)); - String line; - - while ((line = buf.readLine()) != null) + String name = element.getName().toLowerCase(Locale.ENGLISH); + if (name.endsWith(".jar")) { - 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); - } + return JarVersion.getVersion(element); } } - catch (IOException e) - { - usageExit(e,EXIT_USAGE); - } - finally - { - close(buf); - } - System.exit(EXIT_USAGE); + + return ""; } - public void invokeMain(ClassLoader classloader, String classname, List<String> args) throws IllegalAccessException, InvocationTargetException, - NoSuchMethodException, ClassNotFoundException + public void invokeMain(StartArgs args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException, IOException { Class<?> invoked_class = null; + ClassLoader classloader = args.getClasspath().getClassLoader(); + String mainclass = args.getMainClassname(); try { - invoked_class = classloader.loadClass(classname); + invoked_class = classloader.loadClass(mainclass); } catch (ClassNotFoundException e) { - e.printStackTrace(); + System.out.println("WARNING: Nothing to start, exiting ..."); + StartLog.debug(e); + usageExit(ERR_INVOKE_MAIN); + return; } - 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; - } - } + StartLog.debug("%s - %s",invoked_class,invoked_class.getPackage().getImplementationVersion()); - String argArray[] = args.toArray(new String[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() }; @@ -509,555 +305,418 @@ public class Main 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 + public void listConfig(StartArgs args) { - // Load potential Config (start.config) - List<String> configuredXmls = loadConfig(xmls); + // Dump Jetty Home / Base + args.dumpEnvironment(); - // 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."); - } + // Dump JVM Args + args.dumpJvmArgs(); - // Normalize the XML config options passed on the command line. - configuredXmls = resolveXmlConfigs(configuredXmls); + // Dump System Properties + args.dumpSystemProperties(); - // Get Desired Classpath based on user provided Active Options. - Classpath classpath = _config.getActiveClasspath(); + // Dump Properties + args.dumpProperties(); - 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()); - } + // Dump Classpath + dumpClasspathWithVersions(args.getClasspath()); - // Show the usage information and return - if (_showUsage) - { - usage(); - return; - } - - // Show the version information and return - if (_dumpVersions) - { - showClasspathWithVersions(classpath); - return; - } + // Dump Resolved XMLs + args.dumpActiveXmls(baseHome); + } - // Show all options with version information - if (_listOptions) - { - showAllOptionsWithVersions(); - return; - } + private void listModules(StartArgs args) + { + System.out.println(); + System.out.println("Jetty All Available Modules:"); + System.out.println("----------------------------"); + args.getAllModules().dump(); - if (_listConfig) - { - listConfig(); - return; - } + // Dump Enabled Modules + System.out.println(); + System.out.println("Jetty Active Module Tree:"); + System.out.println("-------------------------"); + Modules modules = args.getAllModules(); + modules.dumpEnabledTree(); + } - // Show Command Line to execute Jetty - if (_dryRun) - { - CommandLineBuilder cmd = buildCommandLine(classpath,configuredXmls); - System.out.println(cmd.toString()); - return; - } + 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"); - // execute Jetty in another JVM - if (_exec) + // Is this a module? + Modules modules = args.getAllModules(); + Module module = modules.get(name); + if (module == null) { - 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. + StartLog.warn("ERROR: No known module for %s",name); return; } - if (_jvmArgs.size() > 0 || _sysProps.size() > 0) + // 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()) { - 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) + module_ini = new StartIni(ini); + if (module_ini.getLineMatches(Pattern.compile("--module=(.*, *)*" + name)).size() == 0) { - classname = mainClass; + StartLog.warn("ERROR: %s is not enabled in %s!",name,short_ini); + return; } - - Config.debug("main.class=" + classname); - - invokeMain(cl,classname,configuredXmls); } - catch (Exception e) - { - usageExit(e,ERR_INVOKE_MAIN); - } - } - private void copyInThread(final InputStream in, final OutputStream out) - { - new Thread(new Runnable() + 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)) { - public void run() + String source = null; + PrintWriter out = null; + try { - try + if (appendStartIni) { - byte[] buf = new byte[1024]; - int len = in.read(buf); - while (len > 0) + if ((!start_ini.exists() && !start_ini.createNewFile()) || !start_ini.canWrite()) { - out.write(buf,0,len); - len = in.read(buf); + 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)); } - catch (IOException e) + else { - // e.printStackTrace(); + // 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); } - } - - }).start(); - } - - private String resolveXmlConfig(String xmlFilename) throws FileNotFoundException - { - if (!xmlFilename.toLowerCase(Locale.ENGLISH).endsWith(".xml")) - { - // Nothing to resolve. - return xmlFilename; - } - File xml = new File(xmlFilename); - if (xml.exists() && xml.isFile()) - { - return xml.getAbsolutePath(); - } + 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; + } - xml = new File(_jettyHome,fixPath(xmlFilename)); - if (xml.exists() && xml.isFile()) - { - return xml.getAbsolutePath(); + modules.enable(n,Collections.singletonList(source)); + } + } + } + } + finally + { + if (out != null) + { + out.close(); + } + } } - - xml = new File(_jettyHome,fixPath("etc/" + xmlFilename)); - if (xml.exists() && xml.isFile()) + else if (ini.exists()) { - return xml.getAbsolutePath(); + StartLog.info("%-15s initialised in %s",name,short_ini); } - 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) + // Also list other places this module is enabled + for (String source : module.getSources()) { - cmd.addArg(x); + if (!short_ini.equals(source)) + { + StartLog.warn("%-15s enabled in %s",name,baseHome.toShortForm(source)); + } } - cmd.addRawArg("-Djetty.home=" + _jettyHome); - // Special Stop/Shutdown properties - ensureSystemPropertySet("STOP.PORT"); - ensureSystemPropertySet("STOP.KEY"); - - // System Properties - for (String p : _sysProps) + // Do downloads now + for (String file : module.getFiles()) { - String v = System.getProperty(p); - cmd.addEqualsArg("-D" + p,v); + initFile(new FileArg(file)); } - 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) + // Process dependencies from top level only + if (topLevel) { - File prop_file = File.createTempFile("start",".properties"); - if (!_dryRun) - prop_file.deleteOnExit(); - try (OutputStream out = new FileOutputStream(prop_file)) + 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) { - properties.store(out,"start.jar properties"); + moduleIni(args,m.getName(),false,appendStartIni); } - 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 + * Convenience for <code>processCommandLine(cmdLine.toArray(new String[cmdLine.size()]))</code> */ - private void ensureSystemPropertySet(String key) + public StartArgs processCommandLine(List<String> cmdLine) throws Exception { - if (_sysProps.contains(key)) - { - return; // done - } - - Properties props = Config.getProperties(); - if (props.containsKey(key)) - { - String val = props.getProperty(key,null); - if (val == null) - { - return; // no value to set - } - // setup system property - _sysProps.add(key); - System.setProperty(key,val); - } + return this.processCommandLine(cmdLine.toArray(new String[cmdLine.size()])); } - private String findJavaBin() + public StartArgs processCommandLine(String[] cmdLine) throws Exception { - 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(); - } + StartArgs args = new StartArgs(cmdLine); - javabin = findExecutable(javaHome,"bin/java.exe"); - if (javabin != null) - { - return javabin.getAbsolutePath(); - } + // Processing Order is important! + // ------------------------------------------------------------ + // 1) Directory Locations - return "java"; - } + // Set Home and Base at the start, as all other paths encountered + // will be based off of them. + baseHome.initialize(args); - 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; - } + // ------------------------------------------------------------ + // 2) Start Logging + StartLog.getInstance().initialize(baseHome,args); - private void showAllOptionsWithVersions() - { - Set<String> sectionIds = _config.getSectionIds(); + 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()); - StringBuffer msg = new StringBuffer(); - msg.append("There "); - if (sectionIds.size() > 1) + // ------------------------------------------------------------ + // 3) Load Inis + File start_ini = baseHome.getBaseFile("start.ini"); + if (FS.canReadFile(start_ini)) { - msg.append("are "); + StartLog.debug("Reading ${jetty.base}/start.ini - %s",start_ini); + args.parse(baseHome,new StartIni(start_ini)); } - 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) + File start_d = baseHome.getBaseFile("start.d"); + if (FS.canReadDirectory(start_d)) { - if (Config.DEFAULT_SECTION.equals(sectionId)) - { - System.out.println("GLOBAL option (Prepended Entries)"); - } - else if ("*".equals(sectionId)) - { - System.out.println("GLOBAL option (Appended Entries) (*)"); - } - else + List<File> files = new ArrayList<>(); + for (File file : start_d.listFiles(new FS.IniFilter())) { - System.out.printf("Option [%s]",sectionId); - if (Character.isUpperCase(sectionId.charAt(0))) - { - System.out.print(" (Aggregate)"); - } - System.out.println(); + files.add(file); } - System.out.println("-------------------------------------------------------------"); - - Classpath sectionCP = _config.getSectionClasspath(sectionId); - if (sectionCP.isEmpty()) + Collections.sort(files,new NaturalSort.Files()); + for (File file : files) { - System.out.println("Empty option, no classpath entries active."); - System.out.println(); - continue; + StartLog.debug("Reading ${jetty.base}/start.d/%s - %s",file.getName(),file); + args.parse(baseHome,new StartIni(file)); } - - 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(); } - } - private void showClasspathWithVersions(Classpath classpath) - { - // Iterate through active classpath, and fetch Implementation Version from each entry (if present) - // to dump to end user. + // 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(); - System.out.println("Active Options: " + _config.getActiveOptions()); + // 5) Module Registration + Modules modules = new Modules(); + StartLog.debug("Registering all modules"); + modules.registerAll(baseHome); - if (classpath.count() == 0) + // 6) Active Module Resolution + for (String enabledModule : args.getEnabledModules()) { - System.out.println("No version information available show."); - return; + List<String> sources = args.getSources(enabledModule); + modules.enable(enabledModule,sources); } - 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."); + StartLog.debug("Building Module Graph"); + modules.buildGraph(); - 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); - } - } + args.setAllModules(modules); + List<Module> activeModules = modules.resolveEnabled(); - private String fixPath(String path) - { - return path.replace('/',File.separatorChar); + // 7) Lib & XML Expansion / Resolution + args.expandModules(baseHome,activeModules); + + // 8) Resolve Extra XMLs + args.resolveExtraXmls(baseHome); + + return args; } - private String getVersion(File element) + public void start(StartArgs args) throws IOException, InterruptedException { - if (element.isDirectory()) + 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()) { - return "(dir)"; + usage(true); } - if (element.isFile()) + // Show the version information and return + if (args.isListClasspath()) { - String name = element.getName().toLowerCase(Locale.ENGLISH); - if (name.endsWith(".jar")) - { - return JarVersion.getVersion(element); - } + dumpClasspathWithVersions(classpath); } - return ""; - } - - private List<String> resolveXmlConfigs(List<String> xmls) throws FileNotFoundException - { - List<String> ret = new ArrayList<String>(); - for (String xml : xmls) + // Show configuration + if (args.isListConfig()) { - ret.add(resolveXmlConfig(xml)); + listConfig(args); } - return ret; - } - - private void listConfig() - { - InputStream cfgstream = null; - try + // Show modules + if (args.isListModules()) { - cfgstream = getConfigStream(); - byte[] buf = new byte[4096]; - - int len = 0; - - while (len >= 0) - { - len = cfgstream.read(buf); - if (len > 0) - System.out.write(buf,0,len); - } + listModules(args); } - catch (Exception e) + + // Generate Module Graph File + if (args.getModuleGraphFilename() != null) { - usageExit(e,ERR_UNKNOWN); + 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); } - finally + + // Show Command Line to execute Jetty + if (args.isDryRun()) { - close(cfgstream); + CommandLineBuilder cmd = args.getMainArgs(baseHome,true); + System.out.println(cmd.toString()); } - } - /** - * 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. - */ - private List<String> loadConfig(List<String> xmls) - { - InputStream cfgstream = null; - try + if (args.isStopCommand()) { - // 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); + int stopPort = Integer.parseInt(args.getProperties().getProperty("STOP.PORT")); + String stopKey = args.getProperties().getProperty("STOP.KEY"); - _jettyHome = Config.getProperty("jetty.home",_jettyHome); - if (_jettyHome != null) + if (args.getProperties().getProperty("STOP.WAIT") != null) { - _jettyHome = new File(_jettyHome).getCanonicalPath(); - System.setProperty("jetty.home",_jettyHome); - } + int stopWait = Integer.parseInt(args.getProperties().getProperty("STOP.PORT")); - // 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()) + stop(stopPort,stopKey,stopWait); + } + else { - // add xmlconfigs arriving via start.config - if (!ret.contains(xmlconfig)) - { - ret.add(xmlconfig); - } + stop(stopPort,stopKey); } - - return ret; } - catch (Exception e) + + // Initialize + for (String module : args.getModuleStartIni()) { - usageExit(e,ERR_UNKNOWN); - return null; // never executed (just here to satisfy javac compiler) + moduleIni(args,module,true,true); } - finally + + // Initialize + for (String module : args.getModuleIni()) { - close(cfgstream); + moduleIni(args,module,true,false); } - } - private InputStream getConfigStream() throws FileNotFoundException - { - String config = _startConfig; - if (config == null || config.length() == 0) + // Informational command line, don't run jetty + if (!args.isRun()) { - config = System.getProperty("START","org/eclipse/jetty/start/start.config"); + return; } - Config.debug("config=" + config); + // 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(); + } + }); - // Look up config as resource first. - InputStream cfgstream = getClass().getClassLoader().getResourceAsStream(config); + 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; + } - // resource not found, try filesystem next - if (cfgstream == null) + if (args.hasJvmArgs() || args.hasSystemProperties()) { - cfgstream = new FileInputStream(config); + System.err.println("WARNING: System properties and/or JVM args set. Consider using --dry-run or --exec"); } - return cfgstream; + // Set current context class loader to what is selected. + ClassLoader cl = classpath.getClassLoader(); + Thread.currentThread().setContextClassLoader(cl); + + // Invoke the Main Class + try + { + invokeMain(args); + } + catch (Exception e) + { + usageExit(e,ERR_INVOKE_MAIN); + } } /** @@ -1086,32 +745,34 @@ public class Main 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 +789,40 @@ public class Main } } - static void usageExit(Throwable t, int 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) + public void usage(boolean exit) { - if (!ini.exists()) - { - 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 + String usageResource = "org/eclipse/jetty/start/usage.txt"; + boolean usagePresented = false; + try (InputStream usageStream = getClass().getClassLoader().getResourceAsStream(usageResource)) { - 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("#")) - { - continue; - } - - if (arg.endsWith("/")) + try (InputStreamReader reader = new InputStreamReader(usageStream); BufferedReader buf = new BufferedReader(reader)) { - 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"); + } + if (exit) + { + System.exit(EXIT_USAGE); } - - return args; - } - - void addJvmArgs(List<String> jvmArgs) - { - _jvmArgs.addAll(jvmArgs); } } 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 0000000000..fdb1416f18 --- /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 0000000000..7eacae35da --- /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 0000000000..8b21764733 --- /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 0000000000..f6a7e7ac81 --- /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 index 3669750495..fc569bc44a 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/README.txt +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/README.TXT @@ -9,11 +9,12 @@ 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: +To run with the demo: - java -jar start.jar OPTIONS=All + java -jar start.jar --enable=demo + java -jar start.jar -To run with the default options: +To run with the default modules: java -jar start.jar @@ -31,15 +32,15 @@ To see the available options To run with JSP support (if available) - java -jar start.jar OPTIONS=Server,jsp + java -jar start.jar --module=jsp To run with JMX support - java -jar start.jar OPTIONS=Server,jmx etc/jetty-jmx.xml etc/jetty.xml + java -jar start.jar --module=jmx To run with JSP & JMX support - java -jar start.jar OPTIONS=Server,jsp,jmx etc/jetty-jmx.xml etc/jetty.xml + 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 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 0000000000..324e47da64 --- /dev/null +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java @@ -0,0 +1,842 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// 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.*; + +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.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> moduleIni = 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; + } + + @SuppressWarnings("unchecked") + Enumeration<String> keyEnum = (Enumeration<String>)properties.propertyNames(); + while (keyEnum.hasMoreElements()) + { + String name = keyEnum.nextElement(); + String value = properties.getProperty(name); + System.out.printf(" %s = %s%n",name,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; + } + + for (String key : systemPropertyKeys) + { + 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> getModuleIni() + { + return moduleIni; + } + + 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("--module-ini=")) + { + if (!CMD_LINE_SOURCE.equals(source)) + { + throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,source); + } + moduleIni.addAll(getValues(arg)); + run = false; + return; + } + + if (arg.startsWith("--module-start-ini=")) + { + 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); + } + 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 0000000000..ed268ea12b --- /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 0000000000..697731f2c8 --- /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(format + "%n",args); + } + + public static void warn(String format, Object... args) + { + System.err.printf(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 0000000000..4da2951a3b --- /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 0000000000..08ba684bed --- /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 49d823c93a..c49de4aae7 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 @@ public class Version { 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 @@ public class Version { 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 4dde6bfb2c..0000000000 --- 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 e25a73d277..ec19dd47c9 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 @@ -6,17 +6,26 @@ Usage: java -jar start.jar [options...] [properties...] [configs...] 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. @@ -25,32 +34,102 @@ Command Line Options: a sub process. This can be used when start.ini contains -X or -D arguments, but creates an extra JVM instance. + - --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. +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 + (See bellow) - --daemon Start in daemon mode with stderr and stdout - redirected to ${jetty.log}/start.log + --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> + 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. - --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. + --module-ini=<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. - --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. + --module-start-ini=<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. + + --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. + +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 - If the file does not exist at the given location, then - download it from the given http URI + --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: @@ -75,66 +154,28 @@ System Properties: 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 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. - - 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) + jetty.home=[directory] + Set the home directory of the jetty distribution. - 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 0000000000..e7f3e956ee --- /dev/null +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java @@ -0,0 +1,142 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// 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.*; + +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/ConfigTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigTest.java deleted file mode 100644 index 59bbeaa74d..0000000000 --- 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 0000000000..6b608fc0ed --- /dev/null +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java @@ -0,0 +1,256 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// 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.*; + +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 4d399c0473..713d5df949 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)); + StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()])); + BaseHome baseHome = main.getBaseHome(); + System.err.println(args); + + ConfigurationAssert.assertConfiguration(baseHome,args,"assert-home.txt"); } @Test - public void testExpandCommandLine() throws Exception + public void testStopProcessing() throws Exception { + 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(); - List<String> args = main.expandCommandLine(new String[] {}); + StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()])); + System.err.println(args); - 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)); + //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")); } + + @Test + public void testListConfig() throws Exception + { + 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 - public void testProcessCommandLine() throws Exception + @Ignore("Just a bit noisy for general testing") + public void testHelp() throws Exception { Main main = new Main(); - List<String> args = main.expandCommandLine(new String[] {}); - List<String> xmls = main.processCommandLine(args); - - 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)); + main.usage(false); } @Test - public void testBuildCommandLine() throws IOException, NoSuchFieldException, IllegalAccessException + public void testWithCommandLine() throws Exception { - List<String> jvmArgs = new ArrayList<String>(); - jvmArgs.add("--exec"); - jvmArgs.add("-Xms1024m"); - jvmArgs.add("-Xmx1024m"); + List<String> cmdLineArgs = new ArrayList<>(); - List<String> xmls = new ArrayList<String>(); - xmls.add("jetty.xml"); - xmls.add("jetty-jmx.xml"); - xmls.add("jetty-logging.xml"); + 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(); - main.addJvmArgs(jvmArgs); - - 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")); + + 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"); } - private Classpath nastyWayToCreateAClasspathObject(String jettyHome) throws NoSuchFieldException, IllegalAccessException + @Test + public void testJettyHomeWithSpaces() 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<>(); + + 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 0000000000..a8d42e1f24 --- /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.*; + +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 0000000000..6be72f6588 --- /dev/null +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.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.start; + +import static org.hamcrest.Matchers.*; + +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 0000000000..70aac2317b --- /dev/null +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.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.start; + +import static org.hamcrest.Matchers.*; + +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 4928fc0e05..dc81001d82 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 class PropertyDump { public static void main(String[] args) { + // As System Properties Properties props = System.getProperties(); Enumeration<?> names = props.propertyNames(); while (names.hasMoreElements()) @@ -36,6 +40,35 @@ public class PropertyDump 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 9275957109..90ae91549c 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 @@ -32,10 +32,8 @@ import java.util.List; 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 +92,17 @@ public class PropertyPassingTest @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 +113,19 @@ public class PropertyPassingTest } @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 +138,17 @@ public class PropertyPassingTest @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 +184,16 @@ public class PropertyPassingTest 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 +208,6 @@ public class PropertyPassingTest 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 0000000000..ebc67361be --- /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 0000000000..b73061bba4 --- /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 0000000000..01d429aada --- /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 0000000000..327b770420 --- /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 0000000000..e751e99807 --- /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 index e69de29bb2..e69de29bb2 100644 --- a/jetty-start/src/test/resources/jetty.home/lib/example.jar +++ b/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 index e7a73e5775..e7a73e5775 100644 --- a/jetty-start/src/test/resources/jetty.home/resources/example.properties +++ b/jetty-start/src/test/resources/extra-resources/example.properties diff --git a/jetty-start/src/test/resources/jetty.home/lib/LOGGING.JAR b/jetty-start/src/test/resources/hb.1/base/start.d/jmx.ini index e69de29bb2..e69de29bb2 100644 --- a/jetty-start/src/test/resources/jetty.home/lib/LOGGING.JAR +++ b/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 0000000000..d648d1b1fd --- /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/ext/custom-impl.jar b/jetty-start/src/test/resources/hb.1/base/start.d/myapp.ini index e69de29bb2..e69de29bb2 100644 --- a/jetty-start/src/test/resources/jetty.home/lib/ext/custom-impl.jar +++ b/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 0000000000..c0ebe8c9a9 --- /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/spec.zip b/jetty-start/src/test/resources/hb.1/home/start.d/jmx.ini index e69de29bb2..e69de29bb2 100644 --- a/jetty-start/src/test/resources/jetty.home/lib/spec.zip +++ b/jetty-start/src/test/resources/hb.1/home/start.d/jmx.ini diff --git a/jetty-start/src/test/resources/hb.1/home/start.d/jndi.ini b/jetty-start/src/test/resources/hb.1/home/start.d/jndi.ini new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/hb.1/home/start.d/jndi.ini diff --git a/jetty-start/src/test/resources/hb.1/home/start.d/jsp.ini b/jetty-start/src/test/resources/hb.1/home/start.d/jsp.ini new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/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 0000000000..7161329d50 --- /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/hb.1/home/start.d/ssl.ini b/jetty-start/src/test/resources/hb.1/home/start.d/ssl.ini new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/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 0000000000..69a418ea91 --- /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 with spaces/lib/example of a library with spaces.jar b/jetty-start/src/test/resources/jetty home with spaces/lib/example of a library with spaces.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/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 0000000000..f14d52a29c --- /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 0000000000..ad2e4c214d --- /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 38c02081e6..0000000000 --- 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 38c02081e6..0000000000 --- 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 38c02081e6..0000000000 --- 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/core.jar b/jetty-start/src/test/resources/jetty.home/lib/core.jar Binary files differdeleted file mode 100644 index e6110ca8f9..0000000000 --- a/jetty-start/src/test/resources/jetty.home/lib/core.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index a032b2dd80..0000000000 --- a/jetty-start/src/test/resources/jetty.home/lib/foo/bar/foobar.jar +++ /dev/null diff --git a/jetty-start/src/test/resources/jetty.home/lib/http.jar b/jetty-start/src/test/resources/jetty.home/lib/http.jar Binary files differdeleted file mode 100644 index a032b2dd80..0000000000 --- a/jetty-start/src/test/resources/jetty.home/lib/http.jar +++ /dev/null diff --git a/jetty-start/src/test/resources/jetty.home/lib/io.jar b/jetty-start/src/test/resources/jetty.home/lib/io.jar Binary files differdeleted file mode 100644 index 164dee4ee4..0000000000 --- a/jetty-start/src/test/resources/jetty.home/lib/io.jar +++ /dev/null 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 f27a277445..0000000000 --- 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 Binary files differdeleted file mode 100644 index 67fb4296b7..0000000000 --- a/jetty-start/src/test/resources/jetty.home/lib/server.jar +++ /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 Binary files differdeleted file mode 100644 index 515acf6162..0000000000 --- a/jetty-start/src/test/resources/jetty.home/lib/util.jar +++ /dev/null diff --git a/jetty-start/src/test/resources/jetty.home/lib/xml.jar b/jetty-start/src/test/resources/jetty.home/lib/xml.jar Binary files differdeleted file mode 100644 index 9650bac6dc..0000000000 --- a/jetty-start/src/test/resources/jetty.home/lib/xml.jar +++ /dev/null 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 356fccd390..0000000000 --- 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 679a221dd7..0000000000 --- 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 59313d3b99..0000000000 --- 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 eda5499d5c..0000000000 --- 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 f38e5a426a..0000000000 --- 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 38c02081e6..0000000000 --- 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 0000000000..164f3f99f4 --- /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 0000000000..5d9f2af915 --- /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 0000000000..def27d7da7 --- /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 0000000000..165197471f --- /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 0000000000..75fb89ce9f --- /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 0000000000..1c0e065afd --- /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 0000000000..13fa5089f3 --- /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 0000000000..d3950e6c02 --- /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 0000000000..7dd6100e9d --- /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/usecases/base.with.db/lib/db/bonecp.jar b/jetty-start/src/test/resources/usecases/base.with.db/lib/db/bonecp.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/base.with.db/lib/db/bonecp.jar diff --git a/jetty-start/src/test/resources/usecases/base.with.db/lib/db/mysql-driver.jar b/jetty-start/src/test/resources/usecases/base.with.db/lib/db/mysql-driver.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/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 0000000000..5acded8b28 --- /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 0000000000..d5513df249 --- /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/usecases/home/etc/README.spnego b/jetty-start/src/test/resources/usecases/home/etc/README.spnego new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/README.spnego diff --git a/jetty-start/src/test/resources/usecases/home/etc/jdbcRealm.properties b/jetty-start/src/test/resources/usecases/home/etc/jdbcRealm.properties new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jdbcRealm.properties diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-annotations.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-annotations.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-annotations.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-contexts.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-contexts.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-contexts.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-debug.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-debug.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-debug.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-demo.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-demo.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-demo.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-deploy.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-deploy.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-deploy.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-http.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-http.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-http.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-https.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-https.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-https.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-ipaccess.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-ipaccess.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-ipaccess.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-jaas.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-jaas.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-jaas.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-jmx.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-jmx.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-jmx.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-logging.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-logging.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-logging.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-lowresources.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-lowresources.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-lowresources.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-monitor.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-monitor.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-monitor.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-plus.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-plus.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-plus.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-proxy.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-proxy.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-proxy.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-requestlog.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-requestlog.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-requestlog.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-rewrite.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-rewrite.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-rewrite.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-setuid.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-setuid.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-setuid.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-spdy-proxy.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-spdy-proxy.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-spdy-proxy.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-spdy.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-spdy.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-spdy.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-ssl.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-ssl.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-ssl.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-started.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-started.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-started.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-stats.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-stats.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-stats.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-testrealm.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-testrealm.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-testrealm.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-webapps.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-webapps.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-webapps.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-websockets.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-websockets.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-websockets.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty-xinetd.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty-xinetd.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty-xinetd.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty.conf b/jetty-start/src/test/resources/usecases/home/etc/jetty.conf new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty.conf diff --git a/jetty-start/src/test/resources/usecases/home/etc/jetty.xml b/jetty-start/src/test/resources/usecases/home/etc/jetty.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/jetty.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/keystore b/jetty-start/src/test/resources/usecases/home/etc/keystore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/keystore diff --git a/jetty-start/src/test/resources/usecases/home/etc/krb5.ini b/jetty-start/src/test/resources/usecases/home/etc/krb5.ini new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/krb5.ini diff --git a/jetty-start/src/test/resources/usecases/home/etc/realm.properties b/jetty-start/src/test/resources/usecases/home/etc/realm.properties new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/realm.properties diff --git a/jetty-start/src/test/resources/usecases/home/etc/spnego.conf b/jetty-start/src/test/resources/usecases/home/etc/spnego.conf new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/spnego.conf diff --git a/jetty-start/src/test/resources/usecases/home/etc/spnego.properties b/jetty-start/src/test/resources/usecases/home/etc/spnego.properties new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/spnego.properties diff --git a/jetty-start/src/test/resources/usecases/home/etc/test-realm.xml b/jetty-start/src/test/resources/usecases/home/etc/test-realm.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/test-realm.xml diff --git a/jetty-start/src/test/resources/usecases/home/etc/webdefault.xml b/jetty-start/src/test/resources/usecases/home/etc/webdefault.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/etc/webdefault.xml diff --git a/jetty-start/src/test/resources/usecases/home/lib/annotations/javax.annotation-api-1.2.jar b/jetty-start/src/test/resources/usecases/home/lib/annotations/javax.annotation-api-1.2.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/annotations/javax.annotation-api-1.2.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/annotations/org.objectweb.asm-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/annotations/org.objectweb.asm-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/annotations/org.objectweb.asm-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/ext/.nodelete b/jetty-start/src/test/resources/usecases/home/lib/ext/.nodelete new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/ext/.nodelete diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-annotations-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-annotations-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-annotations-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-client-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-client-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-client-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-continuation-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-continuation-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-continuation-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-deploy-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-deploy-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-deploy-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-http-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-http-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-http-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-io-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-io-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-io-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-jaas-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-jaas-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-jaas-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-jmx-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-jmx-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-jmx-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-jndi-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-jndi-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-jndi-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-jsp-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-jsp-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-jsp-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-plus-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-plus-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-plus-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-proxy-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-proxy-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-proxy-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-rewrite-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-rewrite-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-rewrite-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.RC0.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.RC0.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.RC0.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-security-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-security-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-security-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-server-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-server-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-server-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-servlet-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-servlet-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-servlet-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-servlets-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-servlets-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-servlets-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-util-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-util-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-util-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-webapp-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-webapp-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-webapp-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jetty-xml-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/jetty-xml-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jetty-xml-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jndi/javax.activation-1.1.jar b/jetty-start/src/test/resources/usecases/home/lib/jndi/javax.activation-1.1.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jndi/javax.activation-1.1.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jndi/javax.transaction-api-1.2.jar b/jetty-start/src/test/resources/usecases/home/lib/jndi/javax.transaction-api-1.2.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jndi/javax.transaction-api-1.2.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.el-3.0.0.jar b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.el-3.0.0.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.el-3.0.0.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp-2.3.2.jar b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp-2.3.2.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp-2.3.2.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp-api-2.3.1.jar b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp-api-2.3.1.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/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/usecases/home/lib/jsp/javax.servlet.jsp.jstl-1.2.0.jar b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp.jstl-1.2.0.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/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/usecases/home/lib/jsp/org.apache.taglibs.standard.glassfish-1.2.0.jar b/jetty-start/src/test/resources/usecases/home/lib/jsp/org.apache.taglibs.standard.glassfish-1.2.0.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/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/usecases/home/lib/jsp/org.eclipse.jdt.core-3.8.2.jar b/jetty-start/src/test/resources/usecases/home/lib/jsp/org.eclipse.jdt.core-3.8.2.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/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/usecases/home/lib/monitor/jetty-monitor-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/monitor/jetty-monitor-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/monitor/jetty-monitor-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/servlet-api-3.1.jar b/jetty-start/src/test/resources/usecases/home/lib/servlet-api-3.1.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/servlet-api-3.1.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/setuid/jetty-setuid-java-1.0.1.jar b/jetty-start/src/test/resources/usecases/home/lib/setuid/jetty-setuid-java-1.0.1.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/setuid/jetty-setuid-java-1.0.1.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-linux.so b/jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-linux.so new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-linux.so diff --git a/jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-osx.so b/jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-osx.so new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-osx.so diff --git a/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-client-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-client-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-client-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-core-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-core-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-core-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-common-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-common-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-common-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-server-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-server-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-server-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-server-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-server-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-server-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-client-impl-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-client-impl-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-client-impl-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-server-impl-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-server-impl-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-server-impl-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/websocket/javax.websocket-api-1.0.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/javax.websocket-api-1.0.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/websocket/javax.websocket-api-1.0.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-api-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-api-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-api-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-client-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-client-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-client-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-common-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-common-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-common-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-server-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-server-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-server-TEST.jar diff --git a/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-servlet-TEST.jar b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-servlet-TEST.jar new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/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 0000000000..65e4654127 --- /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 0000000000..ad8ea32088 --- /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 0000000000..6788eacf79 --- /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 0000000000..f740ea2c76 --- /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 0000000000..94c0e40316 --- /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 0000000000..85154141ac --- /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 0000000000..281c5dba04 --- /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 0000000000..956ea0f2e3 --- /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 0000000000..9fb04f7a57 --- /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 0000000000..fd8740ae8d --- /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 0000000000..33c077ce68 --- /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 0000000000..f85530d3c8 --- /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 0000000000..4ca96de10e --- /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 0000000000..67f006d0c4 --- /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 0000000000..711bdea13d --- /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 0000000000..b781f00bf2 --- /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 0000000000..7873329afa --- /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 0000000000..2b048dbc76 --- /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 0000000000..85fe5f090d --- /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 0000000000..ba3163275f --- /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 0000000000..d0c2de35d7 --- /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 0000000000..fdb65c57a1 --- /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 0000000000..92e31a2b23 --- /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 0000000000..0922469cdf --- /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 0000000000..f62c554555 --- /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 0000000000..f45babd886 --- /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 0000000000..fdc1b3c7b0 --- /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 0000000000..d53107a84f --- /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/usecases/home/resources/.nodelete b/jetty-start/src/test/resources/usecases/home/resources/.nodelete new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/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 0000000000..a65a9cb002 --- /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 7f8f819f51..4bc3464ad9 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.6-SNAPSHOT</version> + <version>9.1.0-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 894a17fa86..70278f39f6 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 @@ public class JSON /** * 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/pom.xml b/jetty-util/pom.xml index 967a8e6a15..a3a96ab6ce 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.6-SNAPSHOT</version> + <version>9.1.0-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 0000000000..3296369389 --- /dev/null +++ b/jetty-util/src/main/config/modules/logging.mod @@ -0,0 +1,11 @@ +# +# Jetty std err/out logging +# + +[xml] + +etc/jetty-logging.xml + +[ini-template] +# Number of days to retain logs +# jetty.log.retain=90 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 e4f04d6c27..4bebefd105 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 @@ public class ArrayQueue<E> extends AbstractList<E> implements Queue<E> { 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 936984cc01..2cb33a41b3 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 @@ -346,8 +346,7 @@ public class B64Code * 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 7d34cfc5e0..6c62b3be26 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 @@ import java.util.concurrent.locks.ReentrantLock; /** * 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu /** * Creates an unbounded {@link BlockingArrayQueue} with default initial capacity and grow factor. - * + * * @see #DEFAULT_CAPACITY * @see #DEFAULT_GROWTH */ @@ -94,10 +90,10 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } /** - * 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu /** * 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu /** * 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } /*----------------------------------------------------------------------------*/ - /* 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } finally { - headLock.unlock(); + _headLock.unlock(); } } finally { - tailLock.unlock(); + _tailLock.unlock(); } } @@ -178,7 +179,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } /*----------------------------------------------------------------------------*/ - /* Queue methods */ + /* Queue methods */ /*----------------------------------------------------------------------------*/ @SuppressWarnings("unchecked") @@ -189,8 +190,8 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } finally { - headLock.unlock(); + _headLock.unlock(); } return e; } @@ -218,8 +219,8 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } finally { - headLock.unlock(); + _headLock.unlock(); } return e; } @@ -251,7 +252,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } /*----------------------------------------------------------------------------*/ - /* BlockingQueue methods */ + /* BlockingQueue methods */ /*----------------------------------------------------------------------------*/ @Override @@ -259,10 +260,8 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu { 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu // Should we expand array? if (size == _elements.length) { - headLock.lock(); + _headLock.lock(); try { if (!grow()) @@ -280,7 +279,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } finally { - headLock.unlock(); + _headLock.unlock(); } } @@ -292,19 +291,19 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } finally { - tailLock.unlock(); + _tailLock.unlock(); } if (notEmpty) { - headLock.lock(); + _headLock.lock(); try { _notEmpty.signal(); } finally { - headLock.unlock(); + _headLock.unlock(); } } @@ -339,8 +338,8 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } finally { - headLock.unlock(); + _headLock.unlock(); } return e; } @@ -377,8 +376,8 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu { 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } finally { - headLock.unlock(); + _headLock.unlock(); } return e; } @@ -414,12 +413,12 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu @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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } /*----------------------------------------------------------------------------*/ - /* 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } finally { - headLock.unlock(); + _headLock.unlock(); } } finally { - tailLock.unlock(); + _tailLock.unlock(); } } @@ -532,12 +531,11 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu { 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } finally { - headLock.unlock(); + _headLock.unlock(); } } finally { - tailLock.unlock(); + _tailLock.unlock(); } } @@ -635,12 +632,12 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu @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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu 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 class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu */ public int getCapacity() { - return _elements.length; + _tailLock.lock(); + try + { + return _elements.length; + } + finally + { + _tailLock.unlock(); + } } /** @@ -750,7 +755,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } /*----------------------------------------------------------------------------*/ - /* Implementation methods */ + /* Implementation methods */ /*----------------------------------------------------------------------------*/ private boolean grow() @@ -758,12 +763,11 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu 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 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu } 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 2351d7340b..170bd24f4f 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 @@ public class BlockingCallback implements Callback } } - /** 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 198e9e747a..3c779495cf 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 @@ -30,6 +30,8 @@ import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.nio.charset.Charset; +import org.eclipse.jetty.util.resource.Resource; + /* ------------------------------------------------------------------------------- */ /** @@ -787,7 +789,7 @@ public class BufferUtil 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")) { @@ -795,6 +797,29 @@ public class BufferUtil } } + 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/Fields.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Fields.java index 7436dfcb7d..287a1750d9 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 @@ -206,7 +206,7 @@ public class Fields implements Iterable<Fields.Field> private final String name; private final String[] values; - private Field(String name, String value) + public Field(String name, String value) { this(name, new String[]{value}); } 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 f9b2527d56..f8a61b51f7 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 @@ import java.util.concurrent.atomic.AtomicReference; /* ------------------------------------------------------------ */ /** 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 @@ import java.util.concurrent.atomic.AtomicReference; * <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 @@ public abstract class IteratingCallback implements Callback */ 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 @@ -83,7 +82,7 @@ public abstract class IteratingCallback implements Callback // A true return indicates we are finished a 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; @@ -103,7 +102,8 @@ public abstract class IteratingCallback implements Callback failed(e); } } - + + /* ------------------------------------------------------------ */ @Override public void succeeded() { @@ -132,7 +132,13 @@ public abstract class IteratingCallback implements Callback } } } - + + /* ------------------------------------------------------------ */ + /** + * 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 @@ public abstract class IteratingCallback implements Callback 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 0000000000..9415f1667f --- /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 d79365ca61..805a3104e9 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 @@ public class Jetty 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 d75600b86f..4a320f762f 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 @@ import org.eclipse.jetty.util.resource.Resource; 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 40ae7a9945..c778322b24 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 @@ public class MultiMap<V> extends HashMap<String,List<V>> /** * 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 @@ public class MultiMap<V> extends HashMap<String,List<V>> * <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 f47a19726f..228ebee3a6 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 @@ -216,6 +216,16 @@ public class MultiPartInputStreamParser } } + + /** + * @see javax.servlet.http.Part#getSubmittedFileName() + */ + @Override + public String getSubmittedFileName() + { + return getContentDispositionFilename(); + } + public byte[] getBytes() { if (_bout!=null) @@ -301,7 +311,6 @@ public class MultiPartInputStreamParser /** * Get the file, if any, the data has been written to. - * @return */ public File getFile () { @@ -343,8 +352,6 @@ public class MultiPartInputStreamParser /** * Get the already parsed parts. - * - * @return */ public Collection<Part> getParsedParts() { @@ -391,7 +398,6 @@ public class MultiPartInputStreamParser /** * Parse, if necessary, the multipart data and return the list of Parts. * - * @return * @throws IOException * @throws ServletException */ @@ -414,7 +420,6 @@ public class MultiPartInputStreamParser * Get the named Part. * * @param name - * @return * @throws IOException * @throws ServletException */ 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 1b09521530..35a2d03f97 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 @@ public interface Trie<V> /* ------------------------------------------------------------ */ /** Get and exact match from a String key * @param s The key - * @return */ public V get(String s); @@ -58,7 +57,6 @@ public interface Trie<V> * @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 dfe0203385..6a6c3e0699 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 @@ -83,6 +83,11 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable { } + public UrlEncoded(String query) + { + decodeTo(query,this,ENCODING,-1); + } + /* ----------------------------------------------------------------- */ public void decode(String query) { @@ -269,7 +274,6 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable * @param offset the offset within raw to decode from * @param length the length of the section to decode * @param map the {@link MultiMap} to populate - * @param buffer the buffer to decode into */ public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap<String> map) { 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 b51f2afe3c..5db7c533ba 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 @@ public @interface ManagedObject { /** * 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 b9ff0257b6..85f7526cbe 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 @@ public @interface ManagedOperation { /** * 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 5d7f1b7432..9118a62ff9 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 @@ public @interface Name { /** * 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 20a22dc326..a5dde1ea80 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 @@ -46,7 +46,6 @@ public interface Container /** * Removes the given bean. * @return whether the bean was removed - * @see #removeBeans() */ public boolean removeBean(Object o); 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 bdbd00e609..0acf4e369f 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 @@ public abstract class AbstractLogger implements Logger } 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 d01305f626..c9047bc4b7 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 @@ public class JavaUtilLog extends AbstractLogger _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 6a26c734f0..686d18bcf1 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 @@ public class Log * 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 afe405cc71..a6a1647c72 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 @@ public interface Logger * @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 args the optional arguments + */ + 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 7909ced413..5747b46ec6 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 class LoggerLog extends AbstractLogger } } + public void debug(String msg, Object... args) { if (!_debug) @@ -186,6 +187,21 @@ public class LoggerLog extends AbstractLogger } } + 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 d9ebd177b2..3c944d6e7b 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 @@ public class Slf4jLog extends AbstractLogger { _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 1439b8af66..4488480ab9 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 class StdErrLog extends AbstractLogger } } + 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); 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 f4b11a96e5..d32230e812 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 @@ -261,7 +261,7 @@ public abstract class Resource implements ResourceFactory, Closeable /* ------------------------------------------------------------ */ /** 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 +275,7 @@ public abstract class Resource implements ResourceFactory, Closeable 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); 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 4d2d9118e2..85ae39f03e 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 @@ import java.util.Arrays; /* ------------------------------------------------------------ */ /** + * Constraint + * * Describe an auth and/or data constraint. * * @@ -65,6 +67,8 @@ public class Constraint implements Cloneable, Serializable 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 @@ public class Constraint implements Cloneable, Serializable private int _dataConstraint = DC_UNSET; private boolean _anyRole = false; + + private boolean _anyAuth = false; private boolean _authenticate = false; @@ -119,9 +125,15 @@ public class Constraint implements Cloneable, Serializable { _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 @@ public class Constraint implements Cloneable, Serializable { 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/test/java/org/eclipse/jetty/util/BenchmarkHelper.java b/jetty-util/src/test/java/org/eclipse/jetty/util/BenchmarkHelper.java index 49e7195a89..0f0ced9292 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 @@ public class BenchmarkHelper implements Runnable 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 @@ public class BenchmarkHelper implements Runnable 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/MultiMapTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiMapTest.java index ed983e0c57..2efe3cfe7c 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 @@ -102,7 +102,7 @@ public class MultiMapTest } /** - * Tests {@link MultiMap#putValues(Object, List)} + * Tests {@link MultiMap#putValues(String, List)} */ @Test public void testPutValues_List() @@ -122,7 +122,7 @@ public class MultiMapTest } /** - * Tests {@link MultiMap#putValues(Object, String...)} + * Tests {@link MultiMap#putValues(String, String...)} */ @Test public void testPutValues_StringArray() @@ -138,7 +138,7 @@ public class MultiMapTest } /** - * Tests {@link MultiMap#putValues(Object, String...)} + * Tests {@link MultiMap#putValues(String, String...)} */ @Test public void testPutValues_VarArgs() @@ -153,7 +153,7 @@ public class MultiMapTest } /** - * Tests {@link MultiMap#add(Object, Object)} + * Tests {@link MultiMap#add(String, Object)} */ @Test public void testAdd() @@ -176,7 +176,7 @@ public class MultiMapTest } /** - * Tests {@link MultiMap#addValues(Object, List)} + * Tests {@link MultiMap#addValues(String, List)} */ @Test public void testAddValues_List() @@ -202,7 +202,7 @@ public class MultiMapTest } /** - * Tests {@link MultiMap#addValues(Object, List)} + * Tests {@link MultiMap#addValues(String, List)} */ @Test public void testAddValues_List_Empty() @@ -225,7 +225,7 @@ public class MultiMapTest } /** - * Tests {@link MultiMap#addValues(Object, String[])} + * Tests {@link MultiMap#addValues(String, Object[])} */ @Test public void testAddValues_StringArray() @@ -248,7 +248,7 @@ public class MultiMapTest } /** - * Tests {@link MultiMap#addValues(Object, String[])} + * Tests {@link MultiMap#addValues(String, Object[])} */ @Test public void testAddValues_StringArray_Empty() @@ -271,7 +271,7 @@ public class MultiMapTest } /** - * Tests {@link MultiMap#removeValue(Object, Object)} + * Tests {@link MultiMap#removeValue(String, Object)} */ @Test public void testRemoveValue() @@ -293,7 +293,7 @@ public class MultiMapTest } /** - * Tests {@link MultiMap#removeValue(Object, Object)} + * Tests {@link MultiMap#removeValue(String, Object)} */ @Test public void testRemoveValue_InvalidItem() @@ -314,7 +314,7 @@ public class MultiMapTest } /** - * Tests {@link MultiMap#removeValue(Object, Object)} + * Tests {@link MultiMap#removeValue(String, Object)} */ @Test public void testRemoveValue_AllItems() @@ -344,7 +344,7 @@ public class MultiMapTest } /** - * Tests {@link MultiMap#removeValue(Object, Object)} + * Tests {@link MultiMap#removeValue(String, Object)} */ @Test public void testRemoveValue_FromEmpty() 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 a352d1244c..e096b24e23 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 @@ -589,7 +589,7 @@ public class MultiPartInputStreamTest 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 +611,7 @@ public class MultiPartInputStreamTest 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 +632,7 @@ public class MultiPartInputStreamTest 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 +683,7 @@ public class MultiPartInputStreamTest 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/resources/TestData/WindowsDir.zip b/jetty-util/src/test/resources/TestData/WindowsDir.zip Binary files differnew file mode 100644 index 0000000000..26f2357b06 --- /dev/null +++ b/jetty-util/src/test/resources/TestData/WindowsDir.zip diff --git a/jetty-webapp/pom.xml b/jetty-webapp/pom.xml index 536e1926ac..167f139284 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.6-SNAPSHOT</version> + <version>9.1.0-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 7c25cf41d7..c3f350fd64 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. @@ -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 0000000000..f62c554555 --- /dev/null +++ b/jetty-webapp/src/main/config/modules/webapp.mod @@ -0,0 +1,9 @@ +# +# Base server +# + +[depend] +servlet + +[lib] +lib/jetty-webapp-${jetty.version}.jar 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 2986c7467c..20df5165ca 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.HashSet; 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.FilterHolder; 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 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor { 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 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor } } - 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 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor (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 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor // <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); @@ -686,8 +673,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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: { @@ -722,8 +708,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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: { @@ -758,8 +743,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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: { @@ -794,8 +778,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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: { @@ -831,8 +814,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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: { @@ -868,8 +850,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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: { @@ -905,8 +886,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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: { @@ -954,8 +934,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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: { @@ -994,8 +973,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor */ 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: { @@ -1051,8 +1029,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor if (encoding != null) { - Origin o = context.getMetaData().getOrigin("locale-encoding."+locale); - switch (o) + switch (context.getMetaData().getOrigin("locale-encoding."+locale)) { case NotSet: { @@ -1105,11 +1082,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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: { @@ -1180,7 +1154,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor { 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()) @@ -1483,8 +1458,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor if (method != null) { //handle auth-method merge - Origin o = context.getMetaData().getOrigin("auth-method"); - switch (o) + switch (context.getMetaData().getOrigin("auth-method")) { case NotSet: { @@ -1517,8 +1491,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor //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: { @@ -1563,8 +1536,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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: { @@ -1595,8 +1567,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor } //handle form-error-page - o = context.getMetaData().getOrigin("form-error-page"); - switch (o) + switch (context.getMetaData().getOrigin("form-error-page")) { case NotSet: { @@ -1669,8 +1640,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor { ((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: { @@ -1699,7 +1669,6 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor break; } } - } Iterator<XmlParser.Node> iter = node.iterator("init-param"); @@ -1709,8 +1678,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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: { @@ -1748,8 +1716,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor 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: { @@ -1779,7 +1746,6 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor } } } - } /** @@ -1793,13 +1759,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor //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: { @@ -1887,6 +1848,21 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor //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 4456a0ecc7..48b6b00048 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 @@ public class TagLibConfiguration extends AbstractConfiguration 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/WebAppContext.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java index 8daa1c61c4..2fe990edb6 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.EventListener; 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.ServletSecurityElement; 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; @@ -655,13 +655,13 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL /* ------------------------------------------------------------ */ /** 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 +673,13 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL /* ------------------------------------------------------------ */ /** 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 +705,13 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL /* ------------------------------------------------------------ */ /** 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 +724,13 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL /* ------------------------------------------------------------ */ /** 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 +1058,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL 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 +1073,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL 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); @@ -1326,7 +1328,6 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL @Override public Set<String> setServletSecurity(Dynamic registration, ServletSecurityElement servletSecurityElement) { - Set<String> unchangedURLMappings = new HashSet<String>(); //From javadoc for ServletSecurityElement: /* @@ -1361,6 +1362,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL 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 +1386,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL constraintMappings.addAll(freshMappings); ((ConstraintSecurityHandler)getSecurityHandler()).setConstraintMappings(constraintMappings); + ((ConstraintAware)getSecurityHandler()).checkPathsWithUncoveredHttpMethods(); break; } } @@ -1398,6 +1401,32 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL /* ------------------------------------------------------------ */ 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 +1473,6 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL } } - } /* ------------------------------------------------------------ */ 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 602c0638bd..4b65a819f1 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 @@ public class WebDescriptor extends Descriptor { 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 @@ public class WebDescriptor extends Descriptor } 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-websocket/README.txt b/jetty-websocket/README.TXT index 9e678e4ba8..9e678e4ba8 100644 --- a/jetty-websocket/README.txt +++ b/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 0000000000..86ca4b4ceb --- /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.0-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 0000000000..6f1184e559 --- /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 0000000000..72a327140c --- /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 0000000000..f9b98e296b --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.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.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.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 final WebSocketClient client; + + public ClientContainer() + { + 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(); + 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 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 0000000000..6f8f7d62d6 --- /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 0000000000..aa4b9c5fee --- /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 0000000000..5c9c0bb91c --- /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 0000000000..0e6527f2f8 --- /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 0000000000..7bb67b872f --- /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 0000000000..3463ae87d9 --- /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 0000000000..9021065b7c --- /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 0000000000..3851c63169 --- /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 0000000000..1c050f6628 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrExtension.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; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.websocket.Extension; + +import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; + +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; + } +} 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 0000000000..1e01dd3f57 --- /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 0000000000..0ce15977a1 --- /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 0000000000..a4edcf9443 --- /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 0000000000..14476bd47f --- /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 0000000000..7a5c1c5a39 --- /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 0000000000..7d339c1e7b --- /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 0000000000..1dd6eadc7f --- /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 0000000000..1b69499bdd --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerWrapper.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 javax.websocket.Decoder.BinaryStream; +import javax.websocket.Decoder.TextStream; +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 0000000000..4e1d952c30 --- /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 0000000000..776e5effb0 --- /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 0000000000..70075e47b7 --- /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(JsrParamIdBinary.INSTANCE); + paramsOnMessage.add(JsrParamIdText.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 0000000000..0e3f86b485 --- /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 0000000000..9654f2b40e --- /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 0000000000..0261677697 --- /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 0000000000..2d0d284917 --- /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 0000000000..19bfdb2aba --- /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 0000000000..aafe1ad22e --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBinary.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.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; + +/** + * 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); + // Streaming have no decoder + 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 0000000000..8942eec639 --- /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 0000000000..7b271a4af1 --- /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 0000000000..765f7b40a7 --- /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 0000000000..c9bb86bebf --- /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 0000000000..1340187839 --- /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 0000000000..2f7d093027 --- /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 0000000000..d7eb8f9cb6 --- /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 0000000000..f854aaa3b0 --- /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 0000000000..2f951e6caa --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnErrorCallable.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.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 >= 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 0000000000..fc38004985 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryCallable.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.annotations; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; + +import javax.websocket.DecodeException; +import javax.websocket.Decoder; +import javax.websocket.Decoder.Binary; +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 0000000000..393d7ae3f4 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryStreamCallable.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.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.Decoder.BinaryStream; +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 0000000000..44f9fec67f --- /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 0000000000..8ac8656e4b --- /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 0000000000..d9d5b32ba9 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextCallable.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.io.Reader; +import java.lang.reflect.Method; + +import javax.websocket.DecodeException; +import javax.websocket.Decoder; +import javax.websocket.Decoder.Text; +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 0000000000..fff760d8c4 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextStreamCallable.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.Reader; +import java.lang.reflect.Method; + +import javax.websocket.DecodeException; +import javax.websocket.Decoder; +import javax.websocket.Decoder.TextStream; +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 0000000000..388bee650e --- /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 0000000000..f5068adc45 --- /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 0000000000..1175f3dace --- /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 0000000000..70f6d53bc1 --- /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 0000000000..f3363d4606 --- /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 0000000000..776ab89280 --- /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 0000000000..80b6b4b551 --- /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 0000000000..cdd0edad4f --- /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 0000000000..3dd9afc3d8 --- /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 0000000000..b3f90bc217 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/BooleanDecoder.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.decoders; + +import javax.websocket.DecodeException; +import javax.websocket.Decoder; +import javax.websocket.Decoder.Text; + + +/** + * 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 0000000000..71b62db1c4 --- /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 0000000000..d1fcb91a76 --- /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 0000000000..f7136caa93 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteDecoder.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; +import javax.websocket.Decoder.Text; + + +/** + * 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 0000000000..b0813a139c --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/CharacterDecoder.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.decoders; + +import javax.websocket.DecodeException; +import javax.websocket.Decoder; +import javax.websocket.Decoder.Text; + + +/** + * 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 0000000000..f32eb4c33a --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/DoubleDecoder.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; +import javax.websocket.Decoder.Text; + + +/** + * 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 0000000000..d8e0790b5f --- /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 0000000000..47f37bd9c7 --- /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 0000000000..62104dff62 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/IntegerDecoder.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.decoders; + +import javax.websocket.DecodeException; +import javax.websocket.Decoder; +import javax.websocket.Decoder.Text; + + +/** + * 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 0000000000..f42901e9a7 --- /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 0000000000..c81454e9a5 --- /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 0000000000..e746a7635c --- /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 0000000000..2a36c88905 --- /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 0000000000..8adecfc4f0 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ShortDecoder.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; +import javax.websocket.Decoder.Text; + + +/** + * 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 0000000000..5e7a5be171 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/StringDecoder.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.jsr356.decoders; + +import javax.websocket.DecodeException; +import javax.websocket.Decoder; +import javax.websocket.Decoder.Text; + + +/** + * 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 0000000000..93e4104768 --- /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 0000000000..10cf9e92cc --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/BooleanEncoder.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.encoders; + +import javax.websocket.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.Encoder.Text; + +/** + * 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 0000000000..a1f934aa7b --- /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/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/NoOpValidator.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteBufferEncoder.java index f4d4e9089e..bee78b74df 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/NoOpValidator.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteBufferEncoder.java @@ -16,27 +16,30 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.io.payload; +package org.eclipse.jetty.websocket.jsr356.encoders; import java.nio.ByteBuffer; -import org.eclipse.jetty.websocket.api.extensions.Frame; +import javax.websocket.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.EndpointConfig; -/** - * payload validator does no validation. - */ -public class NoOpValidator implements PayloadProcessor +public class ByteBufferEncoder implements Encoder.Binary<ByteBuffer> { - public static final NoOpValidator INSTANCE = new NoOpValidator(); + @Override + public void destroy() + { + /* do nothing */ + } @Override - public void process(ByteBuffer payload) + public ByteBuffer encode(ByteBuffer object) throws EncodeException { - /* all payloads are valid in this case */ + return object; } @Override - public void reset(Frame frame) + 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 0000000000..05745ab173 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteEncoder.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.encoders; + +import javax.websocket.EncodeException; +import javax.websocket.Encoder.Text; +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 0000000000..d5b5072fb1 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/CharacterEncoder.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.encoders; + +import javax.websocket.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.Encoder.Text; + +/** + * 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/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressionMethod.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryEncoder.java index 49c415d2ff..77a4f89550 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressionMethod.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryEncoder.java @@ -16,29 +16,18 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.compress; +package org.eclipse.jetty.websocket.jsr356.encoders; import java.nio.ByteBuffer; -/** - * Compression Method - */ -public interface CompressionMethod +import javax.websocket.EncodeException; +import javax.websocket.Encoder; + +public class DefaultBinaryEncoder extends AbstractEncoder implements Encoder.Binary<ByteBuffer> { - public interface Process + @Override + public ByteBuffer encode(ByteBuffer message) throws EncodeException { - public void begin(); - - public void end(); - - public void input(ByteBuffer input); - - public boolean isDone(); - - public ByteBuffer process(); + return message; } - - public Process compress(); - - public Process decompress(); } 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 0000000000..8eab6916e7 --- /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 0000000000..e3e52a404b --- /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 0000000000..72ac319a0f --- /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 0000000000..8c8e68d88b --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DoubleEncoder.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.encoders; + +import javax.websocket.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.Encoder.Text; + +/** + * 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 0000000000..3841144985 --- /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 0000000000..3ee7ab8152 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/FloatEncoder.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; +import javax.websocket.Encoder.Text; +/** + * 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 0000000000..071fd9e14a --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/IntegerEncoder.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.encoders; + +import javax.websocket.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.Encoder.Text; + +/** + * 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 0000000000..36f64eb6ab --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/LongEncoder.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.encoders; + +import javax.websocket.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.Encoder.Text; + +/** + * 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 0000000000..89dc8693ae --- /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 0000000000..4ec73f8ef9 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ShortEncoder.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.encoders; + +import javax.websocket.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.Encoder.Text; + +/** + * 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 0000000000..5cc16d1a16 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/StringEncoder.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.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.Encoder.Text; + +/** + * 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 0000000000..8eb4091f91 --- /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 0000000000..3aa5575cca --- /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 0000000000..cc55e2e849 --- /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 0000000000..d0e49d3e7c --- /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 0000000000..f14a6505a7 --- /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 0000000000..ce95a047e4 --- /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 0000000000..db98eca21f --- /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 0000000000..21c60a5850 --- /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 0000000000..b1da55b4d3 --- /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 0000000000..b33f64c661 --- /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 0000000000..23934c42d3 --- /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 0000000000..12b4e7b97e --- /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 0000000000..f98be92566 --- /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 0000000000..07f3bd5c60 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadata.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.metadata; + +import javax.websocket.Decoder; +import javax.websocket.Encoder; + +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 0000000000..9835c05a89 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadataSet.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.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 javax.websocket.Encoder; + +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) + { + return null; + } + return metadatas.get(idx); + } + + @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 0000000000..3c01df08f5 --- /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 0000000000..0307b3caf8 --- /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 0000000000..3dcdbb2d37 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DuplicateCoderException.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.metadata; + +import javax.websocket.Decoder; +import javax.websocket.Encoder; + +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 0000000000..8a43564f07 --- /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 0000000000..5f1b279e0b --- /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 0000000000..a6ba012bbb --- /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 0000000000..e5cdc82f0a --- /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 0000000000..f72be7ad98 --- /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 0000000000..e6dffe775f --- /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 0000000000..56bdcd9464 --- /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 0000000000..01d9940151 --- /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 0000000000..13489202fd --- /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.*; + +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 0000000000..c6cb6796fc --- /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.*; + +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 0000000000..9d414f0b8a --- /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 0000000000..3afac77a7c --- /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 0000000000..3cce94ac8b --- /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.*; + +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 0000000000..1337b50481 --- /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.*; + +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 0000000000..f2eceee08b --- /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.*; + +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 0000000000..2eb52476d8 --- /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 0000000000..426e42d902 --- /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.*; + +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 0000000000..6f06ae83fc --- /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.*; + +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 0000000000..b1dd6495a6 --- /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 0000000000..a07b2ddeaa --- /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 0000000000..6316918f2a --- /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.*; + +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 0000000000..6e3520a4fc --- /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 0000000000..8191aaf372 --- /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 0000000000..f901983029 --- /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 0000000000..822ffe8af2 --- /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.*; + +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 0000000000..97bc2711c8 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/PrimitiveDecoderMetadataSetTest.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.decoders; + +import static org.hamcrest.Matchers.*; + +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 0000000000..1f3a28e548 --- /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 0000000000..1454d57d29 --- /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/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 0000000000..81149abf78 --- /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 0000000000..cc7b664f2a --- /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 0000000000..4a684a88f1 --- /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 0000000000..7475020b7a --- /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 0000000000..fc316dae6b --- /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 0000000000..7df367863b --- /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 0000000000..9370f47dc8 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_GoodSignaturesTest.java @@ -0,0 +1,162 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.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.*; + +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.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 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); + // @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 0000000000..791fb2f6cf --- /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.*; + +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 0000000000..8d5958ca43 --- /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.*; + +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 0000000000..953ab3e5db --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/TrackingSocket.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.jsr356.endpoints; + +import static org.hamcrest.Matchers.*; + +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 0000000000..8516b49444 --- /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 0000000000..5629359e89 --- /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 0000000000..79fa6819ee --- /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 0000000000..7aade74653 --- /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 0000000000..4381df56ec --- /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 0000000000..aa71e20a70 --- /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/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 0000000000..63fc76661e --- /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 0000000000..ffb8208343 --- /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 0000000000..fb87077c7a --- /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 0000000000..6db95f8a43 --- /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 0000000000..07dd3941d7 --- /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 0000000000..019aa6d647 --- /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 0000000000..01f68abfc4 --- /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 0000000000..7ed4ad7c2a --- /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 0000000000..a63d334848 --- /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 0000000000..03e5d220b8 --- /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 0000000000..9692ecf5f7 --- /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 0000000000..675f2e74a5 --- /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 0000000000..8f21c75e6f --- /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 0000000000..64aba6c62e --- /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 0000000000..caf8fe3c87 --- /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 0000000000..2b84946a8f --- /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 0000000000..ee7439f5b4 --- /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 0000000000..599deaa14e --- /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 0000000000..e184022302 --- /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 0000000000..a025f646c2 --- /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 0000000000..ca56ac285a --- /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 0000000000..1cd68b8ec2 --- /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 0000000000..7d70ed9071 --- /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 0000000000..1fb628f1a1 --- /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 0000000000..4dea4742d8 --- /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 0000000000..c10fbee104 --- /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 0000000000..b2c73d4b3f --- /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 0000000000..de9c6377f0 --- /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 0000000000..ccb9324375 --- /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 0000000000..549a65a9d5 --- /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 0000000000..4a8fd759a2 --- /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 0000000000..5a0c05babc --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSetTest.java @@ -0,0 +1,132 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.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.*; + +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 0000000000..48b58ff078 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSetTest.java @@ -0,0 +1,132 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.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.*; + +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 0000000000..dec7740172 --- /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 0000000000..3ca26bf9ab --- /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 0000000000..558c3004ac --- /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 0000000000..d52d4a90f3 --- /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 0000000000..8073105b76 --- /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 0000000000..47b59f8697 --- /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 0000000000..6cd6f79145 --- /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 0000000000..b3fbb925d4 --- /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 0000000000..edc69ea3ae --- /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 0000000000..5c55ad2a72 --- /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 0000000000..f8e040bbee --- /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.*; + +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 0000000000..648ebd41d0 --- /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 0000000000..6c5baaec7f --- /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 0000000000..d86f16134d --- /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.0-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/etc/jetty-websockets.xml b/jetty-websocket/javax-websocket-server-impl/src/main/config/etc/jetty-websockets.xml new file mode 100644 index 0000000000..797f84efd8 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/main/config/etc/jetty-websockets.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 javax.websocket Configuring classes to all webapps for this Server --> + <!-- =========================================================== --> + <Call class="org.eclipse.jetty.webapp.Configuration$ClassList" name="setServerDefault"> + <Arg><Ref refid="Server" /></Arg> + <Call name="addBefore"> + <Arg name="beforeClass">org.eclipse.jetty.annotations.AnnotationConfiguration</Arg> + <Arg> + <Array type="String"> + <Item>org.eclipse.jetty.websocket.jsr356.server.WebSocketConfiguration</Item> + </Array> + </Arg> + </Call> + </Call> + + <!-- Enable/Disable JSR356 Container for all contexts --> + <!-- This attribute may be enabled/disabled either on the server or on individual contexts --> + <Call name="setAttribute"> + <Arg>org.eclipse.jetty.websocket.jsr356</Arg> + <Arg type="Boolean">false</Arg> + </Call> + +</Configure> 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 0000000000..ecf5b3c070 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/main/config/modules/websocket.mod @@ -0,0 +1,17 @@ +# +# WebSocket Feature +# + +[depend] +# WebSocket needs Annotations feature +server +annotations + +[lib] +# WebSocket needs websocket jars (as defined in start.config) +lib/websocket/*.jar + +[xml] +# WebSocket needs websocket configuration +etc/jetty-websockets.xml + 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 0000000000..684699142f --- /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() == null) + { + 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 0000000000..e1850744ed --- /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 0000000000..c77b01f286 --- /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 0000000000..d95c46977b --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/BasicServerEndpointConfigurator.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.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; + +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) + { + /* do nothing */ + return null; + } + + @Override + public String getNegotiatedSubprotocol(List<String> supported, List<String> requested) + { + for (String possible : requested) + { + if (supported.contains(possible)) + { + return possible; + } + } + 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 0000000000..0bde7dab94 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.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.websocket.jsr356.server; + +import java.io.IOException; +import java.util.List; + +import javax.websocket.server.ServerEndpointConfig; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +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; + + public JsrCreator(ServerEndpointMetadata metadata) + { + this.metadata = metadata; + } + + @Override + public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) + { + JsrHandshakeRequest hsreq = new JsrHandshakeRequest(req); + JsrHandshakeResponse hsresp = new JsrHandshakeResponse(resp); + + ServerEndpointConfig config = metadata.getConfig(); + + ServerEndpointConfig.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); + } + + // 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 0000000000..f004efbf4a --- /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 0000000000..0eaf62f3ee --- /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 0000000000..bc7ddacd82 --- /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 0000000000..660734e16c --- /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 0000000000..57a2d7e53e --- /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 0000000000..758b6061a1 --- /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 0000000000..18b7d95264 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java @@ -0,0 +1,184 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.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.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) + { + super(); + 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); + 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) + { + webSocketServerFactory.getPolicy().setAsyncWriteTimeout(ms); + } + + @Override + public void setDefaultMaxBinaryMessageBufferSize(int 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) + { + webSocketServerFactory.getPolicy().setIdleTimeout(ms); + } + + @Override + public void setDefaultMaxTextMessageBufferSize(int 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 0000000000..25e98c583b --- /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 0000000000..ced82468ca --- /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/WebSocketConfiguration.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/WebSocketConfiguration.java new file mode 100644 index 0000000000..971d6d11c5 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/WebSocketConfiguration.java @@ -0,0 +1,103 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.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.annotations.AnnotationConfiguration; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.webapp.AbstractConfiguration; +import org.eclipse.jetty.webapp.Configuration; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.websocket.jsr356.server.deploy.DiscoveredEndpoints; +import org.eclipse.jetty.websocket.jsr356.server.deploy.ServerEndpointAnnotationHandler; +import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter; + +/** + * WebSocket Server Configuration component. + * This configuration will configure a context for JSR356 Websockets if + * the attribute "org.eclipse.jetty.websocket.jsr356" is set to true. This + * attribute may be set on an individual context or on the server to affect + * all deployed contexts. + */ +public class WebSocketConfiguration extends AbstractConfiguration +{ + public static final String ENABLE="org.eclipse.jetty.websocket.jsr356"; + private static final Logger LOG = Log.getLogger(WebSocketConfiguration.class); + + public static ServerContainer configureContext(ServletContextHandler context) + { + WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context); + + // Create the Jetty ServerContainer implementation + ServerContainer jettyContainer = new ServerContainer(filter,filter.getFactory()); + 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); + + // Store reference to DiscoveredEndpoints + context.setAttribute(DiscoveredEndpoints.class.getName(),new DiscoveredEndpoints()); + + return jettyContainer; + } + + public static boolean isJSR356Context(WebAppContext context) + { + Object enable=context.getAttribute(ENABLE); + if (enable instanceof Boolean) + return ((Boolean)enable).booleanValue(); + + enable=context.getServer().getAttribute(ENABLE); + if (enable instanceof Boolean) + return ((Boolean)enable).booleanValue(); + + return false; + } + + @Override + public void configure(WebAppContext context) throws Exception + { + if (isJSR356Context(context)) + { + LOG.debug("Configure javax.websocket for WebApp {}",context); + WebSocketConfiguration.configureContext(context); + } + } + + @Override + public void preConfigure(WebAppContext context) throws Exception + { + if (isJSR356Context(context)) + { + boolean scanningAdded = false; + // Add the annotation scanning handlers (if annotation scanning enabled) + for (Configuration config : context.getConfigurations()) + { + if (config instanceof AnnotationConfiguration) + { + AnnotationConfiguration annocfg = (AnnotationConfiguration)config; + annocfg.addDiscoverableAnnotationHandler(new ServerEndpointAnnotationHandler(context)); + scanningAdded = true; + } + } + LOG.debug("@ServerEndpoint scanning added: {}", scanningAdded); + } + } +} diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/DiscoveredEndpoints.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/DiscoveredEndpoints.java new file mode 100644 index 0000000000..124d78159d --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/DiscoveredEndpoints.java @@ -0,0 +1,158 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.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.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.HashSet; +import java.util.Set; + +import javax.websocket.Endpoint; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * Tracking for Discovered Endpoints. + * <p> + * This is a collection of endpoints, grouped by type (by Annotation or by extending Endpoint). Along with some better error messages for conflicting endpoints. + */ +public class DiscoveredEndpoints +{ + private static class LocatedClass + { + private Class<?> clazz; + + private URI location; + public LocatedClass(Class<?> clazz) + { + this.clazz = clazz; + this.location = getArchiveURI(clazz); + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("LocatedClass["); + builder.append(clazz.getName()); + builder.append("]"); + return builder.toString(); + } + } + + private static final Logger LOG = Log.getLogger(DiscoveredEndpoints.class); + + public static URI getArchiveURI(Class<?> clazz) + { + String resourceName = clazz.getName().replace('.','/') + ".class"; + URL classUrl = clazz.getClassLoader().getResource(resourceName); + if (classUrl == null) + { + // is this even possible at this point? + return null; + } + try + { + URI uri = classUrl.toURI(); + String scheme = uri.getScheme(); + if (scheme.equalsIgnoreCase("jar")) + { + String ssp = uri.getSchemeSpecificPart(); + int idx = ssp.indexOf("!/"); + if (idx >= 0) + { + ssp = ssp.substring(0,idx); + } + return URI.create(ssp); + } + } + catch (URISyntaxException e) + { + LOG.warn("Class reference not a valid URI? " + classUrl,e); + } + + return null; + } + private Set<LocatedClass> extendedEndpoints; + + private Set<LocatedClass> annotatedEndpoints; + + public DiscoveredEndpoints() + { + extendedEndpoints = new HashSet<>(); + annotatedEndpoints = new HashSet<>(); + } + + public void addAnnotatedEndpoint(Class<?> endpoint) + { + this.annotatedEndpoints.add(new LocatedClass(endpoint)); + } + + public void addExtendedEndpoint(Class<? extends Endpoint> endpoint) + { + this.extendedEndpoints.add(new LocatedClass(endpoint)); + } + + public Set<Class<?>> getAnnotatedEndpoints() + { + Set<Class<?>> endpoints = new HashSet<>(); + for (LocatedClass lc : annotatedEndpoints) + { + endpoints.add(lc.clazz); + } + return endpoints; + } + + public void getArchiveSpecificAnnnotatedEndpoints(URI archiveURI, Set<Class<?>> archiveSpecificEndpoints) + { + for (LocatedClass lc : annotatedEndpoints) + { + if ((archiveURI == null) || lc.location.getPath().equals(archiveURI.getPath())) + { + archiveSpecificEndpoints.add(lc.clazz); + } + } + } + + @SuppressWarnings("unchecked") + public void getArchiveSpecificExtendedEndpoints(URI archiveURI, Set<Class<? extends Endpoint>> archiveSpecificEndpoints) + { + for (LocatedClass lc : extendedEndpoints) + { + if ((archiveURI == null) || (lc.location.getPath().equals(archiveURI.getPath()) && Endpoint.class.isAssignableFrom(lc.clazz))) + { + archiveSpecificEndpoints.add((Class<? extends Endpoint>)lc.clazz); + } + } + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("DiscoveredEndpoints [extendedEndpoints="); + builder.append(extendedEndpoints); + builder.append(", annotatedEndpoints="); + builder.append(annotatedEndpoints); + builder.append("]"); + return builder.toString(); + } +} diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/ServerApplicationConfigListener.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/ServerApplicationConfigListener.java new file mode 100644 index 0000000000..5eebf2935f --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/ServerApplicationConfigListener.java @@ -0,0 +1,162 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.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.net.URI; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +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.ServerEndpointConfig; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.websocket.jsr356.server.ServerContainer; +import org.eclipse.jetty.websocket.jsr356.server.WebSocketConfiguration; +import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter; + +@HandlesTypes( +{ ServerApplicationConfig.class, Endpoint.class }) +public class ServerApplicationConfigListener implements ServletContainerInitializer +{ + private static final Logger LOG = Log.getLogger(ServerApplicationConfigListener.class); + + @Override + public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException + { + if (!WebSocketConfiguration.isJSR356Context(WebAppContext.getCurrentWebAppContext())) + return; + + WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)ctx.getAttribute(WebSocketUpgradeFilter.class.getName()); + if (filter == null) + { + LOG.warn("Required attribute not available: " + WebSocketUpgradeFilter.class.getName()); + return; + } + + DiscoveredEndpoints discovered = (DiscoveredEndpoints)ctx.getAttribute(DiscoveredEndpoints.class.getName()); + if (discovered == null) + { + LOG.warn("Required attribute not available: " + DiscoveredEndpoints.class.getName()); + return; + } + + LOG.debug("Found {} classes",c.size()); + LOG.debug("Discovered: {}",discovered); + + // First add all of the Endpoints + addEndpoints(c,discovered); + + // Now process the ServerApplicationConfig entries + ServerContainer container = (ServerContainer)ctx.getAttribute(javax.websocket.server.ServerContainer.class.getName()); + Set<Class<? extends Endpoint>> archiveSpecificExtendEndpoints = new HashSet<>(); + Set<Class<?>> archiveSpecificAnnotatedEndpoints = new HashSet<>(); + List<Class<? extends ServerApplicationConfig>> serverAppConfigs = filterServerApplicationConfigs(c); + + if(serverAppConfigs.size() >= 1) { + for (Class<? extends ServerApplicationConfig> clazz : filterServerApplicationConfigs(c)) + { + LOG.debug("Found ServerApplicationConfig: {}",clazz); + try + { + ServerApplicationConfig config = (ServerApplicationConfig)clazz.newInstance(); + URI archiveURI = DiscoveredEndpoints.getArchiveURI(clazz); + archiveSpecificExtendEndpoints.clear(); + archiveSpecificAnnotatedEndpoints.clear(); + discovered.getArchiveSpecificExtendedEndpoints(archiveURI,archiveSpecificExtendEndpoints); + discovered.getArchiveSpecificAnnnotatedEndpoints(archiveURI,archiveSpecificAnnotatedEndpoints); + + Set<ServerEndpointConfig> seconfigs = config.getEndpointConfigs(archiveSpecificExtendEndpoints); + if (seconfigs != null) + { + for (ServerEndpointConfig sec : seconfigs) + { + container.addEndpoint(sec); + } + } + Set<Class<?>> annotatedClasses = config.getAnnotatedEndpointClasses(archiveSpecificAnnotatedEndpoints); + if (annotatedClasses != null) + { + for (Class<?> annotatedClass : annotatedClasses) + { + container.addEndpoint(annotatedClass); + } + } + } + catch (InstantiationException | IllegalAccessException e) + { + throw new ServletException("Unable to instantiate: " + clazz.getName(),e); + } + catch (DeploymentException e) + { + throw new ServletException(e); + } + } + } else { + // Default behavior (no ServerApplicationConfigs found) + // Note: it is impossible to determine path of "extends Endpoint" discovered classes + for(Class<?> annotatedClass: discovered.getAnnotatedEndpoints()) + { + try + { + container.addEndpoint(annotatedClass); + } + catch (DeploymentException e) + { + throw new ServletException(e); + } + } + } + } + + @SuppressWarnings("unchecked") + private List<Class<? extends ServerApplicationConfig>> filterServerApplicationConfigs(Set<Class<?>> c) + { + List<Class<? extends ServerApplicationConfig>> configs = new ArrayList<>(); + for (Class<?> clazz : c) + { + if (ServerApplicationConfig.class.isAssignableFrom(clazz)) + { + configs.add((Class<? extends ServerApplicationConfig>)clazz); + } + } + return configs; + } + + @SuppressWarnings("unchecked") + private void addEndpoints(Set<Class<?>> c, DiscoveredEndpoints discovered) + { + for (Class<?> clazz : c) + { + if (Endpoint.class.isAssignableFrom(clazz)) + { + discovered.addExtendedEndpoint((Class<? extends Endpoint>)clazz); + } + } + } +} diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/ServerEndpointAnnotation.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/ServerEndpointAnnotation.java new file mode 100644 index 0000000000..a0ba33c8d4 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/ServerEndpointAnnotation.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.deploy; + +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.DiscoveredAnnotation; +import org.eclipse.jetty.webapp.WebAppContext; + +/** + * Once an Annotated Server Endpoint is discovered, add it to the list of + * discovered Annotated Endpoints. + */ +public class ServerEndpointAnnotation extends DiscoveredAnnotation +{ + private static final Logger LOG = Log.getLogger(ServerEndpointAnnotation.class); + + public ServerEndpointAnnotation(WebAppContext context, String className) + { + super(context,className); + } + + public ServerEndpointAnnotation(WebAppContext context, String className, Resource resource) + { + super(context,className,resource); + } + + @Override + public void apply() + { + Class<?> clazz = getTargetClass(); + + if (clazz == null) + { + LOG.warn(_className + " cannot be loaded"); + return; + } + + DiscoveredEndpoints discovered = (DiscoveredEndpoints)_context.getAttribute(DiscoveredEndpoints.class.getName()); + if(discovered == null) { + LOG.warn("Context attribute not found: " + DiscoveredEndpoints.class.getName()); + return; + } + + discovered.addAnnotatedEndpoint(clazz); + } +} diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/ServerEndpointAnnotationHandler.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/ServerEndpointAnnotationHandler.java new file mode 100644 index 0000000000..7b97671e45 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/ServerEndpointAnnotationHandler.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.deploy; + +import java.util.List; + +import javax.websocket.server.ServerEndpoint; + +import org.eclipse.jetty.annotations.AbstractDiscoverableAnnotationHandler; +import org.eclipse.jetty.annotations.AnnotationParser.Value; +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; + +/** + * Processing for @{@link ServerEndpoint} annotations during Web App Annotation Scanning + */ +public class ServerEndpointAnnotationHandler extends AbstractDiscoverableAnnotationHandler +{ + private static final String ANNOTATION_NAME = "javax.websocket.server.ServerEndpoint"; + private static final Logger LOG = Log.getLogger(ServerEndpointAnnotationHandler.class); + + public ServerEndpointAnnotationHandler(WebAppContext context) + { + super(context); + } + + public ServerEndpointAnnotationHandler(WebAppContext context, List<DiscoveredAnnotation> list) + { + super(context,list); + } + + @Override + public String getAnnotationName() + { + return ANNOTATION_NAME; + } + + @Override + public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotationName, + List<Value> values) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("handleClass: {}, {}, {}",className,annotationName,values); + } + + if (!ANNOTATION_NAME.equals(annotationName)) + { + // Not the one we are interested in + return; + } + + ServerEndpointAnnotation annotation = new ServerEndpointAnnotation(_context,className,_resource); + addAnnotation(annotation); + } + + @Override + public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation, + List<Value> values) + { + /* @ServerEndpoint annotation not supported for fields */ + } + + @Override + public void handleMethod(String className, String methodName, int access, String desc, String signature, String[] exceptions, String annotation, + List<Value> values) + { + /* @ServerEndpoint annotation not supported for methods */ + } +} 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 0000000000..9442f06da6 --- /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 0000000000..fd1c3cbca2 --- /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.ServerApplicationConfigListener
\ 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 0000000000..57e3ef37cb --- /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 0000000000..1eff0401d4 --- /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 0000000000..9873c9a6e7 --- /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 0000000000..acd724bbd2 --- /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 0000000000..7cb91337c6 --- /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/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 0000000000..1b5f535392 --- /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 0000000000..e06c5514c8 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicEndpointTest.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.server; + +import java.net.URI; +import java.util.Queue; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import javax.websocket.Endpoint; +import javax.websocket.server.ServerContainer; + +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/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 0000000000..86034cc807 --- /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 0000000000..ac5609854a --- /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 0000000000..5f9abef220 --- /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 0000000000..7ffd66cc2d --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoClientSocket.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.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.Session; + +@ClientEndpoint +public class EchoClientSocket extends TrackingSocket +{ + public final CountDownLatch eventCountLatch; + private Session session; + + public EchoClientSocket(int expectedEventCount) + { + this.eventCountLatch = new CountDownLatch(expectedEventCount); + } + + public void close() throws IOException + { + 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; + 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 + { + session.getBasicRemote().sendObject(obj); + } + + public void sendPartialBinary(ByteBuffer part, boolean fin) throws IOException + { + session.getBasicRemote().sendBinary(part,fin); + } + + public void sendPartialText(String part, boolean fin) throws IOException + { + session.getBasicRemote().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 0000000000..0588563735 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoTest.java @@ -0,0 +1,294 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.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.*; + +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.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.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.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"); + + // 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 0000000000..377bdf9205 --- /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 0000000000..91b43a422b --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyServerEndpointConfiguratorTest.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 static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +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 0000000000..8fe0fc218d --- /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 0000000000..fb1564dd25 --- /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 0000000000..36f3862ae7 --- /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 0000000000..9dd290ff6b --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.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.*; + +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 0000000000..4a29554b2e --- /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.*; + +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 0000000000..144949f9d9 --- /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.*; + +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 0000000000..feb3c39a49 --- /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 0000000000..e68437bed8 --- /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 0000000000..3ad75a55f2 --- /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 0000000000..8cd49ae13c --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTest.java @@ -0,0 +1,171 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.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.*; + +import java.net.URI; +import java.util.Queue; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +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.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class SessionTest +{ + private static WSServer server; + private static URI serverUri; + + @BeforeClass + public static 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(); + server.deployWebapp(webapp); + } + + @AfterClass + public static 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 0000000000..296c947136 --- /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 0000000000..e2d114cb1e --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/TrackingSocket.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.jsr356.server; + +import static org.hamcrest.Matchers.*; + +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 0000000000..1fa5333de3 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/WSServer.java @@ -0,0 +1,198 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.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.*; + +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)); + context.setAttribute(WebSocketConfiguration.ENABLE,Boolean.TRUE); + + // @formatter:off + context.setConfigurations(new Configuration[] { + new WebSocketConfiguration(), + 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); + 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/deploy/DiscoveredEndpointsTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/deploy/DiscoveredEndpointsTest.java new file mode 100644 index 0000000000..3bcaf075b8 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/deploy/DiscoveredEndpointsTest.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.server.deploy; + +import static org.hamcrest.Matchers.*; + +import java.net.URI; + +import org.junit.Assert; +import org.junit.Test; + +public class DiscoveredEndpointsTest +{ + /** + * Attempt to get an Archive URI, to a class known to be in an archive. + */ + @Test + public void testGetArchiveURI_InJar() + { + Class<?> clazz = javax.websocket.server.ServerContainer.class; + URI archiveURI = DiscoveredEndpoints.getArchiveURI(clazz); + // should point to a JAR file + Assert.assertThat("Archive URI for: " + clazz.getName(), + archiveURI.toASCIIString(), + endsWith("javax.websocket-api-1.0.jar")); + } + + /** + * Get an Archive URI for a class reference that is known to not be in an archive. + */ + @Test + public void testGetArchiveURI_InClassDirectory() + { + Class<?> clazz = DiscoveredEndpointsTest.class; + URI archivePath = DiscoveredEndpoints.getArchiveURI(clazz); + // Should be null, as it does not point to an archive + Assert.assertThat("Archive URI for: " + clazz, + archivePath, + nullValue()); + } +} 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 0000000000..247b738f90 --- /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.*; + +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 0000000000..24e443a661 --- /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.*; + +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 0000000000..d15cee68ed --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecTest.java @@ -0,0 +1,283 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.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.*; +import static org.junit.Assert.*; + +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"); + 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 0000000000..c654e35970 --- /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 0000000000..bf26e84831 --- /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 0000000000..33b8125077 --- /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 0000000000..ec7b630481 --- /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 0000000000..c8408aa53e --- /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 0000000000..36c99b1eb7 --- /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 0000000000..5f7d6f9390 --- /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 0000000000..86f42d8a64 --- /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 0000000000..172cd18447 --- /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 0000000000..6b3b8ac800 --- /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 0000000000..62ad1b7370 --- /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 0000000000..3a3c69cf07 --- /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 0000000000..014a41d2b1 --- /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 0000000000..a62f1d91a7 --- /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 0000000000..979d21271c --- /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 0000000000..a4a0a52103 --- /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 0000000000..5c16ced31f --- /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 0000000000..12b5b6f135 --- /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 0000000000..206578b106 --- /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 0000000000..2c8fb3dd57 --- /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 0000000000..72593c7f94 --- /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 0000000000..b703d5d185 --- /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 0000000000..dc5290ebe6 --- /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 0000000000..6afdfd96fd --- /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 0000000000..b2ab7e576f --- /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 0000000000..4171d3e959 --- /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 0000000000..07644d017c --- /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,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.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 }) +public class DateTextSocket +{ + private static final Logger LOG = Log.getLogger(DateTextSocket.class); + + private Session session; + + @OnOpen + public void onOpen(Session session) + { + this.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); + } + } + + @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 0000000000..56892dc0f4 --- /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 0000000000..67dcf224dd --- /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 0000000000..326804cf9e --- /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 0000000000..87e49ba037 --- /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/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 0000000000..761fc0cde4 --- /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 0000000000..fa14891d07 --- /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,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.Endpoint; +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 0000000000..3a90bc8e96 --- /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,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.Endpoint; +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 0000000000..49a7e2e504 --- /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 0000000000..70fe55df66 --- /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,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.echo; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.websocket.DeploymentException; +import javax.websocket.Endpoint; +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 0000000000..e278c7274a --- /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 0000000000..c9f01659c4 --- /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 0000000000..f0acfb468d --- /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 0000000000..9a73823b44 --- /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 0000000000..851a1ac2bb --- /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 0000000000..fac9bab51b --- /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 0000000000..009c3fa72d --- /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 0000000000..475b59da61 --- /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 0000000000..441a2f128b --- /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 0000000000..0a531346ed --- /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 0000000000..d9d08247cb --- /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 0000000000..d90e94172e --- /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 0000000000..1d56470d1a --- /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 0000000000..d2f997129b --- /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 0000000000..d7fee3cc61 --- /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 0000000000..0b9f3472e6 --- /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 0000000000..9abc5724c8 --- /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 0000000000..bd3669ec9b --- /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 0000000000..c6e44f2181 --- /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 0000000000..0c86cab748 --- /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 0000000000..14f25be234 --- /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 0000000000..0e35130ad6 --- /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 0000000000..ebee7b8a33 --- /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 0000000000..fa8fce5421 --- /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 0000000000..96e74fa085 --- /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 0000000000..cac5856435 --- /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 0000000000..8f83284cad --- /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 0000000000..fc4d528dd6 --- /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/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 0000000000..a15ca8476c --- /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 0000000000..0bf437266d --- /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 0000000000..81d7a500f5 --- /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 0000000000..afd9cef68d --- /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 0000000000..18aafa5938 --- /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 0000000000..88b96eead1 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jetty-logging.properties @@ -0,0 +1,8 @@ +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 + 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 0000000000..08e696f876 --- /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 0ec128e75f..f30f2b47cc 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.6-SNAPSHOT</version> + <version>9.1.0-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 0c2585e42c..e23ef1d397 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.6-SNAPSHOT</version> + <version>9.1.0-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 70f57af746..262150b9ff 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 @@ package org.eclipse.jetty.websocket.api; /** * 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 3ffe7f124d..2c3da1a130 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,8 @@ 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 dfdefecd7c..c1d000c37b 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 @@ public interface RemoteEndpoint 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 @@ public interface RemoteEndpoint 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 7748dd0899..28b3b77986 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 @@ public interface Session extends Closeable * @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 @@ public interface Session extends Closeable * @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 @@ public interface Session extends Closeable * @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 interface Session extends Closeable 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 @@ public interface Session extends Closeable 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 f286e7dbc4..ee3a13c4b9 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 @@ public class StatusCode * 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 e805f8614b..826a808138 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 @@ package org.eclipse.jetty.websocket.api; 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 @@ public class UpgradeRequest 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 class UpgradeRequest } } + public void clearHeaders() + { + headers.clear(); + } + public List<HttpCookie> getCookies() { return cookies; @@ -88,7 +94,7 @@ public class UpgradeRequest 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 class UpgradeRequest 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,11 +183,21 @@ public class UpgradeRequest * * @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); } + public String getProtocolVersion() + { + String version = getHeader("Sec-WebSocket-Version"); + if (version == null) + { + return "13"; + } + return version; + } + public String getQueryString() { return requestURI.getQuery(); @@ -209,6 +225,19 @@ public class UpgradeRequest 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) @@ -238,14 +267,26 @@ public class UpgradeRequest 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) @@ -258,7 +299,7 @@ public class UpgradeRequest 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 51a04c0049..147c5a01d8 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 @@ package org.eclipse.jetty.websocket.api; 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 class UpgradeResponse 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 class UpgradeResponse public List<String> getHeaders(String name) { - return headers.get(name.toLowerCase()); + return headers.get(name); } public int getStatusCode() @@ -163,8 +163,6 @@ public class UpgradeResponse /** * Set the list of extensions that are approved for use with this websocket. * <p> - * This is Advanced usage of the {@link 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 @@ public class UpgradeResponse { 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 89c712736f..8f632a8e6c 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 @@ public interface WebSocketListener /** * 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 348a0e06d1..ce20b2ca28 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 @@ public class WebSocketPolicy /** * 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 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 long maxMessageSize = 64 * KB; + 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 @@ public class WebSocketPolicy this.behavior = behavior; } - public void assertValidMessageSize(int requestedSize) + private void assertLessThan(String name, long size, String otherName, long otherSize) + { + 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 > maxBinaryMessageSize) + { + throw new MessageTooLargeException("Binary message size [" + requestedSize + "] exceeds maximum size [" + maxBinaryMessageSize + "]"); + } + } + } + + public void assertValidTextMessageSize(int requestedSize) { - if (maxMessageSize > 0) + if (maxTextMessageSize > 0) { // validate it - if (requestedSize > maxMessageSize) + if (requestedSize > maxTextMessageSize) { - throw new MessageTooLargeException("Requested message size [" + requestedSize + "] exceeds maximum size [" + maxMessageSize + "]"); + throw new MessageTooLargeException("Text message size [" + requestedSize + "] exceeds maximum size [" + maxTextMessageSize + "]"); } } } @@ -82,43 +146,213 @@ public class WebSocketPolicy { 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 maxBinaryMessageBufferSize; + } + + /** + * 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() + { + return maxBinaryMessageSize; + } + + /** + * Get the maximum size of a text message buffer (for streaming writing) + * + * @return the maximum size of a text message buffer + */ + public int getMaxTextMessageBufferSize() + { + return maxTextMessageBufferSize; + } + + /** + * 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() + { + 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) { - return maxMessageSize; + assertPositive("MaxBinaryMessageSize",size); + + this.maxBinaryMessageSize = size; } - public void setIdleTimeout(long idleTimeout) + /** + * 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) { - this.idleTimeout = idleTimeout; + assertPositive("MaxTextMessageBufferSize",size); + + this.maxTextMessageBufferSize = size; } - public void setInputBufferSize(int inputBufferSize) + /** + * 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) { - this.inputBufferSize = inputBufferSize; + assertPositive("MaxTextMessageSize",size); + + this.maxTextMessageSize = size; } - public void setMaxMessageSize(long maxMessageSize) + @Override + public String toString() { - this.maxMessageSize = maxMessageSize; + 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/WebSocket.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java index a95378b6ec..a62c36b3f9 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 @@ public @interface WebSocket { 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 1db2187866..09064ffb48 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 @@ package org.eclipse.jetty.websocket.api.extensions; /** * 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 interface Extension extends IncomingFrames, OutgoingFrames 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 @@ public interface Extension extends IncomingFrames, OutgoingFrames * 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 b03f0cb245..04c01217de 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 @@ public class ExtensionConfig { 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 @@ public class ExtensionConfig } /** - * 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 b95d10d1a7..7edb2db06f 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 @@ public abstract class ExtensionFactory implements Iterable<Class<? extends Exten 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 c4cf84bc6e..7a201f8fbc 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 interface Frame 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 @@ public interface Frame * * @return true if final frame. */ + // FIXME: remove public boolean isLast(); public boolean isMasked(); @@ -108,5 +110,10 @@ public interface Frame 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 2ea7e09dd2..a4b578007f 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 42a63fcc0f..2e17cef9b3 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 @@ -342,7 +342,6 @@ public class QuoteUtil * 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,30 @@ public class QuoteUtil } 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(); + } } diff --git a/jetty-websocket/websocket-client/pom.xml b/jetty-websocket/websocket-client/pom.xml index 03943ec1db..297e9ed503 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.6-SNAPSHOT</version> + <version>9.1.0-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 282ef1e825..6e5096cfb9 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 @@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.client; import java.net.CookieStore; import java.net.HttpCookie; import java.net.URI; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -172,7 +173,7 @@ public class ClientUpgradeRequest extends UpgradeRequest // 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 @@ -215,7 +216,7 @@ public class ClientUpgradeRequest extends UpgradeRequest super.setRequestURI(uri); // parse parameter map - Map<String, String[]> pmap = new HashMap<>(); + Map<String, List<String>> pmap = new HashMap<>(); String query = uri.getQuery(); @@ -229,12 +230,14 @@ public class ClientUpgradeRequest extends UpgradeRequest 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 e875612a05..83604efa45 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.ExtensionConfig; 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 @@ public class WebSocketClient extends ContainerLifeCycle 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; @@ -82,9 +86,11 @@ public class WebSocketClient extends ContainerLifeCycle { 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 +104,11 @@ public class WebSocketClient extends ContainerLifeCycle 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 +144,39 @@ public class WebSocketClient extends ContainerLifeCycle } // Validate websocket URI - LOG.debug("connect websocket:{} to:{}",websocket,toUri); + LOG.debug("connect websocket {} to {}",websocket,toUri); // Grab Connection Manager 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); @@ -211,6 +244,16 @@ public class WebSocketClient extends ContainerLifeCycle 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 +279,11 @@ public class WebSocketClient extends ContainerLifeCycle return cookieStore; } + public EventDriverFactory getEventDriverFactory() + { + return eventDriverFactory; + } + public Executor getExecutor() { return executor; @@ -252,6 +300,26 @@ public class WebSocketClient extends ContainerLifeCycle } /** + * 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 +329,26 @@ public class WebSocketClient extends ContainerLifeCycle 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 +359,14 @@ public class WebSocketClient extends ContainerLifeCycle 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 +403,11 @@ public class WebSocketClient extends ContainerLifeCycle return new ConnectionManager(this); } + public void setAsyncWriteTimeout(long ms) + { + this.policy.setAsyncWriteTimeout(ms); + } + public void setBindAdddress(SocketAddress bindAddress) { this.bindAddress = bindAddress; @@ -323,16 +421,16 @@ public class WebSocketClient extends ContainerLifeCycle /** * 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,6 +438,11 @@ public class WebSocketClient extends ContainerLifeCycle this.cookieStore = cookieStore; } + public void setEventDriverFactory(EventDriverFactory factory) + { + this.eventDriverFactory = factory; + } + public void setExecutor(Executor executor) { this.executor = executor; @@ -350,16 +453,31 @@ public class WebSocketClient extends ContainerLifeCycle 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(ms); + } + + public void setMaxTextMessageBufferSize(int max) + { + this.policy.setMaxTextMessageBufferSize(max); + } + + public void setSessionFactory(SessionFactory sessionFactory) { - this.policy.setIdleTimeout(milliseconds); + 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 f918abf216..f52e1d3bd4 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 @@ public abstract class ConnectPromise extends FuturePromise<Session> implements R 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 @@ public abstract class ConnectPromise extends FuturePromise<Session> implements R 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 d2281441ab..0eefe651ee 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 @@ public class ConnectionManager extends ContainerLifeCycle 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 ca5a9fe2f2..3b62685dd7 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 @@ -40,6 +40,7 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; 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; @@ -60,6 +61,13 @@ public class UpgradeConnection extends AbstractConnection { 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,StringUtil.__UTF8_CHARSET); @@ -74,6 +82,14 @@ public class UpgradeConnection extends AbstractConnection // 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 */ @@ -113,6 +129,12 @@ public class UpgradeConnection extends AbstractConnection private void notifyConnect(ClientUpgradeResponse response) { connectPromise.setResponse(response); + + UpgradeListener handshakeListener = connectPromise.getUpgradeListener(); + if (handshakeListener != null) + { + handshakeListener.onHandshakeResponse(response); + } } @Override @@ -213,9 +235,10 @@ public class UpgradeConnection extends AbstractConnection // Initialize / Negotiate Extensions EventDriver websocket = connectPromise.getDriver(); - WebSocketPolicy policy = connectPromise.getClient().getPolicy(); + WebSocketPolicy policy = websocket.getPolicy(); - 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 0000000000..3edfe510ff --- /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/package-info.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/package-info.java index 1f5df7d340..1087907399 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 dca965e8e8..801eddd0b3 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 @@ import org.eclipse.jetty.websocket.api.annotations.WebSocket; /** * 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 bab94162fd..c7df302526 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 @@ public class BadNetworkTest @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 @@ public class BadNetworkTest @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 92a588897f..713073ef70 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 @@ -55,7 +55,7 @@ public class ClientConnectTest 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(); @@ -104,7 +104,7 @@ public class ClientConnectTest @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 +133,7 @@ public class ClientConnectTest @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 +162,7 @@ public class ClientConnectTest @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 +198,7 @@ public class ClientConnectTest @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 +234,7 @@ public class ClientConnectTest @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 +270,7 @@ public class ClientConnectTest @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 +300,7 @@ public class ClientConnectTest @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 +330,7 @@ public class ClientConnectTest @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"); @@ -359,7 +359,7 @@ public class ClientConnectTest @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/TrackingSocket.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/JettyTrackingSocket.java index eb1c4ae565..7b293869f6 100644 --- 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/JettyTrackingSocket.java @@ -35,9 +35,9 @@ import org.junit.Assert; /** * Testing Socket used on client side WebSocket testing. */ -public class TrackingSocket extends WebSocketAdapter +public class JettyTrackingSocket extends WebSocketAdapter { - private static final Logger LOG = Log.getLogger(TrackingSocket.class); + private static final Logger LOG = Log.getLogger(JettyTrackingSocket.class); public int closeCode = -1; public Exchanger<String> messageExchanger; @@ -57,7 +57,7 @@ public class TrackingSocket extends WebSocketAdapter 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)); + Assert.assertThat("Close Code / Received [" + closeMessage + "]",closeCode,is(expectedCode)); } private void assertCloseReason(String expectedReason) 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 d3a705c563..66f6d7244a 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 @@ -27,6 +27,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 +72,7 @@ public class ServerWriteThread extends Thread { 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 49ca8c1ac2..6b706e6b71 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 @@ -75,7 +75,7 @@ public class SlowClientTest @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 741d8fa4f4..f059f51cdb 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 @@ -76,7 +76,7 @@ public class SlowServerTest @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 class SlowServerTest 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 ecfd50ceb2..4bd494b76b 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 @@ -80,7 +80,7 @@ public class TimeoutTest @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 @@ public class TimeoutTest // 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/WebSocketClientBadUriTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientBadUriTest.java index a25b6dd8c7..7258c2a7ba 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 @@ public class WebSocketClientBadUriTest @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 a9152fcb39..45b007f712 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 @@ -22,6 +22,8 @@ import static org.hamcrest.Matchers.*; 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; @@ -32,6 +34,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; @@ -73,7 +77,7 @@ public class WebSocketClientTest @Test(expected = IllegalArgumentException.class) public void testAddExtension_NotInstalled() throws Exception { - TrackingSocket cliSock = new TrackingSocket(); + JettyTrackingSocket cliSock = new JettyTrackingSocket(); client.getPolicy().setIdleTimeout(10000); @@ -89,7 +93,7 @@ public class WebSocketClientTest @Test public void testBasicEcho_FromClient() throws Exception { - TrackingSocket cliSock = new TrackingSocket(); + JettyTrackingSocket cliSock = new JettyTrackingSocket(); client.getPolicy().setIdleTimeout(10000); @@ -112,7 +116,45 @@ public class WebSocketClientTest Assert.assertThat("client.connectionManager.sessions.size",client.getConnectionManager().getSessions().size(),is(1)); - cliSock.getSession().getRemote().sendStringByFuture("Hello World!"); + cliSock.getSession().getRemote().sendString("Hello World!",null); + srvSock.echoMessage(1,TimeUnit.MILLISECONDS,500); + // wait for response from server + cliSock.waitForMessage(500,TimeUnit.MILLISECONDS); + + cliSock.assertMessage("Hello World!"); + } + + @Test + public void testBasicEcho_UsingCallback() throws Exception + { + 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); + srvSock.echoMessage(1,TimeUnit.MILLISECONDS,500); // wait for response from server cliSock.waitForMessage(500,TimeUnit.MILLISECONDS); @@ -123,7 +165,7 @@ public class WebSocketClientTest @Test public void testBasicEcho_FromServer() throws Exception { - TrackingSocket wsocket = new TrackingSocket(); + JettyTrackingSocket wsocket = new JettyTrackingSocket(); Future<Session> future = client.connect(wsocket,server.getWsUri()); // Server @@ -138,7 +180,7 @@ public class WebSocketClientTest 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); @@ -155,7 +197,7 @@ public class WebSocketClientTest fact.start(); try { - TrackingSocket wsocket = new TrackingSocket(); + JettyTrackingSocket wsocket = new JettyTrackingSocket(); URI wsUri = server.getWsUri(); Future<Session> future = client.connect(wsocket,wsUri); @@ -195,7 +237,7 @@ public class WebSocketClientTest { int bufferSize = 512; - TrackingSocket wsocket = new TrackingSocket(); + JettyTrackingSocket wsocket = new JettyTrackingSocket(); URI wsUri = server.getWsUri(); Future<Session> future = client.connect(wsocket,wsUri); @@ -233,7 +275,7 @@ public class WebSocketClientTest 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 = client.connect(wsocket,wsUri); @@ -249,15 +291,15 @@ public class WebSocketClientTest 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 cb1936824c..c5b0b6b845 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 @@ -49,12 +49,10 @@ import org.eclipse.jetty.util.BufferUtil; 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 +63,7 @@ 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.frames.CloseFrame; import org.junit.Assert; /** @@ -118,7 +117,7 @@ public class BlockheadServer public void close() throws IOException { - write(new WebSocketFrame(OpCode.CLOSE)); + write(new CloseFrame()); flush(); disconnect(); } @@ -204,7 +203,7 @@ public class BlockheadServer } @Override - public void incomingError(WebSocketException e) + public void incomingError(Throwable e) { incomingFrames.incomingError(e); } @@ -218,10 +217,9 @@ public class BlockheadServer { 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); @@ -231,22 +229,23 @@ public class BlockheadServer @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(); } @@ -511,7 +510,7 @@ public class BlockheadServer 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 5c0312c6c8..697749cd11 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 @@ -36,7 +36,7 @@ public class IncomingFramesCapture implements IncomingFrames { 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 +87,7 @@ public class IncomingFramesCapture implements IncomingFrames public int getErrorCount(Class<? extends WebSocketException> errorType) { int count = 0; - for (WebSocketException error : errors) + for (Throwable error : errors) { if (errorType.isInstance(error)) { @@ -97,7 +97,7 @@ public class IncomingFramesCapture implements IncomingFrames return count; } - public LinkedList<WebSocketException> getErrors() + public LinkedList<Throwable> getErrors() { return errors; } @@ -121,7 +121,7 @@ public class IncomingFramesCapture implements IncomingFrames } @Override - public void incomingError(WebSocketException e) + public void incomingError(Throwable e) { LOG.debug(e); errors.add(e); @@ -130,7 +130,7 @@ public class IncomingFramesCapture implements IncomingFrames @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/resources/jetty-logging.properties b/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties index f5e58f4560..7c9bd36834 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 d3459acf77..8b72c85ae3 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.6-SNAPSHOT</version> + <version>9.1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> 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 0000000000..b16d350e31 --- /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 a56308e4ac..49e85a9f22 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.BadPayloadException; 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 @@ public class CloseInfo this.reason = reason; } - private byte[] asByteBuffer() + private ByteBuffer asByteBuffer() { if ((statusCode == StatusCode.NO_CLOSE) || (statusCode == StatusCode.NO_CODE) || (statusCode == (-1))) { @@ -143,22 +144,23 @@ public class CloseInfo 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 @@ public class CloseInfo { 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 aefc7193a5..ba565cf85c 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,7 @@ 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 +48,7 @@ public enum ConnectionState * <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 f16e7dec1e..b431dbbff1 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 java.util.List; 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 @@ import org.eclipse.jetty.websocket.api.extensions.Frame; */ 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 @@ public class Generator 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 @@ public class Generator 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 @@ public class Generator * * 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 @@ public class Generator this.rsv3InUse = false; // configure from list of extensions in use - for(Extension ext: exts) + for (Extension ext : exts) { if (ext.isRsv1User()) { @@ -192,186 +189,145 @@ public class Generator } } - /** - * 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) - { - int bufferSize = frame.getPayloadLength() + OVERHEAD; - return generate(bufferSize,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) + public ByteBuffer generateHeaderBytes(Frame frame) { - if (windowSize < OVERHEAD) - { - throw new IllegalArgumentException("Cannot have windowSize less than " + OVERHEAD); - } + // we need a framing header + assertFrameValid(frame); - 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); - - /* - * start the generation process - */ - byte b; - - // 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; - } - - // NOTE: using .getOpCode() here, not .getType().getOpCode() for testing reasons - byte opcode = frame.getOpCode(); + b |= 0x40; // 0100_0000 + } + if (frame.isRsv2()) + { + b |= 0x20; // 0010_0000 + } + if (frame.isRsv3()) + { + b |= 0x10; // 0001_0000 + } - if (frame.isContinuation()) - { - // 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.getOpCode() == OpCode.CONTINUATION) + { + // Continuations are not the same OPCODE + opcode = OpCode.CONTINUATION; + } - buffer.put(b); + b |= opcode & 0x0F; - // is masked - b = 0x00; - b |= (frame.isMasked()?0x80:0x00); + buffer.put(b); - // payload lengths - int payloadLength = frame.getPayloadLength(); + // is masked + b = 0x00; + b |= (frame.isMasked()?0x80:0x00); - /* - * 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); - } + // payload lengths + int payloadLength = frame.getPayloadLength(); - // masking key - if (frame.isMasked()) - { - buffer.put(frame.getMask()); - } + /* + * 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)); } - - // copy payload - if (frame.hasPayload()) + /* + * if payload is greater 126 we have a 7 + 16 bit length + */ + else if (payloadLength >= 0x7E) { - // remember the position - int maskingStartPosition = buffer.position(); - - // 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(); + 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); + } - // put as much as possible into the buffer - BufferUtil.put(frame.getPayload(),buffer); + // masking key + if (frame.isMasked()) + { + byte[] mask = frame.getMask(); + buffer.put(mask); + int maskInt = ByteBuffer.wrap(mask).getInt(); - // 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()) + 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()) { - LOG.debug("Generated Buffer: {}",BufferUtil.toDetailString(buffer)); + buf.put(getPayloadWindow(frame.getPayloadLength(),frame)); } - return buffer; } public ByteBufferPool getBufferPool() @@ -379,6 +335,37 @@ public class Generator 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 dd6431d0c9..d825e28c0a 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 @@ public interface LogicalConnection extends OutgoingFrames, SuspendToken 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 @@ public interface LogicalConnection extends OutgoingFrames, SuspendToken /** * 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 @@ public interface LogicalConnection extends OutgoingFrames, SuspendToken /** * 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 bca7e30eb6..46c4f7c253 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.WebSocketPolicy; 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 @@ public class Parser 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 @@ public class Parser 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 @@ public class Parser 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 @@ public class Parser // 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 @@ public class Parser 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 @@ public class Parser { this.rsv3InUse = true; } - if (ext.isTextDataDecoder()) - { - this.isTextFrameValidated = false; - } } } @@ -334,63 +340,64 @@ public class Parser 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())) + { + throw new ProtocolException("Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION"); + } + break; + case OpCode.CONTINUATION: + frame = new ContinuationFrame(); + // continuation validation + if (priorDataFrame == null) { - strictnessProcessor = NoOpValidator.INSTANCE; + 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; + case OpCode.PING: + frame = new PingFrame(); + // control frame validation + if (!fin) + { + throw new ProtocolException("Fragmented Ping Frame [" + OpCode.name(opcode) + "]"); + } break; - default: - strictnessProcessor = NoOpValidator.INSTANCE; + 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 @@ public class Parser } 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 class Parser 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 @@ public class Parser { 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 0000000000..9119f3fad7 --- /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 750e07e7b2..ddbe0b0e8d 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 @@ -22,9 +22,13 @@ import java.nio.ByteBuffer; 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> @@ -50,187 +54,106 @@ import org.eclipse.jetty.websocket.api.extensions.Frame; * +---------------------------------------------------------------+ * </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() - { - return new WebSocketFrame(OpCode.BINARY); + public static WebSocketFrame copy(Frame copy) + { + 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); - } - - 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); - } + /** + * 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; - 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; - - private Type type; - private boolean continuation = false; - private int continuationIndex = 0; + protected ByteBuffer data; - /** - * 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) - { - if (frame instanceof WebSocketFrame) - { - WebSocketFrame wsf = (WebSocketFrame)frame; - copy(wsf,wsf.data); - } - 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()); - } - } + public abstract void assertValid(); - /** - * 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 copy(WebSocketFrame copy, ByteBuffer payload) { - copy(copy,copy.data); + copyHeaders(copy); + setPayload(payload); } - /** - * 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) + protected void copyHeaders(Frame frame) { - copy(copy,altPayload); - } + 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; - public void assertValid() - { - if (OpCode.isControlFrame(opcode)) + masked = frame.isMasked(); + if (masked) { - 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"); - } + mask = frame.getMask(); + } + else + { + mask = null; } } - private final void copy(WebSocketFrame copy, ByteBuffer payload) + protected void copyHeaders(WebSocketFrame copy) { - 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) @@ -238,10 +161,6 @@ public class WebSocketFrame implements Frame mask = new byte[copy.mask.length]; System.arraycopy(copy.mask,0,mask,0,mask.length); } - continuationIndex = copy.continuationIndex; - continuation = copy.continuation; - - setPayload(payload); } @Override @@ -260,14 +179,6 @@ public class WebSocketFrame implements Frame 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) @@ -279,7 +190,7 @@ public class WebSocketFrame implements Frame { return false; } - if (fin != other.fin) + if (finRsvOp != other.finRsvOp) { return false; } @@ -291,53 +202,19 @@ public class WebSocketFrame implements Frame { 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); } /** @@ -351,23 +228,12 @@ public class WebSocketFrame implements Frame @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 @@ -381,19 +247,9 @@ public class WebSocketFrame implements Frame } @Override - public int getPayloadStart() - { - if (data == null) - { - return -1; - } - return payloadStart; - } - - @Override public Type getType() { - return type; + return Type.from(getOpCode()); } @Override @@ -401,16 +257,9 @@ public class WebSocketFrame implements Frame { 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; } @@ -420,37 +269,20 @@ public class WebSocketFrame implements Frame return ((data != null) && (payloadLength > 0)); } - @Override - public boolean isContinuation() - { - return continuation; - } - - public boolean isControlFrame() - { - return OpCode.isControlFrame(opcode); - } + public abstract boolean isControlFrame(); - 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 @@ -462,19 +294,19 @@ public class WebSocketFrame implements Frame @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; } /** @@ -512,34 +344,17 @@ public class WebSocketFrame implements Frame 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; } @@ -556,74 +371,9 @@ public class WebSocketFrame implements Frame return this; } - public 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) + protected WebSocketFrame setOpCode(byte op) { - 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; } @@ -645,41 +395,29 @@ public class WebSocketFrame implements Frame 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,StringUtil.__UTF8_CHARSET)); - 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; } @@ -687,17 +425,15 @@ public class WebSocketFrame implements Frame 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 4f4d88d2de..1ca4e4a20f 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,25 @@ package org.eclipse.jetty.websocket.common; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; +import java.util.Objects; 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.BlockingCallback; 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 +47,31 @@ import org.eclipse.jetty.websocket.common.io.FutureWriteCallback; */ 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 +85,11 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint 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 +107,7 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint 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 +117,255 @@ public class WebSocketRemoteEndpoint implements RemoteEndpoint @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(); + } + } + else + { + throw new IllegalStateException(PRIORMSG_ERROR); } - WebSocketFrame frame = WebSocketFrame.binary().setPayload(data); - blockingWrite(frame); } @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 sendPartialBytes(ByteBuffer fragment, boolean isLast) throws IOException + public void sendBytes(ByteBuffer data, WriteCallback callback) { + msgType.set(BINARY); if (LOG.isDebugEnabled()) { - LOG.debug("sendPartialBytes({}, {})",BufferUtil.toDetailString(fragment),isLast); + 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 (msgLock.tryLock()) + { + 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(); + } + } + else + { + throw new IllegalStateException(PRIORMSG_ERROR); } - WebSocketFrame frame = WebSocketFrame.binary().setPayload(fragment).setFin(isLast); - blockingWrite(frame); } @Override public void sendPartialString(String fragment, boolean isLast) throws IOException { - if (LOG.isDebugEnabled()) + if (msgLock.tryLock()) + { + 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(); + } + } + else { - LOG.debug("sendPartialString({}, {})",fragment,isLast); + throw new IllegalStateException(PRIORMSG_ERROR); } - WebSocketFrame frame = WebSocketFrame.text(fragment).setFin(isLast); - blockingWrite(frame); } @Override public void sendPing(ByteBuffer applicationData) throws IOException { - if (LOG.isDebugEnabled()) + if (msgLock.tryLock()) + { + 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(); + } + } + else { - LOG.debug("sendPing with {}",BufferUtil.toDetailString(applicationData)); + throw new IllegalStateException(PRIORMSG_ERROR); } - WebSocketFrame frame = WebSocketFrame.ping().setPayload(applicationData); - blockingWrite(frame); } @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(); + } + } + else + { + throw new IllegalStateException(PRIORMSG_ERROR); } - WebSocketFrame frame = WebSocketFrame.pong().setPayload(applicationData); - blockingWrite(frame); } @Override public void sendString(String text) throws IOException { - WebSocketFrame frame = WebSocketFrame.text(text); - if (LOG.isDebugEnabled()) + if (msgLock.tryLock()) + { + 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(); + } + } + else { - LOG.debug("sendString with {}",BufferUtil.toDetailString(frame.getPayload())); + throw new IllegalStateException(PRIORMSG_ERROR); } - blockingWrite(WebSocketFrame.text(text)); } @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 282c05f026..6188a29ef1 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 @@ -24,10 +24,9 @@ import java.net.URI; 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; @@ -58,8 +57,8 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc 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; @@ -79,30 +78,15 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc 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,StringUtil.__UTF8_CHARSET,-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); } @@ -132,6 +116,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc notifyClose(StatusCode.NO_CLOSE,"Harsh disconnect"); } + public void dispatch(Runnable runnable) + { + executor.execute(runnable); + } + @Override public void dump(Appendable out, String indent) throws IOException { @@ -187,6 +176,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc return true; } + public ByteBufferPool getBufferPool() + { + return this.connection.getBufferPool(); + } + public LogicalConnection getConnection() { return connection; @@ -218,12 +212,6 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc return connection.getLocalAddress(); } - @Override - public long getMaximumMessageSize() - { - return maximumMessageSize; - } - @ManagedAttribute(readonly = true) public OutgoingFrames getOutgoingHandler() { @@ -291,12 +279,12 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc * 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); } } @@ -341,6 +329,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc websocket.onClose(new CloseInfo(statusCode,reason)); } + public void notifyError(Throwable cause) + { + incomingError(cause); + } + @Override public void onConnectionStateChange(ConnectionState state) { @@ -361,8 +354,6 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc /** * Open/Activate the session - * - * @throws IOException */ public void open() { @@ -404,12 +395,6 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc connection.setMaxIdleTimeout(ms); } - @Override - public void setMaximumMessageSize(long length) - { - this.maximumMessageSize = length; - } - public void setOutgoingHandler(OutgoingFrames outgoing) { this.outgoingHandler = outgoing; @@ -423,6 +408,23 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc public void setUpgradeRequest(UpgradeRequest request) { 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) 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 0000000000..c64936982d --- /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 0000000000..2e214b80be --- /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/EventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java index 5461df4188..ff0d584c7a 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 EventDriver(WebSocketPolicy policy, Object websocket) - { - this.policy = policy; - this.websocket = websocket; - } - - public WebSocketPolicy getPolicy() - { - return policy; - } - - public WebSocketSession getSession() - { - return session; - } - - @Override - public final void incomingError(WebSocketException 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); - } - - onFrame(frame); - - try - { - switch (frame.getType().getOpCode()) - { - case OpCode.CLOSE: - { - boolean validate = true; - CloseInfo close = new CloseInfo(frame,validate); - - // notify user websocket pojo - onClose(close); - - // process handshake - session.getConnection().getIOState().onCloseRemote(close); - - 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 abstract void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException; - - 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 WebSocketPolicy getPolicy(); + + public WebSocketSession getSession(); + + public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException; + + public void onBinaryMessage(byte[] data); + + public void onClose(CloseInfo close); + + public void onConnect(); + + public void onContinuationFrame(ByteBuffer buffer, boolean fin) throws IOException; + + public void onError(Throwable t); + + public void onFrame(Frame frame); + + public void onInputStream(InputStream stream); + + public void onPing(ByteBuffer buffer); + + public void onPong(ByteBuffer buffer); + + public void onReader(Reader reader); + + public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException; + + public void onTextMessage(String message); + + 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 e8e9ea6d02..05cfc0ff3f 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<>(); - } - - private 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"); + this.implementations = new ArrayList<>(); - 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()); - } - } - - private 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()); - } - } - - private void assertUnset(EventMethod event, Class<? extends Annotation> annoClass, Method method) - { - 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()); - } + addImplementation(new JettyListenerImpl()); + addImplementation(new JettyAnnotatedImpl()); } - private void assertValidSignature(Method method, Class<? extends Annotation> annoClass, ParamList validParams) + public void addImplementation(EventDriverImpl impl) { - assertIsPublicNonStatic(method); - assertIsReturn(method,Void.TYPE); - - boolean valid = false; - - // validate parameters - Class<?> actual[] = method.getParameterTypes(); - for (Class<?>[] params : validParams) + if (implementations.contains(impl)) { - if (isSameParameters(actual,params)) - { - valid = true; - break; - } + LOG.warn("Ignoring attempt to add duplicate EventDriverImpl: " + impl); + return; } - if (!valid) - { - throw InvalidSignatureException.build(method,annoClass,validParams); - } + implementations.add(impl); } - /** - * 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 void clearImplementations() { - WebSocket anno = pojo.getAnnotation(WebSocket.class); - if (anno == null) - { - return null; - } - - return scanAnnotatedMethods(pojo); + this.implementations.clear(); } - public EventMethods getMethods(Class<?> pojo) throws InvalidWebSocketException + protected String getClassName(Object websocket) { - 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; + return websocket.getClass().getName(); } - private boolean isSameParameters(Class<?>[] actual, Class<?>[] params) + public List<EventDriverImpl> getImplementations() { - 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; + return implementations; } - private boolean isSignatureMatch(Method method, ParamList validParams) + public boolean removeImplementation(EventDriverImpl impl) { - assertIsPublicNonStatic(method); - assertIsReturn(method,Void.TYPE); - - // validate parameters - Class<?> actual[] = method.getParameterTypes(); - for (Class<?>[] params : validParams) - { - if (isSameParameters(actual,params)) - { - return true; - } - } - - return false; + return this.implementations.remove(impl); } - private EventMethods scanAnnotatedMethods(Class<?> pojo) + @Override + public String toString() { - Class<?> clazz = pojo; - EventMethods events = new EventMethods(pojo); - - clazz = pojo; - while (clazz.getAnnotation(WebSocket.class) != null) + StringBuilder msg = new StringBuilder(); + msg.append(this.getClass().getSimpleName()); + msg.append("[implementations=["); + boolean delim = false; + for (EventDriverImpl impl : implementations) { - for (Method method : clazz.getDeclaredMethods()) + if (delim) { - 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 + msg.append(','); } - - // try superclass now - clazz = clazz.getSuperclass(); + msg.append(impl.toString()); + delim = true; } - - return events; - } - - @Override - public String toString() - { - return String.format("EventMethodsCache [cache.count=%d]",cache.size()); + msg.append("]"); + return msg.toString(); } /** @@ -368,21 +111,39 @@ public class EventDriverFactory 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 0000000000..1ea9b4343a --- /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/AnnotatedEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedEventDriver.java index 083904f618..a2bec047ae 100644 --- 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/JettyAnnotatedEventDriver.java @@ -36,22 +36,25 @@ import org.eclipse.jetty.websocket.common.message.SimpleTextMessage; /** * Handler for Annotated User WebSocket objects. */ -public class AnnotatedEventDriver extends EventDriver +public class JettyAnnotatedEventDriver extends AbstractEventDriver { - private final EventMethods events; - private MessageAppender activeMessage; + private final JettyAnnotatedMetadata events; private boolean hasCloseBeenCalled = false; - public AnnotatedEventDriver(WebSocketPolicy policy, Object websocket, EventMethods events) + 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.maxMessageSize() > 0) + if (anno.maxTextMessageSize() > 0) { - this.policy.setMaxMessageSize(anno.maxMessageSize()); + this.policy.setMaxTextMessageSize(anno.maxTextMessageSize()); + } + if (anno.maxBinaryMessageSize() > 0) + { + this.policy.setMaxTextMessageSize(anno.maxBinaryMessageSize()); } if (anno.inputBufferSize() > 0) { @@ -76,7 +79,16 @@ public class AnnotatedEventDriver extends EventDriver { if (events.onBinary.isStreaming()) { - activeMessage = new MessageInputStream(this); + activeMessage = new MessageInputStream(session.getConnection()); + final MessageAppender msg = activeMessage; + dispatch(new Runnable() + { + @Override + public void run() + { + events.onBinary.call(websocket,session,msg); + } + }); } else { @@ -84,13 +96,7 @@ public class AnnotatedEventDriver extends EventDriver } } - activeMessage.appendMessage(buffer); - - if (fin) - { - activeMessage.messageComplete(); - activeMessage = null; - } + appendMessage(buffer,fin); } @Override @@ -144,6 +150,7 @@ public class AnnotatedEventDriver extends EventDriver } } + @Override public void onInputStream(InputStream stream) { if (events.onBinary != null) @@ -152,6 +159,7 @@ public class AnnotatedEventDriver extends EventDriver } } + @Override public void onReader(Reader reader) { if (events.onText != null) @@ -173,7 +181,16 @@ public class AnnotatedEventDriver extends EventDriver { if (events.onText.isStreaming()) { - activeMessage = new MessageReader(this); + 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 { @@ -181,13 +198,7 @@ public class AnnotatedEventDriver extends EventDriver } } - activeMessage.appendMessage(buffer); - - if (fin) - { - activeMessage.messageComplete(); - activeMessage = null; - } + appendMessage(buffer,fin); } @Override 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 0000000000..07cf5e5311 --- /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 0000000000..4a5265f9b4 --- /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 0000000000..eb8c2af190 --- /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/ListenerEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerEventDriver.java index 07bad1a23c..a122b254ff 100644 --- 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/JettyListenerEventDriver.java @@ -19,6 +19,8 @@ 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; @@ -27,21 +29,19 @@ 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 +public class JettyListenerEventDriver extends AbstractEventDriver { - private static final Logger LOG = Log.getLogger(ListenerEventDriver.class); + private static final Logger LOG = Log.getLogger(JettyListenerEventDriver.class); private final WebSocketListener listener; - private MessageAppender activeMessage; private boolean hasCloseBeenCalled = false; - public ListenerEventDriver(WebSocketPolicy policy, WebSocketListener listener) + public JettyListenerEventDriver(WebSocketPolicy policy, WebSocketListener listener) { super(policy,listener); this.listener = listener; @@ -55,13 +55,7 @@ public class ListenerEventDriver extends EventDriver activeMessage = new SimpleBinaryMessage(this); } - activeMessage.appendMessage(buffer); - - if (fin) - { - activeMessage.messageComplete(); - activeMessage = null; - } + appendMessage(buffer,fin); } @Override @@ -105,6 +99,18 @@ public class ListenerEventDriver extends EventDriver } @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) @@ -112,13 +118,7 @@ public class ListenerEventDriver extends EventDriver activeMessage = new SimpleTextMessage(this); } - activeMessage.appendMessage(buffer); - - if (fin) - { - activeMessage.messageComplete(); - activeMessage = null; - } + appendMessage(buffer,fin); } @Override @@ -126,4 +126,10 @@ public class ListenerEventDriver extends EventDriver { 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 0000000000..d62e9e72c6 --- /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/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 0000000000..5abd9d723c --- /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 0000000000..1b40451146 --- /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/EventMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethod.java index e551d26565..dcc1e93df8 100644 --- 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/annotated/EventMethod.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.events; +package org.eclipse.jetty.websocket.common.events.annotated; import java.io.InputStream; import java.io.Reader; @@ -27,6 +27,7 @@ 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 { @@ -103,12 +104,12 @@ public class EventMethod } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - String err = String.format("Cannot call method %s on %s with args: %s",method,pojo,args); + String err = String.format("Cannot call method %s on %s with args: %s",method,pojo, QuoteUtil.join(args,",")); throw new WebSocketException(err,e); } } - protected Method getMethod() + public Method getMethod() { return method; } 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/annotated/EventMethods.java index 80e2bfe32a..bda2209895 100644 --- 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/annotated/EventMethods.java @@ -16,12 +16,10 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.events; +package org.eclipse.jetty.websocket.common.events.annotated; /** * 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 { 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/annotated/InvalidSignatureException.java index bae7e421ba..a95316423a 100644 --- 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/annotated/InvalidSignatureException.java @@ -16,13 +16,14 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.events; +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 @@ -70,4 +71,9 @@ public class InvalidSignatureException extends InvalidWebSocketException { 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 0000000000..b0a17ed698 --- /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 5e208ec964..84e2f14261 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.annotation.ManagedObject; 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 @@ public abstract class AbstractExtension extends ContainerLifeCycle implements Ex } @Override - public void incomingError(WebSocketException e) + public void incomingError(Throwable e) { nextIncomingError(e); } @@ -152,24 +151,7 @@ public abstract class AbstractExtension extends ContainerLifeCycle implements Ex 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 29450cf82a..d0c1ae2dd9 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.annotation.ManagedObject; 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 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames } @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 6ed9224448..f56fd1bb51 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.WebSocketPolicy; 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 @@ public class WebSocketExtensionFactory extends ExtensionFactory 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/DeflateCompressionMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateCompressionMethod.java deleted file mode 100644 index a989254fb8..0000000000 --- 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 0000000000..7823c2fb16 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtension.java @@ -0,0 +1,228 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://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); + + // Perform decompression + while (decompressor.getRemaining() > 0 && !decompressor.finished()) + { + DataFrame out = new DataFrame(frame); + out.setRsv1(false); // Unset RSV1 + 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) + { + out.setPayload(ByteBuffer.wrap(outbuf,0,len)); + } + nextIncomingFrame(out); + } + catch (DataFormatException e) + { + LOG.warn(e); + throw new BadPayloadException(e); + } + } + } + + /** + * 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 a548f20782..0000000000 --- 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 75d7817e82..0000000000 --- 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 0000000000..2bba442576 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtension.java @@ -0,0 +1,253 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://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); + + // Perform decompression + while (decompressor.getRemaining() > 0 && !decompressor.finished()) + { + DataFrame out = new DataFrame(frame); + out.setRsv1(false); // Unset RSV1 + 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) + { + out.setPayload(ByteBuffer.wrap(outbuf,0,len)); + } + nextIncomingFrame(out); + } + catch (DataFormatException e) + { + LOG.warn(e); + throw new BadPayloadException(e); + } + } + } + + /** + * 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 0000000000..94b09d46ea --- /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 13f13a5d70..8bbc63367a 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 @@ package org.eclipse.jetty.websocket.common.extensions.fragment; 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 @@ import org.eclipse.jetty.websocket.common.extensions.AbstractExtension; 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 @@ public class FragmentExtension extends AbstractExtension @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 @@ public class FragmentExtension extends AbstractExtension 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 @@ public class FragmentExtension extends AbstractExtension // 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 @@ public class FragmentExtension extends AbstractExtension 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 fa6b53506f..e57f166162 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 @@ package org.eclipse.jetty.websocket.common.extensions.identity; 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 @@ public class IdentityExtension extends AbstractExtension { 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/frames/BinaryFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/BinaryFrame.java new file mode 100644 index 0000000000..3b355637b5 --- /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 0000000000..129843511b --- /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 0000000000..403a5a19a5 --- /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 0000000000..421b7cdc7c --- /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 0000000000..4490f23b42 --- /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 0000000000..59ffac8d74 --- /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 0000000000..b3a1ca1d49 --- /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/io/payload/CloseReasonValidator.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/TextFrame.java index 5a9af00f41..4100f04025 100644 --- 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/frames/TextFrame.java @@ -16,35 +16,39 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.io.payload; +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; -/** - * Validate UTF8 correctness for {@link OpCode#CLOSE} Reason message. - */ -public class CloseReasonValidator extends UTF8Validator implements PayloadProcessor +public class TextFrame extends DataFrame { - private int statusCodeBytes = 2; + public TextFrame() + { + super(OpCode.TEXT); + } @Override - public void process(ByteBuffer payload) + public Type getType() { - if ((payload == null) || (payload.remaining() <= 2)) - { - // no validation needed - return; - } + return Type.TEXT; + } - ByteBuffer copy = payload.slice(); - while (statusCodeBytes > 0) + public TextFrame setPayload(String str) + { + setPayload(ByteBuffer.wrap(StringUtil.getUtf8Bytes(str))); + return this; + } + + public String getPayloadAsUTF8() + { + if (data == null) { - copy.get(); - statusCodeBytes--; + return null; } - - super.process(copy); + 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 032c45c121..7514babac5 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 @@ package org.eclipse.jetty.websocket.common.io; 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; @@ -46,7 +47,6 @@ 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.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 +59,7 @@ import org.eclipse.jetty.websocket.common.WebSocketSession; 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 +71,12 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp @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 +102,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp // 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 +147,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp return String.format("%s@%x",FlushInvoker.class.getSimpleName(),hashCode()); } } - + public class OnDisconnectCallback implements WriteCallback { @Override @@ -218,15 +224,40 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp } @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 +279,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp @Override public void disconnect() { - LOG.debug("{} disconnect()", policy.getBehavior()); + LOG.debug("{} disconnect()",policy.getBehavior()); synchronized (writeBytes) { if (!writeBytes.isClosed()) @@ -259,7 +290,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp 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 +304,6 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp } } - /** - * 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 +325,13 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp public void flush() { - ByteBuffer buffer = null; + List<ByteBuffer> buffers = null; synchronized (writeBytes) { if (flushing) { + LOG.debug("Actively flushing"); return; } @@ -330,24 +347,20 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp 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 +384,12 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp } @Override + public long getIdleTimeout() + { + return getEndPoint().getIdleTimeout(); + } + + @Override public IOState getIOState() { return ioState; @@ -450,7 +469,17 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp 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 +495,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp { 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 +536,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp @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 +547,9 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp } // 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 +575,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp 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 +595,20 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp 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 +670,24 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp 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 c3498772ef..229ea71b00 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 @@ public class FramePipes } @Override - public void incomingError(WebSocketException e) + public void incomingError(Throwable t) { /* cannot send exception on */ } @@ -60,7 +59,15 @@ public class FramePipes @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 e5bfd086d4..bf07996cee 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 @@ package org.eclipse.jetty.websocket.common.io; 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 @@ public class IOState 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 class IOState 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 class IOState public CloseInfo getCloseInfo() { - return closeInfo.get(); + return closeInfo; } public ConnectionState getConnectionState() @@ -125,7 +122,7 @@ public class IOState public boolean isInputAvailable() { - return inputAvailable.get(); + return inputAvailable; } public boolean isOpen() @@ -135,7 +132,7 @@ public class IOState public boolean isOutputAvailable() { - return outputAvailable.get(); + return outputAvailable; } private void notifyStateListeners(ConnectionState state) @@ -153,8 +150,9 @@ public class IOState */ 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 @@ public class IOState 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 class IOState */ 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 @@ public class IOState 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 @@ public class IOState event = this.state; } } - - LOG.debug("event = {}",event); // Only notify on state change events if (event != null) @@ -235,22 +236,24 @@ public class IOState 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 @@ public class IOState { LOG.debug("onCloseRemote({})",close); ConnectionState event = null; - synchronized (this.state) + synchronized (this) { if (this.state == ConnectionState.CLOSED) { @@ -269,21 +272,25 @@ public class IOState 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 @@ public class IOState } 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 @@ public class IOState { 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 @@ public class IOState 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 class IOState public void onReadEOF() { ConnectionState event = null; - synchronized (this.state) + synchronized (this) { if (this.state == ConnectionState.CLOSED) { @@ -385,12 +392,13 @@ public class IOState 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 class IOState 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 1cf29b2437..bfe683bf5c 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 @@ package org.eclipse.jetty.websocket.common.io; 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 @@ public class WriteBytesProvider implements Callback 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 @@ public class WriteBytesProvider implements Callback 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 @@ public class WriteBytesProvider implements Callback 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 @@ public class WriteBytesProvider implements Callback 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 class WriteBytesProvider implements Callback */ 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 @@ public class WriteBytesProvider implements Callback 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 class WriteBytesProvider implements Callback 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; - - // fail others - for (FrameEntry fe : queue) - { - fe.notifyFailure(t); - notified = true; - } + callbacks.addAll(past); + callbacks.addAll(queue); + 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 @@ public class WriteBytesProvider implements Callback } /** - * 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 class WriteBytesProvider implements Callback } } + 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 @@ public class WriteBytesProvider implements Callback @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 @@ public class WriteBytesProvider implements Callback { 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 34b2bf19e2..e2cb9aa75f 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.util.Callback; 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/DeMaskProcessor.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessor.java index ead2bc94ca..be702a33b5 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 @@ import org.eclipse.jetty.websocket.api.extensions.Frame; 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/UTF8Validator.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/UTF8Validator.java deleted file mode 100644 index c2dfd952ad..0000000000 --- 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 fb6ba7f29d..96940eb8b6 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 @@ public interface MessageAppender * * @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 21f4ce1886..08e03a7a30 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 @@ package org.eclipse.jetty.websocket.common.message; 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; - - public MessageInputStream(AnnotatedEventDriver driver) + @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(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()) + { + LOG.debug("appendMessage(ByteBuffer,{}): {}",isLast,BufferUtil.toDetailString(payload)); + } + + if (buffersExhausted.get()) { - throw new IOException("Cannot append to finished buffer"); + // 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()) + { + return -1; + } + + if (activeBuffer == null) + { + activeBuffer = buffers.take(); + } + + while (activeBuffer.remaining() <= 0) { - int curPos = buf.position(); - buf.compact(); - int offsetPos = buf.position() - curPos; - readPosition += offsetPos; + if (buffersExhausted.get()) + { + closed.set(true); + return -1; + } + activeBuffer = buffers.take(); } - return b; + + 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 29764e69c2..66993eb8c4 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 @@ package org.eclipse.jetty.websocket.common.message; 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 synchronized void close() throws IOException + { + 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 void write(int b) throws IOException + 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) { - // TODO Auto-generated method stub + 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 e790989cf6..37c065afb8 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 3aed776e57..b324e45c97 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 @@ package org.eclipse.jetty.websocket.common.message; 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); + } + + /** + * Flush whatever is in the buffer. + * + * @param fin + * fin flag + * @throws IOException + */ + private synchronized void flush(boolean fin) throws IOException + { + 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; + } } - public boolean isClosed() + private void notifyFailure(IOException e) { - // TODO Auto-generated method stub - return false; + if (callback != null) + { + callback.writeFailed(e); + } + } + + public void setCallback(WriteCallback callback) + { + this.callback = callback; } @Override - public void write(char[] cbuf, int off, int len) throws IOException + public void write(char[] cbuf) throws IOException { - // TODO Auto-generated method stub + 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 + { + 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 d8802f8e8d..274f7314c1 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 @@ public class SimpleBinaryMessage implements MessageAppender { 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 @@ public class SimpleBinaryMessage implements MessageAppender } @Override - public void appendMessage(ByteBuffer payload) throws IOException + public void appendMessage(ByteBuffer payload, boolean isLast) throws IOException { if (finished) { @@ -54,7 +54,7 @@ public class SimpleBinaryMessage implements MessageAppender 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 8b60aae7aa..e002e1282b 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 @@ import org.eclipse.jetty.websocket.common.events.EventDriver; 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 @@ public class SimpleTextMessage implements MessageAppender } @Override - public void appendMessage(ByteBuffer payload) throws IOException + public void appendMessage(ByteBuffer payload, boolean isLast) throws IOException { if (finished) { @@ -53,7 +53,7 @@ public class SimpleTextMessage implements MessageAppender 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 0000000000..9d634fbe50 --- /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 0000000000..eb6e3c52b5 --- /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 0000000000..85633b73e2 --- /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 0000000000..11b4f35990 --- /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 d0e3b90489..114afdf15e 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 @@ public class AnnotatedBinaryStreamSocket @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 0b71bb5417..002569cb52 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 @@ import org.eclipse.jetty.websocket.api.annotations.WebSocket; /** * Example EchoSocket using Annotations. */ -@WebSocket(maxMessageSize = 64 * 1024) +@WebSocket(maxTextMessageSize = 64 * 1024) public class AnnotatedEchoSocket { @OnWebSocketMessage @@ -35,7 +35,7 @@ public class AnnotatedEchoSocket { 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 6d76f0eb4a..fbfa0271cb 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 @@ public class ListenerEchoSocket implements WebSocketListener { 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/GeneratorParserRoundtripTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorParserRoundtripTest.java index 6eaa773fc8..069f8ec215 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 @@ -27,6 +27,7 @@ import org.eclipse.jetty.io.ByteBufferPool; 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 @@ public class GeneratorParserRoundtripTest { // 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 @@ public class GeneratorParserRoundtripTest 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 @@ public class GeneratorParserRoundtripTest 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 @@ public class GeneratorParserRoundtripTest 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 @@ public class GeneratorParserRoundtripTest 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 d14e1b9956..e76f01516d 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 @@ -21,57 +21,224 @@ package org.eclipse.jetty.websocket.common; import static org.hamcrest.Matchers.*; 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()); - - ByteBuffer completeBuf = UnitGenerator.generate(send); - - // Parse complete buffer (5 bytes at a time) - UnitParser parser = new UnitParser(); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); + frames[pingCount] = new CloseInfo(StatusCode.NORMAL).asFrame(); - 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 +251,22 @@ public class GeneratorTest 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; - int expectedHeaderSize = 4; - int expectedParts = (int)Math.ceil((double)(payload.length + expectedHeaderSize) / windowSize); - - // the generator - Generator generator = new UnitGenerator(); + WindowHelper helper = new WindowHelper(windowSize); + ByteBuffer completeBuffer = helper.generateWindowed(frame); - // 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++; + // Validate + int expectedHeaderSize = 4; + int expectedSize = payload.length + expectedHeaderSize; + int expectedParts = (int)Math.ceil((double)(payload.length) / windowSize); - done = (frame.remaining() <= 0); - } + helper.assertTotalParts(expectedParts); + helper.assertTotalBytes(payload.length + expectedHeaderSize); - // 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 +277,25 @@ public class GeneratorTest 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 - int expectedHeaderSize = 8; - int expectedParts = (int)Math.ceil((double)(payload.length + expectedHeaderSize) / 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); - - // 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++; + // Generate + int windowSize = 2929; + WindowHelper helper = new WindowHelper(windowSize); + ByteBuffer completeBuffer = helper.generateWindowed(frame); - BufferUtil.put(buf,completeBuf); + // Validate + int expectedHeaderSize = 8; + int expectedSize = payload.length + expectedHeaderSize; + int expectedParts = (int)Math.ceil((double)(payload.length) / windowSize); - done = (frame.remaining() <= 0); - } + helper.assertTotalParts(expectedParts); + helper.assertTotalBytes(payload.length + expectedHeaderSize); - 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 +303,7 @@ public class GeneratorTest 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 0000000000..4ca8306daf --- /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 81d207a66d..46479791c6 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 @@ -35,7 +35,7 @@ public class IncomingFramesCapture implements IncomingFrames 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 +44,19 @@ public class IncomingFramesCapture implements IncomingFrames 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 +72,18 @@ public class IncomingFramesCapture implements IncomingFrames 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 +100,7 @@ public class IncomingFramesCapture implements IncomingFrames public int getErrorCount(Class<? extends WebSocketException> errorType) { int count = 0; - for (WebSocketException error : errors) + for (Throwable error : errors) { if (errorType.isInstance(error)) { @@ -96,7 +110,7 @@ public class IncomingFramesCapture implements IncomingFrames return count; } - public LinkedList<WebSocketException> getErrors() + public LinkedList<Throwable> getErrors() { return errors; } @@ -120,7 +134,7 @@ public class IncomingFramesCapture implements IncomingFrames } @Override - public void incomingError(WebSocketException e) + public void incomingError(Throwable e) { LOG.debug(e); errors.add(e); @@ -129,8 +143,7 @@ public class IncomingFramesCapture implements IncomingFrames @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 46d19b9662..7a2ad8ca66 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 @@ -84,8 +84,7 @@ public class OutgoingFramesCapture implements OutgoingFrames @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 963b67c8a8..5a8c26b8eb 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 @@ -62,8 +62,10 @@ public class OutgoingNetworkBytesCapture implements OutgoingFrames @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 abc5205d0a..7d374b2852 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 @@ -22,35 +22,23 @@ import static org.hamcrest.Matchers.*; 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 class ParserTest 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 @@ public class ParserTest 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 class ParserTest 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 class ParserTest 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 @@ public class ParserTest 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,71 +182,70 @@ public class ParserTest 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() + public void testParseNothing() { - 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)); + ByteBuffer buf = ByteBuffer.allocate(16); + // Put nothing in the buffer. + buf.flip(); - UnitParser parser = new UnitParser(); + WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); + Parser parser = new UnitParser(policy); IncomingFramesCapture capture = new IncomingFramesCapture(); parser.setIncomingFramesHandler(capture); + parser.parse(buf); - parseQuietly(parser,part1); - capture.assertErrorCount(0); - parseQuietly(parser,part2); - capture.assertErrorCount(1); - capture.assertHasErrors(BadPayloadException.class,1); + capture.assertNoErrors(); + Assert.assertThat("Frame Count",capture.getFrames().size(),is(0)); } @Test - public void testParseNothing() + public void testWindowedParseLargeFrame() { - ByteBuffer buf = ByteBuffer.allocate(16); - // Put nothing in the buffer. - buf.flip(); + // 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); - parser.parse(buf); + + 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(0)); + 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 ed212dba67..0b07d96f92 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 @@ -25,6 +25,7 @@ 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 @@ public class PingPayloadParserTest 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 81ec460403..f22b913482 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 @@ package org.eclipse.jetty.websocket.common; 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 @@ public class RFC6455ExamplesGeneratorTest @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 @@ public class RFC6455ExamplesGeneratorTest @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 @@ public class RFC6455ExamplesGeneratorTest @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 @@ public class RFC6455ExamplesGeneratorTest { int dataSize = 256; - WebSocketFrame binary = WebSocketFrame.binary(); + BinaryFrame binary = new BinaryFrame(); byte payload[] = new byte[dataSize]; Arrays.fill(payload,(byte)0x44); - binary.setPayload(payload); - - Generator gen = new UnitGenerator(); + binary.setPayload(ByteBuffer.wrap(payload)); - 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 @@ public class RFC6455ExamplesGeneratorTest { 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 @@ public class RFC6455ExamplesGeneratorTest @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 @@ public class RFC6455ExamplesGeneratorTest @Test public void testSingleUnmaskedTextMessage() { - WebSocketFrame text = WebSocketFrame.text("Hello"); - - Generator generator = new UnitGenerator(); + WebSocketFrame text = new TextFrame().setPayload("Hello"); - 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 fb36b4bb5f..e1e101debd 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 @@ -64,12 +64,15 @@ public class RFC6455ExamplesParserTest 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 @@ public class RFC6455ExamplesParserTest 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 @@ public class RFC6455ExamplesParserTest 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 @@ public class RFC6455ExamplesParserTest 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 @@ public class RFC6455ExamplesParserTest 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 0000000000..a92dd15f1d --- /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.*; + +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 ccfb53f068..274f345028 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 @@ -38,14 +38,16 @@ public class TextPayloadParserTest { 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); @@ -80,7 +82,7 @@ public class TextPayloadParserTest 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); @@ -88,7 +90,7 @@ public class TextPayloadParserTest buf.flip(); WebSocketPolicy policy = WebSocketPolicy.newServerPolicy(); - policy.setMaxMessageSize(100000); + policy.setMaxTextMessageSize(100000); Parser parser = new UnitParser(policy); IncomingFramesCapture capture = new IncomingFramesCapture(); parser.setIncomingFramesHandler(capture); @@ -167,7 +169,8 @@ public class TextPayloadParserTest 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 0fbef69156..377e896f7e 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 java.util.List; 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 @@ import org.eclipse.jetty.websocket.api.extensions.Frame; */ 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 @@ public class UnitGenerator extends Generator // 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 447fe285fb..a650f61488 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 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; public class UnitParser extends Parser @@ -33,12 +34,12 @@ public class UnitParser extends Parser 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 class UnitParser extends Parser */ 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 bd12408a95..098b5caf55 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.*; + import java.nio.ByteBuffer; import org.eclipse.jetty.io.ByteBufferPool; @@ -25,10 +27,11 @@ import org.eclipse.jetty.io.MappedByteBufferPool; 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 @@ public class WebSocketFrameTest 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 @@ public class WebSocketFrameTest 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 0000000000..287b78d420 --- /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.*; + +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 755c5012e3..a5c0d49ea5 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 @@ -21,7 +21,9 @@ package org.eclipse.jetty.websocket.common.ab; import static org.hamcrest.Matchers.*; 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.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.TextFrame; import org.junit.Assert; import org.junit.Test; @@ -47,23 +50,18 @@ public class TestABCase1_1 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(); - - for (int i = 0; i < length; ++i) - { - builder.append("*"); - } + Frame textFrame = new TextFrame().setPayload(text); - 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 @@ public class TestABCase1_1 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 @@ public class TestABCase1_1 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 @@ public class TestABCase1_1 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 @@ public class TestABCase1_1 builder.append("*"); } - WebSocketFrame textFrame = WebSocketFrame.text(builder.toString()); - - Generator generator = new UnitGenerator(); + WebSocketFrame textFrame = new TextFrame().setPayload(builder.toString()); - 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 @@ public class TestABCase1_1 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 @@ public class TestABCase1_1 @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 @@ public class TestABCase1_1 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 @@ public class TestABCase1_1 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 @@ public class TestABCase1_1 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 @@ public class TestABCase1_1 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 @@ public class TestABCase1_1 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 @@ public class TestABCase1_1 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 @@ public class TestABCase1_1 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 @@ public class TestABCase1_1 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 @@ public class TestABCase1_1 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 dd6d6eab62..a535f40fe2 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 @@ -34,6 +34,7 @@ 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.BinaryFrame; import org.junit.Assert; import org.junit.Test; @@ -58,10 +59,9 @@ public class TestABCase1_2 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 @@ public class TestABCase1_2 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 @@ public class TestABCase1_2 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 @@ public class TestABCase1_2 } 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 @@ public class TestABCase1_2 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 @@ public class TestABCase1_2 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 @@ public class TestABCase1_2 @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 @@ public class TestABCase1_2 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 @@ public class TestABCase1_2 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 13843bb7d7..ca9fcc8c7b 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 @@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.*; 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.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.PingFrame; import org.junit.Assert; import org.junit.Test; @@ -53,10 +55,9 @@ public class TestABCase2 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 @@ public class TestABCase2 { 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 @@ public class TestABCase2 @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 class TestABCase2 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 class TestABCase2 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; - } - - WebSocketFrame pingFrame = WebSocketFrame.ping().setPayload(bytes); + PingFrame pingFrame = new PingFrame(); + pingFrame.setPayload(ByteBuffer.wrap(bytes)); // should throw exception - 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 c2c13dc0f1..ce6e363170 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 java.util.List; 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 @@ public class TestABCase3 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 @@ public class TestABCase3 @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 562c0b2941..1af04d25ab 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 @@ public class TestABCase4 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 @@ public class TestABCase4 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 @@ public class TestABCase4 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 @@ public class TestABCase4 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 4a3bd826ed..2f833eeb9a 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 @@ -31,13 +31,13 @@ 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; @@ -50,8 +50,7 @@ public class TestABCase7_3 { 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); @@ -90,22 +89,16 @@ public class TestABCase7_3 @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(); @@ -124,8 +117,7 @@ public class TestABCase7_3 { 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); @@ -169,8 +161,7 @@ public class TestABCase7_3 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); @@ -230,8 +221,7 @@ public class TestABCase7_3 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(StringUtil.__UTF8_CHARSET); @@ -299,19 +289,18 @@ public class TestABCase7_3 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 1a36a940b0..cd12edacf9 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 @@ public class MyStatelessEchoSocket @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 f4c13fdaa7..363a4ab253 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,47 +18,61 @@ package org.eclipse.jetty.websocket.common.events; -import java.util.ArrayList; +import static org.hamcrest.Matchers.*; + import java.util.regex.Pattern; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.junit.Assert; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.startsWith; - @SuppressWarnings("serial") -public class EventCapture extends ArrayList<String> +public class EventCapture extends EventQueue<String> { - public void add(String format, Object... args) + public static class Assertable { - super.add(String.format(format,args)); - } + private final String event; - public void assertEvent(int eventNum, String expected) - { - Assert.assertThat("Event[" + eventNum + "]",get(eventNum),is(expected)); - } + public Assertable(String event) + { + this.event = event; + } - public void assertEventContains(int eventNum, String expected) - { - Assert.assertThat("Event[" + eventNum + "]",get(eventNum),containsString(expected)); + 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 assertEventCount(int expectedCount) + public void add(String format, Object... args) { - Assert.assertThat("Event Count",size(),is(expectedCount)); + String msg = String.format(format,args); + System.err.printf("### EVENT: %s%n",msg); + super.offer(msg); } - public void assertEventRegex(int eventNum, String regex) + public Assertable pop() { - String event = get(eventNum); - Assert.assertTrue("Event[" + eventNum + "]: regex:[" + regex + "] in [" + event + "]",Pattern.matches(regex,event)); + return new Assertable(super.poll()); } - public void assertEventStartsWith(int eventNum, String expected) + public void assertEventCount(int expectedCount) { - Assert.assertThat("Event[" + eventNum + "]",get(eventNum),startsWith(expected)); + Assert.assertThat("Event Count",size(),is(expectedCount)); } public String q(String str) 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 8ba6bee2d4..baba8e3332 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 @@ -24,41 +24,15 @@ 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 +44,7 @@ public class EventDriverFactoryTest 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 +78,6 @@ public class EventDriverFactoryTest 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 ecb5adbbf9..01f29b438d 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 @@ public class EventDriverTest 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 @@ public class EventDriverTest 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 @@ public class EventDriverTest 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 @@ public class EventDriverTest 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 @@ public class EventDriverTest 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 @@ public class EventDriverTest 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 @@ public class EventDriverTest { 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 0000000000..eab47d1312 --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScannerTest.java @@ -0,0 +1,340 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://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.*; + +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 b33f3a5657..4c5ed61095 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 39cfc7db0c..b9e045e07e 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 @@ package org.eclipse.jetty.websocket.common.extensions; 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 @@ public class DummyIncomingFrames implements IncomingFrames } @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 278017dcc6..fef0de6c1f 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.util.log.Logger; 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 @@ public class DummyOutgoingFrames implements OutgoingFrames 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/FragmentExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/FragmentExtensionTest.java index c7b67cd57c..6e35645703 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 @@ -38,6 +38,9 @@ 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.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; @@ -68,7 +71,7 @@ public class FragmentExtensionTest // 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); } @@ -112,7 +115,7 @@ public class FragmentExtensionTest 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); @@ -155,22 +158,22 @@ public class FragmentExtensionTest // 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); @@ -183,6 +186,9 @@ public class FragmentExtensionTest 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())); @@ -224,15 +230,15 @@ public class FragmentExtensionTest // 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(); @@ -280,7 +286,7 @@ public class FragmentExtensionTest 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 b1a4528f72..f6569f2140 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 @@ -33,6 +33,7 @@ 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.identity.IdentityExtension; +import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.junit.Assert; import org.junit.Test; @@ -49,7 +50,7 @@ public class IdentityExtensionTest Extension ext = new IdentityExtension(); ext.setNextIncomingFrames(capture); - Frame frame = WebSocketFrame.text("hello"); + Frame frame = new TextFrame().setPayload("hello"); ext.incomingFrame(frame); capture.assertFrameCount(1); @@ -78,7 +79,7 @@ public class IdentityExtensionTest 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 0000000000..c7933f0368 --- /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 32f741ee2c..0000000000 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateCompressionMethodTest.java +++ /dev/null @@ -1,221 +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.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,StringUtil.__UTF8_CHARSET); - - 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/FrameCompressionExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtensionTest.java index 68f47308eb..9d8a293582 100644 --- 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/DeflateFrameExtensionTest.java @@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.*; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collections; +import java.util.List; import java.util.zip.Deflater; import java.util.zip.Inflater; @@ -36,26 +37,33 @@ 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 FrameCompressionExtensionTest +public class DeflateFrameExtensionTest { + @Rule + public TestName testname = new TestName(); + private void assertIncoming(byte[] raw, String... expectedTextDatas) { WebSocketPolicy policy = WebSocketPolicy.newClientPolicy(); - FrameCompressionExtension ext = new FrameCompressionExtension(); + DeflateFrameExtension ext = new DeflateFrameExtension(); ext.setBufferPool(new MappedByteBufferPool()); ext.setPolicy(policy); - ExtensionConfig config = ExtensionConfig.parse("x-webkit-deflate-frame"); + ExtensionConfig config = ExtensionConfig.parse("deflate-frame"); ext.setConfig(config); // Setup capture of incoming frames @@ -94,11 +102,11 @@ public class FrameCompressionExtensionTest { WebSocketPolicy policy = WebSocketPolicy.newClientPolicy(); - FrameCompressionExtension ext = new FrameCompressionExtension(); + DeflateFrameExtension ext = new DeflateFrameExtension(); ext.setBufferPool(new MappedByteBufferPool()); ext.setPolicy(policy); - ExtensionConfig config = ExtensionConfig.parse("x-webkit-deflate-frame"); + ExtensionConfig config = ExtensionConfig.parse("deflate-frame"); ext.setConfig(config); ByteBufferPool bufferPool = new MappedByteBufferPool(); @@ -109,7 +117,7 @@ public class FrameCompressionExtensionTest OutgoingNetworkBytesCapture capture = new OutgoingNetworkBytesCapture(generator); ext.setNextOutgoingFrames(capture); - Frame frame = WebSocketFrame.text(text); + Frame frame = new TextFrame().setPayload(text); ext.outgoingFrame(frame,null); capture.assertBytes(0,expectedHex); @@ -119,49 +127,93 @@ public class FrameCompressionExtensionTest 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); + 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/client) - byte rawbuf[] = TypeUtil.fromHexString("c187832b5c11716391d84a2c5c"); + // 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/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); + // 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/client) - byte rawbuf[] = TypeUtil.fromHexString("c187ca4def7f0081a4b47d4fef"); + // 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/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); + // 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 @@ -203,7 +255,6 @@ public class FrameCompressionExtensionTest String actual = TypeUtil.toHexString(compressed); String expected = "CaCc4bCbB70200"; // what pywebsocket produces - // String expected = "CbCc4bCbB70200"; // what java produces Assert.assertThat("Compressed data",actual,is(expected)); } @@ -213,12 +264,10 @@ public class FrameCompressionExtensionTest { WebSocketPolicy policy = WebSocketPolicy.newClientPolicy(); - FrameCompressionExtension ext = new FrameCompressionExtension(); + DeflateFrameExtension ext = new DeflateFrameExtension(); ext.setBufferPool(new MappedByteBufferPool()); ext.setPolicy(policy); - - ExtensionConfig config = ExtensionConfig.parse("x-webkit-deflate-frame"); - ext.setConfig(config); + ext.setConfig(new ExtensionConfig(ext.getName())); ByteBufferPool bufferPool = new MappedByteBufferPool(); boolean validating = true; @@ -228,11 +277,10 @@ public class FrameCompressionExtensionTest OutgoingNetworkBytesCapture capture = new OutgoingNetworkBytesCapture(generator); ext.setNextOutgoingFrames(capture); - ext.outgoingFrame(WebSocketFrame.text("Hello"),null); - ext.outgoingFrame(WebSocketFrame.text("There"),null); + ext.outgoingFrame(new TextFrame().setPayload("Hello"),null); + ext.outgoingFrame(new TextFrame().setPayload("There"),null); capture.assertBytes(0,"c107f248cdc9c90700"); - capture.assertBytes(1,"c1070ac9482d4a0500"); } @Test 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 0000000000..7725c6196f --- /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/MessageCompressionExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtensionTest.java index 3f8c96d3a6..1e0b33cf14 100644 --- 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/PerMessageDeflateExtensionTest.java @@ -23,7 +23,7 @@ import static org.hamcrest.Matchers.*; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.LinkedList; +import java.util.Collections; import java.util.List; import org.eclipse.jetty.io.MappedByteBufferPool; @@ -34,25 +34,77 @@ 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.extensions.compress.CompressionMethod.Process; +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 MessageCompressionExtensionTest +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 - MessageCompressionExtension ext = new MessageCompressionExtension(); + PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); ext.setBufferPool(new MappedByteBufferPool()); ext.setPolicy(policy); - ExtensionConfig config = ExtensionConfig.parse("permessage-compress"); + ExtensionConfig config = ExtensionConfig.parse("permessage-deflate"); ext.setConfig(config); // Setup capture of incoming frames @@ -64,9 +116,9 @@ public class MessageCompressionExtensionTest // Receive frame String hex = hexStr.replaceAll("\\s*0x",""); byte net[] = TypeUtil.fromHexString(hex); - WebSocketFrame frame = WebSocketFrame.text(); + TextFrame frame = new TextFrame(); frame.setRsv1(true); - frame.setPayload(net); + frame.setPayload(ByteBuffer.wrap(net)); // Send frame into stack ext.incomingFrame(frame); @@ -89,6 +141,54 @@ public class MessageCompressionExtensionTest 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. */ @@ -102,6 +202,88 @@ public class MessageCompressionExtensionTest } /** + * 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 @@ -157,12 +339,24 @@ public class MessageCompressionExtensionTest 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() { - MessageCompressionExtension ext = new MessageCompressionExtension(); + public void testIncomingPing() + { + PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); ext.setBufferPool(new MappedByteBufferPool()); ext.setPolicy(WebSocketPolicy.newServerPolicy()); ExtensionConfig config = ExtensionConfig.parse("permessage-compress"); @@ -175,7 +369,7 @@ public class MessageCompressionExtensionTest 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); @@ -199,7 +393,7 @@ public class MessageCompressionExtensionTest @Test public void testIncomingUncompressedFrames() { - MessageCompressionExtension ext = new MessageCompressionExtension(); + PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); ext.setBufferPool(new MappedByteBufferPool()); ext.setPolicy(WebSocketPolicy.newServerPolicy()); ExtensionConfig config = ExtensionConfig.parse("permessage-compress"); @@ -220,8 +414,7 @@ public class MessageCompressionExtensionTest // leave frames as-is, no compression, and pass into extension for (String q : quote) { - WebSocketFrame frame = new WebSocketFrame(OpCode.TEXT); - frame.setPayload(q); + TextFrame frame = new TextFrame().setPayload(q); frame.setRsv1(false); // indication to extension that frame is not compressed (ie: a normal frame) ext.incomingFrame(frame); } @@ -250,82 +443,12 @@ public class MessageCompressionExtensionTest } /** - * 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,StringUtil.__UTF8_CHARSET)); - 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(); + PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); ext.setBufferPool(new MappedByteBufferPool()); ext.setPolicy(WebSocketPolicy.newServerPolicy()); ExtensionConfig config = ExtensionConfig.parse("permessage-compress"); @@ -338,7 +461,7 @@ public class MessageCompressionExtensionTest 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/io/LocalWebSocketConnection.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java index 92e5e775b8..9494c3be6b 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 @@ public class LocalWebSocketConnection implements LogicalConnection, IncomingFram { 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 class LocalWebSocketConnection implements LogicalConnection, IncomingFram 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 @@ public class LocalWebSocketConnection implements LogicalConnection, IncomingFram 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 @@ public class LocalWebSocketConnection implements LogicalConnection, IncomingFram @Override public long getMaxIdleTimeout() { - // TODO Auto-generated method stub return 0; } @@ -124,7 +153,7 @@ public class LocalWebSocketConnection implements LogicalConnection, IncomingFram } @Override - public void incomingError(WebSocketException e) + public void incomingError(Throwable e) { incoming.incomingError(e); } @@ -169,8 +198,9 @@ public class LocalWebSocketConnection implements LogicalConnection, IncomingFram } } - public void onOpen() { - LOG.debug("onOpen()"); + public void open() + { + LOG.debug("open()"); ioState.onOpened(); } @@ -187,8 +217,6 @@ public class LocalWebSocketConnection implements LogicalConnection, IncomingFram @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 f1c123c634..d19eed06c5 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,27 +29,24 @@ public class LocalWebSocketSession extends WebSocketSession { 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); } - public OutgoingFramesCapture getOutgoingCapture() + @Override + public void dispatch(Runnable runnable) { - return outgoingCapture; + runnable.run(); } - @Override - public void open() + public OutgoingFramesCapture getOutgoingCapture() { - lwsconnection.onOpen(); - super.open(); + return outgoingCapture; } @Override 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 0000000000..1cdefdfa4f --- /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 0000000000..b93a5b2894 --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/WriteBytesProviderTest.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.common.io; + +import static org.hamcrest.Matchers.*; + +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/HttpResponseParseCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java index a7973e6e5c..168a633d55 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 6d075a6968..2c8debdb70 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.*; + 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 @@ public class DeMaskProcessorTest @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 @@ public class DeMaskProcessorTest 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 0125cbd1b7..0000000000 --- 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 0000000000..20e7208569 --- /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 0000000000..72436ca526 --- /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 0000000000..a3334b3af4 --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageInputStreamTest.java @@ -0,0 +1,187 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://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.*; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +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=10000) + public void testBlockOnRead() 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 = 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(); + + // 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 0000000000..e501936256 --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageOutputStreamTest.java @@ -0,0 +1,133 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://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.*; + +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 0000000000..86d7f5ddc7 --- /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.*; + +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 0000000000..d49fd94b4a --- /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.*; + +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 0000000000..73865a26bd --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/TrackingSocket.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.common.message; + +import static org.hamcrest.Matchers.*; + +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 0000000000..1ec0eb0b8d --- /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.*; + +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 17cb306af8..683b76a631 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 0000000000..6a7b8675b6 --- /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.0-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-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/AbstractMuxExtension.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/AbstractMuxExtension.java index f6db8aff36..d57559b9c3 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/AbstractMuxExtension.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/AbstractMuxExtension.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.api.extensions.Frame; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxChannel.java index c3c42ce449..41014b755c 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxChannel.java @@ -16,17 +16,18 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +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.WebSocketException; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.api.extensions.Frame; @@ -71,6 +72,13 @@ public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendTok 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() @@ -92,12 +100,26 @@ public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendTok // 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 @@ -140,7 +162,7 @@ public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendTok * Incoming exceptions from Muxer. */ @Override - public void incomingError(WebSocketException e) + public void incomingError(Throwable e) { incoming.incomingError(e); } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxControlBlock.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxControlBlock.java index 7e364a4ca6..230a867cd7 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxControlBlock.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxControlBlock.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; public interface MuxControlBlock { diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxException.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxException.java index ff8469fcff..162361f3c3 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxException.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxException.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import org.eclipse.jetty.websocket.api.WebSocketException; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGenerator.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxGenerator.java index cf12f37183..20a9611d02 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGenerator.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxGenerator.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import java.io.IOException; import java.nio.ByteBuffer; @@ -28,11 +28,12 @@ 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; +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. @@ -66,12 +67,12 @@ public class MuxGenerator 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 + b |= (byte)(frame.getOpCode() & 0x0F); // opcode muxPayload.put(b); BufferUtil.put(frame.getPayload(),muxPayload); // build muxed frame - WebSocketFrame muxFrame = WebSocketFrame.binary(); + WebSocketFrame muxFrame = new BinaryFrame(); BufferUtil.flipToFlush(muxPayload,0); muxFrame.setPayload(muxPayload); // NOTE: the physical connection will handle masking rules for this frame. @@ -164,7 +165,7 @@ public class MuxGenerator } } BufferUtil.flipToFlush(payload,0); - WebSocketFrame frame = WebSocketFrame.binary(); + WebSocketFrame frame = new BinaryFrame(); frame.setPayload(payload); outgoing.outgoingFrame(frame,callback); } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxOp.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxOp.java index f41383ad45..17b61a737d 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxOp.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxOp.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; public final class MuxOp { diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParser.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxParser.java index 0fb2a49884..fd1d9f06fc 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParser.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxParser.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import java.nio.ByteBuffer; @@ -26,11 +26,11 @@ 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; +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 { @@ -81,7 +81,7 @@ public class MuxParser return; // nothing to parse } - if (frame.getType().getOpCode() != OpCode.BINARY) + if (frame.getOpCode() != OpCode.BINARY) { LOG.debug("Not a binary opcode (base frame), skipping"); return; // not a binary opcode @@ -98,14 +98,14 @@ public class MuxParser return; } - if (frame.isContinuation()) + if (frame.getOpCode() == OpCode.CONTINUATION) { muxframe.reset(); muxframe.setFin(frame.isFin()); muxframe.setFin(frame.isRsv1()); muxframe.setFin(frame.isRsv2()); muxframe.setFin(frame.isRsv3()); - muxframe.setContinuation(true); + muxframe.setIsContinuation(); parseDataFramePayload(buffer); } else @@ -223,12 +223,12 @@ public class MuxParser if (opcode == OpCode.CONTINUATION) { - muxframe.setContinuation(true); + muxframe.setIsContinuation(); } else { muxframe.reset(); - muxframe.setOpCode(opcode); + muxframe.setOp(opcode); } muxframe.setChannelId(channelId); diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxPhysicalConnectionException.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxPhysicalConnectionException.java index a472cb2521..d808dfe8a6 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxPhysicalConnectionException.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxPhysicalConnectionException.java @@ -16,9 +16,9 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; -import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxDropChannel; +import org.eclipse.jetty.websocket.mux.op.MuxDropChannel; public class MuxPhysicalConnectionException extends MuxException { diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxRequest.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxRequest.java index 1b430e5c0f..deb623f78c 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxRequest.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxRequest.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import java.nio.ByteBuffer; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxResponse.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxResponse.java index 0a34448da3..cd2eaf6da4 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxResponse.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxResponse.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import org.eclipse.jetty.websocket.api.UpgradeResponse; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxedFrame.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxedFrame.java index 219b6b74f7..0fffca627b 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxedFrame.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/MuxedFrame.java @@ -16,18 +16,19 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import org.eclipse.jetty.websocket.common.OpCode; import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.frames.DataFrame; -public class MuxedFrame extends WebSocketFrame +public class MuxedFrame extends DataFrame { private long channelId = -1; public MuxedFrame() { - super(); + super(OpCode.BINARY); } public MuxedFrame(MuxedFrame frame) @@ -66,8 +67,13 @@ public class MuxedFrame extends WebSocketFrame b.append(isRsv1()?'1':'.'); b.append(isRsv2()?'1':'.'); b.append(isRsv3()?'1':'.'); - b.append(",continuation=").append(isContinuation()); b.append(']'); return b.toString(); } + + public void setOp(byte opcode) + { + // TODO Auto-generated method stub + + } } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/Muxer.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/Muxer.java index ab93663a7b..dd31fc5bb9 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/Muxer.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/Muxer.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import java.io.IOException; import java.net.InetSocketAddress; @@ -31,7 +31,6 @@ 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; @@ -39,13 +38,14 @@ 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; +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. @@ -141,7 +141,7 @@ public class Muxer implements IncomingFrames, MuxParser.Listener * Incoming parser errors */ @Override - public void incomingError(WebSocketException e) + public void incomingError(Throwable e) { MuxDropChannel.Reason reason = MuxDropChannel.Reason.PHYSICAL_CONNECTION_FAILED; String phrase = String.format("%s: %s", e.getClass().getName(), e.getMessage()); @@ -197,7 +197,7 @@ public class Muxer implements IncomingFrames, MuxParser.Listener } String reason = "Mux[MUST FAIL]" + drop.getPhrase(); - reason = StringUtil.truncate(reason,WebSocketFrame.MAX_CONTROL_PAYLOAD); + reason = StringUtil.truncate(reason,ControlFrame.MAX_CONTROL_PAYLOAD); this.physicalConnection.close(StatusCode.SERVER_ERROR,reason); // TODO: trigger abnormal close for all sub-channels. @@ -309,7 +309,7 @@ public class Muxer implements IncomingFrames, MuxParser.Listener MuxChannel channel = getChannel(channelId,false); String reason = "Mux " + drop.toString(); - reason = StringUtil.truncate(reason,(WebSocketFrame.MAX_CONTROL_PAYLOAD - 2)); + reason = StringUtil.truncate(reason,(ControlFrame.MAX_CONTROL_PAYLOAD - 2)); channel.close(StatusCode.PROTOCOL,reason); // TODO: set channel to inactive? } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxAddClient.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/MuxAddClient.java index 155c615f20..6fa1d35073 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxAddClient.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/MuxAddClient.java @@ -16,10 +16,10 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux.add; +package org.eclipse.jetty.websocket.mux.add; import org.eclipse.jetty.websocket.common.WebSocketSession; -import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelResponse; +import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse; /** * Interface for Mux Client to handle receiving a AddChannelResponse diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxAddServer.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/MuxAddServer.java index fa5b4f88b4..66fb7ab9c2 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxAddServer.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/MuxAddServer.java @@ -16,16 +16,16 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux.add; +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.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.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. @@ -43,7 +43,7 @@ public interface MuxAddServer * the channel to attach the {@link WebSocketSession} to. * @param requestHandshake * the request handshake (request headers) - * @throws AbstractMuxException + * @throws MuxException * if unable to handshake * @throws IOException * if unable to parse request headers 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-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/package-info.java index 87985efb12..1834e97ded 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/add/package-info.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/add/package-info.java @@ -19,5 +19,5 @@ /** * Jetty WebSocket Common : MUX Extension Add Channel Handling [<em>Unstable Early Draft</em>] */ -package org.eclipse.jetty.websocket.common.extensions.mux.add; +package org.eclipse.jetty.websocket.mux.add; diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/MuxClientAddHandler.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/MuxClientAddHandler.java index bd9b5620e8..daa0ac3efc 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/MuxClientAddHandler.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/MuxClientAddHandler.java @@ -16,11 +16,11 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.client.mux; +package org.eclipse.jetty.websocket.mux.client; 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; +import org.eclipse.jetty.websocket.mux.add.MuxAddClient; +import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse; public class MuxClientAddHandler implements MuxAddClient { diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/MuxClientExtension.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/MuxClientExtension.java index 58cec30a5f..fd03392b03 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/MuxClientExtension.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/MuxClientExtension.java @@ -16,10 +16,10 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.client.mux; +package org.eclipse.jetty.websocket.mux.client; -import org.eclipse.jetty.websocket.common.extensions.mux.AbstractMuxExtension; -import org.eclipse.jetty.websocket.common.extensions.mux.Muxer; +import org.eclipse.jetty.websocket.mux.AbstractMuxExtension; +import org.eclipse.jetty.websocket.mux.Muxer; public class MuxClientExtension extends AbstractMuxExtension { diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/package-info.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/package-info.java index 6df80bb520..e8d7942c39 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/mux/package-info.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/client/package-info.java @@ -19,5 +19,5 @@ /** * Jetty WebSocket Client : MUX Extension [<em>Unstable Early Draft</em>] */ -package org.eclipse.jetty.websocket.client.mux; +package org.eclipse.jetty.websocket.mux.client; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxAddChannelRequest.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxAddChannelRequest.java index 64fb928280..4647c5b967 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxAddChannelRequest.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxAddChannelRequest.java @@ -16,14 +16,14 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux.op; +package org.eclipse.jetty.websocket.mux.op; import java.nio.ByteBuffer; 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; +import org.eclipse.jetty.websocket.mux.MuxControlBlock; +import org.eclipse.jetty.websocket.mux.MuxOp; public class MuxAddChannelRequest implements MuxControlBlock { diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxAddChannelResponse.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxAddChannelResponse.java index a30b6ecf7b..b2a2dd64f7 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxAddChannelResponse.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxAddChannelResponse.java @@ -16,14 +16,14 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux.op; +package org.eclipse.jetty.websocket.mux.op; import java.nio.ByteBuffer; 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; +import org.eclipse.jetty.websocket.mux.MuxControlBlock; +import org.eclipse.jetty.websocket.mux.MuxOp; public class MuxAddChannelResponse implements MuxControlBlock { diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxDropChannel.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxDropChannel.java index 1d062e1b7b..edd186fe4e 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxDropChannel.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxDropChannel.java @@ -16,14 +16,14 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux.op; +package org.eclipse.jetty.websocket.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; +import org.eclipse.jetty.websocket.mux.MuxControlBlock; +import org.eclipse.jetty.websocket.mux.MuxOp; public class MuxDropChannel implements MuxControlBlock { diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxFlowControl.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxFlowControl.java index 32b6d964a1..c238c6f1c9 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxFlowControl.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxFlowControl.java @@ -16,10 +16,10 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux.op; +package org.eclipse.jetty.websocket.mux.op; -import org.eclipse.jetty.websocket.common.extensions.mux.MuxControlBlock; -import org.eclipse.jetty.websocket.common.extensions.mux.MuxOp; +import org.eclipse.jetty.websocket.mux.MuxControlBlock; +import org.eclipse.jetty.websocket.mux.MuxOp; public class MuxFlowControl implements MuxControlBlock { diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxNewChannelSlot.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxNewChannelSlot.java index 09aadf181a..4fae241b0d 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/MuxNewChannelSlot.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/MuxNewChannelSlot.java @@ -16,10 +16,10 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux.op; +package org.eclipse.jetty.websocket.mux.op; -import org.eclipse.jetty.websocket.common.extensions.mux.MuxControlBlock; -import org.eclipse.jetty.websocket.common.extensions.mux.MuxOp; +import org.eclipse.jetty.websocket.mux.MuxControlBlock; +import org.eclipse.jetty.websocket.mux.MuxOp; public class MuxNewChannelSlot implements MuxControlBlock { 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-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/package-info.java index cd5c7ac805..a01e182991 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/op/package-info.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/op/package-info.java @@ -19,5 +19,5 @@ /** * Jetty WebSocket Common : MUX Extension OpCode Handling [<em>Unstable Early Draft</em>] */ -package org.eclipse.jetty.websocket.common.extensions.mux.op; +package org.eclipse.jetty.websocket.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-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/package-info.java index 7112f3b2d4..b025466cd4 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/package-info.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/package-info.java @@ -19,5 +19,5 @@ /** * Jetty WebSocket Common : MUX Extension Core [<em>Unstable Early Draft</em>] */ -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/EmptyHttpInput.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/EmptyHttpInput.java index 3084ac4d6b..f34c8731b6 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/EmptyHttpInput.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/EmptyHttpInput.java @@ -16,16 +16,19 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.server.mux; +package org.eclipse.jetty.websocket.mux.server; import java.nio.ByteBuffer; +import org.eclipse.jetty.server.ByteBufferQueuedHttpInput; +import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpInput; +import org.eclipse.jetty.server.QueuedHttpInput; /** * HttpInput for Empty Http body sections. */ -public class EmptyHttpInput extends HttpInput<ByteBuffer> +public class EmptyHttpInput extends ByteBufferQueuedHttpInput { @Override protected int get(ByteBuffer item, byte[] buffer, int offset, int length) @@ -34,12 +37,6 @@ public class EmptyHttpInput extends HttpInput<ByteBuffer> } @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-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/HttpChannelOverMux.java index a3c953fac7..5234550775 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/HttpChannelOverMux.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/HttpChannelOverMux.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.server.mux; +package org.eclipse.jetty.websocket.mux.server; import java.nio.ByteBuffer; diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/HttpTransportOverMux.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/HttpTransportOverMux.java index cc442b1c60..2a7c70cd7c 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/HttpTransportOverMux.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/HttpTransportOverMux.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.server.mux; +package org.eclipse.jetty.websocket.mux.server; import java.io.IOException; import java.nio.ByteBuffer; @@ -28,8 +28,8 @@ 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; +import org.eclipse.jetty.websocket.mux.MuxChannel; +import org.eclipse.jetty.websocket.mux.Muxer; /** * Take {@link ResponseInfo} objects and convert to bytes for response. @@ -50,15 +50,6 @@ public class HttpTransportOverMux implements HttpTransport 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) diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxAddHandler.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/MuxAddHandler.java index 4f46b9d6da..926da83df4 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxAddHandler.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/MuxAddHandler.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.server.mux; +package org.eclipse.jetty.websocket.mux.server; import java.io.IOException; @@ -30,10 +30,10 @@ 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; +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. @@ -71,11 +71,11 @@ public class MuxAddHandler implements MuxAddServer /** * An incoming MuxAddChannel request. * - * @param the + * @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) - * @return the outgoing response headers */ @Override public void handshake(Muxer muxer, MuxChannel channel, UpgradeRequest request) throws MuxException, IOException diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxServerExtension.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/MuxServerExtension.java index 1aa510d7a7..61562b2d25 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/MuxServerExtension.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/MuxServerExtension.java @@ -16,10 +16,10 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.server.mux; +package org.eclipse.jetty.websocket.mux.server; -import org.eclipse.jetty.websocket.common.extensions.mux.AbstractMuxExtension; -import org.eclipse.jetty.websocket.common.extensions.mux.Muxer; +import org.eclipse.jetty.websocket.mux.AbstractMuxExtension; +import org.eclipse.jetty.websocket.mux.Muxer; public class MuxServerExtension extends AbstractMuxExtension { diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/package-info.java b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/package-info.java index 34367885f5..5e44702a41 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/package-info.java +++ b/jetty-websocket/websocket-mux-extension/src/main/java/org/eclipse/jetty/websocket/mux/server/package-info.java @@ -19,5 +19,5 @@ /** * Jetty WebSocket Server : MUX Extension [<em>Unstable Early Draft</em>] */ -package org.eclipse.jetty.websocket.server.mux; +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 0000000000..8c39ac74fd --- /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 0000000000..05c99741af --- /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 0000000000..ad6b3cf529 --- /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-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxDecoder.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxDecoder.java index c06a7418f0..bc004089a5 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxDecoder.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxDecoder.java @@ -16,11 +16,12 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +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; +import org.eclipse.jetty.websocket.mux.MuxParser; /** * Helpful utility class to parse arbitrary mux events from a physical connection's OutgoingFrames. diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxEncoder.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxEncoder.java index 079d5e406c..17e4768852 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxEncoder.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxEncoder.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import java.io.IOException; @@ -25,6 +25,8 @@ 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; +import org.eclipse.jetty.websocket.mux.MuxControlBlock; +import org.eclipse.jetty.websocket.mux.MuxGenerator; /** * Helpful utility class to send arbitrary mux events into a physical connection's IncomingFrames. diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxEventCapture.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxEventCapture.java index f9cfc91a4e..f13b4ceb91 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxEventCapture.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxEventCapture.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import static org.hamcrest.Matchers.*; @@ -25,15 +25,15 @@ 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.eclipse.jetty.websocket.mux.MuxControlBlock; +import org.eclipse.jetty.websocket.mux.MuxException; +import org.eclipse.jetty.websocket.mux.MuxParser; +import org.eclipse.jetty.websocket.mux.MuxedFrame; +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 diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGeneratorWrite139SizeTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxGeneratorWrite139SizeTest.java index acdc204e65..822cd0728f 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGeneratorWrite139SizeTest.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxGeneratorWrite139SizeTest.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import static org.hamcrest.Matchers.*; @@ -28,7 +28,7 @@ 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.eclipse.jetty.websocket.mux.MuxGenerator; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGeneratorWriteChannelIdTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxGeneratorWriteChannelIdTest.java index 4fe5fdbe8a..aa795d1c89 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxGeneratorWriteChannelIdTest.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxGeneratorWriteChannelIdTest.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import static org.hamcrest.Matchers.*; @@ -28,7 +28,7 @@ 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.eclipse.jetty.websocket.mux.MuxGenerator; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRFCTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRFCTest.java index 332fa4eb86..e32a457c5d 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRFCTest.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRFCTest.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import static org.hamcrest.Matchers.*; @@ -33,13 +33,14 @@ 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.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 @@ -94,6 +95,7 @@ public class MuxParserRFCTest } @Test + @Ignore public void testRFCExample1() throws IOException { // Create RFC detailed frames @@ -122,6 +124,7 @@ public class MuxParserRFCTest } @Test + @Ignore public void testRFCExample2() throws IOException { // Create RFC detailed frames @@ -158,7 +161,6 @@ public class MuxParserRFCTest 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(); @@ -166,6 +168,7 @@ public class MuxParserRFCTest } @Test + @Ignore public void testRFCExample3() throws IOException { // Create RFC detailed frames @@ -188,7 +191,6 @@ public class MuxParserRFCTest 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(); @@ -203,7 +205,6 @@ public class MuxParserRFCTest 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(); @@ -218,7 +219,6 @@ public class MuxParserRFCTest 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(); diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRead139Size_BadEncodingTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRead139Size_BadEncodingTest.java index 15c2e060ac..1ed2f7dba9 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRead139Size_BadEncodingTest.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRead139Size_BadEncodingTest.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import static org.hamcrest.Matchers.*; @@ -26,8 +26,8 @@ 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.eclipse.jetty.websocket.mux.MuxException; +import org.eclipse.jetty.websocket.mux.MuxParser; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRead139Size_GoodTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRead139Size_GoodTest.java index eb450fc177..d8f31583c1 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserRead139Size_GoodTest.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserRead139Size_GoodTest.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import static org.hamcrest.Matchers.*; @@ -26,7 +26,7 @@ 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.eclipse.jetty.websocket.mux.MuxParser; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserReadChannelId_BadEncodingTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserReadChannelId_BadEncodingTest.java index 57c17a3fb1..2d3e0513f5 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserReadChannelId_BadEncodingTest.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserReadChannelId_BadEncodingTest.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import static org.hamcrest.Matchers.*; @@ -26,8 +26,8 @@ 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.eclipse.jetty.websocket.mux.MuxException; +import org.eclipse.jetty.websocket.mux.MuxParser; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserReadChannelId_GoodTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserReadChannelId_GoodTest.java index 5a1ad5e9a3..62402cf17b 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxParserReadChannelId_GoodTest.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/MuxParserReadChannelId_GoodTest.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux; +package org.eclipse.jetty.websocket.mux; import static org.hamcrest.Matchers.*; @@ -26,7 +26,7 @@ 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.eclipse.jetty.websocket.mux.MuxParser; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/DummyMuxAddClient.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/DummyMuxAddClient.java index 81ccc3d7c3..a7920cb677 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/DummyMuxAddClient.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/DummyMuxAddClient.java @@ -16,10 +16,11 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux.add; +package org.eclipse.jetty.websocket.mux.add; import org.eclipse.jetty.websocket.common.WebSocketSession; -import org.eclipse.jetty.websocket.common.extensions.mux.op.MuxAddChannelResponse; +import org.eclipse.jetty.websocket.mux.add.MuxAddClient; +import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse; public class DummyMuxAddClient implements MuxAddClient { diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/DummyMuxAddServer.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/DummyMuxAddServer.java index 49b4fcb894..8f997e35fe 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/DummyMuxAddServer.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/DummyMuxAddServer.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux.add; +package org.eclipse.jetty.websocket.mux.add; import java.io.IOException; @@ -28,10 +28,11 @@ 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 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; +import org.eclipse.jetty.websocket.mux.op.MuxAddChannelResponse; import examples.echo.AdapterEchoSocket; diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxerAddClientTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/MuxerAddClientTest.java index 2b87ff23ee..80d922cbf8 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxerAddClientTest.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/MuxerAddClientTest.java @@ -16,17 +16,17 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux.add; +package org.eclipse.jetty.websocket.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.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; @@ -44,7 +44,7 @@ public class MuxerAddClientTest // Client side physical socket LocalWebSocketConnection physical = new LocalWebSocketConnection(testname); physical.setPolicy(WebSocketPolicy.newClientPolicy()); - physical.onOpen(); + physical.open(); // Server Reader MuxDecoder serverRead = new MuxDecoder(); diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxerAddServerTest.java b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/MuxerAddServerTest.java index 0dfffa1e2b..180e97a511 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/mux/add/MuxerAddServerTest.java +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/add/MuxerAddServerTest.java @@ -16,20 +16,21 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.extensions.mux.add; +package org.eclipse.jetty.websocket.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.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; @@ -48,7 +49,7 @@ public class MuxerAddServerTest // Server side physical connection LocalWebSocketConnection physical = new LocalWebSocketConnection(testname); physical.setPolicy(WebSocketPolicy.newServerPolicy()); - physical.onOpen(); + physical.open(); // Client reader MuxDecoder clientRead = new MuxDecoder(); @@ -97,7 +98,7 @@ public class MuxerAddServerTest clientRead.reset(); // Send simple echo request - clientWrite.frame(1,WebSocketFrame.text("Hello World")); + 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 0000000000..6ee03fb488 --- /dev/null +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/IncomingFramesCapture.java @@ -0,0 +1,142 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://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.*; + +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 0000000000..cd86267399 --- /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 0000000000..bc8ede16ad --- /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 0000000000..7617997d10 --- /dev/null +++ b/jetty-websocket/websocket-mux-extension/src/test/java/org/eclipse/jetty/websocket/mux/helper/OutgoingFramesCapture.java @@ -0,0 +1,96 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://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.*; + +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 0000000000..7976515b86 --- /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 0000000000..4d43210552 --- /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 d88518d52c..84bb147587 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.6-SNAPSHOT</version> + <version>9.1.0-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 0000000000..580116d0e9 --- /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 e7f6fb6abe..3622e72573 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 @@ public interface WebSocketHandshake * * @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 f899941a89..3879335abf 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.io.EndPoint; 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 fbbb0b5779..56a60ab37e 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.Log; 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 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; 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 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc 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 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc 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 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc @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 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc context = new UpgradeContext(); setActiveUpgradeContext(context); } + context.setRequest(sockreq); context.setResponse(sockresp); @@ -183,6 +190,15 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc } } + public void addSessionFactory(SessionFactory sessionFactory) + { + if (sessionFactories.contains(sessionFactory)) + { + return; + } + this.sessionFactories.add(sessionFactory); + } + @Override public void cleanup() { @@ -200,14 +216,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc { for (WebSocketSession session : sessions) { - try - { - session.close(); - } - catch (IOException e) - { - LOG.warn("CloseAllConnections Close failure",e); - } + session.close(); } sessions.clear(); } @@ -218,11 +227,36 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc 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 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc return this.creator; } + public EventDriverFactory getEventDriverFactory() + { + return eventDriverFactory; + } + @Override public ExtensionFactory getExtensionFactory() { @@ -267,34 +306,65 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc @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 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc return protocols; } - /* - * (non-Javadoc) - * - * @see org.eclipse.jetty.websocket.server.WebSocketServletFactory#register(java.lang.Class) - */ @Override public void register(Class<?> websocketPojo) { @@ -426,8 +491,8 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc } // 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); response.setExtensions(extensionStack.getNegotiatedExtensions()); session.setUpgradeResponse(response); 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 0000000000..d5d5f1bae5 --- /dev/null +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java @@ -0,0 +1,229 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://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; + String target = httpreq.getServletPath(); + + 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 0000000000..8599968d22 --- /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/pathmap/PathMappings.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java new file mode 100644 index 0000000000..31785adae6 --- /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 0000000000..945b1aacab --- /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 0000000000..2ea700ecd4 --- /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 0000000000..4b29126f90 --- /dev/null +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpec.java @@ -0,0 +1,166 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://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(String 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 0000000000..a6feb8f196 --- /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 2db4a48e2d..88b54394fa 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 @@ -26,13 +26,11 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.websocket.api.UpgradeRequest; -import org.eclipse.jetty.websocket.api.UpgradeResponse; 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; @@ -57,14 +55,7 @@ public class AnnotatedMaxMessageSizeTest @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); } }; @@ -99,7 +90,7 @@ public class AnnotatedMaxMessageSizeTest // 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/ByteBufferAssert.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ByteBufferAssert.java index 84e174bd5c..9b4667ab1e 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,34 @@ package org.eclipse.jetty.websocket.server; +import static org.hamcrest.Matchers.*; + 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) + { + // 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 { - Assert.assertThat(message + " byte[" + i + "]",actual[i],is(expected[i])); + // 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 68bc1b1a3a..f99d21be89 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 @@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.*; 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 +67,7 @@ public class ChromeTest // 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 0000000000..97ed456e9d --- /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.*; + +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 b84ee24b9e..75bf3d6ac3 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 @@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.*; 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 +87,7 @@ public class FragmentExtensionTest 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 78a6be25a2..3ef1c86d67 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 @@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.*; 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 +73,7 @@ public class FrameCompressionExtensionTest 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 +82,7 @@ public class FrameCompressionExtensionTest // 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 47fa7caa52..ebb7f48883 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 @@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.*; 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 +71,7 @@ public class IdentityExtensionTest 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 97e34c6625..a710acfd8e 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,15 @@ 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 +77,14 @@ public class IdleTimeoutTest // 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 cd688c7854..5a7004615a 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 @@ -32,6 +32,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 +69,7 @@ public class LoadTest @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 +99,7 @@ public class LoadTest { 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 b28d79f7f6..d6f051b57e 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 @@ -28,6 +28,8 @@ import org.eclipse.jetty.websocket.api.UpgradeRequest; 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 +47,7 @@ public class RequestHeadersTest 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 aafdea8946..17fac30c3c 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 @@ -20,7 +20,6 @@ package org.eclipse.jetty.websocket.server; import static org.hamcrest.Matchers.*; -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.Logger; 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 @@ public class WebSocketCloseTest { errors.add(cause); } - } @SuppressWarnings("serial") @@ -88,7 +86,7 @@ public class WebSocketCloseTest } @Override - public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp) + public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) { if (req.hasSubProtocol("fastclose")) { @@ -116,14 +114,7 @@ public class WebSocketCloseTest 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 @@ public class WebSocketCloseTest 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/WebSocketOverSSLTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java index 3502924b12..9a326d98cc 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 @@ -26,8 +26,6 @@ import java.util.concurrent.TimeUnit; 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 61940904db..b761dedef2 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 @@ -27,6 +27,7 @@ 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 +69,7 @@ public class WebSocketServerSessionTest client.sendStandardRequest(); client.expectUpgradeResponse(); - client.write(WebSocketFrame.text("harsh-disconnect")); + client.write(new TextFrame().setPayload("harsh-disconnect")); client.awaitDisconnect(1,TimeUnit.SECONDS); } @@ -90,10 +91,10 @@ public class WebSocketServerSessionTest 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 215f491893..520d96ed49 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 @@ -20,7 +20,6 @@ package org.eclipse.jetty.websocket.server; import static org.hamcrest.Matchers.*; -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.OpCode; 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 @@ public class WebSocketServletRFCTest 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 @@ public class WebSocketServletRFCTest // 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 @@ public class WebSocketServletRFCTest @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 @@ public class WebSocketServletRFCTest 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 @@ public class WebSocketServletRFCTest } finally { + // Reenable Long Stacks from EventDriver + enableStacks(EventDriver.class,true); client.close(); } } @@ -253,7 +258,7 @@ public class WebSocketServletRFCTest // 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 @@ public class WebSocketServletRFCTest } @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 @@ public class WebSocketServletRFCTest 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 @@ public class WebSocketServletRFCTest // 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 72f79f5bac..4c606d0339 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 @@ public class ABServlet extends WebSocketServlet // 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 ecd166295b..d8da5f99eb 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.WebSocketException; 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 @@ public class ABSocket 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 @@ public class ABSocket // 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 @@ public class ABSocket } 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 0da9a58cfd..c63ec081de 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 java.nio.ByteBuffer; 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 @@ import org.junit.rules.TestName; 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 @@ public abstract class AbstractABCase { 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 @@ public abstract class AbstractABCase @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 @@ public abstract class AbstractABCase 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 82b2ce94e3..cccc23bff1 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 @@ -62,7 +62,7 @@ public class Fuzzer PER_FRAME, SLOW } - + public static enum DisconnectMode { /** Disconnect occurred after a proper close handshake */ @@ -92,7 +92,8 @@ public class Fuzzer 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 +109,14 @@ public class Fuzzer 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 +266,16 @@ public class Fuzzer 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 +298,11 @@ public class Fuzzer { 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 0000000000..2478b80822 --- /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.*; + +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 a3e183cfa1..178a78f13a 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 java.util.List; 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 class TestABCase1 extends AbstractABCase 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 @@ public class TestABCase1 extends AbstractABCase { 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 @@ public class TestABCase1 extends AbstractABCase { 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 @@ public class TestABCase1 extends AbstractABCase { 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 @@ public class TestABCase1 extends AbstractABCase { 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 @@ public class TestABCase1 extends AbstractABCase { 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 @@ public class TestABCase1 extends AbstractABCase { 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 @@ public class TestABCase1 extends AbstractABCase { 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 class TestABCase1 extends AbstractABCase 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 @@ public class TestABCase1 extends AbstractABCase { 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 @@ public class TestABCase1 extends AbstractABCase { 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 @@ public class TestABCase1 extends AbstractABCase { 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 @@ public class TestABCase1 extends AbstractABCase { 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 @@ public class TestABCase1 extends AbstractABCase { 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 @@ public class TestABCase1 extends AbstractABCase { 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 @@ public class TestABCase1 extends AbstractABCase { 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 3a9f5e1576..593cf8b477 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.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.PingFrame; +import org.eclipse.jetty.websocket.common.frames.PongFrame; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,9 +45,9 @@ public class TestABCase2 extends AbstractABCase @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 @@ public class TestABCase2 extends AbstractABCase 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 @@ public class TestABCase2 extends AbstractABCase 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 @@ public class TestABCase2 extends AbstractABCase 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 @@ public class TestABCase2 extends AbstractABCase { 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 @@ public class TestABCase2 extends AbstractABCase 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 @@ public class TestABCase2 extends AbstractABCase { 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 @@ public class TestABCase2 extends AbstractABCase 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 class TestABCase2 extends AbstractABCase 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 class TestABCase2 extends AbstractABCase 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 class TestABCase2 extends AbstractABCase 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 1dc6c4885a..4a45b53ff2 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 java.util.List; 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 @@ import org.junit.runner.RunWith; @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 class TestABCase3 extends AbstractABCase 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 class TestABCase3 extends AbstractABCase 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 class TestABCase3 extends AbstractABCase 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 @@ public class TestABCase3 extends AbstractABCase 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 @@ public class TestABCase3 extends AbstractABCase 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 01bfd2fe79..ba2a85f549 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.toolchain.test.AdvancedRunner; 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 @@ import org.junit.runner.RunWith; @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 class TestABCase4 extends AbstractABCase 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 class TestABCase4 extends AbstractABCase 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 @@ public class TestABCase4 extends AbstractABCase @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 @@ public class TestABCase4 extends AbstractABCase @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 @@ public class TestABCase4 extends AbstractABCase @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 class TestABCase4 extends AbstractABCase 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 @@ public class TestABCase4 extends AbstractABCase @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 @@ public class TestABCase4 extends AbstractABCase @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 0c3a4eee4f..434cc4d87e 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 java.util.concurrent.TimeUnit; 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 @@ public class TestABCase5 extends AbstractABCase @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 @@ public class TestABCase5 extends AbstractABCase finally { fuzzer.close(); - enableStacks(Parser.class,true); } } @@ -76,19 +76,16 @@ public class TestABCase5 extends AbstractABCase @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 @@ public class TestABCase5 extends AbstractABCase } finally { - enableStacks(Parser.class,true); fuzzer.close(); } } @@ -108,19 +104,16 @@ public class TestABCase5 extends AbstractABCase @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 @@ public class TestABCase5 extends AbstractABCase } finally { - enableStacks(Parser.class,true); fuzzer.close(); } } @@ -141,19 +133,16 @@ public class TestABCase5 extends AbstractABCase @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 @@ public class TestABCase5 extends AbstractABCase } finally { - enableStacks(Parser.class,true); fuzzer.close(); } } @@ -173,18 +161,16 @@ public class TestABCase5 extends AbstractABCase @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 @@ public class TestABCase5 extends AbstractABCase } finally { - enableStacks(Parser.class,true); fuzzer.close(); } } @@ -204,19 +189,16 @@ public class TestABCase5 extends AbstractABCase @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 @@ public class TestABCase5 extends AbstractABCase } finally { - enableStacks(Parser.class,false); fuzzer.close(); } } @@ -237,22 +218,19 @@ public class TestABCase5 extends AbstractABCase @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 @@ public class TestABCase5 extends AbstractABCase } finally { - enableStacks(Parser.class,true); fuzzer.close(); } } @@ -272,23 +249,20 @@ public class TestABCase5 extends AbstractABCase @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 @@ public class TestABCase5 extends AbstractABCase } finally { - enableStacks(Parser.class,true); fuzzer.close(); } } @@ -308,23 +281,20 @@ public class TestABCase5 extends AbstractABCase @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 @@ public class TestABCase5 extends AbstractABCase } finally { - enableStacks(Parser.class,true); fuzzer.close(); } } @@ -344,19 +313,16 @@ public class TestABCase5 extends AbstractABCase @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 @@ public class TestABCase5 extends AbstractABCase } finally { - enableStacks(Parser.class,true); fuzzer.close(); } } @@ -379,28 +344,28 @@ public class TestABCase5 extends AbstractABCase { // 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 @@ public class TestABCase5 extends AbstractABCase @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 @@ public class TestABCase5 extends AbstractABCase } finally { - enableStacks(Parser.class,true); fuzzer.close(); } } @@ -461,27 +422,27 @@ public class TestABCase5 extends AbstractABCase 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 class TestABCase5 extends AbstractABCase 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 class TestABCase5 extends AbstractABCase 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 class TestABCase5 extends AbstractABCase 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 class TestABCase5 extends AbstractABCase 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 class TestABCase5 extends AbstractABCase 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 class TestABCase5 extends AbstractABCase 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 class TestABCase5 extends AbstractABCase 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 @@ public class TestABCase5 extends AbstractABCase @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 @@ public class TestABCase5 extends AbstractABCase } 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 b7aa7b37cf..24178f8b8c 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.TypeUtil; 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 @@ public class TestABCase6 extends AbstractABCase 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 class TestABCase6 extends AbstractABCase 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 class TestABCase6 extends AbstractABCase 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 class TestABCase6 extends AbstractABCase 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 @@ public class TestABCase6 extends AbstractABCase @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 @@ public class TestABCase6 extends AbstractABCase 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 @@ public class TestABCase6 extends AbstractABCase 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 @@ public class TestABCase6 extends AbstractABCase 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 @@ public class TestABCase6 extends AbstractABCase { 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 class TestABCase6 extends AbstractABCase 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 @@ public class TestABCase6 extends AbstractABCase 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 @@ public class TestABCase6 extends AbstractABCase 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 @@ public class TestABCase6 extends AbstractABCase @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 @@ public class TestABCase6 extends AbstractABCase } 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 6767586a4b..7304a6cc84 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 class TestABCase6_BadUTF extends AbstractABCase 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 @@ public class TestABCase6_BadUTF extends AbstractABCase 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 ecf10a23d0..b530191219 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.Log; 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 @@ public class TestABCase6_GoodUTF extends AbstractABCase 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 d6e71a9658..50cf3a0224 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 java.util.List; 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 class TestABCase7 extends AbstractABCase 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 @@ public class TestABCase7 extends AbstractABCase { 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 @@ public class TestABCase7 extends AbstractABCase { 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 class TestABCase7 extends AbstractABCase 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 @@ public class TestABCase7 extends AbstractABCase { 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 class TestABCase7 extends AbstractABCase 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 @@ public class TestABCase7 extends AbstractABCase { 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 @@ public class TestABCase7 extends AbstractABCase } finally { - enableStacks(Parser.class,true); fuzzer.close(); } } @@ -362,47 +367,6 @@ public class TestABCase7 extends AbstractABCase } /** - * 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 @@ public class TestABCase7 extends AbstractABCase 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 @@ public class TestABCase7 extends AbstractABCase } 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 727be4dd02..6af0d0d77f 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.Log; 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 @@ public class TestABCase7_BadStatusCodes extends AbstractABCase 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 @@ public class TestABCase7_BadStatusCodes extends AbstractABCase 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 7343c57d46..768388c309 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.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.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 @@ public class TestABCase7_GoodStatusCodes extends AbstractABCase 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 class TestABCase7_GoodStatusCodes extends AbstractABCase 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 e94e2560c4..8be2b8ce45 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.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.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 @@ public class TestABCase9 extends AbstractABCase 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 @@ public class TestABCase9 extends AbstractABCase int remaining = msg.length; int offset = 0; boolean fin; + ByteBuffer buf; + ; byte op = opcode; while (remaining > 0) { @@ -60,14 +82,17 @@ public class TestABCase9 extends AbstractABCase 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 @@ public class TestABCase9 extends AbstractABCase { 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 @@ public class TestABCase9 extends AbstractABCase 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 @@ public class TestABCase9 extends AbstractABCase { 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 @@ public class TestABCase9 extends AbstractABCase { 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 @@ public class TestABCase9 extends AbstractABCase { 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 @@ public class TestABCase9 extends AbstractABCase { 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 @@ public class TestABCase9 extends AbstractABCase { 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 @@ public class TestABCase9 extends AbstractABCase 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 @@ public class TestABCase9 extends AbstractABCase { 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 @@ public class TestABCase9 extends AbstractABCase { 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 @@ public class TestABCase9 extends AbstractABCase { 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 @@ public class TestABCase9 extends AbstractABCase { 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 @@ public class TestABCase9 extends AbstractABCase { 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 8573c93133..7fe948890c 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 @@ -49,7 +49,6 @@ 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.WebSocketException; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; @@ -116,6 +115,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti private IOState ioState; private CountDownLatch disconnectedLatch = new CountDownLatch(1); private ByteBuffer remainingBuffer; + private String connectionValue = "Upgrade"; public BlockheadClient(URI destWebsocketURI) throws URISyntaxException { @@ -233,6 +233,36 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti } } + 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(); @@ -294,6 +324,11 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti out.flush(); } + public String getConnectionValue() + { + return connectionValue; + } + private List<ExtensionConfig> getExtensionConfigs(HttpResponse response) { List<ExtensionConfig> configs = new ArrayList<>(); @@ -375,7 +410,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti * Errors received (after extensions) */ @Override - public void incomingError(WebSocketException e) + public void incomingError(Throwable e) { incomingFrames.incomingError(e); } @@ -393,13 +428,13 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti 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); } @@ -433,14 +468,15 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti @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) { @@ -456,45 +492,15 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti } 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) @@ -507,7 +513,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti return BufferUtil.put(remainingBuffer,buf); } - int len = 0; + int len = -1; int b; while ((in.available() > 0) && (buf.remaining() > 0)) { @@ -520,6 +526,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti buf.put((byte)b); len++; } + return len; } @@ -607,7 +614,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti 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); @@ -628,6 +635,11 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti 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/HttpResponse.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java index 59d44ebea8..c183fb9e83 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 @@ public class HttpResponse 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 getExtensionsHeader() @@ -45,7 +44,7 @@ public class HttpResponse implements HttpResponseHeaderParseListener 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 d6186814f4..ccbd42603e 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.ServerConnector; 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 @@ public class BrowserDebugTool implements WebSocketCreator 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 @@ public class BrowserDebugTool implements WebSocketCreator 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 @@ public class BrowserDebugTool implements WebSocketCreator { 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 726ad52df7..a9a4011531 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 @@ public class BrowserSocket 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 @@ public class BrowserSocket } // 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 5a2d1e8008..9a78bbacb7 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 @@ package org.eclipse.jetty.websocket.server.examples; 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 class MyCustomCreationServlet extends WebSocketServlet 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 da5aa8b4e4..ab9f8c0d38 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 @@ import org.eclipse.jetty.websocket.api.annotations.WebSocket; /** * 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 @@ public class BigEchoSocket 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 @@ public class BigEchoSocket 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 6cbe5d4c89..ab9a09cdeb 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 @@ public class EchoBroadcastSocket 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 @@ public class EchoBroadcastSocket { 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 8b21bf2ede..c4775174bb 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 @@ public class EchoCreator implements WebSocketCreator 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 1a5bdae2ee..3377d0319d 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 @@ -27,6 +27,7 @@ import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.api.extensions.Frame; +import org.eclipse.jetty.websocket.common.OpCode; /** * Echo back the incoming text or binary as 2 frames of (roughly) equal size. @@ -37,8 +38,9 @@ public class EchoFragmentSocket @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 +60,13 @@ public class EchoFragmentSocket 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 a1b597aa2b..b983ba98fe 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 class CaptureSocket extends WebSocketAdapter 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 7cbbd2e0e7..66e2352583 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 class EchoServlet extends WebSocketServlet 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 89d7e0e962..d5910e4c72 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 @@ public class EchoSocket // 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 @@ public class EchoSocket 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 1f0129a2f1..f6918554aa 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 @@ public final class Hex 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 82a4723273..e3c4c485eb 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 @@ -37,7 +37,7 @@ public class IncomingFramesCapture implements IncomingFrames { 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 +86,14 @@ public class IncomingFramesCapture implements IncomingFrames 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 +103,7 @@ public class IncomingFramesCapture implements IncomingFrames return count; } - public Queue<WebSocketException> getErrors() + public Queue<Throwable> getErrors() { return errors; } @@ -127,7 +127,7 @@ public class IncomingFramesCapture implements IncomingFrames } @Override - public void incomingError(WebSocketException e) + public void incomingError(Throwable e) { LOG.debug(e); errors.add(e); @@ -136,7 +136,7 @@ public class IncomingFramesCapture implements IncomingFrames @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 78da0e4571..61f592b390 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 @@ -93,7 +93,7 @@ public class OutgoingFramesCapture implements OutgoingFrames @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 9d03d04c76..4adbe3f237 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 @@ public class RFCSocket // 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 @@ public class RFCSocket } // 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 d720634919..e5357e9829 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,8 @@ package org.eclipse.jetty.websocket.server.helper; +import static org.hamcrest.Matchers.*; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -34,8 +36,6 @@ import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.TypeUtil; import org.junit.Assert; -import static org.hamcrest.Matchers.is; - public class SafariD00 { private URI uri; @@ -113,6 +113,7 @@ public class SafariD00 { 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 9c00b8ab8e..8de3013368 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 @@ public class SessionSocket { 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 @@ public class SessionSocket 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 @@ public class SessionSocket } // 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 0000000000..081b7ff5a3 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsBenchmarkTest.java @@ -0,0 +1,226 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://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.eclipse.jetty.websocket.server.pathmap.PathMappings; +import org.eclipse.jetty.websocket.server.pathmap.ServletPathSpec; +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 0000000000..807b168671 --- /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.*; + +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 0000000000..45c684fb19 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpecTest.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.server.pathmap; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +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 0000000000..d68dcb62c9 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpecTest.java @@ -0,0 +1,187 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://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.*; +import static org.junit.Assert.*; + +import org.eclipse.jetty.websocket.server.pathmap.ServletPathSpec; +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/jetty-logging.properties b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties index 6d5c86342b..f5ce469816 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 9c23bd9478..3ccae4668e 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.6-SNAPSHOT</version> + <version>9.1.0-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 fd739e0b51..52c685c139 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 @@ public class ServletUpgradeRequest extends UpgradeRequest 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 @@ public class ServletUpgradeRequest extends UpgradeRequest 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,18 @@ public class ServletUpgradeRequest extends UpgradeRequest 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() + { + return req.getServletPath(); + } } 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 aa3d1627cc..85b0cf32e4 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,8 +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; /** @@ -35,11 +33,6 @@ public interface WebSocketCreator { /** * 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 +40,5 @@ public interface WebSocketCreator * 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 609a812625..7a9eeb1630 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 @@ import org.eclipse.jetty.websocket.api.annotations.WebSocket; /** * 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 @@ import org.eclipse.jetty.websocket.api.annotations.WebSocket; * } * </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 @@ import org.eclipse.jetty.websocket.api.annotations.WebSocket; * <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,36 +108,25 @@ public abstract class WebSocketServlet extends HttpServlet 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("inputBufferSize"); + max = getInitParameter("maxBinaryMessageSize"); if (max != null) { - policy.setInputBufferSize(Integer.parseInt(max)); + policy.setMaxBinaryMessageSize(Integer.parseInt(max)); } - WebSocketServletFactory baseFactory; - Iterator<WebSocketServletFactory> factories = ServiceLoader.load(WebSocketServletFactory.class).iterator(); - - if (factories.hasNext()) - { - baseFactory = factories.next(); - } - else + max = getInitParameter("inputBufferSize"); + if (max != null) { - // 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(); + policy.setInputBufferSize(Integer.parseInt(max)); } - 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 34fbf4d78b..1f02d69abf 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 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory; */ 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 98789a1811..18db717c45 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 @@ public class MyAdvancedEchoCreator implements WebSocketCreator } @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 0000000000..28522e7275 --- /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 0000000000..49421cae83 --- /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 15aeaa95eb..eeb300e947 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 class MyBinaryEchoSocket 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 cb3e016eef..b9d317f49f 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 class MyEchoSocket 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 459672c156..781436126f 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.6-SNAPSHOT</version> + <version>9.1.0-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 a6b93fd5dd..3874da811a 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 @@ public class XmlConfiguration 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 @@ public class XmlConfiguration if (className == null) return null; - return Loader.loadClass(XmlConfiguration.class,className,true); + return Loader.loadClass(XmlConfiguration.class,className); } /** @@ -859,7 +859,7 @@ public class XmlConfiguration aClass = InetAddress.class; break; default: - aClass = Loader.loadClass(XmlConfiguration.class, type, true); + aClass = Loader.loadClass(XmlConfiguration.class, type); break; } } @@ -6,13 +6,12 @@ <version>20</version> </parent> <artifactId>jetty-project</artifactId> - <version>9.0.6-SNAPSHOT</version> + <version>9.1.0-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> @@ -138,37 +137,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> @@ -347,17 +315,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> @@ -460,79 +445,131 @@ </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> + <artifactId>org.objectweb.asm</artifactId> + <version>3.1.0.v200803061910</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.orbit</groupId> + <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.1.0.v201105211820</version> + <version>2.3.2</version> </dependency> + + <dependency> - <groupId>org.eclipse.jetty.orbit</groupId> - <artifactId>javax.servlet.jsp.jstl</artifactId> - <version>1.2.0.v201105211821</version> + <groupId>javax.el</groupId> + <artifactId>javax.el-api</artifactId> + <version>3.0.0</version> </dependency> + <dependency> - <groupId>org.eclipse.jetty.orbit</groupId> + <groupId>org.glassfish</groupId> <artifactId>javax.el</artifactId> - <version>2.1.0.v201105211819</version> + <version>3.0.0</version> </dependency> + <dependency> <groupId>org.eclipse.jetty.orbit</groupId> - <artifactId>com.sun.el</artifactId> - <version>1.0.0.v201105211818</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>org.apache.jasper.glassfish</artifactId> - <version>2.1.0.v201110031002</version> + <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>org.apache.taglibs.standard.glassfish</artifactId> - <version>1.2.0.v201112081803</version> + <artifactId>javax.activation</artifactId> + <version>1.1.0.v201105071233</version> + <scope>provided</scope> </dependency> + <dependency> <groupId>org.eclipse.jetty.orbit</groupId> - <artifactId>org.eclipse.jdt.core</artifactId> - <version>3.7.1</version> + <artifactId>javax.mail.glassfish</artifactId> + <version>1.4.1.v201005082020</version> </dependency> - --> + + <dependency> + <groupId>javax.transaction</groupId> + <artifactId>javax.transaction-api</artifactId> + <version>1.2</version> + <scope>provided</scope> + </dependency> + <!-- Old Deps --> <dependency> @@ -606,12 +643,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 9cc1286a1f..c71bd23b53 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.6-SNAPSHOT</version> + <version>9.1.0-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 6eb7e60f0d..d956ab94ba 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.6-SNAPSHOT</version> + <version>9.1.0-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 e93f0baf29..51e26b1eaf 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 @@ -502,7 +502,6 @@ public abstract class ContinuationBase public void onTimeout(Continuation continuation) { ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","onTimeout"); - // continuation.resume(); } }; diff --git a/tests/test-loginservice/pom.xml b/tests/test-loginservice/pom.xml index 0d3b249d9c..4cc8782adb 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.6-SNAPSHOT</version> + <version>9.1.0-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 794ebccb11..5bc4146ed1 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 @@ -155,7 +155,6 @@ public class JdbcLoginServiceTest 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 7d16313300..a499af03d5 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.6-SNAPSHOT</version> + <version>9.1.0-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 4f446e8904..a2aa3910c8 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.6-SNAPSHOT</version> + <version>9.1.0-SNAPSHOT</version> </parent> <artifactId>test-hash-sessions</artifactId> <name>Jetty Tests :: Sessions :: Hash</name> diff --git a/tests/test-sessions/test-jdbc-sessions/pom.xml b/tests/test-sessions/test-jdbc-sessions/pom.xml index afda126c3b..7baebecbce 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.6-SNAPSHOT</version> + <version>9.1.0-SNAPSHOT</version> </parent> <artifactId>test-jdbc-sessions</artifactId> <name>Jetty Tests :: Sessions :: JDBC</name> 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 4d9bd4e2ed..ffa089ffa1 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.eclipse.jetty.server.session.AbstractTestServer; 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-sessions-common/pom.xml b/tests/test-sessions/test-sessions-common/pom.xml index a90b152f67..d5aa814ad4 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.6-SNAPSHOT</version> + <version>9.1.0-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 e7fe83f1f5..45ed2a38a4 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 @@ -119,10 +119,6 @@ public abstract class AbstractProxySerializationTest //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 070ddf1a5c..34cae2bddd 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,14 @@ import javax.servlet.http.HttpServlet; 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 +52,11 @@ public abstract class AbstractSessionRenewTest 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 +73,7 @@ public abstract class AbstractSessionRenewTest 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 +83,7 @@ public abstract class AbstractSessionRenewTest String renewSessionCookie = renewResponse.getHeaders().getStringField("Set-Cookie"); assertNotNull(renewSessionCookie); assertNotSame(sessionCookie, renewSessionCookie); + assertTrue(testListener.isCalled()); } finally { @@ -84,9 +92,30 @@ public abstract class AbstractSessionRenewTest } } + + + 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 8f3992ee1d..74a820efb1 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.6-SNAPSHOT</version> + <version>9.1.0-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 48d531d613..9e58548f56 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.6-SNAPSHOT</version> + <version>9.1.0-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/webapps.demo/test-jaas.d/login.conf b/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/etc/login.conf index 0978de6f38..a97b0eddee 100644 --- 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/demo-base/etc/login.conf @@ -1,5 +1,5 @@ xyz { org.eclipse.jetty.jaas.spi.PropertyFileLoginModule required debug="true" -file="${jetty.home}/webapps.demo/test-jaas.d/login.properties"; +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 index 61e3203731..61e3203731 100644 --- 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 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 index f3b0a18b1a..f3b0a18b1a 100644 --- 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 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 d9f637bfa6..521db42423 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 @@ -24,7 +24,7 @@ etc/jetty-jaas.xml </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>For the jetty distribution demos, jaas is already enabled in the demo-base/start.ini file and sets the jaas.login.conf property to ${jetty.base}/etc/login.conf for use with the demo-base/webapps/test-jaas.war web application. </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> diff --git a/tests/test-webapps/test-jetty-webapp/pom.xml b/tests/test-webapps/test-jetty-webapp/pom.xml index 13108954fa..9b6377b434 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.6-SNAPSHOT</version> + <version>9.1.0-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 4ed6fbc885..911b9479ff 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 @@ detected. <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 b80faf973d..c220f51b18 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/jetty-distribution/src/main/resources/etc/jetty-demo.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/demo-rewrite-rules.xml index 4dc0aaebcc..35a8f87582 100644 --- a/jetty-distribution/src/main/resources/etc/jetty-demo.xml +++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/demo-rewrite-rules.xml @@ -7,26 +7,6 @@ <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"> 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/demo-base/etc/override-web.xml index 08327c5dcb..08327c5dcb 100644 --- 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/demo-base/etc/override-web.xml 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 index 9d88b852b7..9d88b852b7 100644 --- 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 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/demo-base/etc/test-realm.xml index 5459dd51c7..d5c776ba08 100644 --- a/tests/test-webapps/test-jetty-webapp/src/main/config/etc/test-realm.xml +++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/test-realm.xml @@ -12,13 +12,13 @@ <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="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>test-realm is deployed. DO NOT USE IN PRODUCTION!</Arg></Call> + <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 0000000000..9607fd29f9 --- /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 0000000000..7738e921af --- /dev/null +++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/start.ini @@ -0,0 +1,38 @@ +# +# Example of providing a demo configuration, using a ${jetty.base} +# +# Additional ini files are in demo-base/start.d +# + +# Have webapps be deployed normally from webapps directory +--module=deploy + +# Some of the examples use JSP +--module=jsp + +# We are using annotations + jndi +--module=annotations +--module=jndi + +# 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 + +# The async behavior examples use http client to access remote systems +--module=client + +# Websocket chat examples needs websocket enabled +--module=websocket + +# Create and configure the test realm +etc/test-realm.xml +demo.realm=etc/realm.properties + +# Load test JNDI resources from lib/ext +--module=ext + + 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/demo-base/webapps/test.xml index 423477a2d9..42e03cb333 100644 --- a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.xml +++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml @@ -27,9 +27,14 @@ detected. <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> - + <Set name="overrideDescriptor"><Property name="jetty.base" default="."/>/etc/override-web.xml</Set> + <!-- Enable/Disable JSR356 Container for this context --> + <Call name="setAttribute"> + <Arg>org.eclipse.jetty.websocket.jsr356</Arg> + <Arg type="Boolean">true</Arg> + </Call> + <!-- virtual hosts <Set name="virtualHosts"> <Array type="String"> @@ -54,8 +59,8 @@ detected. <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> + <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 --> <!-- 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 0000000000..c8be36c97b --- /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/") +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 1f25d31946..8c72239459 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 @@ public class RewriteServlet extends HttpServlet 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/WebSocketChatServlet.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/WebSocketChatServlet.java index 03671464aa..1f9f3859ed 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 javax.servlet.http.HttpServletResponse; 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,7 +71,7 @@ public class WebSocketChatServlet extends WebSocketServlet implements WebSocketC } @Override - public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp) + public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) { return new ChatWebSocket(); } @@ -106,14 +106,7 @@ public class WebSocketChatServlet extends WebSocketServlet implements WebSocketC { if (data.contains("disconnect")) { - try - { - session.close(); - } - catch (IOException ignore) - { - // ignore - } + session.close(); return; } @@ -130,7 +123,7 @@ public class WebSocketChatServlet extends WebSocketServlet implements WebSocketC } // 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 51f8aa3060..b27e57def5 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 4b67966d26..3b55b767f3 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 @@ This page contains several links to test the authentication constraints: <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 c81fcb94a2..e7d57b4855 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 @@ -19,7 +19,7 @@ deployed in $JETTY_HOME/webapp.demo and configured by $JETTY_HOME/start.d/900-de <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>JSP: <a href="jsp/">examples</a></li> -<li>Comet Chat: <a href="chat/">Long Polling</a>, <a href="ws">WebSocket</a></li> +<li>Comet Chat: <a href="chat/">Long Polling</a>, <a href="ws">WebSocket (Jetty API)</a>, <a href="javax.websocket">WebSocket (javax.websocket)</a></li> <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> 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 0000000000..7172f47e82 --- /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/rewrite/index.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/rewrite/index.html index fd9e22ffc3..613e13d783 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/test/java/org/eclipse/jetty/TestServer.java b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java index 0865696a1d..fde8fabe86 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 @@ -154,7 +154,7 @@ public class TestServer // 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 442f1218d6..f33a6d4df6 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.6-SNAPSHOT</version> + <version>9.1.0-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 f4b5d583a1..794604398d 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/webapp/index.html b/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html index 5382876041..d9955c2570 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 @@ -27,8 +27,8 @@ This example shows how to configure and lookup resources such as DataSources, a 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. +For the jetty distribution demos, jndi is already enabled in the modules/demo.mod file and also enables various configurations in +the demo directory which includes some mock resources used by the test. </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> diff --git a/tests/test-webapps/test-mock-resources/pom.xml b/tests/test-webapps/test-mock-resources/pom.xml index eb2a298746..1001af5ceb 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.6-SNAPSHOT</version> + <version>9.1.0-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>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.servlet</artifactId> + <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 0000000000..adb86dab5e --- /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 0000000000..5ab3340c05 --- /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 da85339026..4e3251c2f1 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.6-SNAPSHOT</version> + <version>9.1.0-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 8858bdac4b..74f7261b5b 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.6-SNAPSHOT</version> + <version>9.1.0-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 32b7f65785..cde0252957 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.6-SNAPSHOT</version> + <version>9.1.0-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 bcd44bbbdb..bd27bb82f7 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.6-SNAPSHOT</version> + <version>9.1.0-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 2fc52bab23..f16ec5fc3e 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 b90f3561d7..6d06c15bd0 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 @@ public class AnnotationTest extends HttpServlet 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 @@ public class AnnotationTest extends HttpServlet 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 @@ public class AnnotationTest extends HttpServlet } 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 @@ public class AnnotationTest extends HttpServlet 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 @@ public class AnnotationTest extends HttpServlet 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 @@ public class AnnotationTest extends HttpServlet 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 0000000000..b828610abc --- /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 7d7592d0fa..8892fcb8c0 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 @@ import javax.servlet.http.HttpSessionListener; @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 class TestListener implements HttpSessionListener, HttpSessionAttributeL 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/webapp/index.html b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html index 854082f4bd..405b05f856 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 @@ -54,6 +54,12 @@ Test of the annotation: <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 4ecc2cb4ae..f90463dd2c 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 0000000000..def6847d14 --- /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 7f4e40dc01..e272b87c6c 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.6-SNAPSHOT</version> + <version>9.1.0-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 1c28956df2..f458ac0079 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.6-SNAPSHOT</version> + <version>9.1.0-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> |