diff --git a/LICENSE-CONTRIBUTOR/cla-tbecker.txt b/LICENSE-CONTRIBUTOR/cla-tbecker.txt new file mode 100644 index 00000000000..8a44e3614f2 --- /dev/null +++ b/LICENSE-CONTRIBUTOR/cla-tbecker.txt @@ -0,0 +1,145 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +Jetty Project +Contributor License Agreement V1.0 +based on http://www.apache.org/licenses/ + +Thank you for your interest in the Jetty project by Mort Bay +Consulting Pty. Ltd. Australia ("MortBay"). +In order to clarify the intellectual property license +granted with Contributions from any person or entity, MortBay +must have a Contributor License Agreement ("CLA") that has +been signed by each Contributor, indicating agreement to the license +terms below. This license is for your protection as a Contributor as +well as the protection of MortBay and its users; it does not +change your rights to use your own Contributions for any other +purpose. + +If you have not already done so, please complete this agreement +and commit it to the Jetty repository at +svn+ssh://svn.jetty.codehaus.org/home/projects/jetty/scm/jetty +at legal/cla-USERNAME.txt using your authenticated codehaus ssh +login. If you do not have commit privilege to the repository, please +email the file to eclipse@eclipse.com. If possible, digitally sign +the committed file, otherwise also send a signed Agreement to MortBay. + +Please read this document carefully before signing and keep a copy for +your records. + + Full name: Thomas Becker + E-Mail: thomas.becker00@googlemail.com + Mailing Address: + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to MortBay. In return, +MortBay shall not use Your Contributions in a way that is contrary +to the software license in effect at the time of the Contribution. +Except for the license granted herein to MortBay and recipients of +software distributed by MortBay, You reserve all right, title, and +interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with MortBay. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to MortBay for inclusion + in, or documentation of, any of the products owned or managed by + MortBay (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to MortBay or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, MortBay for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to MortBay and to + recipients of software distributed by MortBay a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to MortBay, or that your employer has + executed a separate Corporate CLA with MortBay. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to MortBay separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify MortBay of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Date: 2012-07-17 +Please sign: + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.10 (GNU/Linux) + +iQEcBAEBAgAGBQJQBb4tAAoJEMHhjBmtgF91HDcH/2nQDPuPztWFrBifnEoLF6Jl +RUkfJzAPZaLDtDMfiDz7ucdRL1RDodmz4VIF2+fbKeBYQquZXfXIeEghz+tKriK3 +0M12guFkNLDteQp9h2p3Zu9JU3K0y4m84IDWq72HRmh1nRyD6lzZFhDGZ/D+69fF +tgYG0FwEit00MAq/lRbsXHLpBOY+Jyh/Xy+QRnQTcAQ+tAgOlxds3w+JSs2sGdes +YLAJQQacLeGh7EzD3F+CKuiwT4c5ub64LdXSlAVj1u2OjZBfqLaJ3FA60Ti+I3kn +FNWKpzaeX+SQgMak6hsuatXi6EsVk6sIaskwEgl6+Xk+HYWy23ZQ8BKQRLKOZTw= +=gAqN +-----END PGP SIGNATURE----- diff --git a/VERSION.txt b/VERSION.txt index e14d23e3355..2cbf59f0740 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,10 +1,12 @@ jetty-8.1.6-SNAPSHOT + + 385925: make SslContextFactory.setProtocols and + SslContextFactory.setCipherSuites preserve the order of the given parameters -jetty-8.1.5.v20120716 - 16 July 2012 +jetty-7.6.6-SNAPSHOT + 376717 Balancer Servlet with round robin support, contribution, added missing license + 379250 Server is added to shutdown hook twice - + 380866 maxIdleTime set to 0 after session migration + + 380866 idleTimeout set to 0 after session migration + 381399 Unable to stop a jetty instance that has not finished starting + 381401 Print log warning when stop attempt made with incorrect STOP.KEY + 381402 Make ContextHandler take set of protected directories @@ -176,7 +178,7 @@ jetty-8.1.0.RC4 - 13 January 2012 + 367548 jetty-osgi-boot must not import the nested package twice + 367591 corrected configuration.xml version to 7.6 + 367635 Added support for start.d directory - + 367716 simplified maxIdleTime logic + + 367716 simplified idleTimeout logic + 368035 WebSocketClientFactory does not invoke super.doStop(). + 368060 do not encode sendRedirect URLs + 368112 NPE on element parsing web.xml @@ -255,7 +257,7 @@ jetty-7.6.0.RC4 - 13 January 2012 a Https-resource through a web-proxy. + 366774 removed XSS vulnerbility + 367099 Upgrade jetty-websocket for RFC 6455 - Addendum. - + 367716 simplified maxIdleTime logic + + 367716 simplified idleTimeout logic + 368035 WebSocketClientFactory does not invoke super.doStop(). + 368060 do not encode sendRedirect URLs + 368114 Protect against non-Strings in System properties for Log @@ -623,7 +625,7 @@ jetty-7.4.3.v20110701 - 01 July 2011 String) does not yield an empty map + 349738 set buffer sizes for http client in proxy servlet + 349870 proxy servlet protect continuation against fast failing exchanges - + 349896 SCEP supports zero maxIdleTime + + 349896 SCEP supports zero idleTimeout + 349897 draft -09 websockets + 349997 MBeanContainer uses weak references + 350533 Add "Origin" to the list of allowed headers in CrossOriginFilter @@ -1068,7 +1070,7 @@ jetty-7.1.4.v20100610 + 315748 Removed --fromDaemon from start.jar (replaced with --daemon) + 315925 Improved context xml configuration handling + 315995 Incorrect package name in system classes list - + 316119 Fixed maxIdleTime for SocketEndPoint + + 316119 Fixed idleTimeout for SocketEndPoint + 316254 Implement @DeclareRoles + 316334 Breaking change on org.eclipse.jetty.client.HttpExchange + 316399 Debug output in MultiPartFilter @@ -2490,7 +2492,7 @@ jetty-6.1.5rc0 - 15 July 0200 jetty-6.1.4 - 15 June 2007 + fixed early open() call in NIO connectors - + JETTY-370 ensure maxIdleTime<=0 means connections never expire + + JETTY-370 ensure idleTimeout<=0 means connections never expire + JETTY-371 Fixed chunked HEAD response + JETTY-372 make test for cookie caching more rigorous diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ExpirationWithLimitedConnectionsTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ExpirationWithLimitedConnectionsTest.java index 3db4a5e72b3..05b20fda9e9 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ExpirationWithLimitedConnectionsTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ExpirationWithLimitedConnectionsTest.java @@ -32,10 +32,11 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.junit.Assert; import org.junit.Test; +import org.junit.Ignore; public class ExpirationWithLimitedConnectionsTest { - @Test + @Ignore public void testExpirationWithMaxConnectionPerAddressReached() throws Exception { final Logger logger = Log.getLogger("org.eclipse.jetty.client"); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractAsyncConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractAsyncConnection.java index f7e0c652455..d50e60761ef 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractAsyncConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractAsyncConnection.java @@ -45,7 +45,7 @@ public abstract class AbstractAsyncConnection implements AsyncConnection public AbstractAsyncConnection(AsyncEndPoint endp, Executor executor, final boolean executeOnlyFailure) { if (executor == null) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Executor must not be null!"); _endp = endp; _readCallback = new ExecutorCallback(executor) @@ -129,7 +129,7 @@ public abstract class AbstractAsyncConnection implements AsyncConnection @Override public void onOpen() { - LOG.debug("Opened {}",this); + LOG.debug("{} opened",this); fillInterested(); } 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 3c117e0b4c8..c9917d9d238 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 @@ -167,16 +167,49 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa endpoint.onClose(); } + /** + *

Callback method invoked when a connection is opened.

+ * + * @param connection the connection just opened + */ + public void connectionOpened(AsyncConnection connection) + { + connection.onOpen(); + } + + /** + *

Callback method invoked when a connection is closed.

+ * + * @param connection the connection just closed + */ + public void connectionClosed(AsyncConnection connection) + { + connection.onClose(); + } + /** *

Callback method invoked when a connection is upgraded.

* * @param endpoint the endpoint holding the new connection * @param oldConnection the previous connection */ - protected void connectionUpgraded(AsyncEndPoint endpoint, AsyncConnection oldConnection) + public void connectionUpgraded(AsyncEndPoint endpoint, AsyncConnection oldConnection) { - oldConnection.onClose(); - endpoint.getAsyncConnection().onOpen(); + connectionClosed(oldConnection); + connectionOpened(endpoint.getAsyncConnection()); + } + + /** + *

Callback method invoked when a non-blocking connect cannot be completed.

+ *

By default it just logs with level warning.

+ * + * @param channel the channel that attempted the connect + * @param ex the exception that caused the connect to fail + * @param attachment the attachment object associated at registration + */ + protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment) + { + LOG.warn(String.format("%s - %s", channel, attachment), ex); } /** @@ -205,19 +238,6 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa */ public abstract AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment) throws IOException; - /** - *

Callback method invoked when a non-blocking connect cannot be completed.

- *

By default it just logs with level warning.

- * - * @param channel the channel that attempted the connect - * @param ex the exception that caused the connect to fail - * @param attachment the attachment object associated at registration - */ - protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment) - { - LOG.warn(String.format("%s - %s", channel, attachment), ex); - } - @Override public String dump() { @@ -427,6 +447,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa boolean connected = channel.finishConnect(); if (connected) { + key.interestOps(0); AsyncEndPoint endpoint = createEndPoint(channel, key); key.attach(endpoint); } @@ -468,7 +489,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa endPointOpened(endPoint); AsyncConnection asyncConnection = newConnection(channel, endPoint, selectionKey.attachment()); endPoint.setAsyncConnection(asyncConnection); - asyncConnection.onOpen(); + connectionOpened(asyncConnection); LOG.debug("Created {}", endPoint); return endPoint; } @@ -476,7 +497,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa public void destroyEndPoint(AsyncEndPoint endPoint) { LOG.debug("Destroyed {}", endPoint); - endPoint.getAsyncConnection().onClose(); + connectionClosed(endPoint.getAsyncConnection()); endPointClosed(endPoint); } 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 a9a1b90b5de..522fb84215b 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 @@ -51,7 +51,6 @@ public class SslConnection extends AbstractAsyncConnection private final ByteBufferPool _bufferPool; private final SSLEngine _sslEngine; private final SslEndPoint _appEndPoint; - private final Executor _executor; private ByteBuffer _appIn; private ByteBuffer _netIn; private ByteBuffer _netOut; @@ -63,8 +62,6 @@ public class SslConnection extends AbstractAsyncConnection public SslConnection(ByteBufferPool byteBufferPool, Executor executor, AsyncEndPoint endPoint, SSLEngine sslEngine) { super(endPoint, executor, true); - - _executor = executor; this._bufferPool = byteBufferPool; this._sslEngine = sslEngine; this._appEndPoint = new SslEndPoint(); @@ -85,25 +82,13 @@ public class SslConnection extends AbstractAsyncConnection { try { + super.onOpen(); + // Begin the handshake - _sslEngine.setUseClientMode(false); _sslEngine.beginHandshake(); - LOG.debug("{} onopen", this); - - // Tell the app that we are open, even though we have - // not completed the handshake. All handshaking will be - // done in calls to fill and flush, as it has to be with - // rehandshakes. We dispatch here because it will probably - // result in a call to onReadable - _executor.execute(new Runnable() - { - @Override - public void run() - { - _appEndPoint.getAsyncConnection().onOpen(); - } - }); + if (_sslEngine.getUseClientMode()) + _appEndPoint.write(null, new Callback.Empty<>(), BufferUtil.EMPTY_BUFFER); } catch (SSLException x) { @@ -346,6 +331,7 @@ public class SslConnection extends AbstractAsyncConnection { // Let's try reading some encrypted data... even if we have some already. int net_filled = getEndPoint().fill(_netIn); + LOG.debug("{} filled {} encrypted bytes", SslConnection.this, net_filled); if (net_filled > 0) _underflown = false; @@ -482,7 +468,12 @@ public class SslConnection extends AbstractAsyncConnection @Override public synchronized int flush(ByteBuffer... appOuts) throws IOException { - LOG.debug("{} flush enter", SslConnection.this); + // TODO: it is possible that an application flushes during the SSL handshake, + // TODO: the flush wraps 0 application bytes, and then a need for unwrap is + // TODO: triggered. In that case, we need to save the appOuts and re-attempt + // TODO: to flush it at the first occasion (which may be on a fill ?) + + LOG.debug("{} flush enter {}", SslConnection.this, appOuts); try { if (_netWriting) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SelectChannelConnector.java index c38deedebc2..c24a0e66ced 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/SelectChannelConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SelectChannelConnector.java @@ -273,7 +273,7 @@ public class SelectChannelConnector extends AbstractNetConnector } @Override - protected void connectionUpgraded(AsyncEndPoint endpoint, AsyncConnection oldConnection) + public void connectionUpgraded(AsyncEndPoint endpoint, AsyncConnection oldConnection) { super.connectionUpgraded(endpoint, oldConnection); SelectChannelConnector.this.connectionUpgraded(oldConnection, endpoint.getAsyncConnection()); diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/Connector-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/Connector-mbean.properties index efd5cef83c2..add1cf55f84 100644 --- a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/Connector-mbean.properties +++ b/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/Connector-mbean.properties @@ -10,7 +10,7 @@ confidentialPort: Port to use for confidential redirections confidentialScheme: Scheme to use for confidential redirections host: Host name to accept connections on port: TCP/IP port to accept connections on -maxIdleTime: Maximum time in ms that a connection can be idle before being closed +idleTimeout: Maximum time in ms that a connection can be idle before being closed statsOn: True if statistics collection is turned on. statsOnMs: Time in milliseconds stats have been collected for. statsReset(): Reset statistics. @@ -26,4 +26,4 @@ connectionsRequestsStdDev: Standard deviation of number of requests per connecti connectionsRequestsMax: Maximum number of requests per connection since statsReset() called. Undefined if setStatsOn(false). requests: Number of requests since statsReset() called. Undefined if setStatsOn(false). open(): Open the listening port -close(): Close the listening port (but allow existing connections to continue for graceful shutdown) \ No newline at end of file +close(): Close the listening port (but allow existing connections to continue for graceful shutdown) diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java index 6561166a18c..e69c8050517 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java @@ -66,6 +66,9 @@ import org.eclipse.jetty.util.log.Logger; *
  • exposeHeaders, a comma separated list of HTTP headers that * are allowed to be exposed on the client. Default value is the * empty list
  • + *
  • chainPreflight, if true preflight requests are chained to their + * target resource for normal handling (as an OPTION request). Otherwise the + * filter will response to the preflight. Default is true.
  • *

    *

    A typical configuration could be: *

    @@ -105,7 +108,8 @@ public class CrossOriginFilter implements Filter
         public static final String PREFLIGHT_MAX_AGE_PARAM = "preflightMaxAge";
         public static final String ALLOW_CREDENTIALS_PARAM = "allowCredentials";
         public static final String EXPOSED_HEADERS_PARAM = "exposedHeaders";
    -    public static final String FORWARD_PREFLIGHT_PARAM = "forwardPreflight";
    +    public static final String OLD_CHAIN_PREFLIGHT_PARAM = "forwardPreflight";
    +    public static final String CHAIN_PREFLIGHT_PARAM = "chainPreflight";
         private static final String ANY_ORIGIN = "*";
         private static final List SIMPLE_HTTP_METHODS = Arrays.asList("GET", "POST", "HEAD");
     
    @@ -116,7 +120,7 @@ public class CrossOriginFilter implements Filter
         private List exposedHeaders = new ArrayList();
         private int preflightMaxAge;
         private boolean allowCredentials;
    -    private boolean forwardPreflight;
    +    private boolean chainPreflight;
     
         public void init(FilterConfig config) throws ServletException
         {
    @@ -174,10 +178,14 @@ public class CrossOriginFilter implements Filter
                 exposedHeadersConfig = "";
             exposedHeaders.addAll(Arrays.asList(exposedHeadersConfig.split(",")));
     
    -        String forwardPreflightConfig = config.getInitParameter(FORWARD_PREFLIGHT_PARAM);
    -        if (forwardPreflightConfig == null)
    -            forwardPreflightConfig = "true";
    -        forwardPreflight = Boolean.parseBoolean(forwardPreflightConfig);
    +        String chainPreflightConfig = config.getInitParameter(OLD_CHAIN_PREFLIGHT_PARAM);
    +        if (chainPreflightConfig!=null) // TODO remove this
    +            LOG.warn("DEPRECATED CONFIGURATION: Use "+CHAIN_PREFLIGHT_PARAM+ " instead of "+OLD_CHAIN_PREFLIGHT_PARAM);
    +        else
    +            chainPreflightConfig = config.getInitParameter(CHAIN_PREFLIGHT_PARAM);
    +        if (chainPreflightConfig == null)
    +            chainPreflightConfig = "true";
    +        chainPreflight = Boolean.parseBoolean(chainPreflightConfig);
     
             if (LOG.isDebugEnabled())
             {
    @@ -188,7 +196,7 @@ public class CrossOriginFilter implements Filter
                         PREFLIGHT_MAX_AGE_PARAM + " = " + preflightMaxAgeConfig + ", " +
                         ALLOW_CREDENTIALS_PARAM + " = " + allowedCredentialsConfig + "," +
                         EXPOSED_HEADERS_PARAM + " = " + exposedHeadersConfig + "," +
    -                    FORWARD_PREFLIGHT_PARAM + " = " + forwardPreflightConfig
    +                    CHAIN_PREFLIGHT_PARAM + " = " + chainPreflightConfig
                 );
             }
         }
    @@ -215,7 +223,7 @@ public class CrossOriginFilter implements Filter
                     {
                         LOG.debug("Cross-origin request to {} is a preflight cross-origin request", request.getRequestURI());
                         handlePreflightResponse(request, response, origin);
    -                    if (forwardPreflight)
    +                    if (chainPreflight)
                             LOG.debug("Preflight cross-origin request to {} forwarded to application", request.getRequestURI());
                         else
                             return;
    diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java
    index 6e558483b22..fafa561ec77 100644
    --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java
    +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java
    @@ -408,11 +408,11 @@ public class CrossOriginFilterTest
         }
     
         @Test
    -    public void testForwardPreflightRequest() throws Exception
    +    public void testChainPreflightRequest() throws Exception
         {
             FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
             filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "PUT");
    -        filterHolder.setInitParameter(CrossOriginFilter.FORWARD_PREFLIGHT_PARAM, "false");
    +        filterHolder.setInitParameter(CrossOriginFilter.CHAIN_PREFLIGHT_PARAM, "false");
             tester.getContext().addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));
     
             CountDownLatch latch = new CountDownLatch(1);
    diff --git a/jetty-spdy/pom.xml b/jetty-spdy/pom.xml
    index f89df26e46c..59ac8026679 100644
    --- a/jetty-spdy/pom.xml
    +++ b/jetty-spdy/pom.xml
    @@ -13,14 +13,16 @@
         Jetty :: SPDY :: Parent
     
         
    -        1.0.0.v20120402
    +        1.1.0.v20120525
         
     
         
             spdy-core
             spdy-jetty
    +        
         
     
         
    diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/CompressionDictionary.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/CompressionDictionary.java
    index 0aa024d30f7..8b8b83a134c 100644
    --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/CompressionDictionary.java
    +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/CompressionDictionary.java
    @@ -1,18 +1,15 @@
    -/*
    - * Copyright (c) 2012 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *     http://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    +//========================================================================
    +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd.
    +//------------------------------------------------------------------------
    +//All rights reserved. This program and the accompanying materials
    +//are made available under the terms of the Eclipse Public License v1.0
    +//and Apache License v2.0 which accompanies this distribution.
    +//The Eclipse Public License is available at
    +//http://www.eclipse.org/legal/epl-v10.html
    +//The Apache License v2.0 is available at
    +//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;
     
    diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/CompressionFactory.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/CompressionFactory.java
    index f81a5b3f491..620c24ac100 100644
    --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/CompressionFactory.java
    +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/CompressionFactory.java
    @@ -1,18 +1,15 @@
    -/*
    - * Copyright (c) 2012 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *     http://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    +//========================================================================
    +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd.
    +//------------------------------------------------------------------------
    +//All rights reserved. This program and the accompanying materials
    +//are made available under the terms of the Eclipse Public License v1.0
    +//and Apache License v2.0 which accompanies this distribution.
    +//The Eclipse Public License is available at
    +//http://www.eclipse.org/legal/epl-v10.html
    +//The Apache License v2.0 is available at
    +//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;
     
    diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/FlowControlStrategy.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/FlowControlStrategy.java
    new file mode 100644
    index 00000000000..c08bc421218
    --- /dev/null
    +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/FlowControlStrategy.java
    @@ -0,0 +1,86 @@
    +//========================================================================
    +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd.
    +//------------------------------------------------------------------------
    +//All rights reserved. This program and the accompanying materials
    +//are made available under the terms of the Eclipse Public License v1.0
    +//and Apache License v2.0 which accompanies this distribution.
    +//The Eclipse Public License is available at
    +//http://www.eclipse.org/legal/epl-v10.html
    +//The Apache License v2.0 is available at
    +//http://www.opensource.org/licenses/apache2.0.php
    +//You may elect to redistribute this code under either of these licenses.
    +//========================================================================
    +package org.eclipse.jetty.spdy;
    +
    +import org.eclipse.jetty.spdy.api.DataInfo;
    +
    +// TODO: add methods that tell how much written and whether we're TCP congested ?
    +public interface FlowControlStrategy
    +{
    +    public int getWindowSize(ISession session);
    +
    +    public void setWindowSize(ISession session, int windowSize);
    +
    +    public void onNewStream(ISession session, IStream stream);
    +
    +    public void onWindowUpdate(ISession session, IStream stream, int delta);
    +
    +    public void updateWindow(ISession session, IStream stream, int delta);
    +
    +    public void onDataReceived(ISession session, IStream stream, DataInfo dataInfo);
    +
    +    public void onDataConsumed(ISession session, IStream stream, DataInfo dataInfo, int delta);
    +
    +    public static class None implements FlowControlStrategy
    +    {
    +        private volatile int windowSize;
    +
    +        public None()
    +        {
    +            this(65536);
    +        }
    +
    +        public None(int windowSize)
    +        {
    +            this.windowSize = windowSize;
    +        }
    +
    +        @Override
    +        public int getWindowSize(ISession session)
    +        {
    +            return windowSize;
    +        }
    +
    +        @Override
    +        public void setWindowSize(ISession session, int windowSize)
    +        {
    +            this.windowSize = windowSize;
    +        }
    +
    +        @Override
    +        public void onNewStream(ISession session, IStream stream)
    +        {
    +            stream.updateWindowSize(windowSize);
    +        }
    +
    +        @Override
    +        public void onWindowUpdate(ISession session, IStream stream, int delta)
    +        {
    +        }
    +
    +        @Override
    +        public void updateWindow(ISession session, IStream stream, int delta)
    +        {
    +        }
    +
    +        @Override
    +        public void onDataReceived(ISession session, IStream stream, DataInfo dataInfo)
    +        {
    +        }
    +
    +        @Override
    +        public void onDataConsumed(ISession session, IStream stream, DataInfo dataInfo, int delta)
    +        {
    +        }
    +    }
    +}
    diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IStream.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IStream.java
    index 07934c16e17..ad75f406a7f 100644
    --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IStream.java
    +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IStream.java
    @@ -1,29 +1,24 @@
    -/*
    - * Copyright (c) 2012 the original author or authors.
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *     http://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    +//========================================================================
    +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd.
    +//------------------------------------------------------------------------
    +//All rights reserved. This program and the accompanying materials
    +//are made available under the terms of the Eclipse Public License v1.0
    +//and Apache License v2.0 which accompanies this distribution.
    +//The Eclipse Public License is available at
    +//http://www.eclipse.org/legal/epl-v10.html
    +//The Apache License v2.0 is available at
    +//http://www.opensource.org/licenses/apache2.0.php
    +//You may elect to redistribute this code under either of these licenses.
    +//========================================================================
     
     package org.eclipse.jetty.spdy;
     
    -import java.nio.ByteBuffer;
    -
    +import org.eclipse.jetty.spdy.api.DataInfo;
     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.frames.ControlFrame;
    -import org.eclipse.jetty.spdy.frames.DataFrame;
     
     /**
      * 

    The internal interface that represents a stream.

    @@ -77,37 +72,36 @@ public interface IStream extends Stream * for example by updating the stream's state or by calling listeners.

    * * @param frame the control frame to process - * @see #process(DataFrame, ByteBuffer) + * @see #process(DataInfo) */ public void process(ControlFrame frame); /** - *

    Processes the given data frame along with the given byte buffer, + *

    Processes the given {@code dataInfo}, * for example by updating the stream's state or by calling listeners.

    * - * @param frame the data frame to process - * @param data the byte buffer to process + * @param dataInfo the DataInfo to process * @see #process(ControlFrame) */ - public void process(DataFrame frame, ByteBuffer data); - + public void process(DataInfo dataInfo); + /** *

    Associate the given {@link IStream} to this {@link IStream}.

    - * + * * @param stream the stream to associate with this stream */ public void associate(IStream stream); - + /** *

    remove the given associated {@link IStream} from this stream

    - * + * * @param stream the stream to be removed */ public void disassociate(IStream stream); - + /** *

    Overrides Stream.getAssociatedStream() to return an instance of IStream instead of Stream - * + * * @see Stream#getAssociatedStream() */ @Override diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IdleListener.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IdleListener.java index 8ca26add0d5..42e44496790 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IdleListener.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IdleListener.java @@ -1,19 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - +///======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; public interface IdleListener diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Promise.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Promise.java index acaa1043c81..b6d2e13e1bc 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Promise.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Promise.java @@ -1,21 +1,19 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== package org.eclipse.jetty.spdy; +import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -38,16 +36,15 @@ public class Promise implements Callback, Future private T promise; @Override - public void completed(T context) + public void completed(T result) { - this.promise = context; + this.promise = result; latch.countDown(); } @Override public void failed(T context, Throwable x) { - this.promise = context; this.failure = x; latch.countDown(); } @@ -90,6 +87,8 @@ public class Promise implements Callback, Future private T result() throws ExecutionException { + if (isCancelled()) + throw new CancellationException(); Throwable failure = this.failure; if (failure != null) throw new ExecutionException(failure); diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java index a460d54d7d4..8befef43edb 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java @@ -1,4 +1,16 @@ package org.eclipse.jetty.spdy; +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== import org.eclipse.jetty.spdy.api.SynInfo; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SPDYv3FlowControlStrategy.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SPDYv3FlowControlStrategy.java new file mode 100644 index 00000000000..8eb4b0ebdfe --- /dev/null +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SPDYv3FlowControlStrategy.java @@ -0,0 +1,84 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + +package org.eclipse.jetty.spdy; + +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.spdy.api.DataInfo; +import org.eclipse.jetty.spdy.api.Stream; +import org.eclipse.jetty.spdy.frames.WindowUpdateFrame; + +public class SPDYv3FlowControlStrategy implements FlowControlStrategy +{ + private volatile int windowSize; + + @Override + public int getWindowSize(ISession session) + { + return windowSize; + } + + @Override + public void setWindowSize(ISession session, int windowSize) + { + int prevWindowSize = this.windowSize; + this.windowSize = windowSize; + for (Stream stream : session.getStreams()) + ((IStream)stream).updateWindowSize(windowSize - prevWindowSize); + } + + @Override + public void onNewStream(ISession session, IStream stream) + { + stream.updateWindowSize(windowSize); + } + + @Override + public void onWindowUpdate(ISession session, IStream stream, int delta) + { + if (stream != null) + stream.updateWindowSize(delta); + } + + @Override + public void updateWindow(ISession session, IStream stream, int delta) + { + stream.updateWindowSize(delta); + } + + @Override + public void onDataReceived(ISession session, IStream stream, DataInfo dataInfo) + { + // Do nothing + } + + @Override + public void onDataConsumed(ISession session, IStream stream, DataInfo dataInfo, int delta) + { + // This is the algorithm for flow control. + // This method may be called multiple times with delta=1, but we only send a window + // update when the whole dataInfo has been consumed. + // Other policies may be to send window updates when consumed() is greater than + // a certain threshold, etc. but for now the policy is not pluggable for simplicity. + // Note that the frequency of window updates depends on the read buffer, that + // should not be too smaller than the window size to avoid frequent window updates. + // Therefore, a pluggable policy should be able to modify the read buffer capacity. + int length = dataInfo.length(); + if (dataInfo.consumed() == length && !stream.isClosed() && length > 0) + { + WindowUpdateFrame windowUpdateFrame = new WindowUpdateFrame(session.getVersion(), stream.getId(), length); + session.control(stream, windowUpdateFrame, 0, TimeUnit.MILLISECONDS, null, null); + } + } +} diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SessionException.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SessionException.java index 3e0c1950e58..65f375412d1 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SessionException.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SessionException.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -20,7 +17,6 @@ import org.eclipse.jetty.spdy.api.SessionStatus; public class SessionException extends RuntimeException { - private final SessionStatus sessionStatus; public SessionException(SessionStatus sessionStatus) diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardCompressionFactory.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardCompressionFactory.java index f0a7eebbdff..1e7e6e084d3 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardCompressionFactory.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardCompressionFactory.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; 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 8c645a9f8c1..88f876350d4 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 @@ -1,26 +1,27 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== package org.eclipse.jetty.spdy; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.InterruptedByTimeoutException; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -34,6 +35,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.spdy.api.ByteBufferDataInfo; import org.eclipse.jetty.spdy.api.DataInfo; import org.eclipse.jetty.spdy.api.GoAwayInfo; import org.eclipse.jetty.spdy.api.PingInfo; @@ -50,6 +52,7 @@ import org.eclipse.jetty.spdy.api.StreamStatus; import org.eclipse.jetty.spdy.api.SynInfo; import org.eclipse.jetty.spdy.frames.ControlFrame; import org.eclipse.jetty.spdy.frames.ControlFrameType; +import org.eclipse.jetty.spdy.frames.CredentialFrame; import org.eclipse.jetty.spdy.frames.DataFrame; import org.eclipse.jetty.spdy.frames.GoAwayFrame; import org.eclipse.jetty.spdy.frames.HeadersFrame; @@ -61,11 +64,14 @@ import org.eclipse.jetty.spdy.frames.SynStreamFrame; import org.eclipse.jetty.spdy.frames.WindowUpdateFrame; import org.eclipse.jetty.spdy.generator.Generator; import org.eclipse.jetty.spdy.parser.Parser; +import org.eclipse.jetty.util.Atomics; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.component.AggregateLifeCycle; +import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -public class StandardSession implements ISession, Parser.Listener, Callback +public class StandardSession implements ISession, Parser.Listener, Callback, Dumpable { private static final Logger logger = Log.getLogger(Session.class); private static final ThreadLocal handlerInvocations = new ThreadLocal() @@ -77,6 +83,7 @@ public class StandardSession implements ISession, Parser.Listener, Callback attributes = new ConcurrentHashMap<>(); private final List listeners = new CopyOnWriteArrayList<>(); private final ConcurrentMap streams = new ConcurrentHashMap<>(); private final LinkedList queue = new LinkedList<>(); @@ -93,11 +100,13 @@ public class StandardSession implements ISession, Parser.Listener, Callback controller, IdleListener idleListener, int initialStreamId, SessionFrameListener listener, Generator generator) + Controller controller, IdleListener idleListener, int initialStreamId, SessionFrameListener listener, + Generator generator, FlowControlStrategy flowControlStrategy) { this.version = version; this.bufferPool = bufferPool; @@ -109,6 +118,7 @@ public class StandardSession implements ISession, Parser.Listener, Callback callback) { SettingsFrame frame = new SettingsFrame(version,settingsInfo.getFlags(),settingsInfo.getSettings()); - control(null,frame,timeout,unit, callback,null); + control(null, frame, timeout, unit, callback, null); } @Override @@ -218,7 +230,7 @@ public class StandardSession implements ISession, Parser.Listener, Callback callback) { - goAway(SessionStatus.OK,timeout,unit, callback); + goAway(SessionStatus.OK, timeout, unit, callback); } private void goAway(SessionStatus sessionStatus, long timeout, TimeUnit unit, Callback callback) @@ -247,7 +259,7 @@ public class StandardSession implements ISession, Parser.Listener, Callback void control(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Callback callback, C context) + { + generateAndEnqueueControlFrame(stream,frame,timeout,unit,callback,context); + flush(); + } + + private void generateAndEnqueueControlFrame(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Callback callback, C context) { try { - if (stream != null) - { - updateLastStreamId(stream); - if (stream.isClosed()) - removeStream(stream); - } - // Synchronization is necessary, since we may have concurrent replies // and those needs to be generated and enqueued atomically in order // to maintain a correct compression context synchronized (this) { ByteBuffer buffer = generator.control(frame); - logger.debug("Queuing {} on {}",frame,stream); - ControlFrameBytes frameBytes = new ControlFrameBytes<>(stream, callback,context,frame,buffer); + logger.debug("Queuing {} on {}", frame, stream); + ControlFrameBytes frameBytes = new ControlFrameBytes<>(stream, callback, context, frame, buffer); if (timeout > 0) - frameBytes.task = scheduler.schedule(frameBytes,timeout,unit); + frameBytes.task = scheduler.schedule(frameBytes, timeout, unit); // Special handling for PING frames, they must be sent as soon as possible if (ControlFrameType.PING == frame.getType()) @@ -752,40 +848,27 @@ public class StandardSession implements ISession, Parser.Listener, Callback oldValue) - { - if (lastStreamId.compareAndSet(oldValue,streamId)) - break; - oldValue = lastStreamId.get(); - } - } + if (streamId % 2 != streamIds.get() % 2) + Atomics.updateMax(lastStreamId, streamId); } @Override public void data(IStream stream, DataInfo dataInfo, long timeout, TimeUnit unit, Callback callback, C context) { logger.debug("Queuing {} on {}",dataInfo,stream); - DataFrameBytes frameBytes = new DataFrameBytes<>(stream, callback,context,dataInfo); + DataFrameBytes frameBytes = new DataFrameBytes<>(stream,callback,context,dataInfo); if (timeout > 0) - { frameBytes.task = scheduler.schedule(frameBytes,timeout,unit); - } append(frameBytes); flush(); } @@ -818,9 +901,11 @@ public class StandardSession implements ISession, Parser.Listener, Callback find a better solution - if (stream != null && !streams.containsValue(stream) && !stream.isUnidirectional()) + if (stream != null && stream.isReset()) + { frameBytes.fail(new StreamException(stream.getId(),StreamStatus.INVALID_STREAM)); + return; + } break; } @@ -843,34 +928,50 @@ public class StandardSession implements ISession, Parser.Listener, Callback 0) + failure = this.failure; + if (failure == null) { - FrameBytes element = queue.get(index - 1); - if (element.compareTo(frameBytes) >= 0) - break; - --index; + int index = queue.size(); + while (index > 0) + { + FrameBytes element = queue.get(index - 1); + if (element.compareTo(frameBytes) >= 0) + break; + --index; + } + queue.add(index,frameBytes); } - queue.add(index,frameBytes); } + + if (failure != null) + frameBytes.fail(new SPDYException(failure)); } private void prepend(FrameBytes frameBytes) { + Throwable failure; synchronized (queue) { - int index = 0; - while (index < queue.size()) + failure = this.failure; + if (failure == null) { - FrameBytes element = queue.get(index); - if (element.compareTo(frameBytes) <= 0) - break; - ++index; + int index = 0; + while (index < queue.size()) + { + FrameBytes element = queue.get(index); + if (element.compareTo(frameBytes) <= 0) + break; + ++index; + } + queue.add(index,frameBytes); } - queue.add(index,frameBytes); } + + if (failure != null) + frameBytes.fail(new SPDYException(failure)); } @Override @@ -885,9 +986,23 @@ public class StandardSession implements ISession, Parser.Listener, Callback frameBytesToFail = new ArrayList<>(); + frameBytesToFail.add(frameBytes); + + synchronized (queue) + { + failure = x; + String logMessage = String.format("Failed write of %s, failing all %d frame(s) in queue",frameBytes,queue.size()); + logger.debug(logMessage,x); + frameBytesToFail.addAll(queue); + queue.clear(); + flushing = false; + } + + for (FrameBytes fb : frameBytesToFail) + fb.fail(x); } protected void write(ByteBuffer buffer, Callback callback, FrameBytes frameBytes) @@ -895,14 +1010,14 @@ public class StandardSession implements ISession, Parser.Listener, Callback void complete(final Callback callback, final C context) { // Applications may send and queue up a lot of frames and - // if we call Handler.completed() only synchronously we risk + // if we call Callback.completed() only synchronously we risk // starvation (for the last frames sent) and stack overflow. // Therefore every some invocation, we dispatch to a new thread Integer invocations = handlerInvocations.get(); @@ -914,7 +1029,7 @@ public class StandardSession implements ISession, Parser.Listener, Callback void notifyHandlerCompleted(Callback callback, C context) + private void notifyCallbackCompleted(Callback callback, C context) { try { @@ -943,11 +1058,16 @@ public class StandardSession implements ISession, Parser.Listener, Callback void notifyHandlerFailed(Callback callback, C context, Throwable x) + private void notifyCallbackFailed(Callback callback, C context, Throwable x) { try { @@ -956,10 +1076,46 @@ public class StandardSession implements ISession, Parser.Listener, Callback { public IStream getStream(); @@ -994,8 +1150,16 @@ public class StandardSession implements ISession, Parser.Listener, Callback that.stream.priority => -1 (this.stream has less priority than that.stream) - return that.getStream().getPriority() - getStream().getPriority(); + // FrameBytes may have or not have a related stream (for example, PING do not have a related stream) + // FrameBytes without related streams have higher priority + IStream thisStream = getStream(); + IStream thatStream = that.getStream(); + if (thisStream == null) + return thatStream == null ? 0 : -1; + if (thatStream == null) + return 1; + // If this.stream.priority > that.stream.priority => this.stream has less priority than that.stream + return thatStream.getPriority() - thisStream.getPriority(); } @Override @@ -1009,7 +1173,8 @@ public class StandardSession implements ISession, Parser.Listener, Callback callback, C context, ControlFrame frame, ByteBuffer buffer) { - super(stream, callback,context); + super(stream,callback,context); this.frame = frame; this.buffer = buffer; } @@ -1058,6 +1223,9 @@ public class StandardSession implements ISession, Parser.Listener, Callback callback, C context, DataInfo dataInfo) + private DataFrameBytes(IStream stream, Callback handler, C context, DataInfo dataInfo) { - super(stream, callback,context); + super(stream,handler,context); this.dataInfo = dataInfo; } @@ -1108,14 +1276,14 @@ public class StandardSession implements ISession, Parser.Listener, Callback 0) { // We have written a frame out of this DataInfo, but there is more to write. // We need to keep the correct ordering of frames, to avoid that another // DataInfo for the same stream is written before this one is finished. prepend(this); + flush(); } else { 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 d835c9ee057..3db6a14a342 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 @@ -1,22 +1,18 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== package org.eclipse.jetty.spdy; -import java.nio.ByteBuffer; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -25,22 +21,17 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import org.eclipse.jetty.spdy.api.ByteBufferDataInfo; import org.eclipse.jetty.spdy.api.DataInfo; import org.eclipse.jetty.spdy.api.HeadersInfo; import org.eclipse.jetty.spdy.api.ReplyInfo; import org.eclipse.jetty.spdy.api.RstInfo; -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; import org.eclipse.jetty.spdy.api.SynInfo; import org.eclipse.jetty.spdy.frames.ControlFrame; -import org.eclipse.jetty.spdy.frames.DataFrame; import org.eclipse.jetty.spdy.frames.HeadersFrame; import org.eclipse.jetty.spdy.frames.SynReplyFrame; -import org.eclipse.jetty.spdy.frames.SynStreamFrame; -import org.eclipse.jetty.spdy.frames.WindowUpdateFrame; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -49,28 +40,29 @@ public class StandardStream implements IStream { private static final Logger logger = Log.getLogger(Stream.class); private final Map attributes = new ConcurrentHashMap<>(); - private final IStream associatedStream; - private final SynStreamFrame frame; + private final int id; + private final byte priority; private final ISession session; - private final AtomicInteger windowSize; + private final IStream associatedStream; + private final AtomicInteger windowSize = new AtomicInteger(); private final Set pushedStreams = Collections.newSetFromMap(new ConcurrentHashMap()); private volatile StreamFrameListener listener; private volatile OpenState openState = OpenState.SYN_SENT; private volatile CloseState closeState = CloseState.OPENED; private volatile boolean reset = false; - public StandardStream(SynStreamFrame frame, ISession session, int windowSize, IStream associatedStream) + public StandardStream(int id, byte priority, ISession session, IStream associatedStream) { - this.frame = frame; + this.id = id; + this.priority = priority; this.session = session; - this.windowSize = new AtomicInteger(windowSize); this.associatedStream = associatedStream; } @Override public int getId() { - return frame.getStreamId(); + return id; } @Override @@ -100,7 +92,7 @@ public class StandardStream implements IStream @Override public byte getPriority() { - return frame.getPriority(); + return priority; } @Override @@ -113,11 +105,11 @@ public class StandardStream implements IStream public void updateWindowSize(int delta) { int size = windowSize.addAndGet(delta); - logger.debug("Updated window size by {}, new window size {}",delta,size); + logger.debug("Updated window size {} -> {} for {}", size - delta, size, this); } @Override - public Session getSession() + public ISession getSession() { return session; } @@ -146,6 +138,11 @@ public class StandardStream implements IStream this.listener = listener; } + public StreamFrameListener getStreamFrameListener() + { + return listener; + } + @Override public void updateCloseState(boolean close, boolean local) { @@ -155,7 +152,7 @@ public class StandardStream implements IStream { case OPENED: { - closeState = local?CloseState.LOCALLY_CLOSED:CloseState.REMOTELY_CLOSED; + closeState = local ? CloseState.LOCALLY_CLOSED : CloseState.REMOTELY_CLOSED; break; } case LOCALLY_CLOSED: @@ -196,25 +193,19 @@ public class StandardStream implements IStream { openState = OpenState.REPLY_RECV; SynReplyFrame synReply = (SynReplyFrame)frame; - updateCloseState(synReply.isClose(),false); - ReplyInfo replyInfo = new ReplyInfo(synReply.getHeaders(),synReply.isClose()); + updateCloseState(synReply.isClose(), false); + ReplyInfo replyInfo = new ReplyInfo(synReply.getHeaders(), synReply.isClose()); notifyOnReply(replyInfo); break; } case HEADERS: { HeadersFrame headers = (HeadersFrame)frame; - updateCloseState(headers.isClose(),false); - HeadersInfo headersInfo = new HeadersInfo(headers.getHeaders(),headers.isClose(),headers.isResetCompression()); + updateCloseState(headers.isClose(), false); + HeadersInfo headersInfo = new HeadersInfo(headers.getHeaders(), headers.isClose(), headers.isResetCompression()); notifyOnHeaders(headersInfo); break; } - case WINDOW_UPDATE: - { - WindowUpdateFrame windowUpdate = (WindowUpdateFrame)frame; - updateWindowSize(windowUpdate.getWindowDelta()); - break; - } case RST_STREAM: { reset = true; @@ -229,57 +220,28 @@ public class StandardStream implements IStream } @Override - public void process(DataFrame frame, ByteBuffer data) + public void process(DataInfo dataInfo) { // TODO: in v3 we need to send a rst instead of just ignoring // ignore data frame if this stream is remotelyClosed already - if (isHalfClosed() && !isLocallyClosed()) + if (isRemotelyClosed()) { - logger.debug("Ignoring received dataFrame as this stream is remotely closed: " + frame); + logger.debug("Stream is remotely closed, ignoring {}", dataInfo); return; } if (!canReceive()) { - logger.debug("Can't receive. Sending rst: " + frame); - session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR)); + logger.debug("Protocol error receiving {}, resetting" + dataInfo); + session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR)); return; } - updateCloseState(frame.isClose(),false); - - ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(data,frame.isClose(),frame.isCompress()) - { - @Override - public void consume(int delta) - { - super.consume(delta); - - // This is the algorithm for flow control. - // This method may be called multiple times with delta=1, but we only send a window - // update when the whole dataInfo has been consumed. - // Other policies may be to send window updates when consumed() is greater than - // a certain threshold, etc. but for now the policy is not pluggable for simplicity. - // Note that the frequency of window updates depends on the read buffer, that - // should not be too smaller than the window size to avoid frequent window updates. - // Therefore, a pluggable policy should be able to modify the read buffer capacity. - if (consumed() == length() && !isClosed()) - windowUpdate(length()); - } - }; + updateCloseState(dataInfo.isClose(), false); notifyOnData(dataInfo); session.flush(); } - private void windowUpdate(int delta) - { - if (delta > 0) - { - WindowUpdateFrame windowUpdateFrame = new WindowUpdateFrame(session.getVersion(),getId(),delta); - session.control(this,windowUpdateFrame,0,TimeUnit.MILLISECONDS,null,null); - } - } - private void notifyOnReply(ReplyInfo replyInfo) { final StreamFrameListener listener = this.listener; @@ -287,13 +249,18 @@ public class StandardStream implements IStream { if (listener != null) { - logger.debug("Invoking reply callback with {} on listener {}",replyInfo,listener); - listener.onReply(this,replyInfo); + logger.debug("Invoking reply callback with {} on listener {}", replyInfo, listener); + listener.onReply(this, replyInfo); } } catch (Exception x) { - logger.info("Exception while notifying listener " + listener,x); + logger.info("Exception while notifying listener " + listener, x); + } + catch (Error x) + { + logger.info("Exception while notifying listener " + listener, x); + throw x; } } @@ -304,13 +271,18 @@ public class StandardStream implements IStream { if (listener != null) { - logger.debug("Invoking headers callback with {} on listener {}",frame,listener); - listener.onHeaders(this,headersInfo); + logger.debug("Invoking headers callback with {} on listener {}", headersInfo, listener); + listener.onHeaders(this, headersInfo); } } catch (Exception x) { - logger.info("Exception while notifying listener " + listener,x); + logger.info("Exception while notifying listener " + listener, x); + } + catch (Error x) + { + logger.info("Exception while notifying listener " + listener, x); + throw x; } } @@ -321,14 +293,19 @@ public class StandardStream implements IStream { if (listener != null) { - logger.debug("Invoking data callback with {} on listener {}",dataInfo,listener); - listener.onData(this,dataInfo); - logger.debug("Invoked data callback with {} on listener {}",dataInfo,listener); + logger.debug("Invoking data callback with {} on listener {}", dataInfo, listener); + listener.onData(this, dataInfo); + logger.debug("Invoked data callback with {} on listener {}", dataInfo, listener); } } catch (Exception x) { - logger.info("Exception while notifying listener " + listener,x); + logger.info("Exception while notifying listener " + listener, x); + } + catch (Error x) + { + logger.info("Exception while notifying listener " + listener, x); + throw x; } } @@ -366,9 +343,9 @@ public class StandardStream implements IStream if (isUnidirectional()) throw new IllegalStateException("Protocol violation: cannot send SYN_REPLY frames in unidirectional streams"); openState = OpenState.REPLY_SENT; - updateCloseState(replyInfo.isClose(),true); - SynReplyFrame frame = new SynReplyFrame(session.getVersion(),replyInfo.getFlags(),getId(),replyInfo.getHeaders()); - session.control(this,frame,timeout,unit, callback,null); + updateCloseState(replyInfo.isClose(), true); + SynReplyFrame frame = new SynReplyFrame(session.getVersion(), replyInfo.getFlags(), getId(), replyInfo.getHeaders()); + session.control(this, frame, timeout, unit, callback, null); } @Override @@ -384,18 +361,18 @@ public class StandardStream implements IStream { if (!canSend()) { - session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR)); + session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR)); throw new IllegalStateException("Protocol violation: cannot send a DATA frame before a SYN_REPLY frame"); } if (isLocallyClosed()) { - session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR)); + session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR)); throw new IllegalStateException("Protocol violation: cannot send a DATA frame on a closed stream"); } // Cannot update the close state here, because the data that we send may // be flow controlled, so we need the stream to update the window size. - session.data(this,dataInfo,timeout,unit, callback,null); + session.data(this, dataInfo, timeout, unit, callback, null); } @Override @@ -411,18 +388,18 @@ public class StandardStream implements IStream { if (!canSend()) { - session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR)); + session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR)); throw new IllegalStateException("Protocol violation: cannot send a HEADERS frame before a SYN_REPLY frame"); } if (isLocallyClosed()) { - session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR)); + session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR)); throw new IllegalStateException("Protocol violation: cannot send a HEADERS frame on a closed stream"); } - updateCloseState(headersInfo.isClose(),true); - HeadersFrame frame = new HeadersFrame(session.getVersion(),headersInfo.getFlags(),getId(),headersInfo.getHeaders()); - session.control(this,frame,timeout,unit, callback,null); + updateCloseState(headersInfo.isClose(), true); + HeadersFrame frame = new HeadersFrame(session.getVersion(), headersInfo.getFlags(), getId(), headersInfo.getHeaders()); + session.control(this, frame, timeout, unit, callback, null); } @Override @@ -456,10 +433,16 @@ public class StandardStream implements IStream return closeState == CloseState.LOCALLY_CLOSED || closeState == CloseState.CLOSED; } + private boolean isRemotelyClosed() + { + CloseState closeState = this.closeState; + return closeState == CloseState.REMOTELY_CLOSED || closeState == CloseState.CLOSED; + } + @Override public String toString() { - return String.format("stream=%d v%d %s",getId(),session.getVersion(),closeState); + return String.format("stream=%d v%d windowSize=%db reset=%s %s %s", getId(), session.getVersion(), getWindowSize(), isReset(), openState, closeState); } private boolean canSend() diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StreamException.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StreamException.java index 5b4bd7f4562..4a0a954918e 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StreamException.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StreamException.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ByteBufferDataInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ByteBufferDataInfo.java index 678ff516e36..3b856f66192 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ByteBufferDataInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ByteBufferDataInfo.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; @@ -68,4 +65,10 @@ public class ByteBufferDataInfo extends DataInfo } return space; } + + @Override + protected ByteBuffer allocate(int size) + { + return buffer.isDirect() ? ByteBuffer.allocateDirect(size) : super.allocate(size); + } } diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/BytesDataInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/BytesDataInfo.java index f2150846ca5..9d2123b066f 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/BytesDataInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/BytesDataInfo.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; @@ -23,39 +20,44 @@ import java.nio.ByteBuffer; */ public class BytesDataInfo extends DataInfo { - private byte[] bytes; - private int offset; + private final byte[] bytes; + private final int offset; + private final int length; + private int index; public BytesDataInfo(byte[] bytes, boolean close) { - this(bytes, close, false); + this(bytes, 0, bytes.length, close); } - public BytesDataInfo(byte[] bytes, boolean close, boolean compress) + public BytesDataInfo(byte[] bytes, int offset, int length, boolean close) { - super(close, compress); + super(close, false); this.bytes = bytes; + this.offset = offset; + this.length = length; + this.index = offset; } @Override public int length() { - return bytes.length; + return length; } @Override public int available() { - return length() - offset; + return length - index + offset; } @Override public int readInto(ByteBuffer output) { int space = output.remaining(); - int length = Math.min(available(), space); - output.put(bytes, offset, length); - offset += length; - return length; + int chunk = Math.min(available(), space); + output.put(bytes, index, chunk); + index += chunk; + return chunk; } } diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/DataInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/DataInfo.java index 40364017f57..8253e40fc86 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/DataInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/DataInfo.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/GoAwayInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/GoAwayInfo.java index 4ebc7262fc6..1e16e345348 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/GoAwayInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/GoAwayInfo.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Headers.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Headers.java index 31615006042..261a94f730d 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Headers.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Headers.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; @@ -219,13 +216,15 @@ public class Headers implements Iterable if (obj == null || getClass() != obj.getClass()) return false; Header that = (Header)obj; - return name.equals(that.name) && Arrays.equals(values, that.values); + // Header names must be lowercase, thus we lowercase them before transmission, but keep them as is + // internally. That's why we've to compare them case insensitive. + return name.equalsIgnoreCase(that.name) && Arrays.equals(values, that.values); } @Override public int hashCode() { - int result = name.hashCode(); + int result = name.toLowerCase().hashCode(); result = 31 * result + Arrays.hashCode(values); return result; } @@ -268,6 +267,21 @@ public class Headers implements Iterable return values; } + /** + * @return the values as a comma separated list + */ + public String valuesAsString() + { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < values.length; ++i) + { + if (i > 0) + result.append(", "); + result.append(values[i]); + } + return result.toString(); + } + /** * @return whether the header has multiple values */ diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/HeadersInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/HeadersInfo.java index 62ae07f6091..97a6a7e59cb 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/HeadersInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/HeadersInfo.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PingInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PingInfo.java index bbcd9a87d08..28bc7a7c993 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PingInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PingInfo.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ReplyInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ReplyInfo.java index 9ce0ef6eb5d..9e511436e61 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ReplyInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ReplyInfo.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/RstInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/RstInfo.java index f1f5ee482a0..101fd713967 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/RstInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/RstInfo.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SPDY.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SPDY.java index 8ec2d8d6687..31d7620ad55 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SPDY.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SPDY.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SPDYException.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SPDYException.java index a0585f7ad73..af21428e877 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SPDYException.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SPDYException.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; 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 b16c55306cf..39930905f20 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 @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; @@ -80,14 +77,14 @@ public interface Session /** *

    Sends asynchronously a SYN_FRAME to create a new {@link Stream SPDY stream}.

    - *

    Callers may pass a non-null completion handler to be notified of when the + *

    Callers may pass a non-null completion callback to be notified of when the * stream has been created and use the stream, for example, to send data frames.

    * * @param synInfo the metadata to send on stream creation * @param listener the listener to invoke when events happen on the stream just created * @param timeout the operation's timeout * @param unit the timeout's unit - * @param callback the completion handler that gets notified of stream creation + * @param callback the completion callback that gets notified of stream creation * @see #syn(SynInfo, StreamFrameListener) */ public void syn(SynInfo synInfo, StreamFrameListener listener, long timeout, TimeUnit unit, Callback callback); @@ -105,13 +102,13 @@ public interface Session /** *

    Sends asynchronously a RST_STREAM to abort a stream.

    - *

    Callers may pass a non-null completion handler to be notified of when the + *

    Callers may pass a non-null completion callback to be notified of when the * reset has been actually sent.

    * * @param rstInfo the metadata to reset the stream * @param timeout the operation's timeout * @param unit the timeout's unit - * @param callback the completion handler that gets notified of reset's send + * @param callback the completion callback that gets notified of reset's send * @see #rst(RstInfo) */ public void rst(RstInfo rstInfo, long timeout, TimeUnit unit, Callback callback); @@ -128,13 +125,13 @@ public interface Session /** *

    Sends asynchronously a SETTINGS to configure the SPDY connection.

    - *

    Callers may pass a non-null completion handler to be notified of when the + *

    Callers may pass a non-null completion callback to be notified of when the * settings has been actually sent.

    * * @param settingsInfo the metadata to send * @param timeout the operation's timeout * @param unit the timeout's unit - * @param callback the completion handler that gets notified of settings' send + * @param callback the completion callback that gets notified of settings' send * @see #settings(SettingsInfo) */ public void settings(SettingsInfo settingsInfo, long timeout, TimeUnit unit, Callback callback); @@ -150,12 +147,12 @@ public interface Session /** *

    Sends asynchronously a PING, normally to measure round-trip time.

    - *

    Callers may pass a non-null completion handler to be notified of when the + *

    Callers may pass a non-null completion callback to be notified of when the * ping has been actually sent.

    * * @param timeout the operation's timeout * @param unit the timeout's unit - * @param callback the completion handler that gets notified of ping's send + * @param callback the completion callback that gets notified of ping's send * @see #ping() */ public void ping(long timeout, TimeUnit unit, Callback callback); @@ -171,21 +168,51 @@ public interface Session /** *

    Closes gracefully this session, sending a GO_AWAY frame and then closing the TCP connection.

    - *

    Callers may pass a non-null completion handler to be notified of when the + *

    Callers may pass a non-null completion callback to be notified of when the * go away has been actually sent.

    * * @param timeout the operation's timeout * @param unit the timeout's unit - * @param callback the completion handler that gets notified of go away's send + * @param callback the completion callback that gets notified of go away's send * @see #goAway() */ public void goAway(long timeout, TimeUnit unit, Callback callback); /** - * @return the streams currently active in this session + * @return a snapshot of the streams currently active in this session + * @see #getStream(int) */ public Set getStreams(); + /** + * @param streamId the id of the stream to retrieve + * @return the stream with the given stream id + * @see #getStreams() + */ + public Stream getStream(int streamId); + + /** + * @param key the attribute key + * @return an arbitrary object associated with the given key to this session + * @see #setAttribute(String, Object) + */ + public Object getAttribute(String key); + + /** + * @param key the attribute key + * @param value an arbitrary object to associate with the given key to this session + * @see #getAttribute(String) + * @see #removeAttribute(String) + */ + public void setAttribute(String key, Object value); + + /** + * @param key the attribute key + * @return the arbitrary object associated with the given key to this session + * @see #setAttribute(String, Object) + */ + public Object removeAttribute(String key); + /** *

    Super interface for listeners with callbacks that are invoked on specific session events.

    */ 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 467919c29cf..7e507e3fd84 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 @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SessionStatus.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SessionStatus.java index c9c40d137f5..56e9355ef15 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SessionStatus.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SessionStatus.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Settings.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Settings.java index 6db03e814c8..6ed59a00dbb 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Settings.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Settings.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SettingsInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SettingsInfo.java index 3823382d3d3..d622189797c 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SettingsInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SettingsInfo.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; 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 84d7ab21773..9d9d8ffb877 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 @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; @@ -100,13 +97,13 @@ public interface Stream /** *

    Initiate a unidirectional spdy pushstream associated to this stream asynchronously

    - *

    Callers may pass a non-null completion handler to be notified of when the + *

    Callers may pass a non-null completion callback to be notified of when the * pushstream has been established.

    * * @param synInfo the metadata to send on stream creation * @param timeout the operation's timeout * @param unit the timeout's unit - * @param callback the completion handler that gets notified once the pushstream is established + * @param callback the completion callback that gets notified once the pushstream is established * @see #syn(SynInfo) */ public void syn(SynInfo synInfo, long timeout, TimeUnit unit, Callback callback); @@ -124,13 +121,13 @@ public interface Stream /** *

    Sends asynchronously a SYN_REPLY frame in response to a SYN_STREAM frame.

    - *

    Callers may pass a non-null completion handler to be notified of when the + *

    Callers may pass a non-null completion callback to be notified of when the * reply has been actually sent.

    * * @param replyInfo the metadata to send * @param timeout the operation's timeout * @param unit the timeout's unit - * @param callback the completion handler that gets notified of reply sent + * @param callback the completion callback that gets notified of reply sent * @see #reply(ReplyInfo) */ public void reply(ReplyInfo replyInfo, long timeout, TimeUnit unit, Callback callback); @@ -150,13 +147,13 @@ public interface Stream /** *

    Sends asynchronously a DATA frame on this stream.

    *

    DATA frames should always be sent after a SYN_REPLY frame.

    - *

    Callers may pass a non-null completion handler to be notified of when the + *

    Callers may pass a non-null completion callback to be notified of when the * data has been actually sent.

    * * @param dataInfo the metadata to send * @param timeout the operation's timeout * @param unit the timeout's unit - * @param callback the completion handler that gets notified of data sent + * @param callback the completion callback that gets notified of data sent * @see #data(DataInfo) */ public void data(DataInfo dataInfo, long timeout, TimeUnit unit, Callback callback); @@ -168,7 +165,7 @@ public interface Stream * * @param headersInfo the metadata to send * @return a future to wait for the headers to be sent - * @see #headers(HeadersInfo, long, TimeUnit, Callback) + * @see #headers(HeadersInfo, long, TimeUnit, Callback * @see #reply(ReplyInfo) */ public Future headers(HeadersInfo headersInfo); @@ -176,13 +173,13 @@ public interface Stream /** *

    Sends asynchronously a HEADER frame on this stream.

    *

    HEADERS frames should always be sent after a SYN_REPLY frame.

    - *

    Callers may pass a non-null completion handler to be notified of when the + *

    Callers may pass a non-null completion callback to be notified of when the * headers have been actually sent.

    * * @param headersInfo the metadata to send * @param timeout the operation's timeout * @param unit the timeout's unit - * @param callback the completion handler that gets notified of headers sent + * @param callback the completion callback that gets notified of headers sent * @see #headers(HeadersInfo) */ public void headers(HeadersInfo headersInfo, long timeout, TimeUnit unit, Callback callback); 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 a279f38a01d..23b5b169439 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 @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StreamStatus.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StreamStatus.java index c9908c8a146..b3e48e0b489 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StreamStatus.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StreamStatus.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StringDataInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StringDataInfo.java index 41e9e62330a..164c6fb4302 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StringDataInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StringDataInfo.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; @@ -25,11 +22,6 @@ public class StringDataInfo extends BytesDataInfo { public StringDataInfo(String string, boolean close) { - this(string, close, false); - } - - public StringDataInfo(String string, boolean close, boolean compress) - { - super(string.getBytes(Charset.forName("UTF-8")), close, compress); + super(string.getBytes(Charset.forName("UTF-8")), close); } } diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SynInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SynInfo.java index c51a0016dd0..3f8eb605df0 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SynInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SynInfo.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/server/ServerSessionFrameListener.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/server/ServerSessionFrameListener.java index 79415505698..7b330a9ceae 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/server/ServerSessionFrameListener.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/server/ServerSessionFrameListener.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api.server; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/ControlFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/ControlFrame.java index afd554c67e8..a9c7b0a5af6 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/ControlFrame.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/ControlFrame.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/ControlFrameType.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/ControlFrameType.java index bf638d3bf56..1fb28346d6d 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/ControlFrameType.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/ControlFrameType.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; @@ -29,7 +26,8 @@ public enum ControlFrameType PING((short)6), GO_AWAY((short)7), HEADERS((short)8), - WINDOW_UPDATE((short)9); + WINDOW_UPDATE((short)9), + CREDENTIAL((short)10); public static ControlFrameType from(short code) { diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/CredentialFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/CredentialFrame.java new file mode 100644 index 00000000000..5bd882fb967 --- /dev/null +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/CredentialFrame.java @@ -0,0 +1,46 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; + +import java.security.cert.Certificate; + +public class CredentialFrame extends ControlFrame +{ + private final short slot; + private final byte[] proof; + private final Certificate[] certificateChain; + + public CredentialFrame(short version, short slot, byte[] proof, Certificate[] certificateChain) + { + super(version, ControlFrameType.CREDENTIAL, (byte)0); + this.slot = slot; + this.proof = proof; + this.certificateChain = certificateChain; + } + + public short getSlot() + { + return slot; + } + + public byte[] getProof() + { + return proof; + } + + public Certificate[] getCertificateChain() + { + return certificateChain; + } +} diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/DataFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/DataFrame.java index 3ce17eeb9f7..e261bf8f5ee 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/DataFrame.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/DataFrame.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/GoAwayFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/GoAwayFrame.java index bda5755522f..15f04f1a4dc 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/GoAwayFrame.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/GoAwayFrame.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/HeadersFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/HeadersFrame.java index e0545b68703..ea88513075e 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/HeadersFrame.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/HeadersFrame.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/NoOpFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/NoOpFrame.java index bf9eac00f3f..e4cffcc3f77 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/NoOpFrame.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/NoOpFrame.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/PingFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/PingFrame.java index aa1ac543d27..3989a09ccc0 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/PingFrame.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/PingFrame.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/RstStreamFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/RstStreamFrame.java index 334b8166ac6..0877da45d52 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/RstStreamFrame.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/RstStreamFrame.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SettingsFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SettingsFrame.java index afddb371fa7..8b676cde732 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SettingsFrame.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SettingsFrame.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynReplyFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynReplyFrame.java index 91081686382..82888b14bf6 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynReplyFrame.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynReplyFrame.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynStreamFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynStreamFrame.java index 1b4089541c3..c5aa8d3bc5b 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynStreamFrame.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynStreamFrame.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; @@ -25,14 +22,16 @@ public class SynStreamFrame extends ControlFrame private final int streamId; private final int associatedStreamId; private final byte priority; + private final short slot; private final Headers headers; - public SynStreamFrame(short version, byte flags, int streamId, int associatedStreamId, byte priority, Headers headers) + public SynStreamFrame(short version, byte flags, int streamId, int associatedStreamId, byte priority, short slot, Headers headers) { super(version, ControlFrameType.SYN_STREAM, flags); this.streamId = streamId; this.associatedStreamId = associatedStreamId; this.priority = priority; + this.slot = slot; this.headers = headers; } @@ -51,6 +50,11 @@ public class SynStreamFrame extends ControlFrame return priority; } + public short getSlot() + { + return slot; + } + public Headers getHeaders() { return headers; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/WindowUpdateFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/WindowUpdateFrame.java index 22a4129e769..8ed37d4784c 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/WindowUpdateFrame.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/WindowUpdateFrame.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/ControlFrameGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/ControlFrameGenerator.java index 51d98c7711a..d10614b79c2 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/ControlFrameGenerator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/ControlFrameGenerator.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/CredentialGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/CredentialGenerator.java new file mode 100644 index 00000000000..df512229b59 --- /dev/null +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/CredentialGenerator.java @@ -0,0 +1,82 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; + +import java.nio.ByteBuffer; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.spdy.SessionException; +import org.eclipse.jetty.spdy.api.SessionStatus; +import org.eclipse.jetty.spdy.frames.ControlFrame; +import org.eclipse.jetty.spdy.frames.CredentialFrame; +import org.eclipse.jetty.util.BufferUtil; + +public class CredentialGenerator extends ControlFrameGenerator +{ + public CredentialGenerator(ByteBufferPool bufferPool) + { + super(bufferPool); + } + + @Override + public ByteBuffer generate(ControlFrame frame) + { + CredentialFrame credential = (CredentialFrame)frame; + + byte[] proof = credential.getProof(); + + List certificates = serializeCertificates(credential.getCertificateChain()); + int certificatesLength = 0; + for (byte[] certificate : certificates) + certificatesLength += certificate.length; + + int frameBodyLength = 2 + 4 + proof.length + certificates.size() * 4 + certificatesLength; + + int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength; + ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true); + BufferUtil.clearToFill(buffer); + generateControlFrameHeader(credential, frameBodyLength, buffer); + + buffer.putShort(credential.getSlot()); + buffer.putInt(proof.length); + buffer.put(proof); + for (byte[] certificate : certificates) + { + buffer.putInt(certificate.length); + buffer.put(certificate); + } + + buffer.flip(); + return buffer; + } + + private List serializeCertificates(Certificate[] certificates) + { + try + { + List result = new ArrayList<>(certificates.length); + for (Certificate certificate : certificates) + result.add(certificate.getEncoded()); + return result; + } + catch (CertificateEncodingException x) + { + throw new SessionException(SessionStatus.PROTOCOL_ERROR, x); + } + } +} diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/DataFrameGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/DataFrameGenerator.java index 6a1acfeee36..2db81a94283 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/DataFrameGenerator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/DataFrameGenerator.java @@ -1,19 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; import java.nio.ByteBuffer; @@ -21,6 +17,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.spdy.api.DataInfo; import org.eclipse.jetty.spdy.frames.DataFrame; +import org.eclipse.jetty.util.BufferUtil; public class DataFrameGenerator { @@ -34,6 +31,8 @@ public class DataFrameGenerator public ByteBuffer generate(int streamId, int length, DataInfo dataInfo) { ByteBuffer buffer = bufferPool.acquire(DataFrame.HEADER_LENGTH + length, true); + BufferUtil.clearToFill(buffer); + buffer.limit(length + DataFrame.HEADER_LENGTH); //TODO: thomas show Simone :) buffer.position(DataFrame.HEADER_LENGTH); // Guaranteed to always be >= 0 int read = dataInfo.readInto(buffer); diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/Generator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/Generator.java index 91cd1270b13..aa382face04 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/Generator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/Generator.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; @@ -42,6 +39,7 @@ public class Generator generators.put(ControlFrameType.GO_AWAY, new GoAwayGenerator(bufferPool)); generators.put(ControlFrameType.HEADERS, new HeadersGenerator(bufferPool, headersBlockGenerator)); generators.put(ControlFrameType.WINDOW_UPDATE, new WindowUpdateGenerator(bufferPool)); + generators.put(ControlFrameType.CREDENTIAL, new CredentialGenerator(bufferPool)); dataFrameGenerator = new DataFrameGenerator(bufferPool); } diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/GoAwayGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/GoAwayGenerator.java index f22b6b3772a..fe5a9b0c2eb 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/GoAwayGenerator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/GoAwayGenerator.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; @@ -22,6 +19,7 @@ import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.spdy.api.SPDY; import org.eclipse.jetty.spdy.frames.ControlFrame; import org.eclipse.jetty.spdy.frames.GoAwayFrame; +import org.eclipse.jetty.util.BufferUtil; public class GoAwayGenerator extends ControlFrameGenerator { @@ -38,6 +36,7 @@ public class GoAwayGenerator extends ControlFrameGenerator int frameBodyLength = 8; int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength; ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true); + BufferUtil.clearToFill(buffer); generateControlFrameHeader(goAway, frameBodyLength, buffer); buffer.putInt(goAway.getLastStreamId() & 0x7F_FF_FF_FF); diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java index 93a2dd6210b..6290841b794 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; @@ -43,7 +40,7 @@ public class HeadersBlockGenerator writeCount(version, buffer, headers.size()); for (Headers.Header header : headers) { - String name = header.name(); + String name = header.name().toLowerCase(); byte[] nameBytes = name.getBytes(iso1); writeNameLength(version, buffer, nameBytes.length); buffer.write(nameBytes, 0, nameBytes.length); diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersGenerator.java index dfe43b4f6ca..ec259299e23 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersGenerator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersGenerator.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; @@ -20,9 +17,11 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.spdy.SessionException; +import org.eclipse.jetty.spdy.api.SPDY; import org.eclipse.jetty.spdy.api.SessionStatus; import org.eclipse.jetty.spdy.frames.ControlFrame; import org.eclipse.jetty.spdy.frames.HeadersFrame; +import org.eclipse.jetty.util.BufferUtil; public class HeadersGenerator extends ControlFrameGenerator { @@ -43,6 +42,8 @@ public class HeadersGenerator extends ControlFrameGenerator ByteBuffer headersBuffer = headersBlockGenerator.generate(version, headers.getHeaders()); int frameBodyLength = 4; + if (frame.getVersion() == SPDY.V2) + frameBodyLength += 2; int frameLength = frameBodyLength + headersBuffer.remaining(); if (frameLength > 0xFF_FF_FF) @@ -55,9 +56,12 @@ public class HeadersGenerator extends ControlFrameGenerator int totalLength = ControlFrame.HEADER_LENGTH + frameLength; ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true); + BufferUtil.clearToFill(buffer); generateControlFrameHeader(headers, frameLength, buffer); buffer.putInt(headers.getStreamId() & 0x7F_FF_FF_FF); + if (frame.getVersion() == SPDY.V2) + buffer.putShort((short)0); buffer.put(headersBuffer); diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/NoOpGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/NoOpGenerator.java index d543e9cc59d..281a3d18bef 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/NoOpGenerator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/NoOpGenerator.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; @@ -21,6 +18,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.spdy.frames.ControlFrame; import org.eclipse.jetty.spdy.frames.NoOpFrame; +import org.eclipse.jetty.util.BufferUtil; public class NoOpGenerator extends ControlFrameGenerator { @@ -37,6 +35,7 @@ public class NoOpGenerator extends ControlFrameGenerator int frameBodyLength = 0; int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength; ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true); + BufferUtil.clearToFill(buffer); generateControlFrameHeader(noOp, frameBodyLength, buffer); buffer.flip(); diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/PingGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/PingGenerator.java index 3a431eec011..73ef7c2be27 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/PingGenerator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/PingGenerator.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; @@ -21,6 +18,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.spdy.frames.ControlFrame; import org.eclipse.jetty.spdy.frames.PingFrame; +import org.eclipse.jetty.util.BufferUtil; public class PingGenerator extends ControlFrameGenerator { @@ -37,6 +35,7 @@ public class PingGenerator extends ControlFrameGenerator int frameBodyLength = 4; int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength; ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true); + BufferUtil.clearToFill(buffer); generateControlFrameHeader(ping, frameBodyLength, buffer); buffer.putInt(ping.getPingId()); diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/RstStreamGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/RstStreamGenerator.java index 5805bf9016b..ce7563001ef 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/RstStreamGenerator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/RstStreamGenerator.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; @@ -21,6 +18,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.spdy.frames.ControlFrame; import org.eclipse.jetty.spdy.frames.RstStreamFrame; +import org.eclipse.jetty.util.BufferUtil; public class RstStreamGenerator extends ControlFrameGenerator { @@ -37,6 +35,7 @@ public class RstStreamGenerator extends ControlFrameGenerator int frameBodyLength = 8; int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength; ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true); + BufferUtil.clearToFill(buffer); generateControlFrameHeader(rstStream, frameBodyLength, buffer); buffer.putInt(rstStream.getStreamId() & 0x7F_FF_FF_FF); diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SettingsGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SettingsGenerator.java index 9f53d45f5f8..18f036931ec 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SettingsGenerator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SettingsGenerator.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; @@ -23,6 +20,7 @@ import org.eclipse.jetty.spdy.api.SPDY; import org.eclipse.jetty.spdy.api.Settings; import org.eclipse.jetty.spdy.frames.ControlFrame; import org.eclipse.jetty.spdy.frames.SettingsFrame; +import org.eclipse.jetty.util.BufferUtil; public class SettingsGenerator extends ControlFrameGenerator { @@ -41,6 +39,7 @@ public class SettingsGenerator extends ControlFrameGenerator int frameBodyLength = 4 + 8 * size; int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength; ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true); + BufferUtil.clearToFill(buffer); generateControlFrameHeader(settingsFrame, frameBodyLength, buffer); buffer.putInt(size); diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynReplyGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynReplyGenerator.java index a8e656ebd7b..e559c677efb 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynReplyGenerator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynReplyGenerator.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; @@ -24,6 +21,7 @@ import org.eclipse.jetty.spdy.api.SPDY; import org.eclipse.jetty.spdy.api.SessionStatus; import org.eclipse.jetty.spdy.frames.ControlFrame; import org.eclipse.jetty.spdy.frames.SynReplyFrame; +import org.eclipse.jetty.util.BufferUtil; public class SynReplyGenerator extends ControlFrameGenerator { @@ -56,6 +54,7 @@ public class SynReplyGenerator extends ControlFrameGenerator int totalLength = ControlFrame.HEADER_LENGTH + frameLength; ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true); + BufferUtil.clearToFill(buffer); generateControlFrameHeader(synReply, frameLength, buffer); buffer.putInt(synReply.getStreamId() & 0x7F_FF_FF_FF); diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynStreamGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynStreamGenerator.java index 4ba21ff0c5e..5cc615b57c0 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynStreamGenerator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynStreamGenerator.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; @@ -26,6 +23,7 @@ import org.eclipse.jetty.spdy.api.SessionStatus; import org.eclipse.jetty.spdy.api.StreamStatus; import org.eclipse.jetty.spdy.frames.ControlFrame; import org.eclipse.jetty.spdy.frames.SynStreamFrame; +import org.eclipse.jetty.util.BufferUtil; public class SynStreamGenerator extends ControlFrameGenerator { @@ -58,12 +56,14 @@ public class SynStreamGenerator extends ControlFrameGenerator int totalLength = ControlFrame.HEADER_LENGTH + frameLength; ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true); + BufferUtil.clearToFill(buffer); generateControlFrameHeader(synStream, frameLength, buffer); int streamId = synStream.getStreamId(); buffer.putInt(streamId & 0x7F_FF_FF_FF); buffer.putInt(synStream.getAssociatedStreamId() & 0x7F_FF_FF_FF); writePriority(streamId, version, synStream.getPriority(), buffer); + buffer.put((byte)synStream.getSlot()); buffer.put(headersBuffer); @@ -85,6 +85,5 @@ public class SynStreamGenerator extends ControlFrameGenerator throw new StreamException(streamId, StreamStatus.UNSUPPORTED_VERSION); } buffer.put(priority); - buffer.put((byte)0); } } diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/WindowUpdateGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/WindowUpdateGenerator.java index a53b98eafa5..6c6e3a97c4f 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/WindowUpdateGenerator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/WindowUpdateGenerator.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.generator; @@ -21,6 +18,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.spdy.frames.ControlFrame; import org.eclipse.jetty.spdy.frames.WindowUpdateFrame; +import org.eclipse.jetty.util.BufferUtil; public class WindowUpdateGenerator extends ControlFrameGenerator { @@ -37,6 +35,7 @@ public class WindowUpdateGenerator extends ControlFrameGenerator int frameBodyLength = 8; int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength; ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true); + BufferUtil.clearToFill(buffer); generateControlFrameHeader(windowUpdate, frameBodyLength, buffer); buffer.putInt(windowUpdate.getStreamId() & 0x7F_FF_FF_FF); diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameBodyParser.java index a9499a8ad41..f820b599c87 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameBodyParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameBodyParser.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; 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 af8b78f6548..73c01898664 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 @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -46,6 +43,7 @@ public abstract class ControlFrameParser parsers.put(ControlFrameType.GO_AWAY, new GoAwayBodyParser(this)); parsers.put(ControlFrameType.HEADERS, new HeadersBodyParser(decompressor, this)); parsers.put(ControlFrameType.WINDOW_UPDATE, new WindowUpdateBodyParser(this)); + parsers.put(ControlFrameType.CREDENTIAL, new CredentialBodyParser(this)); } public short getVersion() diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/CredentialBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/CredentialBodyParser.java new file mode 100644 index 00000000000..0dbb3d6b8ea --- /dev/null +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/CredentialBodyParser.java @@ -0,0 +1,269 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.ByteArrayInputStream; +import java.nio.ByteBuffer; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jetty.spdy.SessionException; +import org.eclipse.jetty.spdy.api.SessionStatus; +import org.eclipse.jetty.spdy.frames.ControlFrameType; +import org.eclipse.jetty.spdy.frames.CredentialFrame; + +public class CredentialBodyParser extends ControlFrameBodyParser +{ + private final List certificates = new ArrayList<>(); + private final ControlFrameParser controlFrameParser; + private State state = State.SLOT; + private int totalLength; + private int cursor; + private short slot; + private int proofLength; + private byte[] proof; + private int certificateLength; + private byte[] certificate; + + public CredentialBodyParser(ControlFrameParser controlFrameParser) + { + this.controlFrameParser = controlFrameParser; + } + + @Override + public boolean parse(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + { + switch (state) + { + case SLOT: + { + if (buffer.remaining() >= 2) + { + slot = buffer.getShort(); + checkSlotValid(); + state = State.PROOF_LENGTH; + } + else + { + state = State.SLOT_BYTES; + cursor = 2; + } + break; + } + case SLOT_BYTES: + { + byte currByte = buffer.get(); + --cursor; + slot += (currByte & 0xFF) << 8 * cursor; + if (cursor == 0) + { + checkSlotValid(); + state = State.PROOF_LENGTH; + } + break; + } + case PROOF_LENGTH: + { + if (buffer.remaining() >= 4) + { + proofLength = buffer.getInt() & 0x7F_FF_FF_FF; + state = State.PROOF; + } + else + { + state = State.PROOF_LENGTH_BYTES; + cursor = 4; + } + break; + } + case PROOF_LENGTH_BYTES: + { + byte currByte = buffer.get(); + --cursor; + proofLength += (currByte & 0xFF) << 8 * cursor; + if (cursor == 0) + { + proofLength &= 0x7F_FF_FF_FF; + state = State.PROOF; + } + break; + } + case PROOF: + { + totalLength = controlFrameParser.getLength() - 2 - 4 - proofLength; + proof = new byte[proofLength]; + if (buffer.remaining() >= proofLength) + { + buffer.get(proof); + state = State.CERTIFICATE_LENGTH; + if (totalLength == 0) + { + onCredential(); + return true; + } + } + else + { + state = State.PROOF_BYTES; + cursor = proofLength; + } + break; + } + case PROOF_BYTES: + { + proof[proofLength - cursor] = buffer.get(); + --cursor; + if (cursor == 0) + { + state = State.CERTIFICATE_LENGTH; + if (totalLength == 0) + { + onCredential(); + return true; + } + } + break; + } + case CERTIFICATE_LENGTH: + { + if (buffer.remaining() >= 4) + { + certificateLength = buffer.getInt() & 0x7F_FF_FF_FF; + state = State.CERTIFICATE; + } + else + { + state = State.CERTIFICATE_LENGTH_BYTES; + cursor = 4; + } + break; + } + case CERTIFICATE_LENGTH_BYTES: + { + byte currByte = buffer.get(); + --cursor; + certificateLength += (currByte & 0xFF) << 8 * cursor; + if (cursor == 0) + { + certificateLength &= 0x7F_FF_FF_FF; + state = State.CERTIFICATE; + } + break; + } + case CERTIFICATE: + { + totalLength -= 4 + certificateLength; + certificate = new byte[certificateLength]; + if (buffer.remaining() >= certificateLength) + { + buffer.get(certificate); + if (onCertificate()) + return true; + } + else + { + state = State.CERTIFICATE_BYTES; + cursor = certificateLength; + } + break; + } + case CERTIFICATE_BYTES: + { + certificate[certificateLength - cursor] = buffer.get(); + --cursor; + if (cursor == 0) + { + if (onCertificate()) + return true; + } + break; + } + default: + { + throw new IllegalStateException(); + } + } + } + return false; + } + + private void checkSlotValid() + { + if (slot <= 0) + throw new SessionException(SessionStatus.PROTOCOL_ERROR, + "Invalid slot " + slot + " for " + ControlFrameType.CREDENTIAL + " frame"); + } + + private boolean onCertificate() + { + certificates.add(deserializeCertificate(certificate)); + if (totalLength == 0) + { + onCredential(); + return true; + } + else + { + certificateLength = 0; + state = State.CERTIFICATE_LENGTH; + } + return false; + } + + private Certificate deserializeCertificate(byte[] bytes) + { + try + { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + return certificateFactory.generateCertificate(new ByteArrayInputStream(bytes)); + } + catch (CertificateException x) + { + throw new SessionException(SessionStatus.PROTOCOL_ERROR, x); + } + } + + private void onCredential() + { + CredentialFrame frame = new CredentialFrame(controlFrameParser.getVersion(), slot, + Arrays.copyOf(proof, proof.length), certificates.toArray(new Certificate[certificates.size()])); + controlFrameParser.onControlFrame(frame); + reset(); + } + + private void reset() + { + state = State.SLOT; + totalLength = 0; + cursor = 0; + slot = 0; + proofLength = 0; + proof = null; + certificateLength = 0; + certificate = null; + certificates.clear(); + } + + public enum State + { + SLOT, SLOT_BYTES, PROOF_LENGTH, PROOF_LENGTH_BYTES, PROOF, PROOF_BYTES, + CERTIFICATE_LENGTH, CERTIFICATE_LENGTH_BYTES, CERTIFICATE, CERTIFICATE_BYTES + } +} diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/DataFrameParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/DataFrameParser.java index 74ea686837f..1926ab7352e 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/DataFrameParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/DataFrameParser.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/GoAwayBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/GoAwayBodyParser.java index 753ad804d4d..cb9fd37d93a 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/GoAwayBodyParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/GoAwayBodyParser.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -24,7 +21,7 @@ import org.eclipse.jetty.spdy.frames.GoAwayFrame; public class GoAwayBodyParser extends ControlFrameBodyParser { private final ControlFrameParser controlFrameParser; - private State state = State.LAST_STREAM_ID; + private State state = State.LAST_GOOD_STREAM_ID; private int cursor; private int lastStreamId; private int statusCode; @@ -41,7 +38,7 @@ public class GoAwayBodyParser extends ControlFrameBodyParser { switch (state) { - case LAST_STREAM_ID: + case LAST_GOOD_STREAM_ID: { if (buffer.remaining() >= 4) { @@ -66,12 +63,12 @@ public class GoAwayBodyParser extends ControlFrameBodyParser } else { - state = State.LAST_STREAM_ID_BYTES; + state = State.LAST_GOOD_STREAM_ID_BYTES; cursor = 4; } break; } - case LAST_STREAM_ID_BYTES: + case LAST_GOOD_STREAM_ID_BYTES: { byte currByte = buffer.get(); --cursor; @@ -144,7 +141,7 @@ public class GoAwayBodyParser extends ControlFrameBodyParser private void reset() { - state = State.LAST_STREAM_ID; + state = State.LAST_GOOD_STREAM_ID; cursor = 0; lastStreamId = 0; statusCode = 0; @@ -152,6 +149,6 @@ public class GoAwayBodyParser extends ControlFrameBodyParser private enum State { - LAST_STREAM_ID, LAST_STREAM_ID_BYTES, STATUS_CODE, STATUS_CODE_BYTES + LAST_GOOD_STREAM_ID, LAST_GOOD_STREAM_ID_BYTES, STATUS_CODE, STATUS_CODE_BYTES } } diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java index 2b4b70bafb8..60725f821e3 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBodyParser.java index 98bbc7ca258..a91a19c5c39 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBodyParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBodyParser.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -21,6 +18,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.spdy.CompressionFactory; import org.eclipse.jetty.spdy.api.Headers; import org.eclipse.jetty.spdy.api.HeadersInfo; +import org.eclipse.jetty.spdy.api.SPDY; import org.eclipse.jetty.spdy.frames.ControlFrameType; import org.eclipse.jetty.spdy.frames.HeadersFrame; @@ -51,7 +49,7 @@ public class HeadersBodyParser extends ControlFrameBodyParser if (buffer.remaining() >= 4) { streamId = buffer.getInt() & 0x7F_FF_FF_FF; - state = State.HEADERS; + state = State.ADDITIONAL; } else { @@ -68,14 +66,55 @@ public class HeadersBodyParser extends ControlFrameBodyParser if (cursor == 0) { streamId &= 0x7F_FF_FF_FF; - state = State.HEADERS; + state = State.ADDITIONAL; } break; } + case ADDITIONAL: + { + switch (controlFrameParser.getVersion()) + { + case SPDY.V2: + { + if (buffer.remaining() >= 2) + { + buffer.getShort(); + state = State.HEADERS; + } + else + { + state = State.ADDITIONAL_BYTES; + cursor = 2; + } + break; + } + case SPDY.V3: + { + state = State.HEADERS; + break; + } + default: + { + throw new IllegalStateException(); + } + } + break; + } + case ADDITIONAL_BYTES: + { + assert controlFrameParser.getVersion() == SPDY.V2; + buffer.get(); + --cursor; + if (cursor == 0) + state = State.HEADERS; + break; + } case HEADERS: { short version = controlFrameParser.getVersion(); int length = controlFrameParser.getLength() - 4; + if (version == SPDY.V2) + length -= 2; if (headersBlockParser.parse(streamId, version, length, buffer)) { byte flags = controlFrameParser.getFlags(); @@ -109,7 +148,7 @@ public class HeadersBodyParser extends ControlFrameBodyParser private enum State { - STREAM_ID, STREAM_ID_BYTES, HEADERS + STREAM_ID, STREAM_ID_BYTES, ADDITIONAL, ADDITIONAL_BYTES, HEADERS } private class HeadersHeadersBlockParser extends HeadersBlockParser diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/NoOpBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/NoOpBodyParser.java index ac14c798b55..f0efe09dd49 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/NoOpBodyParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/NoOpBodyParser.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; 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 8cc42e18144..df897603b51 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 @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/PingBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/PingBodyParser.java index 3f078f6e640..0e8ed613343 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/PingBodyParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/PingBodyParser.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/RstStreamBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/RstStreamBodyParser.java index 81c9b3ccf83..e05aea4906d 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/RstStreamBodyParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/RstStreamBodyParser.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SettingsBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SettingsBodyParser.java index d76382ad0e3..e4fc7c76cec 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SettingsBodyParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SettingsBodyParser.java @@ -1,19 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.nio.ByteBuffer; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynReplyBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynReplyBodyParser.java index ec58478f290..4c93210d3f0 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynReplyBodyParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynReplyBodyParser.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; 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 14673b9e7d7..ba54e739f99 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 @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -38,6 +35,7 @@ public class SynStreamBodyParser extends ControlFrameBodyParser private int streamId; private int associatedStreamId; private byte priority; + private short slot; public SynStreamBodyParser(CompressionFactory.Decompressor decompressor, ControlFrameParser controlFrameParser) { @@ -118,7 +116,9 @@ public class SynStreamBodyParser extends ControlFrameBodyParser } else { - // Unused byte after priority, skip it + slot = (short)(currByte & 0xFF); + if (slot < 0) + throw new StreamException(streamId, StreamStatus.INVALID_CREDENTIALS); cursor = 0; state = State.HEADERS; } @@ -134,7 +134,7 @@ public class SynStreamBodyParser extends ControlFrameBodyParser if (flags > (SynInfo.FLAG_CLOSE | PushSynInfo.FLAG_UNIDIRECTIONAL)) throw new IllegalArgumentException("Invalid flag " + flags + " for frame " + ControlFrameType.SYN_STREAM); - SynStreamFrame frame = new SynStreamFrame(version, flags, streamId, associatedStreamId, priority, new Headers(headers, true)); + SynStreamFrame frame = new SynStreamFrame(version, flags, streamId, associatedStreamId, priority, slot, new Headers(headers, true)); controlFrameParser.onControlFrame(frame); reset(); diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/UnknownControlFrameBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/UnknownControlFrameBodyParser.java index ea5890ff6f0..0de0d37eeb9 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/UnknownControlFrameBodyParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/UnknownControlFrameBodyParser.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/WindowUpdateBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/WindowUpdateBodyParser.java index 697b885308d..9010a346bc1 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/WindowUpdateBodyParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/WindowUpdateBodyParser.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; 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 a3aab55814d..2fe4b2adaae 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 @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -48,7 +45,7 @@ public class AsyncTimeoutTest Executor threadPool = Executors.newCachedThreadPool(); ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor()); - Session session = new StandardSession(SPDY.V2, bufferPool, threadPool, scheduler, new TestController(), null, 1, null, generator) + Session session = new StandardSession(SPDY.V2, bufferPool, threadPool, scheduler, new TestController(), null, 1, null, generator, new FlowControlStrategy.None()) { @Override public void flush() @@ -74,7 +71,7 @@ public class AsyncTimeoutTest } @Override - public void failed(Stream context, Throwable x) + public void failed(Stream stream, Throwable x) { failedLatch.countDown(); } @@ -93,17 +90,17 @@ public class AsyncTimeoutTest Executor threadPool = Executors.newCachedThreadPool(); ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor()); - Session session = new StandardSession(SPDY.V2, bufferPool, threadPool, scheduler, new TestController(), null, 1, null, generator) + Session session = new StandardSession(SPDY.V2, bufferPool, threadPool, scheduler, new TestController(), null, 1, null, generator, new FlowControlStrategy.None()) { @Override - protected void write(ByteBuffer buffer, Callback handler, FrameBytes frameBytes) + protected void write(ByteBuffer buffer, Callback callback, FrameBytes frameBytes) { try { // Wait if we're writing the data frame (control frame's first byte is 0x80) if (buffer.get(0) == 0) unit.sleep(2 * timeout); - super.write(buffer, handler, frameBytes); + super.write(buffer, callback, frameBytes); } catch (InterruptedException x) { @@ -114,13 +111,8 @@ public class AsyncTimeoutTest Stream stream = session.syn(new SynInfo(false), null).get(5, TimeUnit.SECONDS); final CountDownLatch failedLatch = new CountDownLatch(1); - stream.data(new StringDataInfo("data", true), timeout, unit, new Callback() + stream.data(new StringDataInfo("data", true), timeout, unit, new Callback.Empty() { - @Override - public void completed(Void context) - { - } - @Override public void failed(Void context, Throwable x) { diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java index a7dd98a4fee..7e0c2e7ad6c 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 @@ -1,22 +1,20 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== package org.eclipse.jetty.spdy; import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -27,6 +25,8 @@ import java.util.concurrent.TimeoutException; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.StandardByteBufferPool; +import org.eclipse.jetty.spdy.StandardSession.FrameBytes; +import org.eclipse.jetty.spdy.api.ByteBufferDataInfo; import org.eclipse.jetty.spdy.api.DataInfo; import org.eclipse.jetty.spdy.api.Headers; import org.eclipse.jetty.spdy.api.HeadersInfo; @@ -48,22 +48,25 @@ import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.never; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class StandardSessionTest { @Mock - private ISession sessionMock; + private Controller controller; + private ByteBufferPool bufferPool; private Executor threadPool; private StandardSession session; @@ -78,13 +81,36 @@ public class StandardSessionTest threadPool = Executors.newCachedThreadPool(); scheduler = Executors.newSingleThreadScheduledExecutor(); generator = new Generator(new StandardByteBufferPool(),new StandardCompressionFactory.StandardCompressor()); - session = new StandardSession(SPDY.V2,bufferPool,threadPool,scheduler,new TestController(),null,1,null,generator); + session = new StandardSession(SPDY.V2,bufferPool,threadPool,scheduler,controller,null,1,null,generator,new FlowControlStrategy.None()); headers = new Headers(); } + @SuppressWarnings("unchecked") + private void setControllerWriteExpectationToFail(final boolean fail) + { + when(controller.write(any(ByteBuffer.class),any(Callback.class),any(StandardSession.FrameBytes.class))).thenAnswer(new Answer() + { + public Integer answer(InvocationOnMock invocation) + { + Object[] args = invocation.getArguments(); + + Callback callback = (Callback)args[1]; + FrameBytes context = (FrameBytes)args[2]; + + if (fail) + callback.failed(context,new ClosedChannelException()); + else + callback.completed(context); + return 0; + } + }); + } + @Test public void testStreamIsRemovedFromSessionWhenReset() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + IStream stream = createStream(); assertThatStreamIsInSession(stream); assertThat("stream is not reset",stream.isReset(),is(false)); @@ -96,6 +122,8 @@ public class StandardSessionTest @Test public void testStreamIsAddedAndRemovedFromSession() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + IStream stream = createStream(); assertThatStreamIsInSession(stream); stream.updateCloseState(true,true); @@ -107,6 +135,8 @@ public class StandardSessionTest @Test public void testStreamIsRemovedWhenHeadersWithCloseFlagAreSent() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + IStream stream = createStream(); assertThatStreamIsInSession(stream); stream.updateCloseState(true,false); @@ -118,6 +148,8 @@ public class StandardSessionTest @Test public void testStreamIsUnidirectional() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + IStream stream = createStream(); assertThat("stream is not unidirectional",stream.isUnidirectional(),not(true)); Stream pushStream = createPushStream(stream); @@ -127,6 +159,8 @@ public class StandardSessionTest @Test public void testPushStreamCreation() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + Stream stream = createStream(); IStream pushStream = createPushStream(stream); assertThat("Push stream must be associated to the first stream created",pushStream.getAssociatedStream().getId(),is(stream.getId())); @@ -136,6 +170,8 @@ public class StandardSessionTest @Test public void testPushStreamIsNotClosedWhenAssociatedStreamIsClosed() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + IStream stream = createStream(); Stream pushStream = createPushStream(stream); assertThatStreamIsNotHalfClosed(stream); @@ -157,6 +193,8 @@ public class StandardSessionTest @Test public void testCreatePushStreamOnClosedStream() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + IStream stream = createStream(); stream.updateCloseState(true,true); assertThatStreamIsHalfClosed(stream); @@ -169,15 +207,10 @@ public class StandardSessionTest { final CountDownLatch failedLatch = new CountDownLatch(1); SynInfo synInfo = new SynInfo(headers,false,stream.getPriority()); - stream.syn(synInfo,5,TimeUnit.SECONDS,new Callback() + stream.syn(synInfo,5,TimeUnit.SECONDS,new Callback.Empty() { @Override - public void completed(Stream context) - { - } - - @Override - public void failed(Stream context, Throwable x) + public void failed(Stream stream, Throwable x) { failedLatch.countDown(); } @@ -188,6 +221,8 @@ public class StandardSessionTest @Test public void testPushStreamIsAddedAndRemovedFromParentAndSessionWhenClosed() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + IStream stream = createStream(); IStream pushStream = createPushStream(stream); assertThatPushStreamIsHalfClosed(pushStream); @@ -202,6 +237,8 @@ public class StandardSessionTest @Test public void testPushStreamIsRemovedWhenReset() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + IStream stream = createStream(); IStream pushStream = (IStream)stream.syn(new SynInfo(false)).get(); assertThatPushStreamIsInSession(pushStream); @@ -214,6 +251,8 @@ public class StandardSessionTest @Test public void testPushStreamWithSynInfoClosedTrue() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + IStream stream = createStream(); SynInfo synInfo = new SynInfo(headers,true,stream.getPriority()); IStream pushStream = (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS); @@ -227,6 +266,8 @@ public class StandardSessionTest public void testPushStreamSendHeadersWithCloseFlagIsRemovedFromSessionAndDisassociateFromParent() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + IStream stream = createStream(); SynInfo synInfo = new SynInfo(headers,false,stream.getPriority()); IStream pushStream = (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS); @@ -242,6 +283,8 @@ public class StandardSessionTest @Test public void testCreatedAndClosedListenersAreCalledForNewStream() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + final CountDownLatch createdListenerCalledLatch = new CountDownLatch(1); final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1); session.addListener(new TestStreamListener(createdListenerCalledLatch,closedListenerCalledLatch)); @@ -255,6 +298,8 @@ public class StandardSessionTest @Test public void testListenerIsCalledForResetStream() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1); session.addListener(new TestStreamListener(null,closedListenerCalledLatch)); IStream stream = createStream(); @@ -265,6 +310,8 @@ public class StandardSessionTest @Test public void testCreatedAndClosedListenersAreCalledForNewPushStream() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + final CountDownLatch createdListenerCalledLatch = new CountDownLatch(2); final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1); session.addListener(new TestStreamListener(createdListenerCalledLatch,closedListenerCalledLatch)); @@ -279,6 +326,8 @@ public class StandardSessionTest @Test public void testListenerIsCalledForResetPushStream() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1); session.addListener(new TestStreamListener(null,closedListenerCalledLatch)); IStream stream = createStream(); @@ -315,31 +364,23 @@ public class StandardSessionTest } } - @SuppressWarnings("unchecked") - @Test(expected = IllegalStateException.class) - public void testSendDataOnHalfClosedStream() throws InterruptedException, ExecutionException, TimeoutException - { - SynStreamFrame synStreamFrame = new SynStreamFrame(SPDY.V2,SynInfo.FLAG_CLOSE,1,0,(byte)0,null); - IStream stream = new StandardStream(synStreamFrame,sessionMock,8184,null); - 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(sessionMock,never()).data(any(IStream.class),any(DataInfo.class),anyInt(),any(TimeUnit.class),any(Callback.class),any(void.class)); - } - @Test @Ignore("In V3 we need to rst the stream if we receive data on a remotely half closed stream.") public void receiveDataOnRemotelyHalfClosedStreamResetsStreamInV3() throws InterruptedException, ExecutionException { + setControllerWriteExpectationToFail(false); + IStream stream = (IStream)session.syn(new SynInfo(false),new StreamFrameListener.Adapter()).get(); stream.updateCloseState(true,false); assertThat("stream is half closed from remote side",stream.isHalfClosed(),is(true)); - stream.process(new DataFrame(stream.getId(),(byte)0,256),ByteBuffer.allocate(256)); + stream.process(new ByteBufferDataInfo(ByteBuffer.allocate(256), true)); } @Test public void testReceiveDataOnRemotelyClosedStreamIsIgnored() throws InterruptedException, ExecutionException, TimeoutException { + setControllerWriteExpectationToFail(false); + final CountDownLatch onDataCalledLatch = new CountDownLatch(1); Stream stream = session.syn(new SynInfo(false),new StreamFrameListener.Adapter() { @@ -355,10 +396,38 @@ public class StandardSessionTest assertThat("onData is never called",onDataCalledLatch.await(1,TimeUnit.SECONDS),not(true)); } + @SuppressWarnings("unchecked") + @Test + public void testControllerWriteFailsInEndPointFlush() throws InterruptedException + { + setControllerWriteExpectationToFail(true); + + final CountDownLatch failedCalledLatch = new CountDownLatch(2); + 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); + stream.updateWindowSize(8192); + Callback.Empty callback = new Callback.Empty() + { + @Override + public void failed(Object context, Throwable x) + { + failedCalledLatch.countDown(); + } + }; + + // first data frame should fail on controller.write() + stream.data(new StringDataInfo("data", false), 5, TimeUnit.SECONDS, callback); + // second data frame should fail without controller.writer() as the connection is expected to be broken after first controller.write() call failed. + stream.data(new StringDataInfo("data", false), 5, TimeUnit.SECONDS, callback); + + verify(controller, times(1)).write(any(ByteBuffer.class), any(Callback.class), any(FrameBytes.class)); + assertThat("Callback.failed has been called twice", failedCalledLatch.await(5, TimeUnit.SECONDS), is(true)); + } + private IStream createStream() throws InterruptedException, ExecutionException, TimeoutException { SynInfo synInfo = new SynInfo(headers,false,(byte)0); - return (IStream)session.syn(synInfo,new StreamFrameListener.Adapter()).get(5,TimeUnit.SECONDS); + return (IStream)session.syn(synInfo,new StreamFrameListener.Adapter()).get(50,TimeUnit.SECONDS); } private IStream createPushStream(Stream stream) throws InterruptedException, ExecutionException, TimeoutException @@ -367,21 +436,6 @@ public class StandardSessionTest return (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS); } - private static class TestController implements Controller - { - @Override - public int write(ByteBuffer buffer, Callback callback, StandardSession.FrameBytes context) - { - callback.completed(context); - return buffer.remaining(); - } - - @Override - public void close(boolean onlyOutput) - { - } - } - private void assertThatStreamIsClosed(IStream stream) { assertThat("stream is closed",stream.isClosed(),is(true)); 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 7ab08abbe61..8dd0a370d3c 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 @@ -1,26 +1,30 @@ -// ======================================================================== -// Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== - +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== package org.eclipse.jetty.spdy; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.eclipse.jetty.spdy.api.DataInfo; +import org.eclipse.jetty.spdy.api.SPDY; 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.frames.SynStreamFrame; import org.eclipse.jetty.util.Callback; @@ -34,20 +38,20 @@ 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.anyLong; 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 { - @Mock private ISession session; - @Mock private SynStreamFrame synStreamFrame; + @Mock + private ISession session; + @Mock + private SynStreamFrame synStreamFrame; /** * Test method for {@link org.eclipse.jetty.spdy.StandardStream#syn(org.eclipse.jetty.spdy.api.SynInfo)}. @@ -56,17 +60,18 @@ public class StandardStreamTest @Test public void testSyn() { - Stream stream = new StandardStream(synStreamFrame,session,0,null); + Stream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null); Set streams = new HashSet<>(); streams.add(stream); when(synStreamFrame.isClose()).thenReturn(false); SynInfo synInfo = new SynInfo(false); when(session.getStreams()).thenReturn(streams); stream.syn(synInfo); - verify(session).syn(argThat(new PushSynInfoMatcher(stream.getId(),synInfo)),any(StreamFrameListener.class),anyLong(),any(TimeUnit.class),any(Callback.class)); + verify(session).syn(argThat(new PushSynInfoMatcher(stream.getId(), synInfo)), any(StreamFrameListener.class), anyLong(), any(TimeUnit.class), any(Callback.class)); } - private class PushSynInfoMatcher extends ArgumentMatcher{ + private class PushSynInfoMatcher extends ArgumentMatcher + { int associatedStreamId; SynInfo synInfo; @@ -75,15 +80,18 @@ public class StandardStreamTest this.associatedStreamId = associatedStreamId; this.synInfo = synInfo; } + @Override public boolean matches(Object argument) { PushSynInfo pushSynInfo = (PushSynInfo)argument; - if(pushSynInfo.getAssociatedStreamId() != associatedStreamId){ + if (pushSynInfo.getAssociatedStreamId() != associatedStreamId) + { System.out.println("streamIds do not match!"); return false; } - if(pushSynInfo.isClose() != synInfo.isClose()){ + if (pushSynInfo.isClose() != synInfo.isClose()) + { System.out.println("isClose doesn't match"); return false; } @@ -92,16 +100,17 @@ public class StandardStreamTest } @Test - public void testSynOnClosedStream(){ - IStream stream = new StandardStream(synStreamFrame,session,0,null); - stream.updateCloseState(true,true); - stream.updateCloseState(true,false); - assertThat("stream expected to be closed",stream.isClosed(),is(true)); + public void testSynOnClosedStream() + { + IStream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null); + stream.updateCloseState(true, true); + stream.updateCloseState(true, false); + assertThat("stream expected to be closed", stream.isClosed(), is(true)); final CountDownLatch failedLatch = new CountDownLatch(1); - stream.syn(new SynInfo(false),1,TimeUnit.SECONDS,new Callback.Adapter() + stream.syn(new SynInfo(false), 1, TimeUnit.SECONDS, new Callback.Empty() { @Override - public void failed(Stream context, Throwable x) + public void failed(Stream stream, Throwable x) { failedLatch.countDown(); } @@ -109,4 +118,16 @@ public class StandardStreamTest assertThat("PushStream creation failed", failedLatch.getCount(), equalTo(0L)); } + @SuppressWarnings("unchecked") + @Test(expected = IllegalStateException.class) + 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); + 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), any(void.class)); + } } diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ClientUsageTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ClientUsageTest.java index 127e72b8579..39e6252b02f 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ClientUsageTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ClientUsageTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; @@ -30,7 +27,7 @@ public class ClientUsageTest @Test public void testClientRequestResponseNoBody() throws Exception { - Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null); + Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null); session.syn(new SynInfo(true), new StreamFrameListener.Adapter() { @@ -49,7 +46,7 @@ public class ClientUsageTest @Test public void testClientRequestWithBodyResponseNoBody() throws Exception { - Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null); + Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null); Stream stream = session.syn(new SynInfo(false), new StreamFrameListener.Adapter() { @@ -70,7 +67,7 @@ public class ClientUsageTest @Test public void testAsyncClientRequestWithBodyResponseNoBody() throws Exception { - Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null); + Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null); final String context = "context"; session.syn(new SynInfo(false), new StreamFrameListener.Adapter() @@ -84,7 +81,7 @@ public class ClientUsageTest // Then issue another similar request stream.getSession().syn(new SynInfo(true), this); } - }, 0, TimeUnit.MILLISECONDS, new Callback.Adapter() + }, 0, TimeUnit.MILLISECONDS, new Callback.Empty() { @Override public void completed(Stream stream) @@ -105,7 +102,7 @@ public class ClientUsageTest @Test public void testAsyncClientRequestWithBodyAndResponseWithBody() throws Exception { - Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null); + Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null); session.syn(new SynInfo(false), new StreamFrameListener.Adapter() { @@ -139,7 +136,7 @@ public class ClientUsageTest } } - }, 0, TimeUnit.MILLISECONDS, new Callback.Adapter() + }, 0, TimeUnit.MILLISECONDS, new Callback.Empty() { @Override public void completed(Stream stream) diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ServerUsageTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ServerUsageTest.java index d74fae271e5..fe10404b8ad 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ServerUsageTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ServerUsageTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.api; @@ -70,7 +67,7 @@ public class ServerUsageTest // // However, the API may allow to initiate the stream - session.syn(new SynInfo(false), null, 0, TimeUnit.MILLISECONDS, new Callback.Adapter() + session.syn(new SynInfo(false), null, 0, TimeUnit.MILLISECONDS, new Callback.Empty() { @Override public void completed(Stream stream) @@ -100,7 +97,7 @@ public class ServerUsageTest Session session = stream.getSession(); // Since it's unidirectional, no need to pass the listener - session.syn(new SynInfo(new Headers(), false, (byte)0), null, 0, TimeUnit.MILLISECONDS, new Callback.Adapter() + session.syn(new SynInfo(new Headers(), false, (byte)0), null, 0, TimeUnit.MILLISECONDS, new Callback.Empty() { @Override public void completed(Stream pushStream) diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/CredentialGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/CredentialGenerateParseTest.java new file mode 100644 index 00000000000..cdd5d85d2b8 --- /dev/null +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/CredentialGenerateParseTest.java @@ -0,0 +1,99 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; + +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.security.KeyStore; +import java.security.cert.Certificate; + +import org.eclipse.jetty.io.StandardByteBufferPool; +import org.eclipse.jetty.spdy.StandardCompressionFactory; +import org.eclipse.jetty.spdy.api.SPDY; +import org.eclipse.jetty.spdy.generator.Generator; +import org.eclipse.jetty.spdy.parser.Parser; +import org.eclipse.jetty.util.resource.Resource; +import org.junit.Assert; +import org.junit.Test; + +public class CredentialGenerateParseTest +{ + @Test + public void testGenerateParse() throws Exception + { + short slot = 1; + byte[] proof = new byte[]{0, 1, 2}; + Certificate[] temp = loadCertificates(); + Certificate[] certificates = new Certificate[temp.length * 2]; + System.arraycopy(temp, 0, certificates, 0, temp.length); + System.arraycopy(temp, 0, certificates, temp.length, temp.length); + CredentialFrame frame1 = new CredentialFrame(SPDY.V3, slot, proof, certificates); + Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor()); + ByteBuffer buffer = generator.control(frame1); + + Assert.assertNotNull(buffer); + + TestSPDYParserListener listener = new TestSPDYParserListener(); + Parser parser = new Parser(new StandardCompressionFactory().newDecompressor()); + parser.addListener(listener); + parser.parse(buffer); + ControlFrame frame2 = listener.getControlFrame(); + + Assert.assertNotNull(frame2); + Assert.assertEquals(ControlFrameType.CREDENTIAL, frame2.getType()); + CredentialFrame credential = (CredentialFrame)frame2; + Assert.assertEquals(SPDY.V3, credential.getVersion()); + Assert.assertEquals(0, credential.getFlags()); + Assert.assertEquals(slot, credential.getSlot()); + Assert.assertArrayEquals(proof, credential.getProof()); + Assert.assertArrayEquals(certificates, credential.getCertificateChain()); + } + + @Test + public void testGenerateParseOneByteAtATime() throws Exception + { + short slot = 1; + byte[] proof = new byte[]{0, 1, 2}; + Certificate[] certificates = loadCertificates(); + CredentialFrame frame1 = new CredentialFrame(SPDY.V3, slot, proof, certificates); + Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor()); + ByteBuffer buffer = generator.control(frame1); + + Assert.assertNotNull(buffer); + + TestSPDYParserListener listener = new TestSPDYParserListener(); + Parser parser = new Parser(new StandardCompressionFactory().newDecompressor()); + parser.addListener(listener); + while (buffer.hasRemaining()) + parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + ControlFrame frame2 = listener.getControlFrame(); + + Assert.assertNotNull(frame2); + Assert.assertEquals(ControlFrameType.CREDENTIAL, frame2.getType()); + CredentialFrame credential = (CredentialFrame)frame2; + Assert.assertEquals(SPDY.V3, credential.getVersion()); + Assert.assertEquals(0, credential.getFlags()); + Assert.assertEquals(slot, credential.getSlot()); + Assert.assertArrayEquals(proof, credential.getProof()); + Assert.assertArrayEquals(certificates, credential.getCertificateChain()); + } + + private Certificate[] loadCertificates() throws Exception + { + KeyStore keyStore = KeyStore.getInstance("JKS"); + InputStream keyStoreStream = Resource.newResource("src/test/resources/keystore.jks").getInputStream(); + keyStore.load(keyStoreStream, "storepwd".toCharArray()); + return keyStore.getCertificateChain("mykey"); + } +} diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/DataGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/DataGenerateParseTest.java index f31f28f605f..4f90dc007f8 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/DataGenerateParseTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/DataGenerateParseTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/GoAwayGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/GoAwayGenerateParseTest.java index 91d197e8e0e..01ebccb9738 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/GoAwayGenerateParseTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/GoAwayGenerateParseTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/HeadersGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/HeadersGenerateParseTest.java index b8c62779dab..67779be01d9 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/HeadersGenerateParseTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/HeadersGenerateParseTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; @@ -25,65 +22,77 @@ import org.eclipse.jetty.spdy.api.HeadersInfo; import org.eclipse.jetty.spdy.api.SPDY; import org.eclipse.jetty.spdy.generator.Generator; import org.eclipse.jetty.spdy.parser.Parser; -import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + public class HeadersGenerateParseTest { - @Test - public void testGenerateParse() throws Exception + + private Headers headers = new Headers(); + private int streamId = 13; + private byte flags = HeadersInfo.FLAG_RESET_COMPRESSION; + private final TestSPDYParserListener listener = new TestSPDYParserListener(); + private final Parser parser = new Parser(new StandardCompressionFactory().newDecompressor()); + private ByteBuffer buffer; + + @Before + public void setUp() { - byte flags = HeadersInfo.FLAG_RESET_COMPRESSION; - int streamId = 13; - Headers headers = new Headers(); + parser.addListener(listener); headers.put("a", "b"); + buffer = createHeadersFrameBuffer(headers); + } + + private ByteBuffer createHeadersFrameBuffer(Headers headers) + { HeadersFrame frame1 = new HeadersFrame(SPDY.V2, flags, streamId, headers); Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor()); ByteBuffer buffer = generator.control(frame1); + assertThat("Buffer is not null", buffer, notNullValue()); + return buffer; + } - Assert.assertNotNull(buffer); - - TestSPDYParserListener listener = new TestSPDYParserListener(); - Parser parser = new Parser(new StandardCompressionFactory().newDecompressor()); - parser.addListener(listener); + @Test + public void testGenerateParse() throws Exception + { parser.parse(buffer); - ControlFrame frame2 = listener.getControlFrame(); - - Assert.assertNotNull(frame2); - Assert.assertEquals(ControlFrameType.HEADERS, frame2.getType()); - HeadersFrame headersFrame = (HeadersFrame)frame2; - Assert.assertEquals(SPDY.V2, headersFrame.getVersion()); - Assert.assertEquals(streamId, headersFrame.getStreamId()); - Assert.assertEquals(flags, headersFrame.getFlags()); - Assert.assertEquals(headers, headersFrame.getHeaders()); + assertExpectationsAreMet(headers); } @Test public void testGenerateParseOneByteAtATime() throws Exception { - byte flags = HeadersInfo.FLAG_RESET_COMPRESSION; - int streamId = 13; - Headers headers = new Headers(); - headers.put("a", "b"); - HeadersFrame frame1 = new HeadersFrame(SPDY.V2, flags, streamId, headers); - Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor()); - ByteBuffer buffer = generator.control(frame1); - - Assert.assertNotNull(buffer); - - TestSPDYParserListener listener = new TestSPDYParserListener(); - Parser parser = new Parser(new StandardCompressionFactory().newDecompressor()); - parser.addListener(listener); while (buffer.hasRemaining()) parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - ControlFrame frame2 = listener.getControlFrame(); - Assert.assertNotNull(frame2); - Assert.assertEquals(ControlFrameType.HEADERS, frame2.getType()); - HeadersFrame headersFrame = (HeadersFrame)frame2; - Assert.assertEquals(SPDY.V2, headersFrame.getVersion()); - Assert.assertEquals(streamId, headersFrame.getStreamId()); - Assert.assertEquals(flags, headersFrame.getFlags()); - Assert.assertEquals(headers, headersFrame.getHeaders()); + assertExpectationsAreMet(headers); + } + + @Test + public void testHeadersAreTranslatedToLowerCase() + { + Headers headers = new Headers(); + headers.put("Via","localhost"); + parser.parse(createHeadersFrameBuffer(headers)); + HeadersFrame parsedHeadersFrame = assertExpectationsAreMet(headers); + Headers.Header viaHeader = parsedHeadersFrame.getHeaders().get("via"); + assertThat("Via Header name is lowercase", viaHeader.name(), is("via")); + } + + private HeadersFrame assertExpectationsAreMet(Headers headers) + { + ControlFrame parsedControlFrame = listener.getControlFrame(); + assertThat("listener received controlFrame", parsedControlFrame, notNullValue()); + assertThat("ControlFrame type is HEADERS", ControlFrameType.HEADERS, is(parsedControlFrame.getType())); + HeadersFrame headersFrame = (HeadersFrame)parsedControlFrame; + assertThat("Version matches", SPDY.V2, is(headersFrame.getVersion())); + assertThat("StreamId matches", streamId, is(headersFrame.getStreamId())); + assertThat("flags match", flags, is(headersFrame.getFlags())); + assertThat("headers match", headers, is(headersFrame.getHeaders())); + return headersFrame; } } diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/NoOpGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/NoOpGenerateParseTest.java index 7519e17ffd2..4f0ff597eb2 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/NoOpGenerateParseTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/NoOpGenerateParseTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/PingGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/PingGenerateParseTest.java index b287b1d6ca9..99ee7a0a15b 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/PingGenerateParseTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/PingGenerateParseTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/RstStreamGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/RstStreamGenerateParseTest.java index fcceab3c5f4..8635931c4c2 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/RstStreamGenerateParseTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/RstStreamGenerateParseTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SettingsGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SettingsGenerateParseTest.java index 2843780706b..5b1fbf1feee 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SettingsGenerateParseTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SettingsGenerateParseTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynReplyGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynReplyGenerateParseTest.java index 4ca4934918d..9f5e8ab0c27 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynReplyGenerateParseTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynReplyGenerateParseTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynStreamGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynStreamGenerateParseTest.java index d9527661d43..4fc2438eedc 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynStreamGenerateParseTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynStreamGenerateParseTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; @@ -37,10 +34,11 @@ public class SynStreamGenerateParseTest int streamId = 13; int associatedStreamId = 11; byte priority = 3; + short slot = 5; Headers headers = new Headers(); headers.put("a", "b"); headers.put("c", "d"); - SynStreamFrame frame1 = new SynStreamFrame(SPDY.V2, flags, streamId, associatedStreamId, priority, headers); + SynStreamFrame frame1 = new SynStreamFrame(SPDY.V2, flags, streamId, associatedStreamId, priority, slot, headers); Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor()); ByteBuffer buffer = generator.control(frame1); @@ -60,6 +58,7 @@ public class SynStreamGenerateParseTest Assert.assertEquals(associatedStreamId, synStream.getAssociatedStreamId()); Assert.assertEquals(flags, synStream.getFlags()); Assert.assertEquals(priority, synStream.getPriority()); + Assert.assertEquals(slot, synStream.getSlot()); Assert.assertEquals(headers, synStream.getHeaders()); } @@ -70,10 +69,11 @@ public class SynStreamGenerateParseTest int streamId = 13; int associatedStreamId = 11; byte priority = 3; + short slot = 5; Headers headers = new Headers(); headers.put("a", "b"); headers.put("c", "d"); - SynStreamFrame frame1 = new SynStreamFrame(SPDY.V2, flags, streamId, associatedStreamId, priority, headers); + SynStreamFrame frame1 = new SynStreamFrame(SPDY.V2, flags, streamId, associatedStreamId, priority, slot, headers); Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor()); ByteBuffer buffer = generator.control(frame1); @@ -94,6 +94,7 @@ public class SynStreamGenerateParseTest Assert.assertEquals(associatedStreamId, synStream.getAssociatedStreamId()); Assert.assertEquals(flags, synStream.getFlags()); Assert.assertEquals(priority, synStream.getPriority()); + Assert.assertEquals(slot, synStream.getSlot()); Assert.assertEquals(headers, synStream.getHeaders()); } } diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/TestSPDYParserListener.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/TestSPDYParserListener.java index 4642ca5d3bf..14e0cf35c87 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/TestSPDYParserListener.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/TestSPDYParserListener.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/WindowUpdateGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/WindowUpdateGenerateParseTest.java index c28b61312d3..1ca52b404bc 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/WindowUpdateGenerateParseTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/WindowUpdateGenerateParseTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.frames; diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/LiveChromiumRequestParserTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/LiveChromiumRequestParserTest.java index 56b65f0b6fe..46b7897f7ac 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/LiveChromiumRequestParserTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/LiveChromiumRequestParserTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/ParseVersusCacheBenchmarkTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/ParseVersusCacheBenchmarkTest.java index abaac53a2c2..cc92e0c6293 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/ParseVersusCacheBenchmarkTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/ParseVersusCacheBenchmarkTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/UnknownControlFrameTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/UnknownControlFrameTest.java index 63171c6cf58..f9810149cf8 100644 --- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/UnknownControlFrameTest.java +++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/UnknownControlFrameTest.java @@ -1,3 +1,16 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.nio.ByteBuffer; @@ -23,7 +36,7 @@ public class UnknownControlFrameTest @Test public void testUnknownControlFrame() throws Exception { - SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, new Headers()); + SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, new Headers()); Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor()); ByteBuffer buffer = generator.control(frame); // Change the frame type to unknown diff --git a/jetty-spdy/spdy-core/src/test/resources/keystore.jks b/jetty-spdy/spdy-core/src/test/resources/keystore.jks new file mode 100644 index 00000000000..428ba54776e Binary files /dev/null and b/jetty-spdy/spdy-core/src/test/resources/keystore.jks differ diff --git a/jetty-spdy/spdy-core/src/test/resources/truststore.jks b/jetty-spdy/spdy-core/src/test/resources/truststore.jks new file mode 100644 index 00000000000..839cb8c3515 Binary files /dev/null and b/jetty-spdy/spdy-core/src/test/resources/truststore.jks differ diff --git a/jetty-spdy/spdy-jetty-http-webapp/pom.xml b/jetty-spdy/spdy-jetty-http-webapp/pom.xml index 14ebb0650cd..6616a1d786a 100644 --- a/jetty-spdy/spdy-jetty-http-webapp/pom.xml +++ b/jetty-spdy/spdy-jetty-http-webapp/pom.xml @@ -5,7 +5,6 @@ spdy-parent 9.0.0-SNAPSHOT - 4.0.0 spdy-jetty-http-webapp war @@ -40,7 +39,7 @@ quit -Dlog4j.configuration=file://${basedir}/src/main/resources/log4j.properties - -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${project.version}/npn-boot-${project.version}.jar + -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn.version}/npn-boot-${npn.version}.jar ${basedir}/src/main/config/etc/jetty-spdy.xml / @@ -61,4 +60,45 @@ -->
    + + + diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy-proxy.xml b/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy-proxy.xml new file mode 100644 index 00000000000..9c637ec41f8 --- /dev/null +++ b/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy-proxy.xml @@ -0,0 +1,98 @@ + + + + + + + src/main/resources/keystore.jks + storepwd + src/main/resources/truststore.jks + storepwd + TLSv1 + + + + + + + + + 9090 + + + spdy/2 + + + + + + + + + + + + + + + + + + + spdy/2 + + + + + + localhost + + + spdy/2 + 127.0.0.1 + 9090 + + + + + + + + + + + + + 8080 + + + + + + + + + 8443 + + + + + + diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml b/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml index 95c9c2b9c27..0d847bcbd48 100644 --- a/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml +++ b/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml @@ -11,9 +11,45 @@ TLSv1 + + + + + + + + 8080 @@ -24,6 +60,24 @@ + + 8443 diff --git a/jetty-spdy/spdy-jetty-http/pom.xml b/jetty-spdy/spdy-jetty-http/pom.xml index c1a1165623b..151cb9cee71 100644 --- a/jetty-spdy/spdy-jetty-http/pom.xml +++ b/jetty-spdy/spdy-jetty-http/pom.xml @@ -5,7 +5,6 @@ spdy-parent 9.0.0-SNAPSHOT - 4.0.0 spdy-jetty-http Jetty :: SPDY :: Jetty HTTP Layer @@ -61,12 +60,23 @@ 1.0.0.v20120402 test + + org.eclipse.jetty + jetty-client + ${project.version} + test + org.slf4j slf4j-log4j12 ${slf4j-version} test + + org.mockito + mockito-core + test + diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYServerConnector.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYServerConnector.java new file mode 100644 index 00000000000..543f78323ca --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYServerConnector.java @@ -0,0 +1,61 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + +package org.eclipse.jetty.spdy.http; + +import java.io.IOException; + +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.spdy.SPDYServerConnector; +import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +public class AbstractHTTPSPDYServerConnector extends SPDYServerConnector +{ + public AbstractHTTPSPDYServerConnector(ServerSessionFrameListener listener, SslContextFactory sslContextFactory) + { + super(listener, sslContextFactory); + } + + @Override + public void customize(EndPoint endPoint, Request request) throws IOException + { + super.customize(endPoint, request); + if (getSslContextFactory() != null) + request.setScheme(HttpSchemes.HTTPS); + } + + @Override + public boolean isConfidential(Request request) + { + if (getSslContextFactory() != null) + { + int confidentialPort = getConfidentialPort(); + return confidentialPort == 0 || confidentialPort == request.getServerPort(); + } + return super.isConfidential(request); + } + + @Override + public boolean isIntegral(Request request) + { + if (getSslContextFactory() != null) + { + int integralPort = getIntegralPort(); + return integralPort == 0 || integralPort == request.getServerPort(); + } + return super.isIntegral(request); + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java new file mode 100644 index 00000000000..7149aa5ae0b --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java @@ -0,0 +1,73 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + + +package org.eclipse.jetty.spdy.http; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jetty.spdy.api.SPDY; + +public enum HTTPSPDYHeader +{ + METHOD("method", ":method"), + URI("url", ":path"), + VERSION("version", ":version"), + SCHEME("scheme", ":scheme"), + HOST("host", ":host"), + STATUS("status", ":status"); + + public static HTTPSPDYHeader from(short version, String name) + { + switch (version) + { + case SPDY.V2: + return Names.v2Names.get(name); + case SPDY.V3: + return Names.v3Names.get(name); + default: + throw new IllegalStateException(); + } + } + + private final String v2Name; + private final String v3Name; + + private HTTPSPDYHeader(String v2Name, String v3Name) + { + this.v2Name = v2Name; + Names.v2Names.put(v2Name, this); + this.v3Name = v3Name; + Names.v3Names.put(v3Name, this); + } + + public String name(short version) + { + switch (version) + { + case SPDY.V2: + return v2Name; + case SPDY.V3: + return v3Name; + default: + throw new IllegalStateException(); + } + } + + private static class Names + { + private static final Map v2Names = new HashMap<>(); + private static final Map v3Names = new HashMap<>(); + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java index 63544a0356b..8b76589cb32 100644 --- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java @@ -1,91 +1,64 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy.http; -import java.io.IOException; +import java.util.Collections; +import java.util.Map; -import org.eclipse.jetty.http.HttpSchemes; -import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.spdy.AsyncConnectionFactory; -import org.eclipse.jetty.spdy.SPDYServerConnector; import org.eclipse.jetty.spdy.api.SPDY; import org.eclipse.jetty.util.ssl.SslContextFactory; -public class HTTPSPDYServerConnector extends SPDYServerConnector +public class HTTPSPDYServerConnector extends AbstractHTTPSPDYServerConnector { - private final AsyncConnectionFactory defaultConnectionFactory; - private final PushStrategy pushStrategy = new PushStrategy.None(); - public HTTPSPDYServerConnector() { - this(null); + this(null, Collections.emptyMap()); + } + + public HTTPSPDYServerConnector(Map pushStrategies) + { + this(null, pushStrategies); } public HTTPSPDYServerConnector(SslContextFactory sslContextFactory) { + this(sslContextFactory, Collections.emptyMap()); + } + + public HTTPSPDYServerConnector(SslContextFactory sslContextFactory, Map pushStrategies) + { + // We pass a null ServerSessionFrameListener because for + // HTTP over SPDY we need one that references the endPoint super(null, sslContextFactory); - // Override the default connection factory for non-SSL connections - defaultConnectionFactory = new ServerHTTPAsyncConnectionFactory(this); - } - - @Override - protected void doStart() throws Exception - { - super.doStart(); - // Override the "spdy/2" protocol by handling HTTP over SPDY - putAsyncConnectionFactory("spdy/2", new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), this, pushStrategy)); - // Add the "http/1.1" protocol for browsers that do not support NPN + clearAsyncConnectionFactories(); + // The "spdy/3" protocol handles HTTP over SPDY + putAsyncConnectionFactory("spdy/3", new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V3, getByteBufferPool(), getExecutor(), getScheduler(), this, getPushStrategy(SPDY.V3,pushStrategies))); + // The "spdy/2" protocol handles HTTP over SPDY + putAsyncConnectionFactory("spdy/2", new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), this, getPushStrategy(SPDY.V2,pushStrategies))); + // The "http/1.1" protocol handles browsers that support NPN but not SPDY putAsyncConnectionFactory("http/1.1", new ServerHTTPAsyncConnectionFactory(this)); + // The default connection factory handles plain HTTP on non-SSL or non-NPN connections + setDefaultAsyncConnectionFactory(getAsyncConnectionFactory("http/1.1")); } - @Override - protected AsyncConnectionFactory getDefaultAsyncConnectionFactory() + private PushStrategy getPushStrategy(short version, Map pushStrategies) { - return defaultConnectionFactory; + PushStrategy pushStrategy = pushStrategies.get(version); + if(pushStrategy == null) + pushStrategy = new PushStrategy.None(); + return pushStrategy; } - @Override - public void customize(EndPoint endPoint, Request request) throws IOException - { - super.customize(endPoint, request); - if (getSslContextFactory() != null) - request.setScheme(HttpSchemes.HTTPS); - } - - @Override - public boolean isConfidential(Request request) - { - if (getSslContextFactory() != null) - { - int confidentialPort = getConfidentialPort(); - return confidentialPort == 0 || confidentialPort == request.getServerPort(); - } - return super.isConfidential(request); - } - - @Override - public boolean isIntegral(Request request) - { - if (getSslContextFactory() != null) - { - int integralPort = getIntegralPort(); - return integralPort == 0 || integralPort == request.getServerPort(); - } - return super.isIntegral(request); - } } diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/PushStrategy.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/PushStrategy.java index 780a5b22d1d..c15fe5baac9 100644 --- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/PushStrategy.java +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/PushStrategy.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy.http; diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategy.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategy.java index cf3fe06f1b6..00798977266 100644 --- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategy.java +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategy.java @@ -1,28 +1,28 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy.http; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedHashSet; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; import org.eclipse.jetty.spdy.api.Headers; @@ -37,64 +37,99 @@ import org.eclipse.jetty.util.log.Logger; * will have a Referer HTTP header that points to index.html, which we * use to link the associated resource to the main resource.

    *

    However, also following a hyperlink generates a HTTP request with a Referer - * HTTP header that points to index.html; therefore main resources and associated - * resources must be distinguishable.

    - *

    This class distinguishes associated resources by their URL path suffix. + * HTTP header that points to index.html; therefore a proper value for {@link #getReferrerPushPeriod()} + * has to be set. If the referrerPushPeriod for a main resource has been passed, no more + * associated resources will be added for that main resource.

    + *

    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.

    - *

    Note however, that CSS stylesheets may refer to images, and the CSS image request - * will have the CSS stylesheet as referrer, so there is some degree of recursion that - * needs to be handled.

    - * - * TODO: this class is kind-of leaking since the resources map is always adding entries - * TODO: although these entries will be limited by the number of application pages. - * TODO: however, there is no ConcurrentLinkedHashMap yet in JDK (there is in Guava though) - * TODO: so we cannot use the built-in LRU features of LinkedHashMap - * - * TODO: Wikipedia maps URLs like http://en.wikipedia.org/wiki/File:PNG-Gradient_hex.png - * TODO: to text/html, so perhaps we need to improve isPushResource() by looking at the - * TODO: response Content-Type header, and not only at the URL extension + * are classified as associated resources. The suffix regexs can be configured by constructor argument

    + *

    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.

    + *

    The push metadata built by this implementation is limited by the number of pages + * of the application itself, and by the + * {@link #getMaxAssociatedResources() 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 final ConcurrentMap> resources = new ConcurrentHashMap<>(); - private final Set pushRegexps = new LinkedHashSet<>(); - private final Set allowedPushOrigins = new LinkedHashSet<>(); + private final ConcurrentMap mainResources = new ConcurrentHashMap<>(); + private final Set pushRegexps = new HashSet<>(); + private final Set pushContentTypes = new HashSet<>(); + private final Set allowedPushOrigins = new HashSet<>(); + private volatile int maxAssociatedResources = 32; + private volatile int referrerPushPeriod = 5000; public ReferrerPushStrategy() { - this(Arrays.asList(".*\\.css", ".*\\.js", ".*\\.png", ".*\\.jpg", ".*\\.gif")); + this(Arrays.asList(".*\\.css", ".*\\.js", ".*\\.png", ".*\\.jpeg", ".*\\.jpg", ".*\\.gif", ".*\\.ico")); } public ReferrerPushStrategy(List pushRegexps) { - this(pushRegexps, Collections.emptyList()); + this(pushRegexps, Arrays.asList( + "text/css", + "text/javascript", "application/javascript", "application/x-javascript", + "image/png", "image/x-png", + "image/jpeg", + "image/gif", + "image/x-icon", "image/vnd.microsoft.icon")); } - public ReferrerPushStrategy(List pushRegexps, List allowedPushOrigins) + public ReferrerPushStrategy(List pushRegexps, List pushContentTypes) + { + this(pushRegexps, pushContentTypes, Collections.emptyList()); + } + + public ReferrerPushStrategy(List pushRegexps, List pushContentTypes, List allowedPushOrigins) { for (String pushRegexp : pushRegexps) this.pushRegexps.add(Pattern.compile(pushRegexp)); + this.pushContentTypes.addAll(pushContentTypes); for (String allowedPushOrigin : allowedPushOrigins) this.allowedPushOrigins.add(Pattern.compile(allowedPushOrigin.replace(".", "\\.").replace("*", ".*"))); } + public int getMaxAssociatedResources() + { + return maxAssociatedResources; + } + + public void setMaxAssociatedResources(int maxAssociatedResources) + { + this.maxAssociatedResources = maxAssociatedResources; + } + + public int getReferrerPushPeriod() + { + return referrerPushPeriod; + } + + public void setReferrerPushPeriod(int referrerPushPeriod) + { + this.referrerPushPeriod = referrerPushPeriod; + } + @Override public Set apply(Stream stream, Headers requestHeaders, Headers responseHeaders) { - Set result = Collections.emptySet(); - String scheme = requestHeaders.get("scheme").value(); - String host = requestHeaders.get("host").value(); - String origin = new StringBuilder(scheme).append("://").append(host).toString(); - String url = requestHeaders.get("url").value(); - String absoluteURL = new StringBuilder(origin).append(url).toString(); - logger.debug("Applying push strategy for {}", absoluteURL); - if (isValidMethod(requestHeaders.get("method").value())) + Set result = Collections.emptySet(); + short version = stream.getSession().getVersion(); + if (!isIfModifiedSinceHeaderPresent(requestHeaders) && isValidMethod(requestHeaders.get(HTTPSPDYHeader.METHOD.name(version)).value())) { + String scheme = requestHeaders.get(HTTPSPDYHeader.SCHEME.name(version)).value(); + String host = requestHeaders.get(HTTPSPDYHeader.HOST.name(version)).value(); + String origin = scheme + "://" + host; + String url = requestHeaders.get(HTTPSPDYHeader.URI.name(version)).value(); + String absoluteURL = origin + url; + logger.debug("Applying push strategy for {}", absoluteURL); if (isMainResource(url, responseHeaders)) { - result = pushResources(absoluteURL); + MainResource mainResource = getOrCreateMainResource(absoluteURL); + result = mainResource.getResources(); } else if (isPushResource(url, responseHeaders)) { @@ -102,18 +137,49 @@ public class ReferrerPushStrategy implements PushStrategy if (referrerHeader != null) { String referrer = referrerHeader.value(); - Set pushResources = resources.get(referrer); - if (pushResources == null || !pushResources.contains(url)) - buildMetadata(origin, url, referrer); + MainResource mainResource = mainResources.get(referrer); + if (mainResource == null) + mainResource = getOrCreateMainResource(referrer); + + Set pushResources = mainResource.getResources(); + if (!pushResources.contains(url)) + mainResource.addResource(url, origin, referrer); else - result = pushResources(absoluteURL); + result = getPushResources(absoluteURL); } } + logger.debug("Pushing {} resources for {}: {}", result.size(), absoluteURL, result); } - logger.debug("Push resources for {}: {}", absoluteURL, result); return result; } + private Set getPushResources(String absoluteURL) + { + Set result = Collections.emptySet(); + if (mainResources.get(absoluteURL) != null) + result = mainResources.get(absoluteURL).getResources(); + return result; + } + + private MainResource getOrCreateMainResource(String absoluteURL) + { + MainResource mainResource = mainResources.get(absoluteURL); + if (mainResource == null) + { + logger.debug("Creating new main resource for {}", absoluteURL); + MainResource value = new MainResource(absoluteURL); + mainResource = mainResources.putIfAbsent(absoluteURL, value); + if (mainResource == null) + mainResource = value; + } + return mainResource; + } + + private boolean isIfModifiedSinceHeaderPresent(Headers headers) + { + return headers.get("if-modified-since") != null; + } + private boolean isValidMethod(String method) { return "GET".equalsIgnoreCase(method); @@ -129,43 +195,85 @@ public class ReferrerPushStrategy implements PushStrategy for (Pattern pushRegexp : pushRegexps) { if (pushRegexp.matcher(url).matches()) - return true; - } - return false; - } - - private Set pushResources(String absoluteURL) - { - Set pushResources = resources.get(absoluteURL); - if (pushResources == null) - return Collections.emptySet(); - return Collections.unmodifiableSet(pushResources); - } - - private void buildMetadata(String origin, String url, String referrer) - { - if (referrer.startsWith(origin) || isPushOriginAllowed(origin)) - { - Set pushResources = resources.get(referrer); - if (pushResources == null) { - pushResources = Collections.newSetFromMap(new ConcurrentHashMap()); - Set existing = resources.putIfAbsent(referrer, pushResources); - if (existing != null) - pushResources = existing; - } - pushResources.add(url); - logger.debug("Built push metadata for {}: {}", referrer, pushResources); - } - } + Headers.Header header = responseHeaders.get("content-type"); + if (header == null) + return true; - private boolean isPushOriginAllowed(String origin) - { - for (Pattern allowedPushOrigin : allowedPushOrigins) - { - if (allowedPushOrigin.matcher(origin).matches()) - return true; + String contentType = header.value().toLowerCase(); + for (String pushContentType : pushContentTypes) + if (contentType.startsWith(pushContentType)) + return true; + } } return false; } + + private class MainResource + { + private final String name; + private final Set resources = Collections.newSetFromMap(new ConcurrentHashMap()); + private final AtomicLong firstResourceAdded = new AtomicLong(-1); + + private MainResource(String name) + { + this.name = name; + } + + public boolean addResource(String url, String origin, String referrer) + { + // We start the push period here and not when initializing the main resource, because a browser with a + // prefilled cache won't request the subresources. If the browser with warmed up cache now hits the main + // resource after a server restart, the push period shouldn't start until the first subresource is + // being requested. + firstResourceAdded.compareAndSet(-1, System.nanoTime()); + + 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", + url, name, origin); + return false; + } + + // This check is not strictly concurrent-safe, but limiting + // the number of associated resources is achieved anyway + // 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", + url, name, maxAssociatedResources); + return false; + } + if (delay > referrerPushPeriod) + { + logger.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); + resources.add(url); + return true; + } + + public Set getResources() + { + return Collections.unmodifiableSet(resources); + } + + public String toString() + { + return "MainResource: " + name + " associated resources:" + resources.size(); + } + + private boolean isPushOriginAllowed(String origin) + { + for (Pattern allowedPushOrigin : allowedPushOrigins) + { + if (allowedPushOrigin.matcher(origin).matches()) + return true; + } + return false; + } + } } diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPAsyncConnectionFactory.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPAsyncConnectionFactory.java index 3622b2fdf69..b0bae06de1c 100644 --- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPAsyncConnectionFactory.java +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPAsyncConnectionFactory.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy.http; @@ -21,18 +19,23 @@ import java.nio.channels.SocketChannel; import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.nio.AsyncConnection; import org.eclipse.jetty.server.AsyncHttpConnection; -import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.spdy.AsyncConnectionFactory; +import org.eclipse.jetty.spdy.SPDYServerConnector; public class ServerHTTPAsyncConnectionFactory implements AsyncConnectionFactory { - private final Connector connector; + private final SPDYServerConnector connector; - public ServerHTTPAsyncConnectionFactory(Connector connector) + public ServerHTTPAsyncConnectionFactory(SPDYServerConnector connector) { this.connector = connector; } + public SPDYServerConnector getConnector() + { + return connector; + } + @Override public AsyncConnection newAsyncConnection(SocketChannel channel, AsyncEndPoint endPoint, Object attachment) { diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java index da14dbe1a8b..ecc241ea975 100644 --- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy.http; @@ -24,8 +22,10 @@ import java.util.LinkedList; import java.util.Queue; import java.util.Set; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutionException; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.eclipse.jetty.http.HttpException; import org.eclipse.jetty.http.HttpFields; @@ -47,14 +47,16 @@ import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.spdy.SPDYAsyncConnection; import org.eclipse.jetty.spdy.api.ByteBufferDataInfo; +import org.eclipse.jetty.spdy.api.BytesDataInfo; import org.eclipse.jetty.spdy.api.DataInfo; +import org.eclipse.jetty.spdy.api.Handler; import org.eclipse.jetty.spdy.api.Headers; import org.eclipse.jetty.spdy.api.ReplyInfo; import org.eclipse.jetty.spdy.api.RstInfo; +import org.eclipse.jetty.spdy.api.SPDY; import org.eclipse.jetty.spdy.api.Stream; import org.eclipse.jetty.spdy.api.StreamStatus; import org.eclipse.jetty.spdy.api.SynInfo; -import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -66,6 +68,7 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem private final Queue tasks = new LinkedList<>(); private final BlockingQueue dataInfos = new LinkedBlockingQueue<>(); + private final short version; private final SPDYAsyncConnection connection; private final PushStrategy pushStrategy; private final Stream stream; @@ -75,9 +78,10 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem private volatile State state = State.INITIAL; private boolean dispatched; // Guarded by synchronization on tasks - public ServerHTTPSPDYAsyncConnection(Connector connector, AsyncEndPoint endPoint, Server server, SPDYAsyncConnection connection, PushStrategy pushStrategy, Stream stream) + public ServerHTTPSPDYAsyncConnection(Connector connector, AsyncEndPoint endPoint, Server server, short version, SPDYAsyncConnection connection, PushStrategy pushStrategy, Stream stream) { super(connector, endPoint, server); + this.version = version; this.connection = connection; this.pushStrategy = pushStrategy; this.stream = stream; @@ -159,14 +163,12 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem } case REQUEST: { - Headers.Header method = headers.get("method"); - Headers.Header uri = headers.get("url"); - Headers.Header version = headers.get("version"); + Headers.Header method = headers.get(HTTPSPDYHeader.METHOD.name(version)); + Headers.Header uri = headers.get(HTTPSPDYHeader.URI.name(version)); + Headers.Header version = headers.get(HTTPSPDYHeader.VERSION.name(this.version)); if (method == null || uri == null || version == null) - { throw new HttpException(HttpStatus.BAD_REQUEST_400); - } String m = method.value(); String u = uri.value(); @@ -174,6 +176,10 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem logger.debug("HTTP > {} {} {}", m, u, v); startRequest(new ByteArrayBuffer(m), new ByteArrayBuffer(u), new ByteArrayBuffer(v)); + Headers.Header schemeHeader = headers.get(HTTPSPDYHeader.SCHEME.name(this.version)); + if(schemeHeader != null) + _request.setScheme(schemeHeader.value()); + updateState(State.HEADERS); handle(); break; @@ -183,15 +189,19 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem for (Headers.Header header : headers) { String name = header.name(); + + // Skip special SPDY headers, unless it's the "host" header + HTTPSPDYHeader specialHeader = HTTPSPDYHeader.from(version, name); + if (specialHeader != null) + { + if (specialHeader == HTTPSPDYHeader.HOST) + name = "host"; + else + continue; + } + switch (name) { - case "method": - case "version": - case "url": - { - // Skip request line headers - continue; - } case "connection": case "keep-alive": case "proxy-connection": @@ -266,8 +276,8 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem else { Headers headers = new Headers(); - headers.put("status", String.valueOf(status)); - headers.put("version", "HTTP/1.1"); + headers.put(HTTPSPDYHeader.STATUS.name(version), String.valueOf(status)); + headers.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1"); stream.reply(new ReplyInfo(headers, true)); } } @@ -395,39 +405,67 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem { if (!stream.isUnidirectional()) stream.reply(replyInfo); - if (replyInfo.getHeaders().get("status").value().startsWith("200") && !stream.isClosed()) + if (replyInfo.getHeaders().get(HTTPSPDYHeader.STATUS.name(version)).value().startsWith("200") && + !stream.isClosed()) { // We have a 200 OK with some content to send - Headers.Header scheme = headers.get("scheme"); - Headers.Header host = headers.get("host"); - Headers.Header url = headers.get("url"); - Set pushResources = pushStrategy.apply(stream, this.headers, replyInfo.getHeaders()); - String referrer = new StringBuilder(scheme.value()).append("://").append(host.value()).append(url.value()).toString(); - for (String pushURL : pushResources) + Headers.Header scheme = headers.get(HTTPSPDYHeader.SCHEME.name(version)); + Headers.Header host = headers.get(HTTPSPDYHeader.HOST.name(version)); + Headers.Header uri = headers.get(HTTPSPDYHeader.URI.name(version)); + Set pushResources = pushStrategy.apply(stream, headers, replyInfo.getHeaders()); + + for (String pushResourcePath : pushResources) { - final Headers pushHeaders = new Headers(); - pushHeaders.put("method", "GET"); - pushHeaders.put("url", pushURL); - pushHeaders.put("version", "HTTP/1.1"); - pushHeaders.put(scheme); - pushHeaders.put(host); - pushHeaders.put("referer", referrer); - // Remember support for gzip encoding - pushHeaders.put(headers.get("accept-encoding")); - stream.syn(new SynInfo(pushHeaders, false), getMaxIdleTime(), TimeUnit.MILLISECONDS, new Callback.Adapter() + final Headers requestHeaders = createRequestHeaders(scheme, host, uri, pushResourcePath); + final Headers pushHeaders = createPushHeaders(scheme, host, pushResourcePath); + + stream.syn(new SynInfo(pushHeaders, false), getMaxIdleTime(), TimeUnit.MILLISECONDS, new Handler.Adapter() { @Override public void completed(Stream pushStream) { - Synchronous pushConnection = new Synchronous(getHttpConnector(), getEndPoint(), getServer(), connection, pushStrategy, pushStream); - pushConnection.beginRequest(pushHeaders, true); + ServerHTTPSPDYAsyncConnection pushConnection = + new ServerHTTPSPDYAsyncConnection(getConnector(), getEndPoint(), getServer(), version, connection, pushStrategy, pushStream); + pushConnection.beginRequest(requestHeaders, true); } }); } } } + private Headers createRequestHeaders(Headers.Header scheme, Headers.Header host, Headers.Header uri, String pushResourcePath) + { + final Headers requestHeaders = new Headers(); + requestHeaders.put(HTTPSPDYHeader.METHOD.name(version), "GET"); + requestHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1"); + requestHeaders.put(scheme); + requestHeaders.put(host); + requestHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath); + String referrer = scheme.value() + "://" + host.value() + uri.value(); + requestHeaders.put("referer", referrer); + // Remember support for gzip encoding + requestHeaders.put(headers.get("accept-encoding")); + requestHeaders.put("x-spdy-push", "true"); + return requestHeaders; + } + + private Headers createPushHeaders(Headers.Header scheme, Headers.Header host, String pushResourcePath) + { + final Headers pushHeaders = new Headers(); + if (version == SPDY.V2) + pushHeaders.put(HTTPSPDYHeader.URI.name(version), scheme.value() + "://" + host.value() + pushResourcePath); + else + { + pushHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath); + pushHeaders.put(scheme); + pushHeaders.put(host); + } + pushHeaders.put(HTTPSPDYHeader.STATUS.name(version), "200"); + pushHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1"); + return pushHeaders; + } + private Buffer consumeContent(long maxIdleTime) throws IOException, InterruptedException { while (true) @@ -609,11 +647,11 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem { Headers headers = new Headers(); String version = "HTTP/1.1"; - headers.put("version", version); + headers.put(HTTPSPDYHeader.VERSION.name(ServerHTTPSPDYAsyncConnection.this.version), version); StringBuilder status = new StringBuilder().append(_status); if (_reason != null) status.append(" ").append(_reason.toString("UTF-8")); - headers.put("status", status.toString()); + headers.put(HTTPSPDYHeader.STATUS.name(ServerHTTPSPDYAsyncConnection.this.version), status.toString()); logger.debug("HTTP < {} {}", version, status); if (fields != null) @@ -629,19 +667,14 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem } // We have to query the HttpGenerator and its buffers to know - // whether there is content buffered; if so, send the data frame + // whether there is content buffered and update the generator state Buffer content = getContentBuffer(); reply(stream, new ReplyInfo(headers, content == null)); if (content != null) { - closed = allContentAdded || isAllContentWritten(); - ByteBuffer buffer = ByteBuffer.wrap(content.asArray()); - logger.debug("HTTP < {} bytes of content", buffer.remaining()); - // Send the data frame - stream.data(new ByteBufferDataInfo(buffer, closed)); + closed = false; // Update HttpGenerator fields so that they remain consistent - content.clear(); - _state = closed ? HttpGenerator.STATE_END : HttpGenerator.STATE_CONTENT; + _state = HttpGenerator.STATE_CONTENT; } else { @@ -655,7 +688,7 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem { if (_buffer != null && _buffer.length() > 0) return _buffer; - if (_bypass && _content != null && _content.length() > 0) + if (_content != null && _content.length() > 0) return _content; return null; } @@ -680,22 +713,48 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem @Override public void flush(long maxIdleTime) throws IOException { - while (_content != null && _content.length() > 0) + try { - _content.skip(_buffer.put(_content)); - ByteBuffer buffer = ByteBuffer.wrap(_buffer.asArray()); - logger.debug("HTTP < {} bytes of content", buffer.remaining()); - _buffer.clear(); - closed = _content.length() == 0 && _last; - stream.data(new ByteBufferDataInfo(buffer, closed)); - - boolean expired = !connection.getEndPoint().blockWritable(maxIdleTime); - if (expired) + Buffer content = getContentBuffer(); + while (content != null) { - stream.getSession().goAway(); - throw new EOFException("write timeout"); + DataInfo dataInfo = toDataInfo(content, closed); + logger.debug("HTTP < {} bytes of content", dataInfo.length()); + stream.data(dataInfo).get(maxIdleTime, TimeUnit.MILLISECONDS); + content.clear(); + _bypass = false; + content = getContentBuffer(); } } + catch (TimeoutException x) + { + stream.getSession().goAway(); + throw new EOFException("write timeout"); + } + catch (InterruptedException x) + { + throw new InterruptedIOException(); + } + catch (ExecutionException x) + { + throw new IOException(x.getCause()); + } + } + + private DataInfo toDataInfo(Buffer buffer, boolean close) + { + if (buffer instanceof ByteArrayBuffer) + return new BytesDataInfo(buffer.array(), buffer.getIndex(), buffer.length(), close); + + if (buffer instanceof NIOBuffer) + { + ByteBuffer byteBuffer = ((NIOBuffer)buffer).getByteBuffer(); + byteBuffer.limit(buffer.putIndex()); + byteBuffer.position(buffer.getIndex()); + return new ByteBufferDataInfo(byteBuffer, close); + } + + return new BytesDataInfo(buffer.asArray(), close); } @Override @@ -722,35 +781,17 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem Buffer content = getContentBuffer(); if (content != null) { - ByteBuffer buffer = ByteBuffer.wrap(content.asArray()); - logger.debug("HTTP < {} bytes of content", buffer.remaining()); - // Update HttpGenerator fields so that they remain consistent - content.clear(); + closed = true; _state = STATE_END; - // Send the data frame - stream.data(new ByteBufferDataInfo(buffer, true)); + flush(getMaxIdleTime()); } else if (!closed) { closed = true; _state = STATE_END; - // Send the data frame + // Send the last, empty, data frame stream.data(new ByteBufferDataInfo(ZERO_BYTES, true)); } } } - - private static class Synchronous extends ServerHTTPSPDYAsyncConnection - { - private Synchronous(Connector connector, AsyncEndPoint endPoint, Server server, SPDYAsyncConnection connection, PushStrategy pushStrategy, Stream stream) - { - super(connector, endPoint, server, connection, pushStrategy, stream); - } - - @Override - protected void execute(Runnable task) - { - task.run(); - } - } } diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java index 25b3d3b0447..7a5f381b3c3 100644 --- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy.http; @@ -20,8 +18,8 @@ import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import org.eclipse.jetty.io.AsyncEndPoint; -import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.spdy.ByteBufferPool; import org.eclipse.jetty.spdy.EmptyAsyncEndPoint; import org.eclipse.jetty.spdy.SPDYAsyncConnection; import org.eclipse.jetty.spdy.ServerSPDYAsyncConnectionFactory; @@ -52,7 +50,7 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect } @Override - protected ServerSessionFrameListener newServerSessionFrameListener(AsyncEndPoint endPoint, Object attachment) + protected ServerSessionFrameListener provideServerSessionFrameListener(AsyncEndPoint endPoint, Object attachment) { return new HTTPServerFrameListener(endPoint); } @@ -78,8 +76,8 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect logger.debug("Received {} on {}", synInfo, stream); HTTPSPDYAsyncEndPoint asyncEndPoint = new HTTPSPDYAsyncEndPoint(endPoint, stream); - ServerHTTPSPDYAsyncConnection connection = new ServerHTTPSPDYAsyncConnection(connector, - asyncEndPoint, connector.getServer(), (SPDYAsyncConnection)endPoint.getHttpChannel(), + ServerHTTPSPDYAsyncConnection connection = new ServerHTTPSPDYAsyncConnection(connector, asyncEndPoint, + connector.getServer(), getVersion(), (SPDYAsyncConnection)endPoint.getConnection(), pushStrategy, stream); asyncEndPoint.setConnection(connection); stream.setAttribute(CONNECTION_ATTRIBUTE, connection); diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/HTTPSPDYProxyConnector.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/HTTPSPDYProxyConnector.java new file mode 100644 index 00000000000..2827af05ceb --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/HTTPSPDYProxyConnector.java @@ -0,0 +1,39 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.proxy; + +import org.eclipse.jetty.spdy.ServerSPDYAsyncConnectionFactory; +import org.eclipse.jetty.spdy.api.SPDY; +import org.eclipse.jetty.spdy.http.AbstractHTTPSPDYServerConnector; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +public class HTTPSPDYProxyConnector extends AbstractHTTPSPDYServerConnector +{ + public HTTPSPDYProxyConnector(ProxyEngineSelector proxyEngineSelector) + { + this(proxyEngineSelector, null); + } + + public HTTPSPDYProxyConnector(ProxyEngineSelector proxyEngineSelector, SslContextFactory sslContextFactory) + { + super(proxyEngineSelector, sslContextFactory); + clearAsyncConnectionFactories(); + + putAsyncConnectionFactory("spdy/3", new ServerSPDYAsyncConnectionFactory(SPDY.V3, getByteBufferPool(), getExecutor(), getScheduler(), proxyEngineSelector)); + putAsyncConnectionFactory("spdy/2", new ServerSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), proxyEngineSelector)); + putAsyncConnectionFactory("http/1.1", new ProxyHTTPAsyncConnectionFactory(this, SPDY.V2, proxyEngineSelector)); + setDefaultAsyncConnectionFactory(getAsyncConnectionFactory("http/1.1")); + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngine.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngine.java new file mode 100644 index 00000000000..0a172614a56 --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngine.java @@ -0,0 +1,94 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.proxy; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.eclipse.jetty.spdy.api.Headers; +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.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + *

    {@link ProxyEngine} is the class for SPDY proxy functionalities that receives a SPDY request and converts it to + * any protocol to its server side.

    + *

    This class listens for SPDY events sent by clients; subclasses are responsible for translating + * these SPDY client events into appropriate events to forward to the server, in the appropriate + * protocol that is understood by the server.

    + */ +public abstract class ProxyEngine +{ + protected final Logger logger = Log.getLogger(getClass()); + private final String name; + + protected ProxyEngine() + { + this(name()); + } + + private static String name() + { + try + { + return InetAddress.getLocalHost().getHostName(); + } + catch (UnknownHostException x) + { + return "localhost"; + } + } + + public abstract StreamFrameListener proxy(Stream clientStream, SynInfo clientSynInfo, ProxyEngineSelector.ProxyServerInfo proxyServerInfo); + + protected ProxyEngine(String name) + { + this.name = name; + } + + public String getName() + { + return name; + } + + protected void addRequestProxyHeaders(Stream stream, Headers headers) + { + addViaHeader(headers); + String address = (String)stream.getSession().getAttribute("org.eclipse.jetty.spdy.remoteAddress"); + if (address != null) + headers.add("X-Forwarded-For", address); + } + + protected void addResponseProxyHeaders(Stream stream, Headers headers) + { + addViaHeader(headers); + } + + private void addViaHeader(Headers headers) + { + headers.add("Via", "http/1.1 " + getName()); + } + + protected void customizeRequestHeaders(Stream stream, Headers headers) + { + } + + protected void customizeResponseHeaders(Stream stream, Headers headers) + { + } + +} diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngineSelector.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngineSelector.java new file mode 100644 index 00000000000..52855be3927 --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngineSelector.java @@ -0,0 +1,169 @@ +package org.eclipse.jetty.spdy.proxy; + +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jetty.spdy.api.GoAwayInfo; +import org.eclipse.jetty.spdy.api.Headers; +import org.eclipse.jetty.spdy.api.PingInfo; +import org.eclipse.jetty.spdy.api.RstInfo; +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; +import org.eclipse.jetty.spdy.api.SynInfo; +import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener; +import org.eclipse.jetty.spdy.http.HTTPSPDYHeader; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + *

    {@link ProxyEngineSelector} is the main entry point for syn stream events of a jetty SPDY proxy. It receives the + * syn stream frames from the clients, checks if there's an appropriate {@link ProxyServerInfo} for the given target + * host and forwards the syn to a {@link ProxyEngine} for the protocol defined in {@link ProxyServerInfo}.

    + * + *

    If no {@link ProxyServerInfo} can be found for the given target host or no {@link ProxyEngine} can be found for + * the given protocol, it resets the client stream.

    + * + *

    This class also provides configuration for the proxy rules.

    + */ +public class ProxyEngineSelector extends ServerSessionFrameListener.Adapter +{ + protected final Logger logger = Log.getLogger(getClass()); + private final Map proxyInfos = new ConcurrentHashMap<>(); + private final Map proxyEngines = new ConcurrentHashMap<>(); + + @Override + public final StreamFrameListener onSyn(final Stream clientStream, SynInfo clientSynInfo) + { + logger.debug("C -> P {} on {}", clientSynInfo, clientStream); + + final Session clientSession = clientStream.getSession(); + short clientVersion = clientSession.getVersion(); + Headers headers = new Headers(clientSynInfo.getHeaders(), false); + + Headers.Header hostHeader = headers.get(HTTPSPDYHeader.HOST.name(clientVersion)); + if (hostHeader == null) + { + logger.debug("No host header found: " + headers); + rst(clientStream); + return null; + } + + String host = hostHeader.value(); + int colon = host.indexOf(':'); + if (colon >= 0) + host = host.substring(0, colon); + + ProxyServerInfo proxyServerInfo = getProxyServerInfo(host); + if (proxyServerInfo == null) + { + logger.debug("No matching ProxyServerInfo found for: " + host); + rst(clientStream); + return null; + } + + String protocol = proxyServerInfo.getProtocol(); + ProxyEngine proxyEngine = proxyEngines.get(protocol); + if (proxyEngine == null) + { + logger.debug("No matching ProxyEngine found for: " + protocol); + rst(clientStream); + return null; + } + + return proxyEngine.proxy(clientStream, clientSynInfo, proxyServerInfo); + } + + @Override + public void onPing(Session clientSession, PingInfo pingInfo) + { + // We do not know to which upstream server + // to send the PING so we just ignore it + } + + @Override + public void onGoAway(Session session, GoAwayInfo goAwayInfo) + { + // TODO: + } + + public Map getProxyEngines() + { + return new HashMap<>(proxyEngines); + } + + public void setProxyEngines(Map proxyEngines) + { + this.proxyEngines.clear(); + this.proxyEngines.putAll(proxyEngines); + } + + public ProxyEngine getProxyEngine(String protocol) + { + return proxyEngines.get(protocol); + } + + public void putProxyEngine(String protocol, ProxyEngine proxyEngine) + { + proxyEngines.put(protocol, proxyEngine); + } + + public Map getProxyServerInfos() + { + return new HashMap<>(proxyInfos); + } + + protected ProxyServerInfo getProxyServerInfo(String host) + { + return proxyInfos.get(host); + } + + public void setProxyServerInfos(Map proxyServerInfos) + { + this.proxyInfos.clear(); + this.proxyInfos.putAll(proxyServerInfos); + } + + public void putProxyServerInfo(String host, ProxyServerInfo proxyServerInfo) + { + proxyInfos.put(host, proxyServerInfo); + } + + private void rst(Stream stream) + { + RstInfo rstInfo = new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM); + stream.getSession().rst(rstInfo); + } + + public static class ProxyServerInfo + { + private final String protocol; + private final String host; + private final InetSocketAddress address; + + public ProxyServerInfo(String protocol, String host, int port) + { + this.protocol = protocol; + this.host = host; + this.address = new InetSocketAddress(host, port); + } + + public String getProtocol() + { + return protocol; + } + + public String getHost() + { + return host; + } + + public InetSocketAddress getAddress() + { + return address; + } + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPAsyncConnectionFactory.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPAsyncConnectionFactory.java new file mode 100644 index 00000000000..f73c7e819bb --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPAsyncConnectionFactory.java @@ -0,0 +1,41 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.proxy; + +import java.nio.channels.SocketChannel; + +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.nio.AsyncConnection; +import org.eclipse.jetty.spdy.SPDYServerConnector; +import org.eclipse.jetty.spdy.http.ServerHTTPAsyncConnectionFactory; + +public class ProxyHTTPAsyncConnectionFactory extends ServerHTTPAsyncConnectionFactory +{ + private final short version; + private final ProxyEngineSelector proxyEngineSelector; + + public ProxyHTTPAsyncConnectionFactory(SPDYServerConnector connector, short version, ProxyEngineSelector proxyEngineSelector) + { + super(connector); + this.version = version; + this.proxyEngineSelector = proxyEngineSelector; + } + + @Override + public AsyncConnection newAsyncConnection(SocketChannel channel, AsyncEndPoint endPoint, Object attachment) + { + return new ProxyHTTPSPDYAsyncConnection(getConnector(), endPoint, version, proxyEngineSelector); + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPSPDYAsyncConnection.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPSPDYAsyncConnection.java new file mode 100644 index 00000000000..92c318479a6 --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPSPDYAsyncConnection.java @@ -0,0 +1,337 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.proxy; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpGenerator; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.DirectNIOBuffer; +import org.eclipse.jetty.io.nio.IndirectNIOBuffer; +import org.eclipse.jetty.io.nio.NIOBuffer; +import org.eclipse.jetty.server.AsyncHttpConnection; +import org.eclipse.jetty.spdy.ISession; +import org.eclipse.jetty.spdy.IStream; +import org.eclipse.jetty.spdy.SPDYServerConnector; +import org.eclipse.jetty.spdy.StandardSession; +import org.eclipse.jetty.spdy.StandardStream; +import org.eclipse.jetty.spdy.api.ByteBufferDataInfo; +import org.eclipse.jetty.spdy.api.BytesDataInfo; +import org.eclipse.jetty.spdy.api.DataInfo; +import org.eclipse.jetty.spdy.api.GoAwayInfo; +import org.eclipse.jetty.spdy.api.Handler; +import org.eclipse.jetty.spdy.api.Headers; +import org.eclipse.jetty.spdy.api.HeadersInfo; +import org.eclipse.jetty.spdy.api.ReplyInfo; +import org.eclipse.jetty.spdy.api.RstInfo; +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.http.HTTPSPDYHeader; + +public class ProxyHTTPSPDYAsyncConnection extends AsyncHttpConnection +{ + private final Headers headers = new Headers(); + private final short version; + private final ProxyEngineSelector proxyEngineSelector; + private final HttpGenerator generator; + private final ISession session; + private HTTPStream stream; + private Buffer content; + + public ProxyHTTPSPDYAsyncConnection(SPDYServerConnector connector, EndPoint endPoint, short version, ProxyEngineSelector proxyEngineSelector) + { + super(connector, endPoint, connector.getServer()); + this.version = version; + this.proxyEngineSelector = proxyEngineSelector; + this.generator = (HttpGenerator)_generator; + this.session = new HTTPSession(version, connector); + this.session.setAttribute("org.eclipse.jetty.spdy.remoteAddress", endPoint.getRemoteAddr()); + } + + @Override + public AsyncEndPoint getEndPoint() + { + return (AsyncEndPoint)super.getEndPoint(); + } + + @Override + protected void startRequest(Buffer method, Buffer uri, Buffer httpVersion) throws IOException + { + SPDYServerConnector connector = (SPDYServerConnector)getConnector(); + String scheme = connector.getSslContextFactory() != null ? "https" : "http"; + headers.put(HTTPSPDYHeader.SCHEME.name(version), scheme); + headers.put(HTTPSPDYHeader.METHOD.name(version), method.toString("UTF-8")); + headers.put(HTTPSPDYHeader.URI.name(version), uri.toString("UTF-8")); + headers.put(HTTPSPDYHeader.VERSION.name(version), httpVersion.toString("UTF-8")); + } + + @Override + protected void parsedHeader(Buffer name, Buffer value) throws IOException + { + String headerName = name.toString("UTF-8").toLowerCase(); + String headerValue = value.toString("UTF-8"); + switch (headerName) + { + case "host": + headers.put(HTTPSPDYHeader.HOST.name(version), headerValue); + break; + default: + headers.put(headerName, headerValue); + break; + } + } + + @Override + protected void headerComplete() throws IOException + { + } + + @Override + protected void content(Buffer buffer) throws IOException + { + if (content == null) + { + stream = syn(false); + content = buffer; + } + else + { + stream.getStreamFrameListener().onData(stream, toDataInfo(buffer, false)); + } + } + + @Override + public void messageComplete(long contentLength) throws IOException + { + if (stream == null) + { + assert content == null; + if (headers.isEmpty()) + proxyEngineSelector.onGoAway(session, new GoAwayInfo(0, SessionStatus.OK)); + else + syn(true); + } + else + { + stream.getStreamFrameListener().onData(stream, toDataInfo(content, true)); + } + headers.clear(); + stream = null; + content = null; + } + + private HTTPStream syn(boolean close) + { + HTTPStream stream = new HTTPStream(1, (byte)0, session, null); + StreamFrameListener streamFrameListener = proxyEngineSelector.onSyn(stream, new SynInfo(headers, close)); + stream.setStreamFrameListener(streamFrameListener); + return stream; + } + + private DataInfo toDataInfo(Buffer buffer, boolean close) + { + if (buffer instanceof ByteArrayBuffer) + return new BytesDataInfo(buffer.array(), buffer.getIndex(), buffer.length(), close); + + if (buffer instanceof NIOBuffer) + { + ByteBuffer byteBuffer = ((NIOBuffer)buffer).getByteBuffer(); + byteBuffer.limit(buffer.putIndex()); + byteBuffer.position(buffer.getIndex()); + return new ByteBufferDataInfo(byteBuffer, close); + } + + return new BytesDataInfo(buffer.asArray(), close); + } + + private class HTTPSession extends StandardSession + { + private HTTPSession(short version, SPDYServerConnector connector) + { + super(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), null, null, 1, proxyEngineSelector, null, null); + } + + @Override + public void rst(RstInfo rstInfo, long timeout, TimeUnit unit, Handler handler) + { + // Not much we can do in HTTP land: just close the connection + goAway(timeout, unit, handler); + } + + @Override + public void goAway(long timeout, TimeUnit unit, Handler handler) + { + try + { + getEndPoint().close(); + handler.completed(null); + } + catch (IOException x) + { + handler.failed(null, x); + } + } + } + + /** + *

    This stream will convert the SPDY invocations performed by the proxy into HTTP to be sent to the client.

    + */ + private class HTTPStream extends StandardStream + { + private final Pattern statusRegexp = Pattern.compile("(\\d{3})\\s*(.*)"); + + private HTTPStream(int id, byte priority, ISession session, IStream associatedStream) + { + super(id, priority, session, associatedStream); + } + + @Override + public void syn(SynInfo synInfo, long timeout, TimeUnit unit, Handler handler) + { + // HTTP does not support pushed streams + handler.completed(new HTTPPushStream(2, getPriority(), getSession(), this)); + } + + @Override + public void headers(HeadersInfo headersInfo, long timeout, TimeUnit unit, Handler handler) + { + // TODO + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + @Override + public void reply(ReplyInfo replyInfo, long timeout, TimeUnit unit, Handler handler) + { + try + { + Headers headers = new Headers(replyInfo.getHeaders(), false); + + 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); + generator.setResponse(code, reason); + + String httpVersion = headers.remove(HTTPSPDYHeader.VERSION.name(version)).value(); + generator.setVersion(Integer.parseInt(httpVersion.replaceAll("\\D", ""))); + + Headers.Header host = headers.remove(HTTPSPDYHeader.HOST.name(version)); + if (host != null) + headers.put("host", host.value()); + + HttpFields fields = new HttpFields(); + for (Headers.Header header : headers) + { + String name = camelize(header.name()); + fields.put(name, header.value()); + } + generator.completeHeader(fields, replyInfo.isClose()); + + if (replyInfo.isClose()) + complete(); + + handler.completed(null); + } + catch (IOException x) + { + handler.failed(null, x); + } + } + + private String camelize(String name) + { + char[] chars = name.toCharArray(); + chars[0] = Character.toUpperCase(chars[0]); + + for (int i = 0; i < chars.length; ++i) + { + char c = chars[i]; + int j = i + 1; + if (c == '-' && j < chars.length) + chars[j] = Character.toUpperCase(chars[j]); + } + return new String(chars); + } + + @Override + public void data(DataInfo dataInfo, long timeout, TimeUnit unit, Handler handler) + { + try + { + // Data buffer must be copied, as the ByteBuffer is pooled + ByteBuffer byteBuffer = dataInfo.asByteBuffer(false); + + Buffer buffer = byteBuffer.isDirect() ? + new DirectNIOBuffer(byteBuffer, false) : + new IndirectNIOBuffer(byteBuffer, false); + + generator.addContent(buffer, dataInfo.isClose()); + generator.flush(unit.toMillis(timeout)); + + if (dataInfo.isClose()) + complete(); + + handler.completed(null); + } + catch (IOException x) + { + handler.failed(null, x); + } + } + + private void complete() throws IOException + { + generator.complete(); + // We need to call asyncDispatch() as if the HTTP request + // has been suspended and now we complete the response + getEndPoint().asyncDispatch(); + } + } + + private class HTTPPushStream extends StandardStream + { + private HTTPPushStream(int id, byte priority, ISession session, IStream associatedStream) + { + super(id, priority, session, associatedStream); + } + + @Override + public void headers(HeadersInfo headersInfo, long timeout, TimeUnit unit, Handler handler) + { + // Ignore pushed headers + handler.completed(null); + } + + @Override + public void data(DataInfo dataInfo, long timeout, TimeUnit unit, Handler handler) + { + // Ignore pushed data + handler.completed(null); + } + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/SPDYProxyEngine.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/SPDYProxyEngine.java new file mode 100644 index 00000000000..c01af3ef0f8 --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/SPDYProxyEngine.java @@ -0,0 +1,514 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.proxy; + +import java.net.InetSocketAddress; +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.spdy.SPDYClient; +import org.eclipse.jetty.spdy.api.ByteBufferDataInfo; +import org.eclipse.jetty.spdy.api.DataInfo; +import org.eclipse.jetty.spdy.api.GoAwayInfo; +import org.eclipse.jetty.spdy.api.Handler; +import org.eclipse.jetty.spdy.api.Headers; +import org.eclipse.jetty.spdy.api.HeadersInfo; +import org.eclipse.jetty.spdy.api.ReplyInfo; +import org.eclipse.jetty.spdy.api.RstInfo; +import org.eclipse.jetty.spdy.api.SPDY; +import org.eclipse.jetty.spdy.api.Session; +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.StreamStatus; +import org.eclipse.jetty.spdy.api.SynInfo; +import org.eclipse.jetty.spdy.http.HTTPSPDYHeader; + +/** + *

    {@link SPDYProxyEngine} implements a SPDY to SPDY proxy, that is, converts SPDY events received by + * clients into SPDY events for the servers.

    + */ +public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener +{ + private static final String STREAM_HANDLER_ATTRIBUTE = "org.eclipse.jetty.spdy.http.proxy.streamHandler"; + private static final String CLIENT_STREAM_ATTRIBUTE = "org.eclipse.jetty.spdy.http.proxy.clientStream"; + + private final ConcurrentMap serverSessions = new ConcurrentHashMap<>(); + private final SessionFrameListener sessionListener = new ProxySessionFrameListener(); + private final SPDYClient.Factory factory; + private volatile long connectTimeout = 15000; + private volatile long timeout = 60000; + + public SPDYProxyEngine(SPDYClient.Factory factory) + { + this.factory = factory; + } + + public long getConnectTimeout() + { + return connectTimeout; + } + + public void setConnectTimeout(long connectTimeout) + { + this.connectTimeout = connectTimeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public StreamFrameListener proxy(final Stream clientStream, SynInfo clientSynInfo, ProxyEngineSelector.ProxyServerInfo proxyServerInfo) + { + Headers headers = new Headers(clientSynInfo.getHeaders(), false); + + short serverVersion = getVersion(proxyServerInfo.getProtocol()); + InetSocketAddress address = proxyServerInfo.getAddress(); + Session serverSession = produceSession(proxyServerInfo.getHost(), serverVersion, address); + if (serverSession == null) + { + rst(clientStream); + return null; + } + + final Session clientSession = clientStream.getSession(); + + addRequestProxyHeaders(clientStream, headers); + customizeRequestHeaders(clientStream, headers); + convert(clientSession.getVersion(), serverVersion, headers); + + SynInfo serverSynInfo = new SynInfo(headers, clientSynInfo.isClose()); + StreamFrameListener listener = new ProxyStreamFrameListener(clientStream); + StreamHandler handler = new StreamHandler(clientStream, serverSynInfo); + clientStream.setAttribute(STREAM_HANDLER_ATTRIBUTE, handler); + serverSession.syn(serverSynInfo, listener, timeout, TimeUnit.MILLISECONDS, handler); + return this; + } + + private static short getVersion(String protocol) + { + switch (protocol) + { + case "spdy/2": + return SPDY.V2; + case "spdy/3": + return SPDY.V3; + default: + throw new IllegalArgumentException("Procotol: " + protocol + " is not a known SPDY protocol"); + } + } + + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + // Servers do not receive replies + } + + @Override + public void onHeaders(Stream stream, HeadersInfo headersInfo) + { + // TODO + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + @Override + public void onData(Stream clientStream, final DataInfo clientDataInfo) + { + logger.debug("C -> P {} on {}", clientDataInfo, clientStream); + + ByteBufferDataInfo serverDataInfo = new ByteBufferDataInfo(clientDataInfo.asByteBuffer(false), clientDataInfo.isClose()) + { + @Override + public void consume(int delta) + { + super.consume(delta); + clientDataInfo.consume(delta); + } + }; + + StreamHandler streamHandler = (StreamHandler)clientStream.getAttribute(STREAM_HANDLER_ATTRIBUTE); + streamHandler.data(serverDataInfo); + } + + private Session produceSession(String host, short version, InetSocketAddress address) + { + try + { + Session session = serverSessions.get(host); + if (session == null) + { + SPDYClient client = factory.newSPDYClient(version); + session = client.connect(address, sessionListener).get(getConnectTimeout(), TimeUnit.MILLISECONDS); + logger.debug("Proxy session connected to {}", address); + Session existing = serverSessions.putIfAbsent(host, session); + if (existing != null) + { + session.goAway(getTimeout(), TimeUnit.MILLISECONDS, new Handler.Adapter()); + session = existing; + } + } + return session; + } + catch (Exception x) + { + logger.debug(x); + return null; + } + } + + private void convert(short fromVersion, short toVersion, Headers headers) + { + if (fromVersion != toVersion) + { + for (HTTPSPDYHeader httpHeader : HTTPSPDYHeader.values()) + { + Headers.Header header = headers.remove(httpHeader.name(fromVersion)); + if (header != null) + { + String toName = httpHeader.name(toVersion); + for (String value : header.values()) + headers.add(toName, value); + } + } + } + } + + private void rst(Stream stream) + { + RstInfo rstInfo = new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM); + stream.getSession().rst(rstInfo, getTimeout(), TimeUnit.MILLISECONDS, new Handler.Adapter()); + } + + private class ProxyStreamFrameListener extends StreamFrameListener.Adapter + { + private final Stream clientStream; + private volatile ReplyInfo replyInfo; + + public ProxyStreamFrameListener(Stream clientStream) + { + this.clientStream = clientStream; + } + + @Override + public void onReply(final Stream stream, ReplyInfo replyInfo) + { + logger.debug("S -> P {} on {}", replyInfo, stream); + + short serverVersion = stream.getSession().getVersion(); + Headers headers = new Headers(replyInfo.getHeaders(), false); + + addResponseProxyHeaders(stream, headers); + customizeResponseHeaders(stream, headers); + short clientVersion = this.clientStream.getSession().getVersion(); + convert(serverVersion, clientVersion, headers); + + this.replyInfo = new ReplyInfo(headers, replyInfo.isClose()); + if (replyInfo.isClose()) + reply(stream); + } + + @Override + public void onHeaders(Stream stream, HeadersInfo headersInfo) + { + // TODO + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + @Override + public void onData(final Stream stream, final DataInfo dataInfo) + { + logger.debug("S -> P {} on {}", dataInfo, stream); + + if (replyInfo != null) + { + if (dataInfo.isClose()) + replyInfo.getHeaders().put("content-length", String.valueOf(dataInfo.available())); + reply(stream); + } + data(stream, dataInfo); + } + + private void reply(final Stream stream) + { + final ReplyInfo replyInfo = this.replyInfo; + this.replyInfo = null; + clientStream.reply(replyInfo, getTimeout(), TimeUnit.MILLISECONDS, new Handler() + { + @Override + public void completed(Void context) + { + logger.debug("P -> C {} from {} to {}", replyInfo, stream, clientStream); + } + + @Override + public void failed(Void context, Throwable x) + { + logger.debug(x); + rst(clientStream); + } + }); + } + + private void data(final Stream stream, final DataInfo dataInfo) + { + clientStream.data(dataInfo, getTimeout(), TimeUnit.MILLISECONDS, new Handler() + { + @Override + public void completed(Void context) + { + dataInfo.consume(dataInfo.length()); + logger.debug("P -> C {} from {} to {}", dataInfo, stream, clientStream); + } + + @Override + public void failed(Void context, Throwable x) + { + logger.debug(x); + rst(clientStream); + } + }); + } + } + + /** + *

    {@link StreamHandler} implements the forwarding of DATA frames from the client to the server.

    + *

    Instances of this class buffer DATA frames sent by clients and send them to the server. + * The buffering happens between the send of the SYN_STREAM to the server (where DATA frames may arrive + * from the client before the SYN_STREAM has been fully sent), and between DATA frames, if the client + * is a fast producer and the server a slow consumer, or if the client is a SPDY v2 client (and hence + * without flow control) while the server is a SPDY v3 server (and hence with flow control).

    + */ + private class StreamHandler implements Handler + { + private final Queue queue = new LinkedList<>(); + private final Stream clientStream; + private final SynInfo serverSynInfo; + private Stream serverStream; + + private StreamHandler(Stream clientStream, SynInfo serverSynInfo) + { + this.clientStream = clientStream; + this.serverSynInfo = serverSynInfo; + } + + @Override + public void completed(Stream serverStream) + { + logger.debug("P -> S {} from {} to {}", serverSynInfo, clientStream, serverStream); + + serverStream.setAttribute(CLIENT_STREAM_ATTRIBUTE, clientStream); + + DataInfoHandler dataInfoHandler; + synchronized (queue) + { + this.serverStream = serverStream; + dataInfoHandler = queue.peek(); + if (dataInfoHandler != null) + { + if (dataInfoHandler.flushing) + { + logger.debug("SYN completed, flushing {}, queue size {}", dataInfoHandler.dataInfo, queue.size()); + dataInfoHandler = null; + } + else + { + dataInfoHandler.flushing = true; + logger.debug("SYN completed, queue size {}", queue.size()); + } + } + else + { + logger.debug("SYN completed, queue empty"); + } + } + if (dataInfoHandler != null) + flush(serverStream, dataInfoHandler); + } + + @Override + public void failed(Stream serverStream, Throwable x) + { + logger.debug(x); + rst(clientStream); + } + + public void data(DataInfo dataInfo) + { + Stream serverStream; + DataInfoHandler dataInfoHandler = null; + DataInfoHandler item = new DataInfoHandler(dataInfo); + synchronized (queue) + { + queue.offer(item); + serverStream = this.serverStream; + if (serverStream != null) + { + dataInfoHandler = queue.peek(); + if (dataInfoHandler.flushing) + { + logger.debug("Queued {}, flushing {}, queue size {}", dataInfo, dataInfoHandler.dataInfo, queue.size()); + serverStream = null; + } + else + { + dataInfoHandler.flushing = true; + logger.debug("Queued {}, queue size {}", dataInfo, queue.size()); + } + } + else + { + logger.debug("Queued {}, SYN incomplete, queue size {}", dataInfo, queue.size()); + } + } + if (serverStream != null) + flush(serverStream, dataInfoHandler); + } + + private void flush(Stream serverStream, DataInfoHandler dataInfoHandler) + { + logger.debug("P -> S {} on {}", dataInfoHandler.dataInfo, serverStream); + serverStream.data(dataInfoHandler.dataInfo, getTimeout(), TimeUnit.MILLISECONDS, dataInfoHandler); + } + + private class DataInfoHandler implements Handler + { + private final DataInfo dataInfo; + private boolean flushing; + + private DataInfoHandler(DataInfo dataInfo) + { + this.dataInfo = dataInfo; + } + + @Override + public void completed(Void context) + { + Stream serverStream; + DataInfoHandler dataInfoHandler; + synchronized (queue) + { + serverStream = StreamHandler.this.serverStream; + assert serverStream != null; + dataInfoHandler = queue.poll(); + assert dataInfoHandler == this; + dataInfoHandler = queue.peek(); + if (dataInfoHandler != null) + { + assert !dataInfoHandler.flushing; + dataInfoHandler.flushing = true; + logger.debug("Completed {}, queue size {}", dataInfo, queue.size()); + } + else + { + logger.debug("Completed {}, queue empty", dataInfo); + } + } + if (dataInfoHandler != null) + flush(serverStream, dataInfoHandler); + } + + @Override + public void failed(Void context, Throwable x) + { + logger.debug(x); + rst(clientStream); + } + } + } + + private class ProxySessionFrameListener extends SessionFrameListener.Adapter implements StreamFrameListener + { + @Override + public StreamFrameListener onSyn(Stream serverStream, SynInfo serverSynInfo) + { + logger.debug("S -> P pushed {} on {}", serverSynInfo, serverStream); + + Headers headers = new Headers(serverSynInfo.getHeaders(), false); + + addResponseProxyHeaders(serverStream, headers); + customizeResponseHeaders(serverStream, headers); + Stream clientStream = (Stream)serverStream.getAssociatedStream().getAttribute(CLIENT_STREAM_ATTRIBUTE); + convert(serverStream.getSession().getVersion(), clientStream.getSession().getVersion(), headers); + + StreamHandler handler = new StreamHandler(clientStream, serverSynInfo); + serverStream.setAttribute(STREAM_HANDLER_ATTRIBUTE, handler); + clientStream.syn(new SynInfo(headers, serverSynInfo.isClose()), getTimeout(), TimeUnit.MILLISECONDS, handler); + + return this; + } + + @Override + public void onRst(Session serverSession, RstInfo serverRstInfo) + { + Stream serverStream = serverSession.getStream(serverRstInfo.getStreamId()); + if (serverStream != null) + { + Stream clientStream = (Stream)serverStream.getAttribute(CLIENT_STREAM_ATTRIBUTE); + if (clientStream != null) + { + Session clientSession = clientStream.getSession(); + RstInfo clientRstInfo = new RstInfo(clientStream.getId(), serverRstInfo.getStreamStatus()); + clientSession.rst(clientRstInfo, getTimeout(), TimeUnit.MILLISECONDS, new Handler.Adapter()); + } + } + } + + @Override + public void onGoAway(Session serverSession, GoAwayInfo goAwayInfo) + { + serverSessions.values().remove(serverSession); + } + + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + // Push streams never send a reply + } + + @Override + public void onHeaders(Stream stream, HeadersInfo headersInfo) + { + throw new UnsupportedOperationException(); //TODO + } + + @Override + public void onData(Stream serverStream, final DataInfo serverDataInfo) + { + logger.debug("S -> P pushed {} on {}", serverDataInfo, serverStream); + + ByteBufferDataInfo clientDataInfo = new ByteBufferDataInfo(serverDataInfo.asByteBuffer(false), serverDataInfo.isClose()) + { + @Override + public void consume(int delta) + { + super.consume(delta); + serverDataInfo.consume(delta); + } + }; + + StreamHandler handler = (StreamHandler)serverStream.getAttribute(STREAM_HANDLER_ATTRIBUTE); + handler.data(clientDataInfo); + } + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYTest.java index 84dd3271a5b..a5c4c280439 100644 --- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYTest.java +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYTest.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy.http; @@ -54,9 +52,14 @@ public abstract class AbstractHTTPSPDYTest protected SPDYServerConnector connector; protected InetSocketAddress startHTTPServer(Handler handler) throws Exception + { + return startHTTPServer(SPDY.V2, handler); + } + + protected InetSocketAddress startHTTPServer(short version, Handler handler) throws Exception { server = new Server(); - connector = newHTTPSPDYServerConnector(); + connector = newHTTPSPDYServerConnector(version); connector.setPort(0); server.addConnector(connector); server.setHandler(handler); @@ -64,20 +67,21 @@ public abstract class AbstractHTTPSPDYTest return new InetSocketAddress("localhost", connector.getLocalPort()); } - protected SPDYServerConnector newHTTPSPDYServerConnector() + protected SPDYServerConnector newHTTPSPDYServerConnector(short version) { // For these tests, we need the connector to speak HTTP over SPDY even in non-SSL - return new HTTPSPDYServerConnector() - { - @Override - protected AsyncConnectionFactory getDefaultAsyncConnectionFactory() - { - return new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), this, new PushStrategy.None()); - } - }; + SPDYServerConnector connector = new HTTPSPDYServerConnector(); + AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, new PushStrategy.None()); + connector.setDefaultAsyncConnectionFactory(defaultFactory); + return connector; } protected Session startClient(InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception + { + return startClient(SPDY.V2, socketAddress, listener); + } + + protected Session startClient(short version, InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception { if (clientFactory == null) { @@ -86,7 +90,7 @@ public abstract class AbstractHTTPSPDYTest clientFactory = newSPDYClientFactory(threadPool); clientFactory.start(); } - return clientFactory.newSPDYClient(SPDY.V2).connect(socketAddress, listener).get(5, TimeUnit.SECONDS); + return clientFactory.newSPDYClient(version).connect(socketAddress, listener).get(5, TimeUnit.SECONDS); } protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool) @@ -107,4 +111,9 @@ public abstract class AbstractHTTPSPDYTest server.join(); } } + + protected short version() + { + return SPDY.V2; + } } diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ConcurrentStreamsTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ConcurrentStreamsTest.java index a26555004b9..56cb5ef7c75 100644 --- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ConcurrentStreamsTest.java +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ConcurrentStreamsTest.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy.http; @@ -73,10 +71,10 @@ public class ConcurrentStreamsTest extends AbstractHTTPSPDYTest // Perform slow request. This will wait on server side until the fast request wakes it up Headers headers = new Headers(); - headers.put("method", "GET"); - headers.put("url", "/slow"); - headers.put("version", "HTTP/1.1"); - headers.put("host", "localhost:" + connector.getLocalPort()); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/slow"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); final CountDownLatch slowClientLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { @@ -91,10 +89,10 @@ public class ConcurrentStreamsTest extends AbstractHTTPSPDYTest // Perform the fast request. This will wake up the slow request headers.clear(); - headers.put("method", "GET"); - headers.put("url", "/fast"); - headers.put("version", "HTTP/1.1"); - headers.put("host", "localhost:" + connector.getLocalPort()); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/fast"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); final CountDownLatch fastClientLatch = new CountDownLatch(1); session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() { diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ProtocolNegotiationTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ProtocolNegotiationTest.java index e9cca9d33b5..5de439b2066 100644 --- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ProtocolNegotiationTest.java +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ProtocolNegotiationTest.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy.http; @@ -27,7 +25,6 @@ import javax.net.ssl.SSLSocket; import org.eclipse.jetty.npn.NextProtoNego; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.spdy.AsyncConnectionFactory; import org.eclipse.jetty.spdy.SPDYServerConnector; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.Assert; @@ -109,9 +106,8 @@ public class ProtocolNegotiationTest public String selectProtocol(List strings) { Assert.assertNotNull(strings); - Assert.assertEquals(1, strings.size()); - String protocol = strings.get(0); - Assert.assertEquals("http/1.1", protocol); + String protocol = "http/1.1"; + Assert.assertTrue(strings.contains(protocol)); return protocol; } }); @@ -166,11 +162,11 @@ public class ProtocolNegotiationTest public String selectProtocol(List strings) { Assert.assertNotNull(strings); - Assert.assertEquals(2, strings.size()); - String spdyProtocol = strings.get(0); - Assert.assertEquals("spdy/2", spdyProtocol); - String httpProtocol = strings.get(1); - Assert.assertEquals("http/1.1", httpProtocol); + String spdyProtocol = "spdy/2"; + Assert.assertTrue(strings.contains(spdyProtocol)); + String httpProtocol = "http/1.1"; + Assert.assertTrue(strings.contains(httpProtocol)); + Assert.assertTrue(strings.indexOf(spdyProtocol) < strings.indexOf(httpProtocol)); return httpProtocol; } }); @@ -198,14 +194,9 @@ public class ProtocolNegotiationTest @Test public void testServerAdvertisingSPDYAndHTTPSpeaksDefaultProtocolWhenNPNMissing() throws Exception { - InetSocketAddress address = startServer(new SPDYServerConnector(null, newSslContextFactory()) - { - @Override - protected AsyncConnectionFactory getDefaultAsyncConnectionFactory() - { - return new ServerHTTPAsyncConnectionFactory(connector); - } - }); + SPDYServerConnector connector = new SPDYServerConnector(null, newSslContextFactory()); + connector.setDefaultAsyncConnectionFactory(new ServerHTTPAsyncConnectionFactory(connector)); + InetSocketAddress address = startServer(connector); connector.putAsyncConnectionFactory("http/1.1", new ServerHTTPAsyncConnectionFactory(connector)); SslContextFactory sslContextFactory = newSslContextFactory(); diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/PushStrategyBenchmarkTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/PushStrategyBenchmarkTest.java new file mode 100644 index 00000000000..9486157de24 --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/PushStrategyBenchmarkTest.java @@ -0,0 +1,395 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + + +package org.eclipse.jetty.spdy.http; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.Assert; +import org.eclipse.jetty.client.Address; +import org.eclipse.jetty.client.ContentExchange; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.spdy.AsyncConnectionFactory; +import org.eclipse.jetty.spdy.api.DataInfo; +import org.eclipse.jetty.spdy.api.Headers; +import org.eclipse.jetty.spdy.api.SPDY; +import org.eclipse.jetty.spdy.api.Session; +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.junit.Test; + +public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest +{ + // Sample resources size from webtide.com home page + private final int[] htmlResources = new int[] + {8 * 1024}; + private final int[] cssResources = new int[] + {12 * 1024, 2 * 1024}; + private final int[] jsResources = new int[] + {75 * 1024, 24 * 1024, 36 * 1024}; + private final int[] pngResources = new int[] + {1024, 45 * 1024, 6 * 1024, 2 * 1024, 2 * 1024, 2 * 1024, 3 * 1024, 512, 512, 19 * 1024, 512, 128, 32}; + private final Set pushedResources = Collections.newSetFromMap(new ConcurrentHashMap()); + private final AtomicReference latch = new AtomicReference<>(); + private final long roundtrip = 100; + private final int runs = 10; + + @Test + public void benchmarkPushStrategy() throws Exception + { + InetSocketAddress address = startHTTPServer(version(), new PushStrategyBenchmarkHandler()); + + // Plain HTTP + AsyncConnectionFactory dacf = new ServerHTTPAsyncConnectionFactory(connector); + connector.setDefaultAsyncConnectionFactory(dacf); + HttpClient httpClient = new HttpClient(); + // Simulate browsers, that open 6 connection per origin + httpClient.setMaxConnectionsPerAddress(6); + httpClient.start(); + benchmarkHTTP(httpClient); + httpClient.stop(); + + // First push strategy + PushStrategy pushStrategy = new PushStrategy.None(); + dacf = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy); + connector.setDefaultAsyncConnectionFactory(dacf); + Session session = startClient(version(), address, new ClientSessionFrameListener()); + benchmarkSPDY(pushStrategy, session); + session.goAway().get(5, TimeUnit.SECONDS); + + // Second push strategy + pushStrategy = new ReferrerPushStrategy(); + dacf = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy); + connector.setDefaultAsyncConnectionFactory(dacf); + session = startClient(version(), address, new ClientSessionFrameListener()); + benchmarkSPDY(pushStrategy, session); + session.goAway().get(5, TimeUnit.SECONDS); + } + + private void benchmarkHTTP(HttpClient httpClient) throws Exception + { + // Warm up + performHTTPRequests(httpClient); + performHTTPRequests(httpClient); + + long total = 0; + for (int i = 0; i < runs; ++i) + { + long begin = System.nanoTime(); + int requests = performHTTPRequests(httpClient); + long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - begin); + total += elapsed; + System.err.printf("HTTP: run %d, %d request(s), roundtrip delay %d ms, elapsed = %d%n", + i, requests, roundtrip, elapsed); + } + System.err.printf("HTTP: roundtrip delay %d ms, average = %d%n%n", + roundtrip, total / runs); + } + + private int performHTTPRequests(HttpClient httpClient) throws Exception + { + int result = 0; + + for (int j = 0; j < htmlResources.length; ++j) + { + latch.set(new CountDownLatch(cssResources.length + jsResources.length + pngResources.length)); + + String primaryPath = "/" + j + ".html"; + String referrer = new StringBuilder("http://localhost:").append(connector.getLocalPort()).append(primaryPath).toString(); + ContentExchange exchange = new ContentExchange(true); + exchange.setMethod("GET"); + exchange.setRequestURI(primaryPath); + exchange.setVersion("HTTP/1.1"); + exchange.setAddress(new Address("localhost", connector.getLocalPort())); + exchange.setRequestHeader("Host", "localhost:" + connector.getLocalPort()); + ++result; + httpClient.send(exchange); + Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone()); + Assert.assertEquals(200, exchange.getResponseStatus()); + + for (int i = 0; i < cssResources.length; ++i) + { + String path = "/" + i + ".css"; + exchange = createExchangeWithReferrer(referrer, path); + ++result; + httpClient.send(exchange); + } + for (int i = 0; i < jsResources.length; ++i) + { + String path = "/" + i + ".js"; + exchange = createExchangeWithReferrer(referrer, path); + ++result; + httpClient.send(exchange); + } + for (int i = 0; i < pngResources.length; ++i) + { + String path = "/" + i + ".png"; + exchange = createExchangeWithReferrer(referrer, path); + ++result; + httpClient.send(exchange); + } + + Assert.assertTrue(latch.get().await(5, TimeUnit.SECONDS)); + } + + return result; + } + + private ContentExchange createExchangeWithReferrer(String referrer, String path) + { + ContentExchange exchange; + exchange = new TestExchange(); + exchange.setMethod("GET"); + exchange.setRequestURI(path); + exchange.setVersion("HTTP/1.1"); + exchange.setAddress(new Address("localhost", connector.getLocalPort())); + exchange.setRequestHeader("Host", "localhost:" + connector.getLocalPort()); + exchange.setRequestHeader("referer", referrer); + return exchange; + } + + + private void benchmarkSPDY(PushStrategy pushStrategy, Session session) throws Exception + { + // Warm up PushStrategy + performRequests(session); + performRequests(session); + + long total = 0; + for (int i = 0; i < runs; ++i) + { + long begin = System.nanoTime(); + int requests = performRequests(session); + long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - begin); + total += elapsed; + System.err.printf("SPDY(%s): run %d, %d request(s), roundtrip delay %d ms, elapsed = %d%n", + pushStrategy.getClass().getSimpleName(), i, requests, roundtrip, elapsed); + } + System.err.printf("SPDY(%s): roundtrip delay %d ms, average = %d%n%n", + pushStrategy.getClass().getSimpleName(), roundtrip, total / runs); + } + + private int performRequests(Session session) throws Exception + { + int result = 0; + + for (int j = 0; j < htmlResources.length; ++j) + { + latch.set(new CountDownLatch(cssResources.length + jsResources.length + pngResources.length)); + pushedResources.clear(); + + String primaryPath = "/" + j + ".html"; + String referrer = new StringBuilder("http://localhost:").append(connector.getLocalPort()).append(primaryPath).toString(); + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), primaryPath); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + // Wait for the HTML to simulate browser's behavior + ++result; + final CountDownLatch htmlLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + htmlLatch.countDown(); + } + }); + Assert.assertTrue(htmlLatch.await(5, TimeUnit.SECONDS)); + + for (int i = 0; i < cssResources.length; ++i) + { + String path = "/" + i + ".css"; + if (pushedResources.contains(path)) + continue; + headers = createRequestHeaders(referrer, path); + ++result; + session.syn(new SynInfo(headers, true), new DataListener()); + } + for (int i = 0; i < jsResources.length; ++i) + { + String path = "/" + i + ".js"; + if (pushedResources.contains(path)) + continue; + headers = createRequestHeaders(referrer, path); + ++result; + session.syn(new SynInfo(headers, true), new DataListener()); + } + for (int i = 0; i < pngResources.length; ++i) + { + String path = "/" + i + ".png"; + if (pushedResources.contains(path)) + continue; + headers = createRequestHeaders(referrer, path); + ++result; + session.syn(new SynInfo(headers, true), new DataListener()); + } + + Assert.assertTrue(latch.get().await(5, TimeUnit.SECONDS)); + } + + return result; + } + + private Headers createRequestHeaders(String referrer, String path) + { + Headers headers; + headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), path); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + headers.put("referer", referrer); + return headers; + } + + private void sleep(long delay) throws ServletException + { + try + { + TimeUnit.MILLISECONDS.sleep(delay); + } + catch (InterruptedException x) + { + throw new ServletException(x); + } + } + + private class PushStrategyBenchmarkHandler extends AbstractHandler + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + + // Sleep half of the roundtrip time, to simulate the delay of responses, even for pushed resources + sleep(roundtrip / 2); + // If it's not a pushed resource, sleep half of the roundtrip time, to simulate the delay of requests + if (request.getHeader("x-spdy-push") == null) + sleep(roundtrip / 2); + + String suffix = target.substring(target.indexOf('.') + 1); + int index = Integer.parseInt(target.substring(1, target.length() - suffix.length() - 1)); + + int contentLength; + String contentType; + switch (suffix) + { + case "html": + contentLength = htmlResources[index]; + contentType = "text/html"; + break; + case "css": + contentLength = cssResources[index]; + contentType = "text/css"; + break; + case "js": + contentLength = jsResources[index]; + contentType = "text/javascript"; + break; + case "png": + contentLength = pngResources[index]; + contentType = "image/png"; + break; + default: + throw new ServletException(); + } + + response.setContentType(contentType); + response.setContentLength(contentLength); + response.getOutputStream().write(new byte[contentLength]); + } + } + + private void addPushedResource(String pushedURI) + { + switch (version()) + { + case SPDY.V2: + { + Matcher matcher = Pattern.compile("https?://[^:]+:\\d+(/.*)").matcher(pushedURI); + Assert.assertTrue(matcher.matches()); + pushedResources.add(matcher.group(1)); + break; + } + case SPDY.V3: + { + pushedResources.add(pushedURI); + break; + } + default: + { + throw new IllegalStateException(); + } + } + } + + private class ClientSessionFrameListener extends SessionFrameListener.Adapter + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + String path = synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version())).value(); + addPushedResource(path); + return new DataListener(); + } + } + + private class DataListener extends StreamFrameListener.Adapter + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + latch.get().countDown(); + } + } + + private class TestExchange extends ContentExchange + { + private TestExchange() + { + super(true); + } + + @Override + protected void onResponseComplete() throws IOException + { + latch.get().countDown(); + } + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyUnitTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyUnitTest.java new file mode 100644 index 00000000000..0edbcf8a333 --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyUnitTest.java @@ -0,0 +1,119 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + +package org.eclipse.jetty.spdy.http; + +import java.util.Set; + +import org.eclipse.jetty.spdy.api.Headers; +import org.eclipse.jetty.spdy.api.SPDY; +import org.eclipse.jetty.spdy.api.Session; +import org.eclipse.jetty.spdy.api.Stream; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ReferrerPushStrategyUnitTest +{ + public static final short VERSION = SPDY.V3; + public static final String SCHEME = "http"; + public static final String HOST = "localhost"; + public static final String MAIN_URI = "/index.html"; + public static final String METHOD = "GET"; + + // class under test + private ReferrerPushStrategy referrerPushStrategy; + + @Mock + Stream stream; + @Mock + Session session; + + + @Before + public void setup() + { + referrerPushStrategy = new ReferrerPushStrategy(); + } + + @Test + public void testReferrerCallsAfterTimeoutAreNotAddedAsPushResources() throws InterruptedException + { + Headers requestHeaders = getBaseHeaders(VERSION); + int referrerCallTimeout = 1000; + referrerPushStrategy.setReferrerPushPeriod(referrerCallTimeout); + setMockExpectations(); + + String referrerUrl = fillPushStrategyCache(requestHeaders); + Set pushResources; + + // sleep to pretend that the user manually clicked on a linked resource instead the browser requesting subresources immediately + Thread.sleep(referrerCallTimeout + 1); + + requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), "image2.jpg"); + requestHeaders.put("referer", referrerUrl); + pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers()); + assertThat("pushResources is empty", pushResources.size(), is(0)); + + requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), MAIN_URI); + pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers()); + // as the image2.jpg request has been a link and not a subresource, we expect that pushResources.size() is still 2 + assertThat("pushResources contains two elements image.jpg and style.css", pushResources.size(), is(2)); + } + + private Headers getBaseHeaders(short version) + { + Headers requestHeaders = new Headers(); + requestHeaders.put(HTTPSPDYHeader.SCHEME.name(version), SCHEME); + requestHeaders.put(HTTPSPDYHeader.HOST.name(version), HOST); + requestHeaders.put(HTTPSPDYHeader.URI.name(version), MAIN_URI); + requestHeaders.put(HTTPSPDYHeader.METHOD.name(version), METHOD); + return requestHeaders; + } + + private void setMockExpectations() + { + when(stream.getSession()).thenReturn(session); + when(session.getVersion()).thenReturn(VERSION); + } + + private String fillPushStrategyCache(Headers requestHeaders) + { + Set pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers()); + assertThat("pushResources is empty", pushResources.size(), is(0)); + + String origin = SCHEME + "://" + HOST; + String referrerUrl = origin + MAIN_URI; + + requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), "image.jpg"); + requestHeaders.put("referer", referrerUrl); + pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers()); + assertThat("pushResources is empty", pushResources.size(), is(0)); + + requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), "style.css"); + pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers()); + assertThat("pushResources is empty", pushResources.size(), is(0)); + + requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), MAIN_URI); + pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers()); + assertThat("pushResources contains two elements image.jpg and style.css", pushResources.size(), is(2)); + return referrerUrl; + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV2Test.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV2Test.java new file mode 100644 index 00000000000..8bc79c3f586 --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV2Test.java @@ -0,0 +1,797 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + + +package org.eclipse.jetty.spdy.http; + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +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; +import org.eclipse.jetty.spdy.AsyncConnectionFactory; +import org.eclipse.jetty.spdy.SPDYServerConnector; +import org.eclipse.jetty.spdy.api.DataInfo; +import org.eclipse.jetty.spdy.api.Headers; +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.SessionFrameListener; +import org.eclipse.jetty.spdy.api.Stream; +import org.eclipse.jetty.spdy.api.StreamFrameListener; +import org.eclipse.jetty.spdy.api.SynInfo; +import org.junit.Assert; +import org.junit.Test; + +public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest +{ + + private final String mainResource = "/index.html"; + private final String cssResource = "/style.css"; + + @Override + protected SPDYServerConnector newHTTPSPDYServerConnector(short version) + { + SPDYServerConnector connector = super.newHTTPSPDYServerConnector(version); + AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, new ReferrerPushStrategy()); + connector.setDefaultAsyncConnectionFactory(defaultFactory); + return connector; + } + + @Test + public void testPushHeadersAreValid() throws Exception + { + InetSocketAddress address = createServer(); + + ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy(); + int referrerPushPeriod = 1000; + pushStrategy.setReferrerPushPeriod(referrerPushPeriod); + AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy); + connector.setDefaultAsyncConnectionFactory(defaultFactory); + + Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource); + Session session1 = sendMainRequestAndCSSRequest(address, mainRequestHeaders); + + // Sleep for pushPeriod This should prevent application.js from being mapped as pushResource + Thread.sleep(referrerPushPeriod + 1); + + sendJSRequest(session1); + + run2ndClientRequests(address, mainRequestHeaders, true); + } + + @Test + public void testReferrerPushPeriod() throws Exception + { + InetSocketAddress address = createServer(); + + ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy(); + int referrerPushPeriod = 1000; + pushStrategy.setReferrerPushPeriod(referrerPushPeriod); + AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy); + connector.setDefaultAsyncConnectionFactory(defaultFactory); + + Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource); + Session session1 = sendMainRequestAndCSSRequest(address, mainRequestHeaders); + + // Sleep for pushPeriod This should prevent application.js from being mapped as pushResource + Thread.sleep(referrerPushPeriod+1); + + sendJSRequest(session1); + + run2ndClientRequests(address, mainRequestHeaders, false); + } + + @Test + public void testMaxAssociatedResources() throws Exception + { + InetSocketAddress address = createServer(); + + ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy(); + pushStrategy.setMaxAssociatedResources(1); + AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy); + connector.setDefaultAsyncConnectionFactory(defaultFactory); + + Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource); + Session session1 = sendMainRequestAndCSSRequest(address, mainRequestHeaders); + + sendJSRequest(session1); + + run2ndClientRequests(address, mainRequestHeaders, false); + } + + private InetSocketAddress createServer() throws Exception + { + return startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + String url = request.getRequestURI(); + PrintWriter output = response.getWriter(); + if (url.endsWith(".html")) + output.print("HELLO"); + else if (url.endsWith(".css")) + output.print("body { background: #FFF; }"); + else if (url.endsWith(".js")) + output.print("function(){}();"); + baseRequest.setHandled(true); + } + }); + } + + private Session sendMainRequestAndCSSRequest(InetSocketAddress address, Headers mainRequestHeaders) throws Exception + { + Session session1 = startClient(version(), address, null); + + final CountDownLatch mainResourceLatch = new CountDownLatch(1); + session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + mainResourceLatch.countDown(); + } + }); + Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS)); + + final CountDownLatch associatedResourceLatch1 = new CountDownLatch(1); + Headers associatedRequestHeaders1 = createHeaders(cssResource); + session1.syn(new SynInfo(associatedRequestHeaders1, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + associatedResourceLatch1.countDown(); + } + }); + Assert.assertTrue(associatedResourceLatch1.await(5, TimeUnit.SECONDS)); + return session1; + } + + + private void sendJSRequest(Session session1) throws InterruptedException + { + final CountDownLatch associatedResourceLatch2 = new CountDownLatch(1); + String jsResource = "/application.js"; + Headers associatedRequestHeaders2 = createHeaders(jsResource); + session1.syn(new SynInfo(associatedRequestHeaders2, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + associatedResourceLatch2.countDown(); + } + }); + Assert.assertTrue(associatedResourceLatch2.await(5, TimeUnit.SECONDS)); + } + + private void run2ndClientRequests(InetSocketAddress address, Headers mainRequestHeaders, final boolean validateHeaders) throws Exception + { + // Create another client, and perform the same request for the main resource, + // we expect the css being pushed, but not the js + + final CountDownLatch mainStreamLatch = new CountDownLatch(2); + final CountDownLatch pushDataLatch = new CountDownLatch(1); + final CountDownLatch pushSynHeadersValid = new CountDownLatch(1); + Session session2 = startClient(version(), address, new SessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + if(validateHeaders) + validateHeaders(synInfo.getHeaders(), pushSynHeadersValid); + + Assert.assertTrue(stream.isUnidirectional()); + Assert.assertTrue(synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version())).value().endsWith(".css")); + return new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + pushDataLatch.countDown(); + } + }; + } + }); + session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertFalse(replyInfo.isClose()); + mainStreamLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + mainStreamLatch.countDown(); + } + }); + + Assert.assertTrue("Main request reply and/or data not received", mainStreamLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue("Pushed data not received", pushDataLatch.await(5, TimeUnit.SECONDS)); + if(validateHeaders) + Assert.assertTrue("Push syn headers not valid", pushSynHeadersValid.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testAssociatedResourceIsPushed() throws Exception + { + InetSocketAddress address = startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + String url = request.getRequestURI(); + PrintWriter output = response.getWriter(); + if (url.endsWith(".html")) + output.print("HELLO"); + else if (url.endsWith(".css")) + output.print("body { background: #FFF; }"); + baseRequest.setHandled(true); + } + }); + Session session1 = startClient(version(), address, null); + + final CountDownLatch mainResourceLatch = new CountDownLatch(1); + Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource); + + session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + mainResourceLatch.countDown(); + } + }); + Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS)); + + final CountDownLatch associatedResourceLatch = new CountDownLatch(1); + Headers associatedRequestHeaders = createHeaders(cssResource); + session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + associatedResourceLatch.countDown(); + } + }); + Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS)); + + // Create another client, and perform the same request for the main resource, we expect the css being pushed + + final CountDownLatch mainStreamLatch = new CountDownLatch(2); + final CountDownLatch pushDataLatch = new CountDownLatch(1); + Session session2 = startClient(version(), address, new SessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + Assert.assertTrue(stream.isUnidirectional()); + return new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + pushDataLatch.countDown(); + } + }; + } + }); + session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertFalse(replyInfo.isClose()); + mainStreamLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + mainStreamLatch.countDown(); + } + }); + + Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testAssociatedResourceWithWrongContentTypeIsNotPushed() throws Exception + { + final String fakeResource = "/fake.png"; + InetSocketAddress address = startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + String url = request.getRequestURI(); + PrintWriter output = response.getWriter(); + if (url.endsWith(".html")) + { + response.setContentType("text/html"); + output.print("HELLO"); + } + else if (url.equals(fakeResource)) + { + response.setContentType("text/html"); + output.print("IMAGE"); + } + else if (url.endsWith(".css")) + { + response.setContentType("text/css"); + output.print("body { background: #FFF; }"); + } + baseRequest.setHandled(true); + } + }); + Session session1 = startClient(version(), address, null); + + final CountDownLatch mainResourceLatch = new CountDownLatch(1); + Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource); + + session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + mainResourceLatch.countDown(); + } + }); + Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS)); + + final CountDownLatch associatedResourceLatch = new CountDownLatch(1); + String cssResource = "/stylesheet.css"; + Headers associatedRequestHeaders = createHeaders(cssResource); + session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + associatedResourceLatch.countDown(); + } + }); + Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS)); + + final CountDownLatch fakeAssociatedResourceLatch = new CountDownLatch(1); + Headers fakeAssociatedRequestHeaders = createHeaders(fakeResource); + session1.syn(new SynInfo(fakeAssociatedRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + fakeAssociatedResourceLatch.countDown(); + } + }); + Assert.assertTrue(fakeAssociatedResourceLatch.await(5, TimeUnit.SECONDS)); + + // Create another client, and perform the same request for the main resource, + // we expect the css being pushed but not the fake PNG + + final CountDownLatch mainStreamLatch = new CountDownLatch(2); + final CountDownLatch pushDataLatch = new CountDownLatch(1); + Session session2 = startClient(version(), address, new SessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + Assert.assertTrue(stream.isUnidirectional()); + Assert.assertTrue(synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version())).value().endsWith(".css")); + return new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + pushDataLatch.countDown(); + } + }; + } + }); + session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertFalse(replyInfo.isClose()); + mainStreamLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + mainStreamLatch.countDown(); + } + }); + + Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testNestedAssociatedResourceIsPushed() throws Exception + { + InetSocketAddress address = startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + String url = request.getRequestURI(); + PrintWriter output = response.getWriter(); + if (url.endsWith(".html")) + output.print("HELLO"); + else if (url.endsWith(".css")) + output.print("body { background: #FFF; }"); + else if (url.endsWith(".gif")) + output.print("\u0000"); + baseRequest.setHandled(true); + } + }); + Session session1 = startClient(version(), address, null); + + final CountDownLatch mainResourceLatch = new CountDownLatch(1); + Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource); + + session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + mainResourceLatch.countDown(); + } + }); + Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS)); + + final CountDownLatch associatedResourceLatch = new CountDownLatch(1); + Headers associatedRequestHeaders = createHeaders(cssResource); + session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + associatedResourceLatch.countDown(); + } + }); + Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS)); + + final CountDownLatch nestedResourceLatch = new CountDownLatch(1); + String imageUrl = "/image.gif"; + Headers nestedRequestHeaders = createHeaders(imageUrl, cssResource); + + session1.syn(new SynInfo(nestedRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + nestedResourceLatch.countDown(); + } + }); + Assert.assertTrue(nestedResourceLatch.await(5, TimeUnit.SECONDS)); + + // Create another client, and perform the same request for the main resource, we expect the css and the image being pushed + + final CountDownLatch mainStreamLatch = new CountDownLatch(2); + final CountDownLatch pushDataLatch = new CountDownLatch(2); + Session session2 = startClient(version(), address, new SessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + Assert.assertTrue(stream.isUnidirectional()); + return new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + pushDataLatch.countDown(); + } + }; + } + }); + session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertFalse(replyInfo.isClose()); + mainStreamLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + mainStreamLatch.countDown(); + } + }); + + Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testMainResourceWithReferrerIsNotPushed() throws Exception + { + InetSocketAddress address = startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + String url = request.getRequestURI(); + PrintWriter output = response.getWriter(); + if (url.endsWith(".html")) + output.print("HELLO"); + baseRequest.setHandled(true); + } + }); + Session session1 = startClient(version(), address, null); + + final CountDownLatch mainResourceLatch = new CountDownLatch(1); + Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource); + + session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + mainResourceLatch.countDown(); + } + }); + Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS)); + + final CountDownLatch associatedResourceLatch = new CountDownLatch(1); + String associatedResource = "/home.html"; + Headers associatedRequestHeaders = createHeaders(associatedResource); + + session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + associatedResourceLatch.countDown(); + } + }); + Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS)); + + // Create another client, and perform the same request for the main resource, we expect nothing being pushed + + final CountDownLatch mainStreamLatch = new CountDownLatch(2); + final CountDownLatch pushLatch = new CountDownLatch(1); + Session session2 = startClient(version(), address, new SessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + pushLatch.countDown(); + return null; + } + }); + session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertFalse(replyInfo.isClose()); + mainStreamLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + mainStreamLatch.countDown(); + } + }); + + Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS)); + Assert.assertFalse(pushLatch.await(1, TimeUnit.SECONDS)); + } + + @Test + public void testRequestWithIfModifiedSinceHeaderPreventsPush() throws Exception + { + InetSocketAddress address = startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + String url = request.getRequestURI(); + PrintWriter output = response.getWriter(); + if (url.endsWith(".html")) + output.print("HELLO"); + else if (url.endsWith(".css")) + output.print("body { background: #FFF; }"); + baseRequest.setHandled(true); + } + }); + Session session1 = startClient(version(), address, null); + + final CountDownLatch mainResourceLatch = new CountDownLatch(1); + Headers mainRequestHeaders = createHeaders(mainResource); + mainRequestHeaders.put("If-Modified-Since", "Tue, 27 Mar 2012 16:36:52 GMT"); + session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + mainResourceLatch.countDown(); + } + }); + Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS)); + + final CountDownLatch associatedResourceLatch = new CountDownLatch(1); + Headers associatedRequestHeaders = createHeaders(cssResource); + session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + associatedResourceLatch.countDown(); + } + }); + Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS)); + + // Create another client, and perform the same request for the main resource, we expect the css NOT being pushed as the main request contains an + // if-modified-since header + + final CountDownLatch mainStreamLatch = new CountDownLatch(2); + final CountDownLatch pushDataLatch = new CountDownLatch(1); + Session session2 = startClient(version(), address, new SessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + Assert.assertTrue(stream.isUnidirectional()); + return new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + pushDataLatch.countDown(); + } + }; + } + }); + session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertFalse(replyInfo.isClose()); + mainStreamLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + mainStreamLatch.countDown(); + } + }); + + Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS)); + Assert.assertFalse("We don't expect data to be pushed as the main request contained an if-modified-since header",pushDataLatch.await(1, TimeUnit.SECONDS)); + } + + private void validateHeaders(Headers headers, CountDownLatch pushSynHeadersValid) + { + if (validateHeader(headers, HTTPSPDYHeader.STATUS.name(version()), "200") + && validateHeader(headers, HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1") + && validateUriHeader(headers)) + pushSynHeadersValid.countDown(); + } + + private boolean validateHeader(Headers headers, String name, String expectedValue) + { + Headers.Header header = headers.get(name); + if (header != null && expectedValue.equals(header.value())) + return true; + System.out.println(name + " not valid! " + headers); + return false; + } + + private boolean validateUriHeader(Headers headers) + { + Headers.Header uriHeader = headers.get(HTTPSPDYHeader.URI.name(version())); + if (uriHeader != null) + if (version() == SPDY.V2 && uriHeader.value().startsWith("http://")) + return true; + else if (version() == SPDY.V3 && uriHeader.value().startsWith("/") + && headers.get(HTTPSPDYHeader.HOST.name(version())) != null && headers.get(HTTPSPDYHeader.SCHEME.name(version())) != null) + return true; + System.out.println(HTTPSPDYHeader.URI.name(version()) + " not valid!"); + return false; + } + + private Headers createHeaders(String resource) + { + return createHeaders(resource, mainResource); + } + + private Headers createHeaders(String resource, String referrer) + { + Headers associatedRequestHeaders = createHeadersWithoutReferrer(resource); + associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + referrer); + return associatedRequestHeaders; + } + + private Headers createHeadersWithoutReferrer(String resource) + { + Headers associatedRequestHeaders = new Headers(); + associatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + associatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), resource); + associatedRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + associatedRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + associatedRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + return associatedRequestHeaders; + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV3Test.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV3Test.java new file mode 100644 index 00000000000..2b637cb2cdb --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV3Test.java @@ -0,0 +1,26 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + + +package org.eclipse.jetty.spdy.http; + +import org.eclipse.jetty.spdy.api.SPDY; + +public class ReferrerPushStrategyV3Test extends ReferrerPushStrategyV2Test +{ + @Override + protected short version() + { + return SPDY.V3; + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/SSLExternalServerTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/SSLExternalServerTest.java new file mode 100644 index 00000000000..42bd5be8fd0 --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/SSLExternalServerTest.java @@ -0,0 +1,94 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + +package org.eclipse.jetty.spdy.http; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jetty.spdy.SPDYClient; +import org.eclipse.jetty.spdy.api.Headers; +import org.eclipse.jetty.spdy.api.ReplyInfo; +import org.eclipse.jetty.spdy.api.SPDY; +import org.eclipse.jetty.spdy.api.Session; +import org.eclipse.jetty.spdy.api.Stream; +import org.eclipse.jetty.spdy.api.StreamFrameListener; +import org.eclipse.jetty.spdy.api.SynInfo; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Test; + +public class SSLExternalServerTest extends AbstractHTTPSPDYTest +{ + @Override + protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool) + { + SslContextFactory sslContextFactory = new SslContextFactory(); + // Force TLSv1 + sslContextFactory.setIncludeProtocols("TLSv1"); + return new SPDYClient.Factory(threadPool, sslContextFactory); + } + + @Test + public void testExternalServer() throws Exception + { + String host = "encrypted.google.com"; + int port = 443; + InetSocketAddress address = new InetSocketAddress(host, port); + + try + { + // Test whether there is connectivity to avoid fail the test when offline + Socket socket = new Socket(); + socket.connect(address, 5000); + socket.close(); + } + catch (IOException x) + { + Assume.assumeNoException(x); + } + + final short version = SPDY.V2; + Session session = startClient(version, address, null); + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.SCHEME.name(version), "https"); + headers.put(HTTPSPDYHeader.HOST.name(version), host + ":" + port); + headers.put(HTTPSPDYHeader.METHOD.name(version), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version), "/"); + headers.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1"); + final CountDownLatch latch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Headers headers = replyInfo.getHeaders(); + Headers.Header versionHeader = headers.get(HTTPSPDYHeader.STATUS.name(version)); + if (versionHeader != null) + { + Matcher matcher = Pattern.compile("(\\d{3}).*").matcher(versionHeader.value()); + if (matcher.matches() && Integer.parseInt(matcher.group(1)) < 400) + latch.countDown(); + } + } + }); + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYv2Test.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYv2Test.java new file mode 100644 index 00000000000..5bab1f512e3 --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYv2Test.java @@ -0,0 +1,1272 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + + +package org.eclipse.jetty.spdy.http; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.continuation.Continuation; +import org.eclipse.jetty.continuation.ContinuationSupport; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.spdy.api.BytesDataInfo; +import org.eclipse.jetty.spdy.api.DataInfo; +import org.eclipse.jetty.spdy.api.Headers; +import org.eclipse.jetty.spdy.api.ReplyInfo; +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.StringDataInfo; +import org.eclipse.jetty.spdy.api.SynInfo; +import org.junit.Assert; +import org.junit.Test; + +public class ServerHTTPSPDYv2Test extends AbstractHTTPSPDYTest +{ + @Test + public void testSimpleGET() throws Exception + { + final String path = "/foo"; + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + Assert.assertEquals("GET", httpRequest.getMethod()); + Assert.assertEquals(path, target); + Assert.assertEquals(path, httpRequest.getRequestURI()); + Assert.assertEquals("localhost:" + connector.getLocalPort(), httpRequest.getHeader("host")); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), path); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertTrue(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithQueryString() throws Exception + { + final String path = "/foo"; + final String query = "p=1"; + final String uri = path + "?" + query; + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + Assert.assertEquals("GET", httpRequest.getMethod()); + Assert.assertEquals(path, target); + Assert.assertEquals(path, httpRequest.getRequestURI()); + Assert.assertEquals(query, httpRequest.getQueryString()); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), uri); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertTrue(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testHEAD() throws Exception + { + final String path = "/foo"; + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + Assert.assertEquals("HEAD", httpRequest.getMethod()); + Assert.assertEquals(path, target); + Assert.assertEquals(path, httpRequest.getRequestURI()); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "HEAD"); + headers.put(HTTPSPDYHeader.URI.name(version()), path); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertTrue(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testPOSTWithParameters() throws Exception + { + final String path = "/foo"; + final String data = "a=1&b=2"; + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + Assert.assertEquals("POST", httpRequest.getMethod()); + Assert.assertEquals("1", httpRequest.getParameter("a")); + Assert.assertEquals("2", httpRequest.getParameter("b")); + Assert.assertNotNull(httpRequest.getRemoteHost()); + Assert.assertNotNull(httpRequest.getRemotePort()); + Assert.assertNotNull(httpRequest.getRemoteAddr()); + Assert.assertNotNull(httpRequest.getLocalPort()); + Assert.assertNotNull(httpRequest.getLocalName()); + Assert.assertNotNull(httpRequest.getLocalAddr()); + Assert.assertNotNull(httpRequest.getServerPort()); + Assert.assertNotNull(httpRequest.getServerName()); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST"); + headers.put(HTTPSPDYHeader.URI.name(version()), path); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + headers.put("content-type", "application/x-www-form-urlencoded"); + final CountDownLatch replyLatch = new CountDownLatch(1); + Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertTrue(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + }).get(5, TimeUnit.SECONDS); + stream.data(new StringDataInfo(data, true)); + + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testPOSTWithParametersInTwoFramesTwoReads() throws Exception + { + final String path = "/foo"; + final String data1 = "a=1&"; + final String data2 = "b=2"; + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + Assert.assertEquals("POST", httpRequest.getMethod()); + Assert.assertEquals("1", httpRequest.getParameter("a")); + Assert.assertEquals("2", httpRequest.getParameter("b")); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST"); + headers.put(HTTPSPDYHeader.URI.name(version()), path); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + headers.put("content-type", "application/x-www-form-urlencoded"); + final CountDownLatch replyLatch = new CountDownLatch(1); + Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertTrue(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + }).get(5, TimeUnit.SECONDS); + // Sleep between the data frames so that they will be read in 2 reads + stream.data(new StringDataInfo(data1, false)); + Thread.sleep(1000); + stream.data(new StringDataInfo(data2, true)); + + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testPOSTWithParametersInTwoFramesOneRead() throws Exception + { + final String path = "/foo"; + final String data1 = "a=1&"; + final String data2 = "b=2"; + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + Assert.assertEquals("POST", httpRequest.getMethod()); + Assert.assertEquals("1", httpRequest.getParameter("a")); + Assert.assertEquals("2", httpRequest.getParameter("b")); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST"); + headers.put(HTTPSPDYHeader.URI.name(version()), path); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + headers.put("content-type", "application/x-www-form-urlencoded"); + final CountDownLatch replyLatch = new CountDownLatch(1); + Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertTrue(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.toString(), replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + }).get(5, TimeUnit.SECONDS); + // Send the data frames consecutively, so the server reads both frames in one read + stream.data(new StringDataInfo(data1, false)); + stream.data(new StringDataInfo(data2, true)); + + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithSmallResponseContent() throws Exception + { + final String data = "0123456789ABCDEF"; + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + httpResponse.setStatus(HttpServletResponse.SC_OK); + ServletOutputStream output = httpResponse.getOutputStream(); + output.write(data.getBytes("UTF-8")); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertFalse(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + Assert.assertTrue(dataInfo.isClose()); + Assert.assertEquals(data, dataInfo.asString("UTF-8", true)); + dataLatch.countDown(); + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithOneByteResponseContent() throws Exception + { + final char data = 'x'; + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + httpResponse.setStatus(HttpServletResponse.SC_OK); + ServletOutputStream output = httpResponse.getOutputStream(); + output.write(data); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertFalse(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + Assert.assertTrue(dataInfo.isClose()); + byte[] bytes = dataInfo.asBytes(true); + Assert.assertEquals(1, bytes.length); + Assert.assertEquals(data, bytes[0]); + dataLatch.countDown(); + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithSmallResponseContentInTwoChunks() throws Exception + { + final String data1 = "0123456789ABCDEF"; + final String data2 = "FEDCBA9876543210"; + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + httpResponse.setStatus(HttpServletResponse.SC_OK); + ServletOutputStream output = httpResponse.getOutputStream(); + output.write(data1.getBytes("UTF-8")); + output.flush(); + output.write(data2.getBytes("UTF-8")); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(2); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + private final AtomicInteger replyFrames = new AtomicInteger(); + private final AtomicInteger dataFrames = new AtomicInteger(); + + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertEquals(1, replyFrames.incrementAndGet()); + Assert.assertFalse(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + int data = dataFrames.incrementAndGet(); + Assert.assertTrue(data >= 1 && data <= 2); + if (data == 1) + Assert.assertEquals(data1, dataInfo.asString("UTF8", true)); + else + Assert.assertEquals(data2, dataInfo.asString("UTF8", true)); + dataLatch.countDown(); + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithBigResponseContentInOneWrite() throws Exception + { + final byte[] data = new byte[128 * 1024]; + Arrays.fill(data, (byte)'x'); + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + httpResponse.setStatus(HttpServletResponse.SC_OK); + ServletOutputStream output = httpResponse.getOutputStream(); + output.write(data); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + private final AtomicInteger contentBytes = new AtomicInteger(); + + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertFalse(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + contentBytes.addAndGet(dataInfo.asByteBuffer(true).remaining()); + if (dataInfo.isClose()) + { + Assert.assertEquals(data.length, contentBytes.get()); + dataLatch.countDown(); + } + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithBigResponseContentInTwoWrites() throws Exception + { + final byte[] data = new byte[128 * 1024]; + Arrays.fill(data, (byte)'y'); + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + httpResponse.setStatus(HttpServletResponse.SC_OK); + ServletOutputStream output = httpResponse.getOutputStream(); + output.write(data); + output.write(data); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + private final AtomicInteger contentBytes = new AtomicInteger(); + + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertFalse(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + contentBytes.addAndGet(dataInfo.asByteBuffer(true).remaining()); + if (dataInfo.isClose()) + { + Assert.assertEquals(2 * data.length, contentBytes.get()); + dataLatch.countDown(); + } + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithOutputStreamFlushedAndClosed() throws Exception + { + final String data = "0123456789ABCDEF"; + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + httpResponse.setStatus(HttpServletResponse.SC_OK); + ServletOutputStream output = httpResponse.getOutputStream(); + output.write(data.getBytes("UTF-8")); + output.flush(); + output.close(); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertFalse(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + ByteBuffer byteBuffer = dataInfo.asByteBuffer(true); + while (byteBuffer.hasRemaining()) + buffer.write(byteBuffer.get()); + if (dataInfo.isClose()) + { + Assert.assertEquals(data, new String(buffer.toByteArray(), Charset.forName("UTF-8"))); + dataLatch.countDown(); + } + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithResponseResetBuffer() throws Exception + { + final String data1 = "0123456789ABCDEF"; + final String data2 = "FEDCBA9876543210"; + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + httpResponse.setStatus(HttpServletResponse.SC_OK); + ServletOutputStream output = httpResponse.getOutputStream(); + // Write some + output.write(data1.getBytes("UTF-8")); + // But then change your mind and reset the buffer + httpResponse.resetBuffer(); + output.write(data2.getBytes("UTF-8")); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertFalse(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + ByteBuffer byteBuffer = dataInfo.asByteBuffer(true); + while (byteBuffer.hasRemaining()) + buffer.write(byteBuffer.get()); + if (dataInfo.isClose()) + { + Assert.assertEquals(data2, new String(buffer.toByteArray(), Charset.forName("UTF-8"))); + dataLatch.countDown(); + } + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithRedirect() throws Exception + { + final String suffix = "/redirect"; + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + String location = httpResponse.encodeRedirectURL(String.format("%s://%s:%d%s", + request.getScheme(), request.getLocalAddr(), request.getLocalPort(), target + suffix)); + httpResponse.sendRedirect(location); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + private final AtomicInteger replies = new AtomicInteger(); + + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertEquals(1, replies.incrementAndGet()); + Assert.assertTrue(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("302")); + Assert.assertTrue(replyHeaders.get("location").value().endsWith(suffix)); + replyLatch.countDown(); + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithSendError() throws Exception + { + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + private final AtomicInteger replies = new AtomicInteger(); + + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertEquals(1, replies.incrementAndGet()); + Assert.assertFalse(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("404")); + replyLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + if (dataInfo.isClose()) + dataLatch.countDown(); + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithException() throws Exception + { + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + throw new NullPointerException("thrown_explicitly_by_the_test"); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + private final AtomicInteger replies = new AtomicInteger(); + + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertEquals(1, replies.incrementAndGet()); + Assert.assertTrue(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("500")); + replyLatch.countDown(); + } + }); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithSmallResponseChunked() throws Exception + { + final String pangram1 = "the quick brown fox jumps over the lazy dog"; + final String pangram2 = "qualche vago ione tipo zolfo, bromo, sodio"; + final CountDownLatch handlerLatch = new CountDownLatch(1); + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + request.setHandled(true); + httpResponse.setHeader("Transfer-Encoding", "chunked"); + ServletOutputStream output = httpResponse.getOutputStream(); + output.write(pangram1.getBytes("UTF-8")); + httpResponse.setHeader("EXTRA", "X"); + output.flush(); + output.write(pangram2.getBytes("UTF-8")); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(2); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + private final AtomicInteger replyFrames = new AtomicInteger(); + private final AtomicInteger dataFrames = new AtomicInteger(); + + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertEquals(1, replyFrames.incrementAndGet()); + Assert.assertFalse(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + Assert.assertTrue(replyHeaders.get("extra").value().contains("X")); + replyLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + int count = dataFrames.incrementAndGet(); + if (count == 1) + { + Assert.assertFalse(dataInfo.isClose()); + Assert.assertEquals(pangram1, dataInfo.asString("UTF-8", true)); + } + else if (count == 2) + { + Assert.assertTrue(dataInfo.isClose()); + Assert.assertEquals(pangram2, dataInfo.asString("UTF-8", true)); + } + dataLatch.countDown(); + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithMediumContentAsInputStreamByPassed() throws Exception + { + byte[] data = new byte[2048]; + testGETWithContentByPassed(new ByteArrayInputStream(data), data.length); + } + + @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(new ByteArrayBuffer(data), data.length); + } + + 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() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + 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); + handlerLatch.countDown(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(1); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + private final AtomicInteger replyFrames = new AtomicInteger(); + private final AtomicInteger contentLength = new AtomicInteger(); + + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertEquals(1, replyFrames.incrementAndGet()); + Assert.assertFalse(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + contentLength.addAndGet(dataInfo.asBytes(true).length); + if (dataInfo.isClose()) + { + Assert.assertEquals(length, contentLength.get()); + dataLatch.countDown(); + } + } + }); + Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testGETWithMultipleMediumContentByPassed() throws Exception + { + final byte[] data = new byte[2048]; + Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException, ServletException + { + // The sequence of write/flush/write/write below triggers a condition where + // HttpGenerator._bypass is set to true on the second write(), and the + // third write causes an infinite spin loop on the third write(). + request.setHandled(true); + OutputStream output = httpResponse.getOutputStream(); + output.write(data); + output.flush(); + output.write(data); + output.write(data); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(1); + final AtomicInteger contentLength = new AtomicInteger(); + session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Assert.assertFalse(replyInfo.isClose()); + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.available()); + contentLength.addAndGet(dataInfo.length()); + if (dataInfo.isClose()) + dataLatch.countDown(); + } + }); + Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + Assert.assertEquals(3 * data.length, contentLength.get()); + } + + @Test + public void testPOSTThenSuspendRequestThenReadOneChunkThenComplete() throws Exception + { + final byte[] data = new byte[2000]; + final CountDownLatch latch = 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 + { + request.setHandled(true); + + final Continuation continuation = ContinuationSupport.getContinuation(request); + continuation.suspend(); + + new Thread() + { + @Override + public void run() + { + try + { + InputStream input = request.getInputStream(); + byte[] buffer = new byte[512]; + int read = 0; + while (read < data.length) + read += input.read(buffer); + continuation.complete(); + latch.countDown(); + } + catch (IOException x) + { + x.printStackTrace(); + } + } + }.start(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + }).get(5, TimeUnit.SECONDS); + stream.data(new BytesDataInfo(data, true)); + + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testPOSTThenSuspendRequestThenReadTwoChunksThenComplete() throws Exception + { + final byte[] data = new byte[2000]; + final CountDownLatch latch = 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 + { + request.setHandled(true); + + final Continuation continuation = ContinuationSupport.getContinuation(request); + continuation.suspend(); + + new Thread() + { + @Override + public void run() + { + try + { + InputStream input = request.getInputStream(); + byte[] buffer = new byte[512]; + int read = 0; + while (read < 2 * data.length) + read += input.read(buffer); + continuation.complete(); + latch.countDown(); + } + catch (IOException x) + { + x.printStackTrace(); + } + } + }.start(); + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + replyLatch.countDown(); + } + }).get(5, TimeUnit.SECONDS); + stream.data(new BytesDataInfo(data, false)); + stream.data(new BytesDataInfo(data, true)); + + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testPOSTThenSuspendRequestThenResumeThenRespond() throws Exception + { + final byte[] data = new byte[1000]; + final CountDownLatch latch = 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 + { + request.setHandled(true); + + final Continuation continuation = ContinuationSupport.getContinuation(request); + + if (continuation.isInitial()) + { + InputStream input = request.getInputStream(); + byte[] buffer = new byte[256]; + int read = 0; + while (read < data.length) + read += input.read(buffer); + continuation.suspend(); + new Thread() + { + @Override + public void run() + { + try + { + TimeUnit.SECONDS.sleep(1); + continuation.resume(); + latch.countDown(); + } + catch (InterruptedException x) + { + x.printStackTrace(); + } + } + }.start(); + } + else + { + OutputStream output = httpResponse.getOutputStream(); + output.write(data); + } + } + }), null); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST"); + headers.put(HTTPSPDYHeader.URI.name(version()), "/foo"); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + final CountDownLatch responseLatch = new CountDownLatch(2); + Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Headers replyHeaders = replyInfo.getHeaders(); + Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200")); + responseLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + if (dataInfo.isClose()) + responseLatch.countDown(); + } + }).get(5, TimeUnit.SECONDS); + stream.data(new BytesDataInfo(data, true)); + + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYv3Test.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYv3Test.java new file mode 100644 index 00000000000..e6c4de2ac30 --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYv3Test.java @@ -0,0 +1,26 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + + +package org.eclipse.jetty.spdy.http; + +import org.eclipse.jetty.spdy.api.SPDY; + +public class ServerHTTPSPDYv3Test extends ServerHTTPSPDYv2Test +{ + @Override + protected short version() + { + return SPDY.V3; + } +} diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPSPDYv2Test.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPSPDYv2Test.java new file mode 100644 index 00000000000..6c2c89bc874 --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPSPDYv2Test.java @@ -0,0 +1,764 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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.proxy; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.spdy.SPDYClient; +import org.eclipse.jetty.spdy.SPDYServerConnector; +import org.eclipse.jetty.spdy.ServerSPDYAsyncConnectionFactory; +import org.eclipse.jetty.spdy.api.BytesDataInfo; +import org.eclipse.jetty.spdy.api.DataInfo; +import org.eclipse.jetty.spdy.api.GoAwayInfo; +import org.eclipse.jetty.spdy.api.Handler; +import org.eclipse.jetty.spdy.api.Headers; +import org.eclipse.jetty.spdy.api.PingInfo; +import org.eclipse.jetty.spdy.api.ReplyInfo; +import org.eclipse.jetty.spdy.api.RstInfo; +import org.eclipse.jetty.spdy.api.SPDY; +import org.eclipse.jetty.spdy.api.Session; +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.StreamStatus; +import org.eclipse.jetty.spdy.api.SynInfo; +import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener; +import org.eclipse.jetty.spdy.http.HTTPSPDYHeader; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestWatchman; +import org.junit.runners.model.FrameworkMethod; + +public class ProxyHTTPSPDYv2Test +{ + @Rule + public final TestWatchman testName = new TestWatchman() + { + @Override + public void starting(FrameworkMethod method) + { + super.starting(method); + System.err.printf("Running %s.%s()%n", + method.getMethod().getDeclaringClass().getName(), + method.getName()); + } + }; + + private SPDYClient.Factory factory; + private Server server; + private Server proxy; + private SPDYServerConnector proxyConnector; + + protected short version() + { + return SPDY.V2; + } + + protected InetSocketAddress startServer(ServerSessionFrameListener listener) throws Exception + { + server = new Server(); + SPDYServerConnector serverConnector = new SPDYServerConnector(listener); + serverConnector.setDefaultAsyncConnectionFactory(new ServerSPDYAsyncConnectionFactory(version(), serverConnector.getByteBufferPool(), serverConnector.getExecutor(), serverConnector.getScheduler(), listener)); + serverConnector.setPort(0); + server.addConnector(serverConnector); + server.start(); + return new InetSocketAddress("localhost", serverConnector.getLocalPort()); + } + + protected InetSocketAddress startProxy(InetSocketAddress address) throws Exception + { + proxy = new Server(); + ProxyEngineSelector proxyEngineSelector = new ProxyEngineSelector(); + SPDYProxyEngine spdyProxyEngine = new SPDYProxyEngine(factory); + proxyEngineSelector.putProxyEngine("spdy/" + version(), spdyProxyEngine); + proxyEngineSelector.putProxyServerInfo("localhost", new ProxyEngineSelector.ProxyServerInfo("spdy/" + version(), address.getHostName(), address.getPort())); + proxyConnector = new HTTPSPDYProxyConnector(proxyEngineSelector); + proxyConnector.setPort(0); + proxy.addConnector(proxyConnector); + proxy.start(); + return new InetSocketAddress("localhost", proxyConnector.getLocalPort()); + } + + @Before + public void init() throws Exception + { + factory = new SPDYClient.Factory(); + 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 testClosingClientDoesNotCloseServer() throws Exception + { + final CountDownLatch closeLatch = new CountDownLatch(1); + InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + Headers responseHeaders = new Headers(); + responseHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + responseHeaders.put(HTTPSPDYHeader.STATUS.name(version()), "200 OK"); + stream.reply(new ReplyInfo(responseHeaders, true)); + return null; + } + + @Override + public void onGoAway(Session session, GoAwayInfo goAwayInfo) + { + closeLatch.countDown(); + } + })); + + Socket client = new Socket(); + client.connect(proxyAddress); + OutputStream output = client.getOutputStream(); + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost:" + proxyAddress.getPort() + "\r\n" + + "\r\n"; + output.write(request.getBytes("UTF-8")); + output.flush(); + + InputStream input = client.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + String line = reader.readLine(); + Assert.assertTrue(line.contains(" 200")); + while (line.length() > 0) + line = reader.readLine(); + Assert.assertFalse(reader.ready()); + + client.close(); + + // Must not close, other clients may still be connected + Assert.assertFalse(closeLatch.await(1, TimeUnit.SECONDS)); + } + + @Test + public void testGETThenNoContentFromTwoClients() throws Exception + { + InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + Assert.assertTrue(synInfo.isClose()); + Headers requestHeaders = synInfo.getHeaders(); + Assert.assertNotNull(requestHeaders.get("via")); + + Headers responseHeaders = new Headers(); + responseHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + responseHeaders.put(HTTPSPDYHeader.STATUS.name(version()), "200 OK"); + ReplyInfo replyInfo = new ReplyInfo(responseHeaders, true); + stream.reply(replyInfo); + return null; + } + })); + + Socket client1 = new Socket(); + client1.connect(proxyAddress); + OutputStream output1 = client1.getOutputStream(); + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost:" + proxyAddress.getPort() + "\r\n" + + "\r\n"; + output1.write(request.getBytes("UTF-8")); + output1.flush(); + + InputStream input1 = client1.getInputStream(); + BufferedReader reader1 = new BufferedReader(new InputStreamReader(input1, "UTF-8")); + String line = reader1.readLine(); + Assert.assertTrue(line.contains(" 200")); + while (line.length() > 0) + line = reader1.readLine(); + Assert.assertFalse(reader1.ready()); + + // Perform another request with another client + Socket client2 = new Socket(); + client2.connect(proxyAddress); + OutputStream output2 = client2.getOutputStream(); + + output2.write(request.getBytes("UTF-8")); + output2.flush(); + + InputStream input2 = client2.getInputStream(); + BufferedReader reader2 = new BufferedReader(new InputStreamReader(input2, "UTF-8")); + line = reader2.readLine(); + Assert.assertTrue(line.contains(" 200")); + while (line.length() > 0) + line = reader2.readLine(); + Assert.assertFalse(reader2.ready()); + + client1.close(); + client2.close(); + } + + @Test + public void testGETThenSmallResponseContent() throws Exception + { + final byte[] data = "0123456789ABCDEF".getBytes("UTF-8"); + InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + Assert.assertTrue(synInfo.isClose()); + Headers requestHeaders = synInfo.getHeaders(); + Assert.assertNotNull(requestHeaders.get("via")); + + Headers responseHeaders = new Headers(); + responseHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + responseHeaders.put(HTTPSPDYHeader.STATUS.name(version()), "200 OK"); + ReplyInfo replyInfo = new ReplyInfo(responseHeaders, false); + stream.reply(replyInfo); + stream.data(new BytesDataInfo(data, true)); + + return null; + } + })); + + Socket client = new Socket(); + client.connect(proxyAddress); + OutputStream output = client.getOutputStream(); + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost:" + proxyAddress.getPort() + "\r\n" + + "\r\n"; + output.write(request.getBytes("UTF-8")); + output.flush(); + + InputStream input = client.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + String line = reader.readLine(); + Assert.assertTrue(line.contains(" 200")); + while (line.length() > 0) + line = reader.readLine(); + for (byte datum : data) + Assert.assertEquals(datum, reader.read()); + Assert.assertFalse(reader.ready()); + + // Perform another request so that we are sure we reset the states of parsers and generators + output.write(request.getBytes("UTF-8")); + output.flush(); + + line = reader.readLine(); + Assert.assertTrue(line.contains(" 200")); + while (line.length() > 0) + line = reader.readLine(); + for (byte datum : data) + Assert.assertEquals(datum, reader.read()); + Assert.assertFalse(reader.ready()); + + client.close(); + } + + @Test + public void testPOSTWithSmallRequestContentThenRedirect() throws Exception + { + final byte[] data = "0123456789ABCDEF".getBytes("UTF-8"); + InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + return new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + { + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + headers.put(HTTPSPDYHeader.STATUS.name(version()), "303 See Other"); + stream.reply(new ReplyInfo(headers, true)); + } + } + }; + } + })); + + Socket client = new Socket(); + client.connect(proxyAddress); + OutputStream output = client.getOutputStream(); + + String request = "" + + "POST / HTTP/1.1\r\n" + + "Host: localhost:" + proxyAddress.getPort() + "\r\n" + + "Content-Length: " + data.length + "\r\n" + + "Content-Type: application/octet-stream\r\n" + + "\r\n"; + output.write(request.getBytes("UTF-8")); + output.write(data); + output.flush(); + + InputStream input = client.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + String line = reader.readLine(); + Assert.assertTrue(line.contains(" 303")); + while (line.length() > 0) + line = reader.readLine(); + Assert.assertFalse(reader.ready()); + + // Perform another request so that we are sure we reset the states of parsers and generators + output.write(request.getBytes("UTF-8")); + output.write(data); + output.flush(); + + line = reader.readLine(); + Assert.assertTrue(line.contains(" 303")); + while (line.length() > 0) + line = reader.readLine(); + Assert.assertFalse(reader.ready()); + + client.close(); + } + + @Test + public void testPOSTWithSmallRequestContentThenSmallResponseContent() throws Exception + { + final byte[] data = "0123456789ABCDEF".getBytes("UTF-8"); + InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + return new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + { + Headers responseHeaders = new Headers(); + responseHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + responseHeaders.put(HTTPSPDYHeader.STATUS.name(version()), "200 OK"); + ReplyInfo replyInfo = new ReplyInfo(responseHeaders, false); + stream.reply(replyInfo); + stream.data(new BytesDataInfo(data, true)); + } + } + }; + } + })); + + Socket client = new Socket(); + client.connect(proxyAddress); + OutputStream output = client.getOutputStream(); + + String request = "" + + "POST / HTTP/1.1\r\n" + + "Host: localhost:" + proxyAddress.getPort() + "\r\n" + + "Content-Length: " + data.length + "\r\n" + + "Content-Type: application/octet-stream\r\n" + + "\r\n"; + output.write(request.getBytes("UTF-8")); + output.write(data); + output.flush(); + + InputStream input = client.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + String line = reader.readLine(); + Assert.assertTrue(line.contains(" 200")); + while (line.length() > 0) + line = reader.readLine(); + for (byte datum : data) + Assert.assertEquals(datum, reader.read()); + Assert.assertFalse(reader.ready()); + + // Perform another request so that we are sure we reset the states of parsers and generators + output.write(request.getBytes("UTF-8")); + output.write(data); + output.flush(); + + line = reader.readLine(); + Assert.assertTrue(line.contains(" 200")); + while (line.length() > 0) + line = reader.readLine(); + for (byte datum : data) + Assert.assertEquals(datum, reader.read()); + Assert.assertFalse(reader.ready()); + + client.close(); + } + + @Test + public void testSYNThenREPLY() throws Exception + { + final String header = "foo"; + InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + Headers requestHeaders = synInfo.getHeaders(); + Assert.assertNotNull(requestHeaders.get("via")); + Assert.assertNotNull(requestHeaders.get(header)); + + Headers responseHeaders = new Headers(); + responseHeaders.put(header, "baz"); + stream.reply(new ReplyInfo(responseHeaders, true)); + return null; + } + })); + proxyConnector.setDefaultAsyncConnectionFactory(proxyConnector.getAsyncConnectionFactory("spdy/" + version())); + + Session client = factory.newSPDYClient(version()).connect(proxyAddress, null).get(5, TimeUnit.SECONDS); + + final CountDownLatch replyLatch = new CountDownLatch(1); + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + proxyAddress.getPort()); + headers.put(header, "bar"); + client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Headers headers = replyInfo.getHeaders(); + Assert.assertNotNull(headers.get(header)); + replyLatch.countDown(); + } + }); + + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + + client.goAway().get(5, TimeUnit.SECONDS); + } + + @Test + public void testSYNThenREPLYAndDATA() throws Exception + { + final byte[] data = "0123456789ABCDEF".getBytes("UTF-8"); + final String header = "foo"; + InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + Headers requestHeaders = synInfo.getHeaders(); + Assert.assertNotNull(requestHeaders.get("via")); + Assert.assertNotNull(requestHeaders.get(header)); + + Headers responseHeaders = new Headers(); + responseHeaders.put(header, "baz"); + stream.reply(new ReplyInfo(responseHeaders, false)); + stream.data(new BytesDataInfo(data, true)); + return null; + } + })); + proxyConnector.setDefaultAsyncConnectionFactory(proxyConnector.getAsyncConnectionFactory("spdy/" + version())); + + Session client = factory.newSPDYClient(version()).connect(proxyAddress, null).get(5, TimeUnit.SECONDS); + + final CountDownLatch replyLatch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(1); + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + proxyAddress.getPort()); + headers.put(header, "bar"); + client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + private final ByteArrayOutputStream result = new ByteArrayOutputStream(); + + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + Headers headers = replyInfo.getHeaders(); + Assert.assertNotNull(headers.get(header)); + replyLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + result.write(dataInfo.asBytes(true), 0, dataInfo.length()); + if (dataInfo.isClose()) + { + Assert.assertArrayEquals(data, result.toByteArray()); + dataLatch.countDown(); + } + } + }); + + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + + client.goAway().get(5, TimeUnit.SECONDS); + } + + @Test + public void testGETThenSPDYPushIsIgnored() throws Exception + { + final byte[] data = "0123456789ABCDEF".getBytes("UTF-8"); + InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + Headers responseHeaders = new Headers(); + responseHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + responseHeaders.put(HTTPSPDYHeader.STATUS.name(version()), "200 OK"); + + Headers pushHeaders = new Headers(); + pushHeaders.put(HTTPSPDYHeader.URI.name(version()), "/push"); + stream.syn(new SynInfo(pushHeaders, false), 5, TimeUnit.SECONDS, new Handler.Adapter() + { + @Override + public void completed(Stream pushStream) + { + pushStream.data(new BytesDataInfo(data, true)); + } + }); + + stream.reply(new ReplyInfo(responseHeaders, true)); + return null; + } + })); + + Socket client = new Socket(); + client.connect(proxyAddress); + OutputStream output = client.getOutputStream(); + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost:" + proxyAddress.getPort() + "\r\n" + + "\r\n"; + output.write(request.getBytes("UTF-8")); + output.flush(); + + client.setSoTimeout(1000); + InputStream input = client.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + String line = reader.readLine(); + Assert.assertTrue(line.contains(" 200")); + while (line.length() > 0) + line = reader.readLine(); + Assert.assertFalse(reader.ready()); + + client.close(); + } + + @Test + public void testSYNThenSPDYPushIsReceived() throws Exception + { + final byte[] data = "0123456789ABCDEF".getBytes("UTF-8"); + InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + Headers responseHeaders = new Headers(); + responseHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + responseHeaders.put(HTTPSPDYHeader.STATUS.name(version()), "200 OK"); + stream.reply(new ReplyInfo(responseHeaders, false)); + + Headers pushHeaders = new Headers(); + pushHeaders.put(HTTPSPDYHeader.URI.name(version()), "/push"); + stream.syn(new SynInfo(pushHeaders, false), 5, TimeUnit.SECONDS, new Handler.Adapter() + { + @Override + public void completed(Stream pushStream) + { + pushStream.data(new BytesDataInfo(data, true)); + } + }); + + stream.data(new BytesDataInfo(data, true)); + + return null; + } + })); + proxyConnector.setDefaultAsyncConnectionFactory(proxyConnector.getAsyncConnectionFactory("spdy/" + version())); + + final CountDownLatch pushSynLatch = new CountDownLatch(1); + final CountDownLatch pushDataLatch = new CountDownLatch(1); + Session client = factory.newSPDYClient(version()).connect(proxyAddress, new SessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + pushSynLatch.countDown(); + return new StreamFrameListener.Adapter() + { + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + pushDataLatch.countDown(); + } + }; + } + }).get(5, TimeUnit.SECONDS); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + proxyAddress.getPort()); + final CountDownLatch replyLatch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(1); + client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter() + { + @Override + public void onReply(Stream stream, ReplyInfo replyInfo) + { + replyLatch.countDown(); + } + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataInfo.consume(dataInfo.length()); + if (dataInfo.isClose()) + dataLatch.countDown(); + } + }); + + Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(pushSynLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + + client.goAway().get(5, TimeUnit.SECONDS); + } + + @Test + public void testPING() throws Exception + { + // PING is per hop, and it does not carry the information to which server to ping to + // We just verify that it works + + InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter())); + proxyConnector.setDefaultAsyncConnectionFactory(proxyConnector.getAsyncConnectionFactory("spdy/" + version())); + + final CountDownLatch pingLatch = new CountDownLatch(1); + Session client = factory.newSPDYClient(version()).connect(proxyAddress, new SessionFrameListener.Adapter() + { + @Override + public void onPing(Session session, PingInfo pingInfo) + { + pingLatch.countDown(); + } + }).get(5, TimeUnit.SECONDS); + + client.ping().get(5, TimeUnit.SECONDS); + + Assert.assertTrue(pingLatch.await(5, TimeUnit.SECONDS)); + + client.goAway().get(5, TimeUnit.SECONDS); + } + + @Test + public void testGETThenReset() throws Exception + { + InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + Assert.assertTrue(synInfo.isClose()); + Headers requestHeaders = synInfo.getHeaders(); + Assert.assertNotNull(requestHeaders.get("via")); + + stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM)); + + return null; + } + })); + + Socket client = new Socket(); + client.connect(proxyAddress); + OutputStream output = client.getOutputStream(); + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost:" + proxyAddress.getPort() + "\r\n" + + "\r\n"; + output.write(request.getBytes("UTF-8")); + output.flush(); + + InputStream input = client.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + Assert.assertNull(reader.readLine()); + + client.close(); + } + + @Test + public void testSYNThenReset() throws Exception + { + InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + Assert.assertTrue(synInfo.isClose()); + Headers requestHeaders = synInfo.getHeaders(); + Assert.assertNotNull(requestHeaders.get("via")); + + stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM)); + + return null; + } + })); + proxyConnector.setDefaultAsyncConnectionFactory(proxyConnector.getAsyncConnectionFactory("spdy/" + version())); + + final CountDownLatch resetLatch = new CountDownLatch(1); + Session client = factory.newSPDYClient(version()).connect(proxyAddress, new SessionFrameListener.Adapter() + { + @Override + public void onRst(Session session, RstInfo rstInfo) + { + resetLatch.countDown(); + } + }).get(5, TimeUnit.SECONDS); + + Headers headers = new Headers(); + headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + proxyAddress.getPort()); + client.syn(new SynInfo(headers, true), null); + + Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS)); + + client.goAway().get(5, TimeUnit.SECONDS); + } +} diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/AsyncConnectionFactory.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/AsyncConnectionFactory.java index cb2b6bb2c7b..a5b4741a944 100644 --- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/AsyncConnectionFactory.java +++ b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/AsyncConnectionFactory.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// ======================================================================== +// Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// 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; diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/EmptyAsyncConnection.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/EmptyAsyncConnection.java deleted file mode 100644 index 31b50e78765..00000000000 --- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/EmptyAsyncConnection.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.eclipse.jetty.spdy; - -import org.eclipse.jetty.io.AbstractAsyncConnection; -import org.eclipse.jetty.io.AsyncEndPoint; - -public class EmptyAsyncConnection extends AbstractAsyncConnection -{ - public EmptyAsyncConnection(AsyncEndPoint endPoint) - { - super(endPoint); - } - - @Override - public void onReadable() - { - } -} diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/EmptyAsyncEndPoint.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/EmptyAsyncEndPoint.java index d84f0172b10..45b9d0228ab 100644 --- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/EmptyAsyncEndPoint.java +++ b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/EmptyAsyncEndPoint.java @@ -1,28 +1,27 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// ======================================================================== +// Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== package org.eclipse.jetty.spdy; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; +import java.nio.channels.ReadPendingException; +import java.nio.channels.WritePendingException; import org.eclipse.jetty.io.AsyncConnection; import org.eclipse.jetty.io.AsyncEndPoint; -import org.eclipse.jetty.io.IOFuture; +import org.eclipse.jetty.util.Callback; public class EmptyAsyncEndPoint implements AsyncEndPoint { @@ -30,7 +29,7 @@ public class EmptyAsyncEndPoint implements AsyncEndPoint private AsyncConnection connection; private boolean oshut; private boolean closed; - private int maxIdleTime; + private long maxIdleTime; @Override public long getCreatedTimeStamp() @@ -38,24 +37,6 @@ public class EmptyAsyncEndPoint implements AsyncEndPoint return 0; } - @Override - public long getIdleTimestamp() - { - return 0; - } - - @Override - public void setCheckForIdle(boolean check) - { - this.checkForIdle = check; - } - - @Override - public boolean isCheckForIdle() - { - return checkForIdle; - } - @Override public AsyncConnection getAsyncConnection() { @@ -98,24 +79,12 @@ public class EmptyAsyncEndPoint implements AsyncEndPoint return 0; } - @Override - public IOFuture readable() throws IllegalStateException - { - return null; - } - @Override public int flush(ByteBuffer... buffer) throws IOException { return 0; } - @Override - public IOFuture write(ByteBuffer... buffers) throws IllegalStateException - { - return null; - } - @Override public InetSocketAddress getLocalAddress() { @@ -141,14 +110,34 @@ public class EmptyAsyncEndPoint implements AsyncEndPoint } @Override - public int getMaxIdleTime() + public long getIdleTimeout() { return maxIdleTime; } @Override - public void setMaxIdleTime(int timeMs) throws IOException + public void setIdleTimeout(long timeMs) { this.maxIdleTime = timeMs; } + + @Override + public void onOpen() + { + } + + @Override + public void onClose() + { + } + + @Override + public void fillInterested(C context, Callback callback) throws ReadPendingException + { + } + + @Override + public void write(C context, Callback callback, ByteBuffer... buffers) throws WritePendingException + { + } } diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/FlowControlStrategyFactory.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/FlowControlStrategyFactory.java new file mode 100644 index 00000000000..b93183154c2 --- /dev/null +++ b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/FlowControlStrategyFactory.java @@ -0,0 +1,36 @@ +// ======================================================================== +// Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.spdy; + +import org.eclipse.jetty.spdy.api.SPDY; + +public class FlowControlStrategyFactory +{ + private FlowControlStrategyFactory() + { + } + + public static FlowControlStrategy newFlowControlStrategy(short version) + { + switch (version) + { + case SPDY.V2: + return new FlowControlStrategy.None(); + case SPDY.V3: + return new SPDYv3FlowControlStrategy(); + default: + throw new IllegalStateException(); + } + } +} diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/NextProtoNegoClientAsyncConnection.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/NextProtoNegoClientAsyncConnection.java new file mode 100644 index 00000000000..3d5e771cfba --- /dev/null +++ b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/NextProtoNegoClientAsyncConnection.java @@ -0,0 +1,101 @@ +// ======================================================================== +// Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.spdy; + +import java.io.IOException; +import java.nio.channels.SocketChannel; +import java.util.List; +import java.util.concurrent.Executor; + +import org.eclipse.jetty.io.AbstractAsyncConnection; +import org.eclipse.jetty.io.AsyncConnection; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.npn.NextProtoNego; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class NextProtoNegoClientAsyncConnection extends AbstractAsyncConnection implements NextProtoNego.ClientProvider +{ + private final Logger logger = Log.getLogger(getClass()); + private final SocketChannel channel; + private final Object attachment; + private final SPDYClient client; + private volatile boolean completed; + + public NextProtoNegoClientAsyncConnection(SocketChannel channel, AsyncEndPoint endPoint, Object attachment, Executor executor, SPDYClient client) + { + super(endPoint, executor); + this.channel = channel; + this.attachment = attachment; + this.client = client; + } + + @Override + public void onFillable() + { + while (true) + { + int filled = fill(); + if (filled == 0 && !completed) + fillInterested(); + if (filled <= 0) + break; + } + } + + private int fill() + { + try + { + return getEndPoint().fill(BufferUtil.EMPTY_BUFFER); + } + catch (IOException x) + { + logger.debug(x); + getEndPoint().close(); + return -1; + } + } + + @Override + public boolean supports() + { + return true; + } + + @Override + public void unsupported() + { + // Server does not support NPN, but this is a SPDY client, so hardcode SPDY + AsyncEndPoint endPoint = getEndPoint(); + AsyncConnection connection = client.getDefaultAsyncConnectionFactory().newAsyncConnection(channel, endPoint, attachment); + client.replaceAsyncConnection(endPoint, connection); + completed = true; + } + + @Override + public String selectProtocol(List protocols) + { + String protocol = client.selectProtocol(protocols); + if (protocol == null) + return null; + AsyncEndPoint endPoint = getEndPoint(); + AsyncConnection connection = client.getAsyncConnectionFactory(protocol).newAsyncConnection(channel, endPoint, attachment); + client.replaceAsyncConnection(endPoint, connection); + completed = true; + return protocol; + } + +} diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/NextProtoNegoServerAsyncConnection.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/NextProtoNegoServerAsyncConnection.java new file mode 100644 index 00000000000..c6e5ee96302 --- /dev/null +++ b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/NextProtoNegoServerAsyncConnection.java @@ -0,0 +1,94 @@ +// ======================================================================== +// Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.spdy; + +import java.io.IOException; +import java.nio.channels.SocketChannel; +import java.util.List; + +import org.eclipse.jetty.io.AbstractAsyncConnection; +import org.eclipse.jetty.io.AsyncConnection; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.npn.NextProtoNego; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class NextProtoNegoServerAsyncConnection extends AbstractAsyncConnection implements NextProtoNego.ServerProvider +{ + private final Logger logger = Log.getLogger(getClass()); + private final SocketChannel channel; + private final SPDYServerConnector connector; + private volatile boolean completed; + + public NextProtoNegoServerAsyncConnection(SocketChannel channel, AsyncEndPoint endPoint, SPDYServerConnector connector) + { + super(endPoint, connector.findExecutor()); + this.channel = channel; + this.connector = connector; + } + + @Override + public void onFillable() + { + while (true) + { + int filled = fill(); + if (filled == 0 && !completed) + fillInterested(); + if (filled <= 0) + break; + } + } + + private int fill() + { + try + { + return getEndPoint().fill(BufferUtil.EMPTY_BUFFER); + } + catch (IOException x) + { + logger.debug(x); + getEndPoint().close(); + return -1; + } + } + + @Override + public void unsupported() + { + AsyncConnectionFactory asyncConnectionFactory = connector.getDefaultAsyncConnectionFactory(); + AsyncEndPoint endPoint = getEndPoint(); + AsyncConnection connection = asyncConnectionFactory.newAsyncConnection(channel, endPoint, connector); + connector.replaceAsyncConnection(endPoint, connection); + completed = true; + } + + @Override + public List protocols() + { + return connector.provideProtocols(); + } + + @Override + public void protocolSelected(String protocol) + { + AsyncConnectionFactory asyncConnectionFactory = connector.getAsyncConnectionFactory(protocol); + AsyncEndPoint endPoint = getEndPoint(); + AsyncConnection connection = asyncConnectionFactory.newAsyncConnection(channel, endPoint, connector); + connector.replaceAsyncConnection(endPoint, connection); + completed = true; + } +} diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYAsyncConnection.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYAsyncConnection.java index 42444f9f31e..02b21dca42a 100644 --- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYAsyncConnection.java +++ b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYAsyncConnection.java @@ -1,32 +1,28 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// ======================================================================== +// Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== package org.eclipse.jetty.spdy; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.concurrent.Executor; import org.eclipse.jetty.io.AbstractAsyncConnection; import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.ByteBufferPool; -import org.eclipse.jetty.io.IOFuture; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.spdy.api.Session; import org.eclipse.jetty.spdy.parser.Parser; -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; @@ -37,26 +33,27 @@ public class SPDYAsyncConnection extends AbstractAsyncConnection implements Cont private final ByteBufferPool bufferPool; private final Parser parser; private volatile Session session; + private volatile boolean idle = false; - public SPDYAsyncConnection(AsyncEndPoint endPoint, ByteBufferPool bufferPool, Parser parser) + public SPDYAsyncConnection(AsyncEndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Executor executor) { - super(endPoint); + super(endPoint, executor); this.bufferPool = bufferPool; this.parser = parser; onIdle(true); } @Override - public void onReadable() + public void onFillable() { - ByteBuffer buffer = bufferPool.acquire(8192, true); - BufferUtil.clear(buffer); - read(buffer); + ByteBuffer buffer = bufferPool.acquire(8192, true); //TODO: 8k window? + boolean readMore = read(buffer) == 0; bufferPool.release(buffer); - scheduleOnReadable(); + if (readMore) + fillInterested(); } - protected void read(ByteBuffer buffer) + protected int read(ByteBuffer buffer) { AsyncEndPoint endPoint = getEndPoint(); while (true) @@ -64,12 +61,12 @@ public class SPDYAsyncConnection extends AbstractAsyncConnection implements Cont int filled = fill(endPoint, buffer); if (filled == 0) { - break; + return 0; } else if (filled < 0) { close(false); - break; + return -1; } else { @@ -94,15 +91,9 @@ public class SPDYAsyncConnection extends AbstractAsyncConnection implements Cont @Override public int write(ByteBuffer buffer, final Callback callback, StandardSession.FrameBytes context) { - int remaining = buffer.remaining(); AsyncEndPoint endPoint = getEndPoint(); - IOFuture write = endPoint.write(buffer); - int written = remaining - buffer.remaining(); - if (write.isDone()) - callback.completed(context); - else - write.setCallback(callback, context); - return written; + endPoint.write(context, callback, buffer); + return -1; //TODO: void or have endPoint.write return int } @Override @@ -123,13 +114,15 @@ public class SPDYAsyncConnection extends AbstractAsyncConnection implements Cont @Override public void onIdle(boolean idle) { - getEndPoint().setCheckForIdle(idle); + this.idle = idle; } @Override - public void onIdleExpired(long idleForMs) + protected boolean onReadTimeout() { - session.goAway(); + if(idle) + session.goAway(); + return idle; } protected Session getSession() diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYClient.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYClient.java index 32f32a4d637..bb0a1b1149b 100644 --- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYClient.java +++ b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYClient.java @@ -1,23 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ +// ======================================================================== +// Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// 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; @@ -37,9 +29,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLException; import org.eclipse.jetty.io.AsyncConnection; import org.eclipse.jetty.io.AsyncEndPoint; @@ -60,10 +50,12 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool; public class SPDYClient { private final Map factories = new ConcurrentHashMap<>(); + private final AsyncConnectionFactory defaultAsyncConnectionFactory = new ClientSPDYAsyncConnectionFactory(); private final short version; private final Factory factory; - private SocketAddress bindAddress; - private long maxIdleTime; + private volatile SocketAddress bindAddress; + private volatile long idleTimeout = -1; + private volatile int initialWindowSize = 65536; protected SPDYClient(short version, Factory factory) { @@ -100,7 +92,7 @@ public class SPDYClient channel.socket().setTcpNoDelay(true); channel.configureBlocking(false); - SessionPromise result = new SessionPromise(this, listener); + SessionPromise result = new SessionPromise(channel, this, listener); channel.connect(address); factory.selector.connect(channel, result); @@ -108,14 +100,24 @@ public class SPDYClient return result; } - public long getMaxIdleTime() + public long getIdleTimeout() { - return maxIdleTime; + return idleTimeout; } - public void setMaxIdleTime(long maxIdleTime) + public void setIdleTimeout(long idleTimeout) { - this.maxIdleTime = maxIdleTime; + this.idleTimeout = idleTimeout; + } + + public int getInitialWindowSize() + { + return initialWindowSize; + } + + public void setInitialWindowSize(int initialWindowSize) + { + this.initialWindowSize = initialWindowSize; } protected String selectProtocol(List serverProtocols) @@ -163,6 +165,11 @@ public class SPDYClient return factories.remove(protocol); } + public AsyncConnectionFactory getDefaultAsyncConnectionFactory() + { + return defaultAsyncConnectionFactory; + } + protected SSLEngine newSSLEngine(SslContextFactory sslContextFactory, SocketChannel channel) { String peerHost = channel.socket().getInetAddress().getHostAddress(); @@ -172,6 +179,18 @@ public class SPDYClient return engine; } + protected FlowControlStrategy newFlowControlStrategy() + { + return FlowControlStrategyFactory.newFlowControlStrategy(version); + } + + public void replaceAsyncConnection(AsyncEndPoint endPoint, AsyncConnection connection) + { + AsyncConnection oldConnection = endPoint.getAsyncConnection(); + endPoint.setAsyncConnection(connection); + factory.selector.connectionUpgraded(endPoint, oldConnection); + } + public static class Factory extends AggregateLifeCycle { private final Map factories = new ConcurrentHashMap<>(); @@ -181,24 +200,43 @@ public class SPDYClient private final Executor threadPool; private final SslContextFactory sslContextFactory; private final SelectorManager selector; + private final long defaultTimeout = 30000; + private final long idleTimeout; + //TODO: Replace with Builder?! public Factory() { - this(null, null); + this(null, null, 30000); } public Factory(SslContextFactory sslContextFactory) { - this(null, sslContextFactory); + this(null, sslContextFactory, 30000); + } + + public Factory(SslContextFactory sslContextFactory, long idleTimeout) + { + this(null, sslContextFactory, idleTimeout); } public Factory(Executor threadPool) { - this(threadPool, null); + this(threadPool, null, 30000); + } + + public Factory(Executor threadPool, long idleTimeout) + { + this(threadPool, null, idleTimeout); } public Factory(Executor threadPool, SslContextFactory sslContextFactory) { + this(threadPool, sslContextFactory, 30000); + } + + public Factory(Executor threadPool, SslContextFactory sslContextFactory, long idleTimeout) + { + this.idleTimeout = idleTimeout; if (threadPool == null) threadPool = new QueuedThreadPool(); this.threadPool = threadPool; @@ -208,7 +246,7 @@ public class SPDYClient if (sslContextFactory != null) addBean(sslContextFactory); - selector = new ClientSelectorManager(threadPool); + selector = new ClientSelectorManager(); addBean(selector); factories.put("spdy/2", new ClientSPDYAsyncConnectionFactory()); @@ -266,25 +304,28 @@ public class SPDYClient private class ClientSelectorManager extends SelectorManager { - private ClientSelectorManager(Executor executor) + + @Override + protected AsyncEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException { - super(executor); + SessionPromise attachment = (SessionPromise)key.attachment(); + + long clientIdleTimeout = attachment.client.getIdleTimeout(); + if (clientIdleTimeout < 0) + clientIdleTimeout = idleTimeout; + AsyncEndPoint result = new SelectChannelEndPoint(channel, selectSet, key, scheduler, clientIdleTimeout); + + return result; } @Override - protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException + protected void execute(Runnable task) { - SessionPromise attachment = (SessionPromise)selectionKey.attachment(); - - long maxIdleTime = attachment.client.getMaxIdleTime(); - if (maxIdleTime < 0) - maxIdleTime = getIdleTimeout(); - - return new SelectChannelEndPoint(channel, selectSet, selectionKey, maxIdleTime); + threadPool.execute(task); } @Override - public AsyncConnection newConnection(final SocketChannel channel, AsyncEndPoint endPoint, Object attachment) + public AsyncConnection newConnection(final SocketChannel channel, AsyncEndPoint endPoint, final Object attachment) { SessionPromise sessionPromise = (SessionPromise)attachment; final SPDYClient client = sessionPromise.client; @@ -293,68 +334,23 @@ public class SPDYClient { if (sslContextFactory != null) { - final AtomicReference sslEndPointRef = new AtomicReference<>(); - final AtomicReference attachmentRef = new AtomicReference<>(attachment); - SSLEngine engine = client.newSSLEngine(sslContextFactory, channel); + final SSLEngine engine = client.newSSLEngine(sslContextFactory, channel); SslConnection sslConnection = new SslConnection(bufferPool, threadPool, endPoint, engine) { @Override public void onClose() { - sslEndPointRef.set(null); - attachmentRef.set(null); + NextProtoNego.remove(engine); super.onClose(); } }; - endPoint.setAsyncConnection(sslConnection); + AsyncEndPoint sslEndPoint = sslConnection.getSslEndPoint(); - sslEndPointRef.set(sslEndPoint); - - // Instances of the ClientProvider inner class strong reference the - // SslEndPoint (via lexical scoping), which strong references the SSLEngine. - // Since NextProtoNego stores in a WeakHashMap the SSLEngine as key - // and this instance as value, we are in the situation where the value - // of a WeakHashMap refers indirectly to the key, which is bad because - // the entry will never be removed from the WeakHashMap. - // We use AtomicReferences to be captured via lexical scoping, - // and we null them out above when the connection is closed. - NextProtoNego.put(engine, new NextProtoNego.ClientProvider() - { - @Override - public boolean supports() - { - return true; - } - - @Override - public void unsupported() - { - // Server does not support NPN, but this is a SPDY client, so hardcode SPDY - ClientSPDYAsyncConnectionFactory connectionFactory = new ClientSPDYAsyncConnectionFactory(); - AsyncEndPoint sslEndPoint = sslEndPointRef.get(); - AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, attachmentRef.get()); - sslEndPoint.setAsyncConnection(connection); - } - - @Override - public String selectProtocol(List protocols) - { - String protocol = client.selectProtocol(protocols); - if (protocol == null) - return null; - - AsyncConnectionFactory connectionFactory = client.getAsyncConnectionFactory(protocol); - AsyncEndPoint sslEndPoint = sslEndPointRef.get(); - AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, attachmentRef.get()); - sslEndPoint.setAsyncConnection(connection); - return protocol; - } - }); - - AsyncConnection connection = new EmptyAsyncConnection(sslEndPoint); + NextProtoNegoClientAsyncConnection connection = new NextProtoNegoClientAsyncConnection(channel, sslEndPoint, attachment, client.factory.threadPool, client); sslEndPoint.setAsyncConnection(connection); + connectionOpened(connection); - startHandshake(engine); + NextProtoNego.put(engine, connection); return sslConnection; } @@ -368,35 +364,40 @@ public class SPDYClient } catch (RuntimeException x) { - sessionPromise.failed(null, x); + sessionPromise.failed(null,x); throw x; } } - - private void startHandshake(SSLEngine engine) - { - try - { - engine.beginHandshake(); - } - catch (SSLException x) - { - throw new RuntimeException(x); - } - } } } private static class SessionPromise extends Promise { + private final SocketChannel channel; private final SPDYClient client; private final SessionFrameListener listener; - private SessionPromise(SPDYClient client, SessionFrameListener listener) + private SessionPromise(SocketChannel channel, SPDYClient client, SessionFrameListener listener) { + this.channel = channel; this.client = client; this.listener = listener; } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) + { + try + { + super.cancel(mayInterruptIfRunning); + channel.close(); + return true; + } + catch (IOException x) + { + return true; + } + } } private static class ClientSPDYAsyncConnectionFactory implements AsyncConnectionFactory @@ -405,7 +406,8 @@ public class SPDYClient public AsyncConnection newAsyncConnection(SocketChannel channel, AsyncEndPoint endPoint, Object attachment) { SessionPromise sessionPromise = (SessionPromise)attachment; - Factory factory = sessionPromise.client.factory; + SPDYClient client = sessionPromise.client; + Factory factory = client.factory; CompressionFactory compressionFactory = new StandardCompressionFactory(); Parser parser = new Parser(compressionFactory.newDecompressor()); @@ -414,7 +416,10 @@ public class SPDYClient SPDYAsyncConnection connection = new ClientSPDYAsyncConnection(endPoint, factory.bufferPool, parser, factory); endPoint.setAsyncConnection(connection); - StandardSession session = new StandardSession(sessionPromise.client.version, factory.bufferPool, factory.threadPool, factory.scheduler, connection, connection, 1, sessionPromise.listener, generator); + FlowControlStrategy flowControlStrategy = client.newFlowControlStrategy(); + + StandardSession session = new StandardSession(client.version, factory.bufferPool, factory.threadPool, factory.scheduler, connection, connection, 1, sessionPromise.listener, generator, flowControlStrategy); + session.setWindowSize(client.getInitialWindowSize()); parser.addListener(session); sessionPromise.completed(session); connection.setSession(session); @@ -430,7 +435,7 @@ public class SPDYClient public ClientSPDYAsyncConnection(AsyncEndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Factory factory) { - super(endPoint, bufferPool, parser); + super(endPoint, bufferPool, parser, factory.threadPool); this.factory = factory; } diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYServerConnector.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYServerConnector.java index 001a7b9bc2d..24498c4ead1 100644 --- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYServerConnector.java +++ b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYServerConnector.java @@ -1,21 +1,19 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// ======================================================================== +// Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== package org.eclipse.jetty.spdy; +import java.io.IOException; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Collection; @@ -27,11 +25,10 @@ import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLException; import org.eclipse.jetty.io.AsyncConnection; import org.eclipse.jetty.io.AsyncEndPoint; @@ -43,10 +40,10 @@ import org.eclipse.jetty.server.SelectChannelConnector; import org.eclipse.jetty.spdy.api.SPDY; import org.eclipse.jetty.spdy.api.Session; import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener; +import org.eclipse.jetty.util.component.AggregateLifeCycle; 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.ThreadPool; public class SPDYServerConnector extends SelectChannelConnector { @@ -56,10 +53,12 @@ public class SPDYServerConnector extends SelectChannelConnector private final Map factories = new LinkedHashMap<>(); private final Queue sessions = new ConcurrentLinkedQueue<>(); private final ByteBufferPool bufferPool = new StandardByteBufferPool(); + private final Executor executor = new LazyExecutor(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final ServerSessionFrameListener listener; private final SslContextFactory sslContextFactory; - private AsyncConnectionFactory defaultConnectionFactory; + private volatile AsyncConnectionFactory defaultConnectionFactory; + private volatile int initialWindowSize = 65536; public SPDYServerConnector(ServerSessionFrameListener listener) { @@ -72,6 +71,9 @@ public class SPDYServerConnector extends SelectChannelConnector this.sslContextFactory = sslContextFactory; if (sslContextFactory != null) addBean(sslContextFactory); + putAsyncConnectionFactory("spdy/3", new ServerSPDYAsyncConnectionFactory(SPDY.V3, bufferPool, executor, scheduler, listener)); + putAsyncConnectionFactory("spdy/2", new ServerSPDYAsyncConnectionFactory(SPDY.V2, bufferPool, executor, scheduler, listener)); + setDefaultAsyncConnectionFactory(getAsyncConnectionFactory("spdy/2")); } public ByteBufferPool getByteBufferPool() @@ -81,17 +83,7 @@ public class SPDYServerConnector extends SelectChannelConnector public Executor getExecutor() { - final ThreadPool threadPool = getThreadPool(); - if (threadPool instanceof Executor) - return (Executor)threadPool; - return new Executor() - { - @Override - public void execute(Runnable command) - { - threadPool.dispatch(command); - } - }; + return executor; } public ScheduledExecutorService getScheduler() @@ -99,6 +91,11 @@ public class SPDYServerConnector extends SelectChannelConnector return scheduler; } + public ServerSessionFrameListener getServerSessionFrameListener() + { + return listener; + } + public SslContextFactory getSslContextFactory() { return sslContextFactory; @@ -108,8 +105,6 @@ public class SPDYServerConnector extends SelectChannelConnector protected void doStart() throws Exception { super.doStart(); - defaultConnectionFactory = new ServerSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), scheduler, listener); - putAsyncConnectionFactory("spdy/2", defaultConnectionFactory); logger.info("SPDY support is experimental. Please report feedback at jetty-dev@eclipse.org"); } @@ -160,6 +155,14 @@ public class SPDYServerConnector extends SelectChannelConnector } } + public void clearAsyncConnectionFactories() + { + synchronized (factories) + { + factories.clear(); + } + } + protected List provideProtocols() { synchronized (factories) @@ -168,70 +171,39 @@ public class SPDYServerConnector extends SelectChannelConnector } } - protected AsyncConnectionFactory getDefaultAsyncConnectionFactory() + public AsyncConnectionFactory getDefaultAsyncConnectionFactory() { return defaultConnectionFactory; } + public void setDefaultAsyncConnectionFactory(AsyncConnectionFactory defaultConnectionFactory) + { + this.defaultConnectionFactory = defaultConnectionFactory; + } + @Override protected AsyncConnection newConnection(final SocketChannel channel, AsyncEndPoint endPoint) { if (sslContextFactory != null) { - SSLEngine engine = newSSLEngine(sslContextFactory, channel); - final AtomicReference sslEndPointRef = new AtomicReference<>(); - SslConnection sslConnection = new SslConnection(engine, endPoint) + final SSLEngine engine = newSSLEngine(sslContextFactory, channel); + Executor executor = findExecutor(); + SslConnection sslConnection = new SslConnection(bufferPool, executor, endPoint, engine) { @Override public void onClose() { - sslEndPointRef.set(null); + NextProtoNego.remove(engine); super.onClose(); } }; - endPoint.setAsyncConnection(sslConnection); - AsyncEndPoint sslEndPoint = sslConnection.getSslEndPoint(); - sslEndPointRef.set(sslEndPoint); - // Instances of the ServerProvider inner class strong reference the - // SslEndPoint (via lexical scoping), which strong references the SSLEngine. - // Since NextProtoNego stores in a WeakHashMap the SSLEngine as key - // and this instance as value, we are in the situation where the value - // of a WeakHashMap refers indirectly to the key, which is bad because - // the entry will never be removed from the WeakHashMap. - // We use AtomicReferences to be captured via lexical scoping, - // and we null them out above when the connection is closed. - NextProtoNego.put(engine, new NextProtoNego.ServerProvider() - { - @Override - public void unsupported() - { - AsyncConnectionFactory connectionFactory = getDefaultAsyncConnectionFactory(); - AsyncEndPoint sslEndPoint = sslEndPointRef.get(); - AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, SPDYServerConnector.this); - sslEndPoint.setAsyncConnection(connection); - } - - @Override - public List protocols() - { - return provideProtocols(); - } - - @Override - public void protocolSelected(String protocol) - { - AsyncConnectionFactory connectionFactory = getAsyncConnectionFactory(protocol); - AsyncEndPoint sslEndPoint = sslEndPointRef.get(); - AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, SPDYServerConnector.this); - sslEndPoint.setAsyncConnection(connection); - } - }); - - AsyncConnection connection = new EmptyAsyncConnection(sslEndPoint); + final AsyncEndPoint sslEndPoint = sslConnection.getSslEndPoint(); + NextProtoNegoServerAsyncConnection connection = new NextProtoNegoServerAsyncConnection(channel, sslEndPoint, this); sslEndPoint.setAsyncConnection(connection); + getSelectorManager().connectionOpened(connection); - startHandshake(engine); + NextProtoNego.put(engine, connection); return sslConnection; } @@ -244,6 +216,11 @@ public class SPDYServerConnector extends SelectChannelConnector } } + protected FlowControlStrategy newFlowControlStrategy(short version) + { + return FlowControlStrategyFactory.newFlowControlStrategy(version); + } + protected SSLEngine newSSLEngine(SslContextFactory sslContextFactory, SocketChannel channel) { String peerHost = channel.socket().getInetAddress().getHostAddress(); @@ -253,18 +230,6 @@ public class SPDYServerConnector extends SelectChannelConnector return engine; } - private void startHandshake(SSLEngine engine) - { - try - { - engine.beginHandshake(); - } - catch (SSLException x) - { - throw new RuntimeException(x); - } - } - protected boolean sessionOpened(Session session) { // Add sessions only if the connector is not stopping @@ -289,4 +254,43 @@ public class SPDYServerConnector extends SelectChannelConnector { return Collections.unmodifiableCollection(sessions); } + + public int getInitialWindowSize() + { + return initialWindowSize; + } + + public void setInitialWindowSize(int initialWindowSize) + { + this.initialWindowSize = initialWindowSize; + } + + public void replaceAsyncConnection(AsyncEndPoint endPoint, AsyncConnection connection) + { + AsyncConnection oldConnection = endPoint.getAsyncConnection(); + endPoint.setAsyncConnection(connection); + getSelectorManager().connectionUpgraded(endPoint, oldConnection); + } + + private class LazyExecutor implements Executor + { + @Override + public void execute(Runnable command) + { + Executor threadPool = findExecutor(); + if (threadPool == null) + throw new RejectedExecutionException(); + threadPool.execute(command); + } + } + + + @Override + public void dump(Appendable out, String indent) throws IOException + { + super.dump(out,indent); + AggregateLifeCycle.dump(out, indent, new ArrayList(sessions)); + } + + } diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/ServerSPDYAsyncConnectionFactory.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/ServerSPDYAsyncConnectionFactory.java index f73c87f2279..46e07b012e5 100644 --- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/ServerSPDYAsyncConnectionFactory.java +++ b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/ServerSPDYAsyncConnectionFactory.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// ======================================================================== +// Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// 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; @@ -49,6 +46,11 @@ public class ServerSPDYAsyncConnectionFactory implements AsyncConnectionFactory this.listener = listener; } + public short getVersion() + { + return version; + } + @Override public AsyncConnection newAsyncConnection(SocketChannel channel, AsyncEndPoint endPoint, Object attachment) { @@ -56,16 +58,17 @@ public class ServerSPDYAsyncConnectionFactory implements AsyncConnectionFactory Parser parser = new Parser(compressionFactory.newDecompressor()); Generator generator = new Generator(bufferPool, compressionFactory.newCompressor()); - ServerSessionFrameListener listener = this.listener; - if (listener == null) - listener = newServerSessionFrameListener(endPoint, attachment); - SPDYServerConnector connector = (SPDYServerConnector)attachment; + ServerSessionFrameListener listener = provideServerSessionFrameListener(endPoint, attachment); SPDYAsyncConnection connection = new ServerSPDYAsyncConnection(endPoint, bufferPool, parser, listener, connector); endPoint.setAsyncConnection(connection); - final StandardSession session = new StandardSession(version, bufferPool, threadPool, scheduler, connection, connection, 2, listener, generator); + FlowControlStrategy flowControlStrategy = connector.newFlowControlStrategy(version); + + StandardSession session = new StandardSession(version, bufferPool, threadPool, scheduler, connection, connection, 2, listener, generator, flowControlStrategy); + session.setAttribute("org.eclipse.jetty.spdy.remoteAddress", endPoint.getRemoteAddress()); + session.setWindowSize(connector.getInitialWindowSize()); parser.addListener(session); connection.setSession(session); @@ -74,7 +77,7 @@ public class ServerSPDYAsyncConnectionFactory implements AsyncConnectionFactory return connection; } - protected ServerSessionFrameListener newServerSessionFrameListener(AsyncEndPoint endPoint, Object attachment) + protected ServerSessionFrameListener provideServerSessionFrameListener(AsyncEndPoint endPoint, Object attachment) { return listener; } @@ -83,10 +86,11 @@ public class ServerSPDYAsyncConnectionFactory implements AsyncConnectionFactory { private final ServerSessionFrameListener listener; private final SPDYServerConnector connector; + private volatile boolean connected; private ServerSPDYAsyncConnection(AsyncEndPoint endPoint, ByteBufferPool bufferPool, Parser parser, ServerSessionFrameListener listener, SPDYServerConnector connector) { - super(endPoint, bufferPool, parser); + super(endPoint, bufferPool, parser, connector.findExecutor()); this.listener = listener; this.connector = connector; } @@ -94,9 +98,14 @@ public class ServerSPDYAsyncConnectionFactory implements AsyncConnectionFactory @Override public void onOpen() { - // NPE guard to support tests - if (listener != null) - listener.onConnect(getSession()); + if (!connected) + { + // NPE guard to support tests + if (listener != null) + listener.onConnect(getSession()); + connected = true; + } + super.onOpen(); } @Override diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/AbstractTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/AbstractTest.java index b30db27724d..ea99abd3e22 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/AbstractTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/AbstractTest.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -52,9 +50,17 @@ public abstract class AbstractTest protected SPDYServerConnector connector; protected InetSocketAddress startServer(ServerSessionFrameListener listener) throws Exception + { + return startServer(SPDY.V2, listener); + } + + protected InetSocketAddress startServer(short version, ServerSessionFrameListener listener) throws Exception { if (connector == null) connector = newSPDYServerConnector(listener); + if (listener == null) + listener = connector.getServerSessionFrameListener(); + connector.setDefaultAsyncConnectionFactory(new ServerSPDYAsyncConnectionFactory(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), listener)); connector.setPort(0); server = new Server(); server.addConnector(connector); @@ -68,6 +74,11 @@ public abstract class AbstractTest } protected Session startClient(InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception + { + return startClient(SPDY.V2, socketAddress, listener); + } + + protected Session startClient(short version, InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception { if (clientFactory == null) { @@ -76,7 +87,7 @@ public abstract class AbstractTest clientFactory = newSPDYClientFactory(threadPool); clientFactory.start(); } - return clientFactory.newSPDYClient(SPDY.V2).connect(socketAddress, listener).get(5, TimeUnit.SECONDS); + return clientFactory.newSPDYClient(version).connect(socketAddress, listener).get(5, TimeUnit.SECONDS); } protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool) diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ClosedStreamTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ClosedStreamTest.java index 9f0b249cec8..b7659c7932a 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ClosedStreamTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ClosedStreamTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -27,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.io.StandardByteBufferPool; import org.eclipse.jetty.spdy.api.BytesDataInfo; import org.eclipse.jetty.spdy.api.DataInfo; +import org.eclipse.jetty.spdy.api.GoAwayInfo; import org.eclipse.jetty.spdy.api.Headers; import org.eclipse.jetty.spdy.api.ReplyInfo; import org.eclipse.jetty.spdy.api.SPDY; @@ -38,7 +36,6 @@ 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.frames.ControlFrame; -import org.eclipse.jetty.spdy.frames.DataFrame; import org.eclipse.jetty.spdy.frames.GoAwayFrame; import org.eclipse.jetty.spdy.frames.RstStreamFrame; import org.eclipse.jetty.spdy.frames.SynReplyFrame; @@ -56,7 +53,8 @@ import static org.junit.Assert.assertThat; public class ClosedStreamTest extends AbstractTest { - //TODO: Right now it sends a rst as the stream is unknown to the session once it's closed. But according to the spec we probably should just ignore the data?! + //TODO: Right now it sends a rst as the stream is unknown to the session once it's closed. + //TODO: But according to the spec we probably should just ignore the data?! @Test public void testDataSentOnClosedStreamIsIgnored() throws Exception { @@ -84,21 +82,21 @@ public class ClosedStreamTest extends AbstractTest ByteBuffer writeBuffer = generator.control(new SynReplyFrame(SPDY.V2, (byte)0, streamId, new Headers())); channel.write(writeBuffer); + Assert.assertThat(writeBuffer.hasRemaining(), is(false)); byte[] bytes = new byte[1]; writeBuffer = generator.data(streamId, bytes.length, new BytesDataInfo(bytes, true)); channel.write(writeBuffer); + Assert.assertThat(writeBuffer.hasRemaining(), is(false)); // Write again to simulate the faulty condition writeBuffer.flip(); channel.write(writeBuffer); + Assert.assertThat(writeBuffer.hasRemaining(), is(false)); Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS)); - writeBuffer = generator.control(new GoAwayFrame(SPDY.V2, 0, SessionStatus.OK.getCode())); - channel.write(writeBuffer); - channel.shutdownOutput(); - channel.close(); + session.goAway().get(5, TimeUnit.SECONDS); server.close(); } @@ -143,45 +141,43 @@ public class ClosedStreamTest extends AbstractTest @Override public void onReply(Stream stream, ReplyInfo replyInfo) { + System.out.println("ONREPLY CLIENT CALLED"); replyReceivedLatch.countDown(); - super.onReply(stream,replyInfo); } @Override public void onData(Stream stream, DataInfo dataInfo) { clientReceivedDataLatch.countDown(); - super.onData(stream,dataInfo); } }).get(); - assertThat("reply has been received by client",replyReceivedLatch.await(5,TimeUnit.SECONDS),is(true)); + assertThat("reply has been received by client",replyReceivedLatch.await(500,TimeUnit.SECONDS),is(true)); assertThat("stream is half closed from server",stream.isHalfClosed(),is(true)); assertThat("client has not received any data sent after stream was half closed by server",clientReceivedDataLatch.await(1,TimeUnit.SECONDS), is(false)); - assertThat("sending data threw an exception",exceptionWhenSendingData.await(5,TimeUnit.SECONDS),is(true)); + assertThat("sending data threw an exception",exceptionWhenSendingData.await(500,TimeUnit.SECONDS),is(true)); //thomas } @Test public void testV2ReceiveDataOnHalfClosedStream() throws Exception { - final CountDownLatch clientResetReceivedLatch = runReceiveDataOnHalfClosedStream(SPDY.V2); - assertThat("server didn't receive data",clientResetReceivedLatch.await(1,TimeUnit.SECONDS),not(true)); + runReceiveDataOnHalfClosedStream(SPDY.V2); } @Test @Ignore("until v3 is properly implemented") public void testV3ReceiveDataOnHalfClosedStream() throws Exception { - final CountDownLatch clientResetReceivedLatch = runReceiveDataOnHalfClosedStream(SPDY.V3); - assertThat("server didn't receive data",clientResetReceivedLatch.await(1,TimeUnit.SECONDS),not(true)); + runReceiveDataOnHalfClosedStream(SPDY.V3); } - private CountDownLatch runReceiveDataOnHalfClosedStream(short version) throws Exception, IOException, InterruptedException + private void runReceiveDataOnHalfClosedStream(short version) throws Exception { final CountDownLatch clientResetReceivedLatch = new CountDownLatch(1); final CountDownLatch serverReplySentLatch = new CountDownLatch(1); final CountDownLatch clientReplyReceivedLatch = new CountDownLatch(1); final CountDownLatch serverDataReceivedLatch = new CountDownLatch(1); + final CountDownLatch goAwayReceivedLatch = new CountDownLatch(1); InetSocketAddress startServer = startServer(new ServerSessionFrameListener.Adapter() { @@ -204,17 +200,23 @@ public class ClosedStreamTest extends AbstractTest public void onData(Stream stream, DataInfo dataInfo) { serverDataReceivedLatch.countDown(); - super.onData(stream,dataInfo); } }; } + @Override + public void onGoAway(Session session, GoAwayInfo goAwayInfo) + { + goAwayReceivedLatch.countDown(); + } }); - final SocketChannel socketChannel = SocketChannel.open(startServer); final Generator generator = new Generator(new StandardByteBufferPool(),new StandardCompressionFactory().newCompressor()); - ByteBuffer synData = generator.control(new SynStreamFrame(version,SynInfo.FLAG_CLOSE,1,0,(byte)0,new Headers())); + int streamId = 1; + ByteBuffer synData = generator.control(new SynStreamFrame(version,SynInfo.FLAG_CLOSE, streamId,0,(byte)0,(short)0,new Headers())); + final SocketChannel socketChannel = SocketChannel.open(startServer); socketChannel.write(synData); + assertThat("synData is fully written", synData.hasRemaining(), is(false)); assertThat("server: syn reply is sent",serverReplySentLatch.await(5,TimeUnit.SECONDS),is(true)); @@ -243,13 +245,6 @@ public class ClosedStreamTest extends AbstractTest { clientResetReceivedLatch.countDown(); } - super.onControlFrame(frame); - } - - @Override - public void onDataFrame(DataFrame frame, ByteBuffer data) - { - super.onDataFrame(frame,data); } }); ByteBuffer response = ByteBuffer.allocate(28); @@ -258,7 +253,14 @@ public class ClosedStreamTest extends AbstractTest parser.parse(response); assertThat("server didn't receive data",serverDataReceivedLatch.await(1,TimeUnit.SECONDS),not(true)); - return clientResetReceivedLatch; - } + assertThat("client didn't receive reset",clientResetReceivedLatch.await(1,TimeUnit.SECONDS),not(true)); + ByteBuffer buffer = generator.control(new GoAwayFrame(version, streamId, SessionStatus.OK.getCode())); + socketChannel.write(buffer); + Assert.assertThat(buffer.hasRemaining(), is(false)); + + assertThat("GoAway frame is received by server", goAwayReceivedLatch.await(5,TimeUnit.SECONDS), is(true)); + + socketChannel.close(); + } } diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/FlowControlTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/FlowControlTest.java index db2303cea30..00c624fc5f4 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/FlowControlTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/FlowControlTest.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -25,9 +23,11 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jetty.spdy.api.ByteBufferDataInfo; import org.eclipse.jetty.spdy.api.BytesDataInfo; import org.eclipse.jetty.spdy.api.DataInfo; import org.eclipse.jetty.spdy.api.ReplyInfo; +import org.eclipse.jetty.spdy.api.SPDY; import org.eclipse.jetty.spdy.api.SPDYException; import org.eclipse.jetty.spdy.api.Session; import org.eclipse.jetty.spdy.api.SessionFrameListener; @@ -40,6 +40,9 @@ import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener; import org.junit.Assert; import org.junit.Test; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + public class FlowControlTest extends AbstractTest { @Test @@ -53,7 +56,7 @@ public class FlowControlTest extends AbstractTest final AtomicReference dataInfoRef = new AtomicReference<>(); final CountDownLatch dataLatch = new CountDownLatch(2); final CountDownLatch settingsLatch = new CountDownLatch(1); - Session session = startClient(startServer(new ServerSessionFrameListener.Adapter() + Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) @@ -112,7 +115,7 @@ public class FlowControlTest extends AbstractTest final int windowSize = 1536; final int length = 5 * windowSize; final CountDownLatch settingsLatch = new CountDownLatch(1); - Session session = startClient(startServer(new ServerSessionFrameListener.Adapter() + Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter() { @Override public void onSettings(Session session, SettingsInfo settingsInfo) @@ -183,43 +186,22 @@ public class FlowControlTest extends AbstractTest }); DataInfo dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS); - // Check that we are flow control stalled - expectException(TimeoutException.class, new Callable() - { - @Override - public DataInfo call() throws Exception - { - return exchanger.exchange(null, 1, TimeUnit.SECONDS); - } - }); + checkThatWeAreFlowControlStalled(exchanger); + Assert.assertEquals(windowSize, dataInfo.available()); Assert.assertEquals(0, dataInfo.consumed()); dataInfo.asByteBuffer(true); dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS); - // Check that we are flow control stalled - expectException(TimeoutException.class, new Callable() - { - @Override - public DataInfo call() throws Exception - { - return exchanger.exchange(null, 1, TimeUnit.SECONDS); - } - }); + checkThatWeAreFlowControlStalled(exchanger); + Assert.assertEquals(0, dataInfo.available()); Assert.assertEquals(0, dataInfo.consumed()); dataInfo.consume(dataInfo.length()); dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS); - // Check that we are flow control stalled - expectException(TimeoutException.class, new Callable() - { - @Override - public DataInfo call() throws Exception - { - return exchanger.exchange(null, 1, TimeUnit.SECONDS); - } - }); + checkThatWeAreFlowControlStalled(exchanger); + Assert.assertEquals(dataInfo.length() / 2, dataInfo.consumed()); dataInfo.asByteBuffer(true); @@ -236,7 +218,7 @@ public class FlowControlTest extends AbstractTest final int windowSize = 1536; final Exchanger exchanger = new Exchanger<>(); final CountDownLatch settingsLatch = new CountDownLatch(1); - Session session = startClient(startServer(new ServerSessionFrameListener.Adapter() + Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter() { @Override public void onConnect(Session session) @@ -312,43 +294,22 @@ public class FlowControlTest extends AbstractTest stream.data(new BytesDataInfo(new byte[length], true)); DataInfo dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS); - // Check that we are flow control stalled - expectException(TimeoutException.class, new Callable() - { - @Override - public DataInfo call() throws Exception - { - return exchanger.exchange(null, 1, TimeUnit.SECONDS); - } - }); + checkThatWeAreFlowControlStalled(exchanger); + Assert.assertEquals(windowSize, dataInfo.available()); Assert.assertEquals(0, dataInfo.consumed()); dataInfo.asByteBuffer(true); dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS); - // Check that we are flow control stalled - expectException(TimeoutException.class, new Callable() - { - @Override - public DataInfo call() throws Exception - { - return exchanger.exchange(null, 1, TimeUnit.SECONDS); - } - }); + checkThatWeAreFlowControlStalled(exchanger); + Assert.assertEquals(0, dataInfo.available()); Assert.assertEquals(0, dataInfo.consumed()); dataInfo.consume(dataInfo.length()); dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS); - // Check that we are flow control stalled - expectException(TimeoutException.class, new Callable() - { - @Override - public DataInfo call() throws Exception - { - return exchanger.exchange(null, 1, TimeUnit.SECONDS); - } - }); + checkThatWeAreFlowControlStalled(exchanger); + Assert.assertEquals(dataInfo.length() / 2, dataInfo.consumed()); dataInfo.asByteBuffer(true); @@ -364,7 +325,7 @@ public class FlowControlTest extends AbstractTest { final int windowSize = 1024; final CountDownLatch settingsLatch = new CountDownLatch(1); - Session session = startClient(startServer(new ServerSessionFrameListener.Adapter() + Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter() { @Override public void onSettings(Session session, SettingsInfo settingsInfo) @@ -451,6 +412,64 @@ public class FlowControlTest extends AbstractTest Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); } + @Test + public void testSendBigFileWithoutFlowControl() throws Exception + { + testSendBigFile(SPDY.V2); + } + + @Test + public void testSendBigFileWithFlowControl() throws Exception + { + testSendBigFile(SPDY.V3); + } + + private void testSendBigFile(short version) throws Exception + { + final int dataSize = 1024 * 1024; + final ByteBufferDataInfo bigByteBufferDataInfo = new ByteBufferDataInfo(ByteBuffer.allocate(dataSize),false); + final CountDownLatch allDataReceivedLatch = new CountDownLatch(1); + + Session session = startClient(version, startServer(version, new ServerSessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) + { + stream.reply(new ReplyInfo(false)); + stream.data(bigByteBufferDataInfo); + return null; + } + }),new SessionFrameListener.Adapter()); + + session.syn(new SynInfo(false),new StreamFrameListener.Adapter() + { + private int dataBytesReceived; + + @Override + public void onData(Stream stream, DataInfo dataInfo) + { + dataBytesReceived = dataBytesReceived + dataInfo.length(); + dataInfo.consume(dataInfo.length()); + if (dataBytesReceived == dataSize) + allDataReceivedLatch.countDown(); + } + }); + + assertThat("all data bytes have been received by the client", allDataReceivedLatch.await(5, TimeUnit.SECONDS), is(true)); + } + + private void checkThatWeAreFlowControlStalled(final Exchanger exchanger) + { + expectException(TimeoutException.class, new Callable() + { + @Override + public DataInfo call() throws Exception + { + return exchanger.exchange(null, 1, TimeUnit.SECONDS); + } + }); + } + private void expectException(Class exception, Callable command) { try diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/GoAwayTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/GoAwayTest.java index ec2a0f6f672..3f6be74c2d4 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/GoAwayTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/GoAwayTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -219,10 +216,10 @@ public class GoAwayTest extends AbstractTest Assert.assertThat(x.getCause(), CoreMatchers.instanceOf(ClosedChannelException.class)); } - // Be sure the last good stream is the first + // The last good stream is the second, because it was received by the server Assert.assertTrue(goAwayLatch.await(5, TimeUnit.SECONDS)); GoAwayInfo goAway = goAwayRef.get(); Assert.assertNotNull(goAway); - Assert.assertEquals(stream1.getId(), goAway.getLastStreamId()); + Assert.assertEquals(stream2.getId(), goAway.getLastStreamId()); } } diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/HeadersTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/HeadersTest.java index 154909b00e1..20f8a2af523 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/HeadersTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/HeadersTest.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/IdleTimeoutTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/IdleTimeoutTest.java index 3138bce5f90..8f4c34383d9 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/IdleTimeoutTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/IdleTimeoutTest.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -20,7 +18,6 @@ import java.net.InetSocketAddress; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import org.eclipse.jetty.server.Server; import org.eclipse.jetty.spdy.api.GoAwayInfo; import org.eclipse.jetty.spdy.api.ReplyInfo; import org.eclipse.jetty.spdy.api.SPDY; @@ -36,10 +33,12 @@ import org.junit.Test; public class IdleTimeoutTest extends AbstractTest { + + private final int idleTimeout = 1000; + @Test public void testServerEnforcingIdleTimeout() throws Exception { - server = new Server(); connector = newSPDYServerConnector(new ServerSessionFrameListener.Adapter() { @Override @@ -49,13 +48,10 @@ public class IdleTimeoutTest extends AbstractTest return null; } }); - server.addConnector(connector); - int maxIdleTime = 1000; - connector.setMaxIdleTime(maxIdleTime); - server.start(); + connector.setIdleTimeout(idleTimeout); final CountDownLatch latch = new CountDownLatch(1); - Session session = startClient(new InetSocketAddress("localhost", connector.getLocalPort()), new SessionFrameListener.Adapter() + Session session = startClient(startServer(null), new SessionFrameListener.Adapter() { @Override public void onGoAway(Session session, GoAwayInfo goAwayInfo) @@ -66,21 +62,17 @@ public class IdleTimeoutTest extends AbstractTest session.syn(new SynInfo(true), null); - Assert.assertTrue(latch.await(2 * maxIdleTime, TimeUnit.MILLISECONDS)); + Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); } @Test public void testServerEnforcingIdleTimeoutWithUnrespondedStream() throws Exception { - server = new Server(); connector = newSPDYServerConnector(null); - server.addConnector(connector); - int maxIdleTime = 1000; - connector.setMaxIdleTime(maxIdleTime); - server.start(); + connector.setIdleTimeout(idleTimeout); final CountDownLatch latch = new CountDownLatch(1); - Session session = startClient(new InetSocketAddress("localhost", connector.getLocalPort()), new SessionFrameListener.Adapter() + Session session = startClient(startServer(null), new SessionFrameListener.Adapter() { @Override public void onGoAway(Session session, GoAwayInfo goAwayInfo) @@ -92,14 +84,12 @@ public class IdleTimeoutTest extends AbstractTest // The SYN is not replied, and the server should idle timeout session.syn(new SynInfo(true), null); - Assert.assertTrue(latch.await(2 * maxIdleTime, TimeUnit.MILLISECONDS)); + Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); } @Test public void testServerNotEnforcingIdleTimeoutWithPendingStream() throws Exception { - final int maxIdleTime = 1000; - server = new Server(); connector = newSPDYServerConnector(new ServerSessionFrameListener.Adapter() { @Override @@ -107,7 +97,7 @@ public class IdleTimeoutTest extends AbstractTest { try { - Thread.sleep(2 * maxIdleTime); + Thread.sleep(2 * idleTimeout); stream.reply(new ReplyInfo(true)); return null; } @@ -118,12 +108,10 @@ public class IdleTimeoutTest extends AbstractTest } } }); - server.addConnector(connector); - connector.setMaxIdleTime(maxIdleTime); - server.start(); + connector.setIdleTimeout(idleTimeout); final CountDownLatch latch = new CountDownLatch(1); - Session session = startClient(new InetSocketAddress("localhost", connector.getLocalPort()), new SessionFrameListener.Adapter() + Session session = startClient(startServer(null), new SessionFrameListener.Adapter() { @Override public void onGoAway(Session session, GoAwayInfo goAwayInfo) @@ -142,7 +130,7 @@ public class IdleTimeoutTest extends AbstractTest } }); - Assert.assertTrue(replyLatch.await(3 * maxIdleTime, TimeUnit.MILLISECONDS)); + Assert.assertTrue(replyLatch.await(3 * idleTimeout, TimeUnit.MILLISECONDS)); Assert.assertFalse(latch.await(1000, TimeUnit.MILLISECONDS)); } @@ -171,13 +159,12 @@ public class IdleTimeoutTest extends AbstractTest clientFactory = newSPDYClientFactory(threadPool); clientFactory.start(); SPDYClient client = clientFactory.newSPDYClient(SPDY.V2); - long maxIdleTime = 1000; - client.setMaxIdleTime(maxIdleTime); + client.setIdleTimeout(idleTimeout); Session session = client.connect(address, null).get(5, TimeUnit.SECONDS); session.syn(new SynInfo(true), null); - Assert.assertTrue(latch.await(2 * maxIdleTime, TimeUnit.MILLISECONDS)); + Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); } @Test @@ -198,19 +185,17 @@ public class IdleTimeoutTest extends AbstractTest clientFactory = newSPDYClientFactory(threadPool); clientFactory.start(); SPDYClient client = clientFactory.newSPDYClient(SPDY.V2); - long maxIdleTime = 1000; - client.setMaxIdleTime(maxIdleTime); + client.setIdleTimeout(idleTimeout); Session session = client.connect(address, null).get(5, TimeUnit.SECONDS); session.syn(new SynInfo(true), null); - Assert.assertTrue(latch.await(2 * maxIdleTime, TimeUnit.MILLISECONDS)); + Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); } @Test public void testClientNotEnforcingIdleTimeoutWithPendingStream() throws Exception { - final long maxIdleTime = 1000; final CountDownLatch latch = new CountDownLatch(1); InetSocketAddress address = startServer(new ServerSessionFrameListener.Adapter() { @@ -233,7 +218,7 @@ public class IdleTimeoutTest extends AbstractTest clientFactory = newSPDYClientFactory(threadPool); clientFactory.start(); SPDYClient client = clientFactory.newSPDYClient(SPDY.V2); - client.setMaxIdleTime(maxIdleTime); + client.setIdleTimeout(idleTimeout); Session session = client.connect(address, null).get(5, TimeUnit.SECONDS); final CountDownLatch replyLatch = new CountDownLatch(1); @@ -244,7 +229,7 @@ public class IdleTimeoutTest extends AbstractTest { try { - Thread.sleep(2 * maxIdleTime); + Thread.sleep(2 * idleTimeout); replyLatch.countDown(); } catch (InterruptedException e) @@ -254,7 +239,7 @@ public class IdleTimeoutTest extends AbstractTest } }); - Assert.assertFalse(latch.await(2 * maxIdleTime, TimeUnit.MILLISECONDS)); - Assert.assertTrue(replyLatch.await(3 * maxIdleTime, TimeUnit.MILLISECONDS)); + Assert.assertFalse(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); + Assert.assertTrue(replyLatch.await(3 * idleTimeout, TimeUnit.MILLISECONDS)); } } diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PingTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PingTest.java index 63691bdfcca..dc4dd2a2c40 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PingTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PingTest.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -65,7 +63,7 @@ public class PingTest extends AbstractTest @Override public void onConnect(Session session) { - session.ping(0, TimeUnit.MILLISECONDS, new Callback.Adapter() + session.ping(0, TimeUnit.MILLISECONDS, new Callback.Empty() { @Override public void completed(PingInfo pingInfo) diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ProtocolViolationsTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ProtocolViolationsTest.java index 133e2ab91b7..9499c164e4c 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ProtocolViolationsTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ProtocolViolationsTest.java @@ -1,3 +1,16 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy; import java.net.InetSocketAddress; @@ -16,7 +29,6 @@ import org.eclipse.jetty.spdy.api.RstInfo; import org.eclipse.jetty.spdy.api.SPDY; import org.eclipse.jetty.spdy.api.Session; import org.eclipse.jetty.spdy.api.SessionFrameListener; -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.StreamStatus; @@ -24,12 +36,14 @@ 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.frames.ControlFrameType; -import org.eclipse.jetty.spdy.frames.GoAwayFrame; import org.eclipse.jetty.spdy.frames.SynReplyFrame; import org.eclipse.jetty.spdy.generator.Generator; import org.junit.Assert; import org.junit.Test; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + public class ProtocolViolationsTest extends AbstractTest { @Test @@ -86,6 +100,7 @@ public class ProtocolViolationsTest extends AbstractTest byte[] bytes = new byte[1]; ByteBuffer writeBuffer = generator.data(streamId, bytes.length, new BytesDataInfo(bytes, true)); channel.write(writeBuffer); + assertThat("data is fully written", writeBuffer.hasRemaining(),is(false)); readBuffer.clear(); channel.read(readBuffer); @@ -93,11 +108,8 @@ public class ProtocolViolationsTest extends AbstractTest Assert.assertEquals(ControlFrameType.RST_STREAM.getCode(), readBuffer.getShort(2)); Assert.assertEquals(streamId, readBuffer.getInt(8)); - writeBuffer = generator.control(new GoAwayFrame(SPDY.V2, 0, SessionStatus.OK.getCode())); - channel.write(writeBuffer); - channel.shutdownOutput(); - channel.close(); - + session.goAway().get(5,TimeUnit.SECONDS); + server.close(); } @@ -130,7 +142,6 @@ public class ProtocolViolationsTest extends AbstractTest @Override public void onData(Stream stream, DataInfo dataInfo) { - System.out.println("ondata"); dataLatch.countDown(); } }); @@ -145,21 +156,21 @@ public class ProtocolViolationsTest extends AbstractTest ByteBuffer writeBuffer = generator.control(new SynReplyFrame(SPDY.V2, (byte)0, streamId, new Headers())); channel.write(writeBuffer); + assertThat("SynReply is fully written", writeBuffer.hasRemaining(), is(false)); byte[] bytes = new byte[1]; writeBuffer = generator.data(streamId, bytes.length, new BytesDataInfo(bytes, true)); channel.write(writeBuffer); + assertThat("data is fully written", writeBuffer.hasRemaining(), is(false)); // Write again to simulate the faulty condition writeBuffer.flip(); channel.write(writeBuffer); + assertThat("data is fully written", writeBuffer.hasRemaining(), is(false)); Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS)); - writeBuffer = generator.control(new GoAwayFrame(SPDY.V2, 0, SessionStatus.OK.getCode())); - channel.write(writeBuffer); - channel.shutdownOutput(); - channel.close(); + session.goAway().get(5,TimeUnit.SECONDS); server.close(); } diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PushStreamTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PushStreamTest.java index fce3cb48a85..0fbc9cdd30f 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PushStreamTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PushStreamTest.java @@ -1,41 +1,61 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy; +import java.io.IOException; +import java.net.InetSocketAddress; import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; import java.util.Arrays; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.Exchanger; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jetty.io.StandardByteBufferPool; import org.eclipse.jetty.spdy.api.BytesDataInfo; import org.eclipse.jetty.spdy.api.DataInfo; +import org.eclipse.jetty.spdy.api.GoAwayInfo; +import org.eclipse.jetty.spdy.api.Headers; import org.eclipse.jetty.spdy.api.ReplyInfo; +import org.eclipse.jetty.spdy.api.RstInfo; +import org.eclipse.jetty.spdy.api.SPDY; import org.eclipse.jetty.spdy.api.Session; import org.eclipse.jetty.spdy.api.SessionFrameListener; +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.StreamStatus; 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.frames.ControlFrame; +import org.eclipse.jetty.spdy.frames.DataFrame; +import org.eclipse.jetty.spdy.frames.GoAwayFrame; +import org.eclipse.jetty.spdy.frames.RstStreamFrame; +import org.eclipse.jetty.spdy.frames.SynStreamFrame; +import org.eclipse.jetty.spdy.frames.WindowUpdateFrame; +import org.eclipse.jetty.spdy.generator.Generator; +import org.eclipse.jetty.spdy.parser.Parser; +import org.eclipse.jetty.spdy.parser.Parser.Listener; import org.eclipse.jetty.util.Callback; +import org.junit.Assert; import org.junit.Test; import static org.hamcrest.Matchers.is; @@ -66,10 +86,10 @@ public class PushStreamTest extends AbstractTest @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) { - assertThat("streamId is even", stream.getId() % 2, is(0)); - assertThat("stream is unidirectional", stream.isUnidirectional(), is(true)); - assertThat("stream is closed", stream.isClosed(), is(true)); - assertThat("stream has associated stream", stream.getAssociatedStream(), notNullValue()); + assertThat("streamId is even",stream.getId() % 2,is(0)); + assertThat("stream is unidirectional",stream.isUnidirectional(),is(true)); + assertThat("stream is closed",stream.isClosed(),is(true)); + assertThat("stream has associated stream",stream.getAssociatedStream(),notNullValue()); try { stream.reply(new ReplyInfo(false)); @@ -85,10 +105,10 @@ public class PushStreamTest extends AbstractTest } }); - Stream stream = clientSession.syn(new SynInfo(true), null).get(); - assertThat("onSyn has been called", pushStreamLatch.await(5, TimeUnit.SECONDS), is(true)); + Stream stream = clientSession.syn(new SynInfo(true),null).get(); + assertThat("onSyn has been called",pushStreamLatch.await(5,TimeUnit.SECONDS),is(true)); Stream pushStream = pushStreamRef.get(); - assertThat("main stream and associated stream are the same", stream, sameInstance(pushStream.getAssociatedStream())); + assertThat("main stream and associated stream are the same",stream,sameInstance(pushStream.getAssociatedStream())); } @Test @@ -218,10 +238,10 @@ public class PushStreamTest extends AbstractTest public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) { stream.reply(new ReplyInfo(true)); - stream.syn(new SynInfo(false),1,TimeUnit.SECONDS,new Callback.Adapter() + stream.syn(new SynInfo(false),1,TimeUnit.SECONDS,new Callback.Empty() { @Override - public void failed(Throwable x) + public void failed(Stream stream, Throwable x) { pushStreamFailedLatch.countDown(); } @@ -321,6 +341,170 @@ public class PushStreamTest extends AbstractTest return bytes; } + + @Test + public void testClientResetsStreamAfterPushSynDoesPreventSendingDataFramesWithFlowControl() throws Exception + { + final boolean flowControl = true; + testNoMoreFramesAreSentOnPushStreamAfterClientResetsThePushStream(flowControl); + } + + @Test + public void testClientResetsStreamAfterPushSynDoesPreventSendingDataFramesWithoutFlowControl() throws Exception + { + final boolean flowControl = false; + testNoMoreFramesAreSentOnPushStreamAfterClientResetsThePushStream(flowControl); + } + + private volatile boolean read = true; + private void testNoMoreFramesAreSentOnPushStreamAfterClientResetsThePushStream(final boolean flowControl) throws Exception, IOException, InterruptedException + { + final short version = SPDY.V3; + final AtomicBoolean unexpectedExceptionOccured = new AtomicBoolean(false); + final CountDownLatch resetReceivedLatch = new CountDownLatch(1); + final CountDownLatch allDataFramesReceivedLatch = new CountDownLatch(1); + final CountDownLatch goAwayReceivedLatch = new CountDownLatch(1); + final int dataSizeInBytes = 1024 * 256; + final byte[] transferBytes = createHugeByteArray(dataSizeInBytes); + + InetSocketAddress serverAddress = startServer(new ServerSessionFrameListener.Adapter() + { + @Override + public StreamFrameListener onSyn(final Stream stream, SynInfo synInfo) + { + new Thread(new Runnable() + { + + @Override + public void run() + { + Stream pushStream=null; + try + { + stream.reply(new ReplyInfo(false)); + pushStream = stream.syn(new SynInfo(false)).get(); + resetReceivedLatch.await(5,TimeUnit.SECONDS); + } + catch (InterruptedException | ExecutionException e) + { + e.printStackTrace(); + unexpectedExceptionOccured.set(true); + } + pushStream.data(new BytesDataInfo(transferBytes,true)); + stream.data(new StringDataInfo("close",true)); + } + }).start(); + return null; + } + + @Override + public void onRst(Session session, RstInfo rstInfo) + { + resetReceivedLatch.countDown(); + } + + @Override + public void onGoAway(Session session, GoAwayInfo goAwayInfo) + { + goAwayReceivedLatch.countDown(); + } + }/*TODO, flowControl*/); + + final SocketChannel channel = SocketChannel.open(serverAddress); + final Generator generator = new Generator(new StandardByteBufferPool(),new StandardCompressionFactory.StandardCompressor()); + int streamId = 1; + ByteBuffer writeBuffer = generator.control(new SynStreamFrame(version,(byte)0,streamId,0,(byte)0,(short)0,new Headers())); + channel.write(writeBuffer); + assertThat("writeBuffer is fully written",writeBuffer.hasRemaining(), is(false)); + + final Parser parser = new Parser(new StandardCompressionFactory.StandardDecompressor()); + parser.addListener(new Listener.Adapter() + { + int bytesRead = 0; + + @Override + public void onControlFrame(ControlFrame frame) + { + if(frame instanceof SynStreamFrame){ + int pushStreamId = ((SynStreamFrame)frame).getStreamId(); + ByteBuffer writeBuffer = generator.control(new RstStreamFrame(version,pushStreamId,StreamStatus.CANCEL_STREAM.getCode(version))); + try + { + channel.write(writeBuffer); + } + catch (IOException e) + { + e.printStackTrace(); + unexpectedExceptionOccured.set(true); + } + } + } + + @Override + public void onDataFrame(DataFrame frame, ByteBuffer data) + { + if(frame.getStreamId() == 2) + bytesRead = bytesRead + frame.getLength(); + if(bytesRead == dataSizeInBytes){ + allDataFramesReceivedLatch.countDown(); + return; + } + if (flowControl) + { + ByteBuffer writeBuffer = generator.control(new WindowUpdateFrame(version,frame.getStreamId(),frame.getLength())); + try + { + channel.write(writeBuffer); + } + catch (IOException e) + { + e.printStackTrace(); + unexpectedExceptionOccured.set(true); + } + } + } + }); + + Thread reader = new Thread(new Runnable() + { + @Override + public void run() + { + ByteBuffer readBuffer = ByteBuffer.allocate(dataSizeInBytes*2); + while (read) + { + try + { + channel.read(readBuffer); + } + catch (IOException e) + { + e.printStackTrace(); + unexpectedExceptionOccured.set(true); + } + readBuffer.flip(); + parser.parse(readBuffer); + readBuffer.clear(); + } + + } + }); + reader.start(); + read = false; + + assertThat("no unexpected exceptions occured", unexpectedExceptionOccured.get(), is(false)); + assertThat("not all dataframes have been received as the pushstream has been reset by the client.",allDataFramesReceivedLatch.await(streamId,TimeUnit.SECONDS),is(false)); + + + ByteBuffer buffer = generator.control(new GoAwayFrame(version, streamId, SessionStatus.OK.getCode())); + channel.write(buffer); + Assert.assertThat(buffer.hasRemaining(), is(false)); + + assertThat("GoAway frame is received by server", goAwayReceivedLatch.await(5,TimeUnit.SECONDS), is(true)); + channel.shutdownOutput(); + channel.close(); + } + @Test public void testOddEvenStreamIds() throws Exception { @@ -367,6 +551,6 @@ public class PushStreamTest extends AbstractTest private void assertThatNoExceptionOccured(final CountDownLatch exceptionCountDownLatch) throws InterruptedException { - assertThat("No exception occured", exceptionCountDownLatch.await(1,TimeUnit.SECONDS),is(false)); + assertThat("No exception occured",exceptionCountDownLatch.await(1,TimeUnit.SECONDS),is(false)); } } diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ResetStreamTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ResetStreamTest.java index 8e02d5851be..28773746fcb 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ResetStreamTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ResetStreamTest.java @@ -1,3 +1,16 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy; import java.util.concurrent.CountDownLatch; @@ -28,7 +41,7 @@ public class ResetStreamTest extends AbstractTest @Test public void testResetStreamIsRemoved() throws Exception { - Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()),null); + Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()/*TODO, true*/),null); Stream stream = session.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS); session.rst(new RstInfo(stream.getId(),StreamStatus.CANCEL_STREAM)).get(5,TimeUnit.SECONDS); @@ -116,7 +129,7 @@ public class ResetStreamTest extends AbstractTest }); Stream stream = session.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS); - stream.data(new StringDataInfo("data",true),5,TimeUnit.SECONDS,new Callback.Adapter() + stream.data(new StringDataInfo("data",true),5,TimeUnit.SECONDS,new Callback.Empty() { @Override public void completed(Void context) @@ -166,10 +179,10 @@ public class ResetStreamTest extends AbstractTest assertThat("syn is received by server", synLatch.await(5,TimeUnit.SECONDS),is(true)); stream.data(new StringDataInfo("data",false),5,TimeUnit.SECONDS,null); assertThat("stream is reset",rstLatch.await(5,TimeUnit.SECONDS),is(true)); - stream.data(new StringDataInfo("2nd dataframe",false),5L,TimeUnit.SECONDS,new Callback.Adapter() + stream.data(new StringDataInfo("2nd dataframe",false),5L,TimeUnit.SECONDS,new Callback.Empty() { @Override - public void failed(Throwable x) + public void failed(Void context, Throwable x) { failLatch.countDown(); } diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYClientFactoryTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYClientFactoryTest.java index 8124d674a8e..84e571b312d 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYClientFactoryTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYClientFactoryTest.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYServerConnectorTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYServerConnectorTest.java index d168b11d783..0b46d750ca2 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYServerConnectorTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYServerConnectorTest.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLEngineLeakTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLEngineLeakTest.java index 2e5855118cd..d6c4b600f72 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLEngineLeakTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLEngineLeakTest.java @@ -1,3 +1,16 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy; import java.lang.reflect.Field; diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLSynReplyTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLSynReplyTest.java index ee8ecdf70c4..aa398e0073c 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLSynReplyTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLSynReplyTest.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SettingsTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SettingsTest.java index 9ac5f678c44..8a7731d267f 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SettingsTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SettingsTest.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynDataReplyDataLoadTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynDataReplyDataLoadTest.java index e25639530c4..d094b2199bf 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynDataReplyDataLoadTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynDataReplyDataLoadTest.java @@ -1,18 +1,16 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -169,7 +167,7 @@ public class SynDataReplyDataLoadTest extends AbstractTest latch.countDown(); } } - }, 0, TimeUnit.SECONDS, new Callback.Adapter() + }, 0, TimeUnit.SECONDS, new Callback.Empty() { @Override public void completed(Stream stream) diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynReplyTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynReplyTest.java index 804a2215edd..d6cd49f92e5 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynReplyTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynReplyTest.java @@ -1,18 +1,15 @@ -/* - * Copyright (c) 2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//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; @@ -197,7 +194,7 @@ public class SynReplyTest extends AbstractTest Assert.assertTrue(stream.isHalfClosed()); stream.reply(new ReplyInfo(false)); - stream.data(new StringDataInfo(data1, false), 5, TimeUnit.SECONDS, new Callback.Adapter() + stream.data(new StringDataInfo(data1, false), 5, TimeUnit.SECONDS, new Callback.Empty() { @Override public void completed(Void context) @@ -276,7 +273,7 @@ public class SynReplyTest extends AbstractTest Assert.assertEquals(clientData, data); clientDataLatch.countDown(); } - }, 0, TimeUnit.MILLISECONDS, new Callback.Adapter() + }, 0, TimeUnit.MILLISECONDS, new Callback.Empty() { @Override public void completed(Stream stream) diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/UnsupportedVersionTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/UnsupportedVersionTest.java index aa1d3131943..755e3ba884d 100644 --- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/UnsupportedVersionTest.java +++ b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/UnsupportedVersionTest.java @@ -1,3 +1,16 @@ +//======================================================================== +//Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + package org.eclipse.jetty.spdy; import java.net.InetSocketAddress; @@ -45,7 +58,7 @@ public class UnsupportedVersionTest extends AbstractTest } }); - SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, new Headers()); + SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, new Headers()); Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor()); ByteBuffer buffer = generator.control(frame); // Replace the version byte with an unsupported version diff --git a/jetty-spdy/spdy-jetty/src/test/resources/log4j.properties b/jetty-spdy/spdy-jetty/src/test/resources/log4j.properties index aa88d6427ed..c7c11c1d807 100644 --- a/jetty-spdy/spdy-jetty/src/test/resources/log4j.properties +++ b/jetty-spdy/spdy-jetty/src/test/resources/log4j.properties @@ -11,4 +11,6 @@ log4j.appender.CONSOLE.target=System.err # Level tuning log4j.logger.org.eclipse.jetty=INFO -#log4j.logger.org.eclipse.jetty.spdy=DEBUG +#log4j.logger.org.eclipse.jetty.io=DEBUG +log4j.logger.org.eclipse.jetty.spdy=DEBUG +# thomas diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Monitor.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Monitor.java index 4cc367736c9..60e40dd65dd 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Monitor.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Monitor.java @@ -62,7 +62,7 @@ public class Monitor extends Thread catch(Exception e) { Config.debug(e); - System.err.println(e.toString()); + System.err.println("Error binding monitor port "+port+": "+e.toString()); } finally { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java index ba2e1a237cd..b91eff60e8d 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java @@ -1219,8 +1219,8 @@ public class SslContextFactory extends AbstractLifeCycle if (_includeProtocols!=null) { // Use only the supported included protocols - for (String protocol : supportedProtocols) - if (_includeProtocols.contains(protocol)) + for (String protocol : _includeProtocols) + if(Arrays.asList(supportedProtocols).contains(protocol)) selected_protocols.add(protocol); } else @@ -1251,8 +1251,8 @@ public class SslContextFactory extends AbstractLifeCycle if (_includeCipherSuites!=null) { // Use only the supported included ciphers - for (String cipherSuite : supportedCipherSuites) - if (_includeCipherSuites.contains(cipherSuite)) + for (String cipherSuite : _includeCipherSuites) + if(Arrays.asList(supportedCipherSuites).contains(cipherSuite)) selected_ciphers.add(cipherSuite); } else diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java index 2a79e28ede7..5abb60be0c5 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java @@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.AggregateLifeCycle; import org.eclipse.jetty.util.component.Dumpable; @@ -143,17 +144,18 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo int size=_threads.size(); if (size>0) { - LOG.warn(size+" threads could not be stopped"); + LOG.warn("{} threads could not be stopped", size); if (size==1 || LOG.isDebugEnabled()) { for (Thread unstopped : _threads) { - LOG.info("Couldn't stop "+unstopped); + StringBuilder dmp = new StringBuilder(); for (StackTraceElement element : unstopped.getStackTrace()) { - LOG.info(" at "+element); + dmp.append(StringUtil.__LINE_SEPARATOR).append("\tat ").append(element); } + LOG.debug("Couldn't stop {}{}", unstopped, dmp.toString()); } } } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java index ad40f3c6b4f..eb2be3cc674 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java @@ -12,10 +12,8 @@ package org.eclipse.jetty.util.ssl; //You may elect to redistribute this code under either of these licenses. //======================================================================== -import static junit.framework.Assert.assertTrue; - -import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.security.KeyStore; import org.eclipse.jetty.util.component.AbstractLifeCycle; @@ -23,16 +21,30 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.StdErrLog; import org.eclipse.jetty.util.resource.Resource; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import static junit.framework.Assert.assertTrue; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + public class SslContextFactoryTest { + + private SslContextFactory cf; + + @Before + public void setUp() throws Exception + { + cf = new SslContextFactory(); + } + @Test public void testNoTsFileKs() throws Exception { String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore"; - SslContextFactory cf = new SslContextFactory(keystorePath); cf.setKeyStorePassword("storepwd"); cf.setKeyManagerPassword("keypwd"); @@ -44,11 +56,9 @@ public class SslContextFactoryTest @Test public void testNoTsStreamKs() throws Exception { - String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore"; - - SslContextFactory cf = new SslContextFactory(); - - cf.setKeyStoreInputStream(new FileInputStream(keystorePath)); + InputStream keystoreInputStream = this.getClass().getResourceAsStream("keystore"); + + cf.setKeyStoreInputStream(keystoreInputStream); cf.setKeyStorePassword("storepwd"); cf.setKeyManagerPassword("keypwd"); @@ -60,12 +70,11 @@ public class SslContextFactoryTest @Test public void testNoTsSetKs() throws Exception { - String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore"; - + InputStream keystoreInputStream = this.getClass().getResourceAsStream("keystore"); + KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(new FileInputStream(keystorePath),"storepwd".toCharArray()); - - SslContextFactory cf = new SslContextFactory(); + ks.load(keystoreInputStream, "storepwd".toCharArray()); + cf.setKeyStore(ks); cf.setKeyManagerPassword("keypwd"); @@ -77,7 +86,6 @@ public class SslContextFactoryTest @Test public void testNoTsNoKs() throws Exception { - SslContextFactory cf = new SslContextFactory(); cf.start(); assertTrue(cf.getSslContext()!=null); } @@ -85,7 +93,6 @@ public class SslContextFactoryTest @Test public void testTrustAll() throws Exception { - SslContextFactory cf = new SslContextFactory(); cf.start(); assertTrue(cf.getSslContext()!=null); } @@ -95,7 +102,6 @@ public class SslContextFactoryTest { Resource keystoreResource = Resource.newSystemResource("keystore"); - SslContextFactory cf = new SslContextFactory(); cf.setKeyStoreResource(keystoreResource); cf.setKeyStorePassword("storepwd"); cf.setKeyManagerPassword("keypwd"); @@ -103,7 +109,6 @@ public class SslContextFactoryTest cf.start(); assertTrue(cf.getSslContext()!=null); - } @Test @@ -112,7 +117,6 @@ public class SslContextFactoryTest Resource keystoreResource = Resource.newSystemResource("keystore"); Resource truststoreResource = Resource.newSystemResource("keystore"); - SslContextFactory cf = new SslContextFactory(); cf.setKeyStoreResource(keystoreResource); cf.setTrustStoreResource(truststoreResource); cf.setKeyStorePassword("storepwd"); @@ -131,7 +135,6 @@ public class SslContextFactoryTest Resource keystoreResource = Resource.newSystemResource("keystore"); Resource truststoreResource = Resource.newSystemResource("keystore"); - SslContextFactory cf = new SslContextFactory(); cf.setKeyStoreResource(keystoreResource); cf.setTrustStoreResource(truststoreResource); cf.setKeyStorePassword("storepwd"); @@ -156,7 +159,6 @@ public class SslContextFactoryTest Resource keystoreResource = Resource.newSystemResource("keystore"); Resource truststoreResource = Resource.newSystemResource("keystore"); - SslContextFactory cf = new SslContextFactory(); cf.setKeyStoreResource(keystoreResource); cf.setTrustStoreResource(truststoreResource); cf.setKeyStorePassword("storepwd"); @@ -177,7 +179,6 @@ public class SslContextFactoryTest @Test public void testNoKeyConfig() throws Exception { - SslContextFactory cf = new SslContextFactory(); try { SslContextFactory.LOG.info("EXPECT SslContextFactory@????????(null,/foo): java.lang.IllegalStateException: SSL doesn't have a valid keystore..."); @@ -195,4 +196,36 @@ public class SslContextFactoryTest Assert.fail("Unexpected exception"); } } + + @Test + public void testSetIncludeCipherSuitesPreservesOrder() + { + String[] supportedCipherSuites = new String[]{"cipher4", "cipher2", "cipher1", "cipher3"}; + String[] includeCipherSuites = {"cipher1", "cipher3", "cipher4"}; + + cf.setIncludeCipherSuites(includeCipherSuites); + String[] selectedCipherSuites = cf.selectCipherSuites(null, supportedCipherSuites); + + assertSelectedMatchesIncluded(includeCipherSuites, selectedCipherSuites); + } + + @Test + public void testSetIncludeProtocolsPreservesOrder() + { + String[] supportedProtocol = new String[]{"cipher4", "cipher2", "cipher1", "cipher3"}; + String[] includeProtocol = {"cipher1", "cipher3", "cipher4"}; + + cf.setIncludeProtocols(includeProtocol); + String[] selectedProtocol = cf.selectProtocols(null, supportedProtocol); + + assertSelectedMatchesIncluded(includeProtocol, selectedProtocol); + } + + private void assertSelectedMatchesIncluded(String[] includeStrings, String[] selectedStrings) + { + assertThat(includeStrings.length + " strings are selected", selectedStrings.length, is(includeStrings.length)); + assertThat("order from includeStrings is preserved", selectedStrings[0], equalTo(includeStrings[0])); + assertThat("order from includeStrings is preserved", selectedStrings[1], equalTo(includeStrings[1])); + assertThat("order from includeStrings is preserved", selectedStrings[2], equalTo(includeStrings[2])); + } } diff --git a/jetty-websocket/websocket-core/AllTests (websocket-core).launch b/jetty-websocket/websocket-core/AllTests (websocket-core).launch index e8c853d79ab..126e38af855 100644 --- a/jetty-websocket/websocket-core/AllTests (websocket-core).launch +++ b/jetty-websocket/websocket-core/AllTests (websocket-core).launch @@ -6,6 +6,9 @@ + + + diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/WebSocket.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/WebSocket.java index c4dd0b5f612..aced62f7518 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/WebSocket.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/WebSocket.java @@ -30,11 +30,11 @@ import java.lang.annotation.Target; { ElementType.TYPE }) public @interface WebSocket { - int maxBinarySize() default 8192; + int maxBinarySize() default -2; - int maxBufferSize() default 8192; + int maxBufferSize() default -2; - int maxIdleTime() default 300000; + int maxIdleTime() default -2; - int maxTextSize() default 8192; + int maxTextSize() default -2; } diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java index 208aafd4c0e..48712dbcab2 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java @@ -128,10 +128,12 @@ public class WebSocketPolicy public WebSocketPolicy clonePolicy() { WebSocketPolicy clone = new WebSocketPolicy(this.behavior); - clone.bufferSize = this.bufferSize; + clone.autoFragment = this.autoFragment; clone.masker = this.masker; - clone.maxBinaryMessageSize = this.maxBinaryMessageSize; clone.idleTimeout = this.idleTimeout; + clone.bufferSize = this.bufferSize; + clone.maxPayloadSize = this.maxPayloadSize; + clone.maxBinaryMessageSize = this.maxBinaryMessageSize; clone.maxTextMessageSize = this.maxTextMessageSize; return clone; } @@ -146,6 +148,11 @@ public class WebSocketPolicy return bufferSize; } + public int getIdleTimeout() + { + return idleTimeout; + } + public Masker getMasker() { return masker; @@ -156,11 +163,6 @@ public class WebSocketPolicy return maxBinaryMessageSize; } - public int getIdleTimeout() - { - return idleTimeout; - } - public int getMaxPayloadSize() { return maxPayloadSize; @@ -186,6 +188,11 @@ public class WebSocketPolicy this.bufferSize = bufferSize; } + public void setIdleTimeout(int idleTimeout) + { + this.idleTimeout = idleTimeout; + } + public void setMasker(Masker masker) { this.masker = masker; @@ -196,11 +203,6 @@ public class WebSocketPolicy this.maxBinaryMessageSize = maxBinaryMessageSize; } - public void setIdleTimeout(int idleTimeout) - { - this.idleTimeout = idleTimeout; - } - public void setMaxPayloadSize(int maxPayloadSize) { if (maxPayloadSize < bufferSize) diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/driver/WebSocketEventDriver.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/driver/WebSocketEventDriver.java index cb3c28e6876..89cd01d03d3 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/driver/WebSocketEventDriver.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/driver/WebSocketEventDriver.java @@ -54,6 +54,7 @@ import org.eclipse.jetty.websocket.protocol.WebSocketFrame; public class WebSocketEventDriver implements IncomingFrames { private static final Logger LOG = Log.getLogger(WebSocketEventDriver.class); + private final Logger socketLog; private final Object websocket; private final WebSocketPolicy policy; private final EventMethods events; @@ -64,7 +65,7 @@ public class WebSocketEventDriver implements IncomingFrames /** * Establish the driver for the Websocket POJO - * + * * @param websocket */ public WebSocketEventDriver(Object websocket, EventMethodsCache methodsCache, WebSocketPolicy policy, ByteBufferPool bufferPool) @@ -74,24 +75,48 @@ public class WebSocketEventDriver implements IncomingFrames this.events = methodsCache.getMethods(websocket.getClass()); this.bufferPool = bufferPool; + this.socketLog = Log.getLogger(websocket.getClass()); + if (events.isAnnotated()) { WebSocket anno = websocket.getClass().getAnnotation(WebSocket.class); // Setup the policy - policy.setBufferSize(anno.maxBufferSize()); - policy.setMaxBinaryMessageSize(anno.maxBinarySize()); - policy.setMaxTextMessageSize(anno.maxTextSize()); - policy.setIdleTimeout(anno.maxIdleTime()); + if (anno.maxBufferSize() > 0) + { + this.policy.setBufferSize(anno.maxBufferSize()); + } + if (anno.maxBinarySize() > 0) + { + this.policy.setMaxBinaryMessageSize(anno.maxBinarySize()); + } + if (anno.maxTextSize() > 0) + { + this.policy.setMaxTextMessageSize(anno.maxTextSize()); + } + if (anno.maxIdleTime() > 0) + { + this.policy.setIdleTimeout(anno.maxIdleTime()); + } } } - private void appendBuffer(ByteBuffer msgBuf, ByteBuffer byteBuffer) + private void appendBuffer(ByteBuffer msgBuf, ByteBuffer payloadBuf) { - if (msgBuf.remaining() < byteBuffer.remaining()) + if (payloadBuf == null) { - throw new MessageTooLargeException("Message exceeded maximum buffer"); + // nothing to do (empty payload is possible) + return; } - msgBuf.put(byteBuffer); + if (msgBuf.remaining() < payloadBuf.remaining()) + { + if (LOG.isDebugEnabled()) + { + LOG.debug(" msgBuf = {}",BufferUtil.toDetailString(msgBuf)); + LOG.debug("payloadBuf = {}",BufferUtil.toDetailString(msgBuf)); + } + throw new MessageTooLargeException("Message exceeded maximum buffer size of [" + payloadBuf.capacity() + "]"); + } + msgBuf.put(payloadBuf); } public WebSocketPolicy getPolicy() @@ -101,7 +126,7 @@ public class WebSocketEventDriver implements IncomingFrames /** * Get the Websocket POJO in use - * + * * @return the Websocket POJO */ public Object getWebSocketObject() @@ -131,7 +156,7 @@ public class WebSocketEventDriver implements IncomingFrames /** * Internal entry point for incoming frames - * + * * @param frame * the frame that appeared */ @@ -351,7 +376,7 @@ public class WebSocketEventDriver implements IncomingFrames /** * Set the connection to use for this driver - * + * * @param conn * the connection */ @@ -384,7 +409,7 @@ public class WebSocketEventDriver implements IncomingFrames private void unhandled(Throwable t) { - LOG.warn("Unhandled Error (closing connection)",t); + socketLog.warn("Unhandled Error (closing connection)",t); // Unhandled Error, close the connection. switch (policy.getBehavior()) diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/DataFrameBytes.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/DataFrameBytes.java index 57f7e49d31d..eaabfb4aaa9 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/DataFrameBytes.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/DataFrameBytes.java @@ -62,15 +62,9 @@ public class DataFrameBytes extends FrameBytes try { int windowSize = connection.getPolicy().getBufferSize(); - // TODO: create a window size? - size = frame.getPayloadLength(); - if (size > windowSize) - { - size = windowSize; - } - - buffer = connection.getGenerator().generate(size,frame); + // TODO: windowSize should adjust according to some sort of flow control rules. + buffer = connection.getGenerator().generate(windowSize,frame); return buffer; } catch (Throwable x) diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/MessageInputStream.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/MessageInputStream.java index f344e74d619..0bec1613dc6 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/MessageInputStream.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/MessageInputStream.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; +import org.eclipse.jetty.util.BufferUtil; + /** * Support class for reading binary message data as an InputStream. */ @@ -28,6 +30,7 @@ public class MessageInputStream extends InputStream implements StreamAppender public MessageInputStream(ByteBuffer buf) { + BufferUtil.clearToFill(buf); this.buffer = buf; } diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/MessageReader.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/MessageReader.java index 6bbdccbd1aa..7587e2d540d 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/MessageReader.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/io/MessageReader.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.io.Reader; import java.nio.ByteBuffer; +import org.eclipse.jetty.util.BufferUtil; + /** * Support class for reading text message data as an Reader. *

    @@ -30,6 +32,7 @@ public class MessageReader extends Reader implements StreamAppender public MessageReader(ByteBuffer buf) { + BufferUtil.clearToFill(buf); this.buffer = buf; } diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/Generator.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/Generator.java index c7a09493a02..e57d921bb6b 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/Generator.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/Generator.java @@ -160,15 +160,17 @@ public class Generator } - /* - * The generate method needs to perform two functions. - * - * 1 - on the initial call for a given frame it needs to generate the framing bytecode and as much of the payload as will fit in the given buffer size - * - * 2 - on subsequent calls it needs to return as much of the payload as will fit in the given buffer size + /** + * 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 ByteBuffer generate(int bufferSize, WebSocketFrame frame) + public ByteBuffer generate(int windowSize, WebSocketFrame frame) { + if (windowSize < OVERHEAD) + { + throw new IllegalArgumentException("Cannot have windowSize less than " + OVERHEAD); + } + if (LOG.isDebugEnabled()) { StringBuilder dbg = new StringBuilder(); @@ -192,8 +194,11 @@ public class Generator /* * prepare the byte buffer to put frame into */ - ByteBuffer buffer = bufferPool.acquire(bufferSize,true); + ByteBuffer buffer = bufferPool.acquire(windowSize,true); BufferUtil.clearToFill(buffer); + // since the buffer from the pool can exceed the window size, artificially + // limit the buffer to the window size. + buffer.limit(buffer.position() + windowSize); if (frame.remaining() == frame.getPayloadLength()) { diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/Parser.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/Parser.java index d728dd7c709..4de04141cda 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/Parser.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/Parser.java @@ -95,41 +95,6 @@ public class Parser } } - /** - * Copy the bytes from one buffer to the other, demasking the content if necessary. - * - * @param src - * the source {@link ByteBuffer} - * @param dest - * the destination {@link ByteBuffer} - * @param length - * the length of bytes to worry about - * @return the number of bytes copied - */ - protected int copyBuffer(ByteBuffer src, ByteBuffer dest, int length) - { - int amt = Math.min(length,src.remaining()); - if (frame.isMasked()) - { - // Demask the content 1 byte at a time - // FIXME: on partially parsed frames this needs an offset from prior parse - byte mask[] = frame.getMask(); - for (int i = 0; i < amt; i++) - { - dest.put((byte)(src.get() ^ mask[i % 4])); - } - } - else - { - // Copy the content as-is - // TODO: Look into having a BufferUtil.put(from,to,len) method - byte b[] = new byte[amt]; - src.get(b,0,amt); - dest.put(b,0,amt); - } - return amt; - } - public IncomingFrames getIncomingFramesHandler() { return incomingFramesHandler; @@ -441,11 +406,23 @@ public class Parser payload = ByteBuffer.allocate(payloadLength); } - copyBuffer(buffer,payload,payload.remaining()); + BufferUtil.put(buffer,payload); if (payload.position() >= payloadLength) { BufferUtil.flipToFlush(payload,0); + + // demask (if needed) + if (frame.isMasked()) + { + byte mask[] = frame.getMask(); + int end = payload.limit(); + for (int i = payload.position(); i < end; i++) + { + payload.put(i,(byte)(payload.get(i) ^ mask[i % 4])); + } + } + frame.setPayload(payload); this.payload = null; return true; diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/WebSocketFrame.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/WebSocketFrame.java index 7a1e7edbb92..9594dd1e754 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/WebSocketFrame.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/protocol/WebSocketFrame.java @@ -141,7 +141,10 @@ public class WebSocketFrame implements Frame } payloadLength = copy.payloadLength; payloadStart = copy.payloadStart; - data = copy.data.slice(); + if (copy.data != null) // deal with empty payloads + { + data = copy.data.slice(); + } continuationIndex = copy.continuationIndex; continuation = copy.continuation; } diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/extensions/DeflateFrameExtensionTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/extensions/DeflateFrameExtensionTest.java index 28e32f10e96..90ba98fe304 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/extensions/DeflateFrameExtensionTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/extensions/DeflateFrameExtensionTest.java @@ -391,10 +391,6 @@ public class DeflateFrameExtensionTest ByteBuffer compressed = actual.getPayload().slice(); ByteBuffer uncompressed = ext.inflate(compressed); - System.err.printf("Expected : %s%n",BufferUtil.toDetailString(expected)); - System.err.printf("Compressed : %s%n",BufferUtil.toDetailString(compressed)); - System.err.printf("Uncompressed: %s%n",BufferUtil.toDetailString(uncompressed)); - Assert.assertThat(prefix + ".payloadLength",uncompressed.remaining(),is(expected.remaining())); ByteBufferAssert.assertEquals(prefix + ".payload",expected,uncompressed); } diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/io/LocalWebSocketSession.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/io/LocalWebSocketSession.java index 3282c790fa2..266d23eebe7 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/io/LocalWebSocketSession.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/io/LocalWebSocketSession.java @@ -2,15 +2,19 @@ package org.eclipse.jetty.websocket.io; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.driver.WebSocketEventDriver; +import org.eclipse.jetty.websocket.protocol.OutgoingFramesCapture; import org.junit.rules.TestName; public class LocalWebSocketSession extends WebSocketSession { private String id; + private OutgoingFramesCapture outgoingCapture; public LocalWebSocketSession(TestName testname) { this(testname,null); + outgoingCapture = new OutgoingFramesCapture(); + setOutgoing(outgoingCapture); } public LocalWebSocketSession(TestName testname, WebSocketEventDriver driver) @@ -19,6 +23,11 @@ public class LocalWebSocketSession extends WebSocketSession this.id = testname.getMethodName(); } + public OutgoingFramesCapture getOutgoingCapture() + { + return outgoingCapture; + } + @Override public String toString() { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/GeneratorTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/GeneratorTest.java index e854ff92d83..57f3360709d 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/GeneratorTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/GeneratorTest.java @@ -12,29 +12,37 @@ import org.junit.Test; public class GeneratorTest { + /** + * Test the windowed generate of a frame that has no masking. + */ @Test public void testWindowedGenerate() { + // A decent sized frame, no masking byte payload[] = new byte[10240]; Arrays.fill(payload,(byte)0x44); WebSocketFrame frame = WebSocketFrame.binary(payload); + // tracking values int totalParts = 0; int totalBytes = 0; int windowSize = 1024; int expectedHeaderSize = 4; int expectedParts = (int)Math.ceil((double)(payload.length + expectedHeaderSize) / windowSize); + // the generator Generator generator = new UnitGenerator(); + // lets see how many parts the generator makes boolean done = false; while (!done) { - Assert.assertThat("Too many parts",totalParts,lessThan(20)); + // 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); - // System.out.printf("Generated buf.limit() = %,d%n",buf.limit()); + Assert.assertThat("Generated should not exceed window size",buf.remaining(),lessThanOrEqualTo(windowSize)); totalBytes += buf.remaining(); totalParts++; @@ -42,6 +50,7 @@ public class GeneratorTest done = (frame.remaining() <= 0); } + // validate Assert.assertThat("Created Parts",totalParts,is(expectedParts)); Assert.assertThat("Created Bytes",totalBytes,is(payload.length + expectedHeaderSize)); } @@ -49,6 +58,7 @@ public class GeneratorTest @Test public void testWindowedGenerateWithMasking() { + // A decent sized frame, with masking byte payload[] = new byte[10240]; Arrays.fill(payload,(byte)0x55); @@ -56,15 +66,17 @@ public class GeneratorTest { 0x2A, (byte)0xF0, 0x0F, 0x00 }; WebSocketFrame frame = WebSocketFrame.binary(payload); - frame.setMask(mask); + frame.setMask(mask); // masking! + // tracking values int totalParts = 0; int totalBytes = 0; - int windowSize = 2929; // important, use an odd # window size to test masking across window barriers + 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 + // 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); @@ -74,10 +86,11 @@ public class GeneratorTest boolean done = false; while (!done) { - Assert.assertThat("Too many parts",totalParts,lessThan(20)); + // 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); - // System.out.printf("Generated buf.limit() = %,d%n",buf.limit()); + Assert.assertThat("Generated should not exceed window size",buf.remaining(),lessThanOrEqualTo(windowSize)); totalBytes += buf.remaining(); totalParts++; @@ -104,7 +117,7 @@ public class GeneratorTest Assert.assertThat("Frame.opcode",actual.getOpCode(),is(OpCode.BINARY)); Assert.assertThat("Frame.payloadLength",actual.getPayloadLength(),is(payload.length)); - // Validate payload content for proper masking + // Validate payload contents for proper masking ByteBuffer actualData = actual.getPayload().slice(); Assert.assertThat("Frame.payload.remaining",actualData.remaining(),is(payload.length)); while (actualData.remaining() > 0) diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/RFC6455ExamplesParserTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/RFC6455ExamplesParserTest.java index e335822a86f..4d5a6859fff 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/RFC6455ExamplesParserTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/protocol/RFC6455ExamplesParserTest.java @@ -19,11 +19,9 @@ import static org.hamcrest.Matchers.*; 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.protocol.OpCode; -import org.eclipse.jetty.websocket.protocol.Parser; -import org.eclipse.jetty.websocket.protocol.WebSocketFrame; import org.junit.Assert; import org.junit.Test; @@ -41,23 +39,24 @@ public class RFC6455ExamplesParserTest parser.setIncomingFramesHandler(capture); ByteBuffer buf = ByteBuffer.allocate(16); + BufferUtil.clearToFill(buf); // Raw bytes as found in RFC 6455, Section 5.7 - Examples // A fragmented unmasked text message (part 1 of 2 "Hel") buf.put(new byte[] { (byte)0x01, (byte)0x03, 0x48, (byte)0x65, 0x6c }); - buf.flip(); // Parse #1 + BufferUtil.flipToFlush(buf,0); parser.parse(buf); // part 2 of 2 "lo" (A continuation frame of the prior text message) - buf.flip(); + BufferUtil.flipToFill(buf); buf.put(new byte[] { (byte)0x80, 0x02, 0x6c, 0x6f }); - buf.flip(); // Parse #2 + BufferUtil.flipToFlush(buf,0); parser.parse(buf); capture.assertNoErrors(); diff --git a/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties index 9e3703a01fc..fbf69445511 100644 --- a/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties @@ -1,2 +1,2 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.websocket.LEVEL=DEBUG \ No newline at end of file +org.eclipse.jetty.websocket.LEVEL=WARN \ No newline at end of file diff --git a/jetty-websocket/websocket-server/AllTests (websocket-server).launch b/jetty-websocket/websocket-server/AllTests (websocket-server).launch index e964382257e..d74c4679248 100644 --- a/jetty-websocket/websocket-server/AllTests (websocket-server).launch +++ b/jetty-websocket/websocket-server/AllTests (websocket-server).launch @@ -6,6 +6,9 @@ + + + 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 f2aa021f1e2..b6afbcf6317 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 @@ -34,6 +34,12 @@ public class ByteBufferAssert } } + public static void assertEquals(String message, byte[] expectedBytes, ByteBuffer actualBuffer) + { + byte actualBytes[] = BufferUtil.toArray(actualBuffer); + assertEquals(message,expectedBytes,actualBytes); + } + public static void assertEquals(String message, ByteBuffer expectedBuffer, ByteBuffer actualBuffer) { byte expectedBytes[] = BufferUtil.toArray(expectedBuffer); 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 8829d0e8653..afcbc8ac476 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 @@ -17,28 +17,21 @@ package org.eclipse.jetty.websocket.server; import static org.hamcrest.Matchers.*; -import java.io.IOException; import java.net.SocketException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Queue; import java.util.concurrent.TimeUnit; -import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception; import org.eclipse.jetty.util.Utf8StringBuilder; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.websocket.annotations.OnWebSocketConnect; -import org.eclipse.jetty.websocket.annotations.OnWebSocketMessage; -import org.eclipse.jetty.websocket.annotations.WebSocket; import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.api.WebSocketConnection; import org.eclipse.jetty.websocket.protocol.CloseInfo; import org.eclipse.jetty.websocket.protocol.Generator; import org.eclipse.jetty.websocket.protocol.OpCode; import org.eclipse.jetty.websocket.protocol.WebSocketFrame; import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient; +import org.eclipse.jetty.websocket.server.helper.RFCServlet; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -52,68 +45,6 @@ import org.junit.Test; */ public class WebSocketServletRFCTest { - @SuppressWarnings("serial") - public static class RFCServlet extends WebSocketServlet - { - @Override - public void registerWebSockets(WebSocketServerFactory factory) - { - factory.register(RFCSocket.class); - } - } - - @WebSocket - public static class RFCSocket - { - private static Logger LOG = Log.getLogger(RFCSocket.class); - - private WebSocketConnection conn; - - @OnWebSocketMessage - public void onBinary(byte buf[], int offset, int len) - { - LOG.debug("onBinary(byte[{}],{},{})",buf.length,offset,len); - - // echo the message back. - try - { - this.conn.write(null,new FutureCallback(),buf,offset,len); - } - catch (IOException e) - { - e.printStackTrace(System.err); - } - } - - @OnWebSocketConnect - public void onOpen(WebSocketConnection conn) - { - this.conn = conn; - } - - @OnWebSocketMessage - public void onText(String message) - { - LOG.debug("onText({})",message); - // Test the RFC 6455 close code 1011 that should close - // trigger a WebSocket server terminated close. - if (message.equals("CRASH")) - { - throw new RuntimeException("Something bad happened"); - } - - // echo the message back. - try - { - this.conn.write(null,new FutureCallback(),message); - } - catch (IOException e) - { - e.printStackTrace(System.err); - } - } - } - private static Generator generator = new UnitGenerator(); private static SimpleServletServer server; 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 new file mode 100644 index 00000000000..dec50ed7bac --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABServlet.java @@ -0,0 +1,24 @@ +package org.eclipse.jetty.websocket.server.ab; + +import org.eclipse.jetty.websocket.server.WebSocketServerFactory; +import org.eclipse.jetty.websocket.server.WebSocketServlet; + +/** + * Servlet with bigger message policy sizes, with registered simple echo socket. + */ +@SuppressWarnings("serial") +public class ABServlet extends WebSocketServlet +{ + private static final int KBYTE = 1024; + private static final int MBYTE = KBYTE * KBYTE; + + @Override + public void registerWebSockets(WebSocketServerFactory factory) + { + factory.register(ABSocket.class); + + factory.getPolicy().setBufferSize(2 * MBYTE); + factory.getPolicy().setMaxTextMessageSize(2 * MBYTE); + factory.getPolicy().setMaxBinaryMessageSize(2 * MBYTE); + } +} 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 new file mode 100644 index 00000000000..94652702287 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABSocket.java @@ -0,0 +1,60 @@ +package org.eclipse.jetty.websocket.server.ab; + +import java.io.IOException; + +import org.eclipse.jetty.util.FutureCallback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.annotations.WebSocket; +import org.eclipse.jetty.websocket.api.WebSocketConnection; + +/** + * Simple Echo WebSocket, using async writes of echo + */ +@WebSocket +public class ABSocket +{ + private static Logger LOG = Log.getLogger(ABSocket.class); + + private WebSocketConnection conn; + + @OnWebSocketMessage + public void onBinary(byte buf[], int offset, int len) + { + LOG.debug("onBinary(byte[{}],{},{})",buf.length,offset,len); + + // echo the message back. + try + { + this.conn.write(null,new FutureCallback(),buf,offset,len); + } + catch (IOException e) + { + e.printStackTrace(System.err); + } + } + + @OnWebSocketConnect + public void onOpen(WebSocketConnection conn) + { + this.conn = conn; + } + + @OnWebSocketMessage + public void onText(String message) + { + LOG.debug("onText({})",message); + + // echo the message back. + try + { + this.conn.write(null,new FutureCallback(),message); + } + catch (IOException e) + { + e.printStackTrace(System.err); + } + } +} 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 new file mode 100644 index 00000000000..ead17962a47 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AbstractABCase.java @@ -0,0 +1,86 @@ +package org.eclipse.jetty.websocket.server.ab; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.websocket.server.SimpleServletServer; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +public abstract class AbstractABCase +{ + protected static final byte FIN = (byte)0x80; + protected static final byte NOFIN = 0x00; + private static final byte MASKED_BIT = (byte)0x80; + private static final byte[] MASK = + { 0x12, 0x34, 0x56, 0x78 }; + + protected static SimpleServletServer server; + + @BeforeClass + public static void startServer() throws Exception + { + server = new SimpleServletServer(new ABServlet()); + server.start(); + } + + @AfterClass + public static void stopServer() + { + server.stop(); + } + + protected 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; + } + + private 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)); + } + } + + public void putMask(ByteBuffer buf) + { + buf.put(MASK); + } + + public 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/AllTests.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AllTests.java index d0dcd671412..360af21c9eb 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AllTests.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AllTests.java @@ -5,7 +5,7 @@ import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses( -{ TestABCase5.class, TestABCase7_9.class }) +{ TestABCase1.class, TestABCase5.class, TestABCase7_9.class }) public class AllTests { /* let junit do the rest */ 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 new file mode 100644 index 00000000000..0878b265f99 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java @@ -0,0 +1,311 @@ +// ======================================================================== +// Copyright 2011-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://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 java.util.Arrays; +import java.util.Queue; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.protocol.Generator; +import org.eclipse.jetty.websocket.protocol.OpCode; +import org.eclipse.jetty.websocket.protocol.WebSocketFrame; +import org.eclipse.jetty.websocket.server.ByteBufferAssert; +import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient; +import org.junit.Assert; +import org.junit.Test; + +public class TestABCase1 extends AbstractABCase +{ + /** + * Echo 0 byte text message + */ + @Test + public void testCase1_1_1() throws Exception + { + BlockheadClient client = new BlockheadClient(server.getServerUri()); + try + { + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); + + ByteBuffer buf = ByteBuffer.allocate(16); + BufferUtil.clearToFill(buf); + + buf.put((byte)(0x00 | FIN | OpCode.TEXT.getCode())); + putPayloadLength(buf,0); + putMask(buf); + + BufferUtil.flipToFlush(buf,0); + client.writeRaw(buf); + + // Read frame + Queue frames = client.readFrames(1,TimeUnit.MILLISECONDS,500); + WebSocketFrame frame = frames.remove(); + Assert.assertThat("frame should be TEXT frame",frame.getOpCode(),is(OpCode.TEXT)); + Assert.assertThat("Text.payloadLength",frame.getPayloadLength(),is(0)); + } + finally + { + client.close(); + } + } + + /** + * Echo 125 byte text message (uses small 7-bit payload length) + */ + @Test + public void testCase1_1_2() throws Exception + { + BlockheadClient client = new BlockheadClient(server.getServerUri()); + try + { + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); + + byte msg[] = new byte[125]; + Arrays.fill(msg,(byte)'*'); + + ByteBuffer buf = ByteBuffer.allocate(msg.length + Generator.OVERHEAD); + BufferUtil.clearToFill(buf); + + buf.put((byte)(0x00 | FIN | OpCode.TEXT.getCode())); + putPayloadLength(buf,msg.length); + putMask(buf); + buf.put(masked(msg)); + + BufferUtil.flipToFlush(buf,0); + client.writeRaw(buf); + + // Read frame + Queue frames = client.readFrames(1,TimeUnit.MILLISECONDS,500); + WebSocketFrame frame = frames.remove(); + Assert.assertThat("frame should be TEXT frame",frame.getOpCode(),is(OpCode.TEXT)); + Assert.assertThat("Text.payloadLength",frame.getPayloadLength(),is(msg.length)); + ByteBufferAssert.assertEquals("Text.payload",msg,frame.getPayload()); + } + finally + { + client.close(); + } + } + + /** + * Echo 126 byte text message (uses medium 2 byte payload length) + */ + @Test + public void testCase1_1_3() throws Exception + { + BlockheadClient client = new BlockheadClient(server.getServerUri()); + try + { + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); + + byte msg[] = new byte[126]; + Arrays.fill(msg,(byte)'*'); + + ByteBuffer buf = ByteBuffer.allocate(msg.length + Generator.OVERHEAD); + BufferUtil.clearToFill(buf); + + buf.put((byte)(0x00 | FIN | OpCode.TEXT.getCode())); + putPayloadLength(buf,msg.length); + putMask(buf); + buf.put(masked(msg)); + + BufferUtil.flipToFlush(buf,0); + client.writeRaw(buf); + + // Read frame + Queue frames = client.readFrames(1,TimeUnit.MILLISECONDS,500); + WebSocketFrame frame = frames.remove(); + Assert.assertThat("frame should be TEXT frame",frame.getOpCode(),is(OpCode.TEXT)); + Assert.assertThat("Text.payloadLength",frame.getPayloadLength(),is(msg.length)); + ByteBufferAssert.assertEquals("Text.payload",msg,frame.getPayload()); + } + finally + { + client.close(); + } + } + + /** + * Echo 127 byte text message (uses medium 2 byte payload length) + */ + @Test + public void testCase1_1_4() throws Exception + { + BlockheadClient client = new BlockheadClient(server.getServerUri()); + try + { + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); + + byte msg[] = new byte[127]; + Arrays.fill(msg,(byte)'*'); + + ByteBuffer buf = ByteBuffer.allocate(msg.length + Generator.OVERHEAD); + BufferUtil.clearToFill(buf); + + buf.put((byte)(0x00 | FIN | OpCode.TEXT.getCode())); + putPayloadLength(buf,msg.length); + putMask(buf); + buf.put(masked(msg)); + + BufferUtil.flipToFlush(buf,0); + client.writeRaw(buf); + + // Read frame + Queue frames = client.readFrames(1,TimeUnit.MILLISECONDS,500); + WebSocketFrame frame = frames.remove(); + Assert.assertThat("frame should be TEXT frame",frame.getOpCode(),is(OpCode.TEXT)); + Assert.assertThat("Text.payloadLength",frame.getPayloadLength(),is(msg.length)); + ByteBufferAssert.assertEquals("Text.payload",msg,frame.getPayload()); + } + finally + { + client.close(); + } + } + + /** + * Echo 128 byte text message (uses medium 2 byte payload length) + */ + @Test + public void testCase1_1_5() throws Exception + { + BlockheadClient client = new BlockheadClient(server.getServerUri()); + try + { + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); + + byte msg[] = new byte[128]; + Arrays.fill(msg,(byte)'*'); + + ByteBuffer buf = ByteBuffer.allocate(msg.length + Generator.OVERHEAD); + BufferUtil.clearToFill(buf); + + buf.put((byte)(0x00 | FIN | OpCode.TEXT.getCode())); + putPayloadLength(buf,msg.length); + putMask(buf); + buf.put(masked(msg)); + + BufferUtil.flipToFlush(buf,0); + client.writeRaw(buf); + + // Read frame + Queue frames = client.readFrames(1,TimeUnit.MILLISECONDS,500); + WebSocketFrame frame = frames.remove(); + Assert.assertThat("frame should be TEXT frame",frame.getOpCode(),is(OpCode.TEXT)); + Assert.assertThat("Text.payloadLength",frame.getPayloadLength(),is(msg.length)); + ByteBufferAssert.assertEquals("Text.payload",msg,frame.getPayload()); + } + finally + { + client.close(); + } + } + + /** + * Echo 65535 byte text message (uses medium 2 byte payload length) + */ + @Test + public void testCase1_1_6() throws Exception + { + BlockheadClient client = new BlockheadClient(server.getServerUri()); + try + { + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); + + byte msg[] = new byte[65535]; + Arrays.fill(msg,(byte)'*'); + + ByteBuffer buf = ByteBuffer.allocate(msg.length + Generator.OVERHEAD); + BufferUtil.clearToFill(buf); + + buf.put((byte)(0x00 | FIN | OpCode.TEXT.getCode())); + putPayloadLength(buf,msg.length); + putMask(buf); + buf.put(masked(msg)); + + BufferUtil.flipToFlush(buf,0); + client.writeRaw(buf); + + // Read frame + Queue frames = client.readFrames(1,TimeUnit.MILLISECONDS,500); + WebSocketFrame frame = frames.remove(); + Assert.assertThat("frame should be TEXT frame",frame.getOpCode(),is(OpCode.TEXT)); + Assert.assertThat("Text.payloadLength",frame.getPayloadLength(),is(msg.length)); + ByteBufferAssert.assertEquals("Text.payload",msg,frame.getPayload()); + } + finally + { + client.close(); + } + } + + /** + * Echo 65536 byte text message (uses large 8 byte payload length) + */ + @Test + public void testCase1_1_7() throws Exception + { + BlockheadClient client = new BlockheadClient(server.getServerUri()); + try + { + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); + + byte msg[] = new byte[65536]; + Arrays.fill(msg,(byte)'*'); + + ByteBuffer buf = ByteBuffer.allocate(msg.length + Generator.OVERHEAD); + BufferUtil.clearToFill(buf); + + buf.put((byte)(0x00 | FIN | OpCode.TEXT.getCode())); + putPayloadLength(buf,msg.length); + putMask(buf); + buf.put(masked(msg)); + + BufferUtil.flipToFlush(buf,0); + client.writeRaw(buf); + + // Read frame + Queue frames = client.readFrames(1,TimeUnit.MILLISECONDS,500); + WebSocketFrame frame = frames.remove(); + Assert.assertThat("frame should be TEXT frame",frame.getOpCode(),is(OpCode.TEXT)); + Assert.assertThat("Text.payloadLength",frame.getPayloadLength(),is(msg.length)); + ByteBufferAssert.assertEquals("Text.payload",msg,frame.getPayload()); + } + finally + { + client.close(); + } + } + +} diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCServlet.java new file mode 100644 index 00000000000..268229f486b --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCServlet.java @@ -0,0 +1,14 @@ +package org.eclipse.jetty.websocket.server.helper; + +import org.eclipse.jetty.websocket.server.WebSocketServerFactory; +import org.eclipse.jetty.websocket.server.WebSocketServlet; + +@SuppressWarnings("serial") +public class RFCServlet extends WebSocketServlet +{ + @Override + public void registerWebSockets(WebSocketServerFactory factory) + { + factory.register(RFCSocket.class); + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..15f01704e72 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCSocket.java @@ -0,0 +1,63 @@ +package org.eclipse.jetty.websocket.server.helper; + +import java.io.IOException; + +import org.eclipse.jetty.util.FutureCallback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.annotations.WebSocket; +import org.eclipse.jetty.websocket.api.WebSocketConnection; + +@WebSocket +public class RFCSocket +{ + private static Logger LOG = Log.getLogger(RFCSocket.class); + + private WebSocketConnection conn; + + @OnWebSocketMessage + public void onBinary(byte buf[], int offset, int len) + { + LOG.debug("onBinary(byte[{}],{},{})",buf.length,offset,len); + + // echo the message back. + try + { + this.conn.write(null,new FutureCallback(),buf,offset,len); + } + catch (IOException e) + { + e.printStackTrace(System.err); + } + } + + @OnWebSocketConnect + public void onOpen(WebSocketConnection conn) + { + this.conn = conn; + } + + @OnWebSocketMessage + public void onText(String message) + { + LOG.debug("onText({})",message); + // Test the RFC 6455 close code 1011 that should close + // trigger a WebSocket server terminated close. + if (message.equals("CRASH")) + { + throw new RuntimeException("Something bad happened"); + } + + // echo the message back. + try + { + this.conn.write(null,new FutureCallback(),message); + } + catch (IOException e) + { + e.printStackTrace(System.err); + } + } +} \ No newline at end of file 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 607bf07594d..f64a66e3c3c 100644 --- a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties @@ -1,8 +1,10 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog org.eclipse.jetty.io.LEVEL=WARN org.eclipse.jetty.server.LEVEL=WARN +org.eclipse.jetty.websocket.server.helper.RFCSocket.LEVEL=OFF +# org.eclipse.jetty.util.thread.QueuedThreadPool.LEVEL=DEBUG # org.eclipse.jetty.io.SelectorManager.LEVEL=INFO -org.eclipse.jetty.websocket.LEVEL=WARN +# org.eclipse.jetty.websocket.LEVEL=DEBUG # org.eclipse.jetty.websocket.extensions.LEVEL=DEBUG # org.eclipse.jetty.websocket.protocol.Generator.LEVEL=INFO # org.eclipse.jetty.websocket.protocol.Parser.LEVEL=INFO diff --git a/pom.xml b/pom.xml index 4755226c870..e951ee6e7d8 100644 --- a/pom.xml +++ b/pom.xml @@ -374,8 +374,8 @@ -