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 Callback method invoked when a connection is opened. Callback method invoked when a connection is closed. Callback method invoked when a connection is upgraded. Callback method invoked when a non-blocking connect cannot be completed. By default it just logs with level warning. Callback method invoked when a non-blocking connect cannot be completed. By default it just logs with level warning.
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 ListSIMPLE_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; /** * + + + 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 @@ + + + +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, Callbackcallback); @@ -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, Callbackcallback); @@ -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, Callbackcallback); @@ -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, Callbackcallback); @@ -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, Callbackcallback); /** - * @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, Callbackcallback); @@ -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, Callbackcallback); @@ -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, Callbackcallback); @@ -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, Callbackcallback); 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.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 @@+ + + + + +src/main/resources/keystore.jks +storepwd +src/main/resources/truststore.jks +storepwd +TLSv1 ++ + + ++ ++ +9090 ++ ++ +spdy/2 ++ + + ++ ++ ++ + + + ++ +spdy/2 ++ + + ++ ++ ++ ++ 8080 ++ + + ++ ++ ++ + 8443 +TLSv1 + + + + + +@@ -24,6 +60,24 @@ + + 8080 + + 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 + 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 Maporg.mockito +mockito-core +test +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 MapproxyInfos = 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, Handlerhandler) + { + // 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 ConcurrentMapserverSessions = 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 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.Adapterstrings) { 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 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("pushedResources = Collections.newSetFromMap(new ConcurrentHashMap 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("()); + private final AtomicReference 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("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 HELLO"); + } + else if (url.equals(fakeResource)) + { + response.setContentType("text/html"); + output.print("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 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("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("() + { + @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