diff options
5 files changed, 222 insertions, 147 deletions
diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-spdy.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-spdy.xml index cef6903cd8..25ed116071 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-spdy.xml +++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-spdy.xml @@ -7,16 +7,19 @@ <!-- HttpChannel Configuration --> <!-- =========================================================== --> <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration"> - <Set name="secureScheme">https</Set> - <Set name="securePort"><SystemProperty name="jetty.spdy.port" default="8443"/></Set> - <Set name="outputBufferSize">32768</Set> - <Set name="requestHeaderSize">8192</Set> - <Set name="responseHeaderSize">8192</Set> - <Call name="addCustomizer"> - <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg> - </Call> + <Set name="secureScheme">https</Set> + <Set name="securePort"> + <SystemProperty name="jetty.spdy.port" default="8443"/> + </Set> + <Set name="outputBufferSize">32768</Set> + <Set name="requestHeaderSize">8192</Set> + <Set name="responseHeaderSize">8192</Set> + <Call name="addCustomizer"> + <Arg> + <New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/> + </Arg> + </Call> </New> - <!-- =========================================================== --> @@ -44,18 +47,39 @@ </Ref> <!-- =========================================================== --> - <!-- Create a push strategy --> + <!-- Create a push strategy which can be used by reference by --> + <!-- individual connection factories below. --> + <!-- --> + <!-- Consult the javadoc of o.e.j.spdy.server.http.ReferrerPushStrategy --> + <!-- for all configuration that may be set here. --> <!-- =========================================================== --> <New id="pushStrategy" class="org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy"> - <Arg type="List"> - <Array type="String"> - <Item>.*\.css</Item> - <Item>.*\.js</Item> - <Item>.*\.png</Item> - <Item>.*\.jpg</Item> - <Item>.*\.gif</Item> - </Array> - </Arg> + <!-- Uncomment to blacklist browsers for this push strategy. If one of the blacklisted Strings occurs in the + user-agent header sent by the client, push will be disabled for this browser. This is case insensitive" --> + <!-- + <Set name="BlacklistUserAgents"> + <Array type="String"> + <Item>.*(?i)firefox/14.*</Item> + <Item>.*(?i)firefox/15.*</Item> + <Item>.*(?i)firefox/16.*</Item> + </Array> + </Set> + --> + + <!-- Uncomment to override default file extensions to push --> + <!-- + <Set name="PushRegexps"> + <Array type="String"> + <Item>.*\.css</Item> + <Item>.*\.js</Item> + <Item>.*\.png</Item> + <Item>.*\.jpg</Item> + <Item>.*\.gif</Item> + </Array> + </Set> + --> + <Set name="referrerPushPeriod">5000</Set> + <Set name="maxAssociatedResources">32</Set> </New> <!-- =========================================================== --> diff --git a/jetty-spdy/spdy-example-webapp/src/main/config/etc/jetty-spdy.xml b/jetty-spdy/spdy-example-webapp/src/main/config/etc/jetty-spdy.xml index 3b1b54f759..1c9b95b16c 100644 --- a/jetty-spdy/spdy-example-webapp/src/main/config/etc/jetty-spdy.xml +++ b/jetty-spdy/spdy-example-webapp/src/main/config/etc/jetty-spdy.xml @@ -25,7 +25,7 @@ <Set name="TrustStorePath"><Property name="jetty.home" default="." />/etc/keystore</Set> <Set name="TrustStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set> </New> - + <!-- =========================================================== --> <!-- Create a TLS specific HttpConfiguration based on the --> <!-- common HttpConfiguration defined in jetty.xml --> @@ -38,7 +38,7 @@ <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg> </Call> </New> - + <!-- =========================================================== --> <!-- Create a push strategy which can be used by reference by --> <!-- individual connection factories below. --> @@ -47,15 +47,30 @@ <!-- for all configuration that may be set here. --> <!-- =========================================================== --> <New id="pushStrategy" class="org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy"> - <Arg name="pushPatterns" type="List"> - <Array type="String"> - <Item>.*\.css</Item> - <Item>.*\.js</Item> - <Item>.*\.png</Item> - <Item>.*\.jpg</Item> - <Item>.*\.gif</Item> - </Array> - </Arg> + <!-- Uncomment to blacklist browsers for this push strategy. If one of the blacklisted Strings occurs in the + user-agent header sent by the client, push will be disabled for this browser. This is case insensitive" --> + <!-- + <Set name="BlacklistUserAgents"> + <Array type="String"> + <Item>.*(?i)firefox/14.*</Item> + <Item>.*(?i)firefox/15.*</Item> + <Item>.*(?i)firefox/16.*</Item> + </Array> + </Set> + --> + + <!-- Uncomment to override default file extensions to push --> + <!-- + <Set name="PushRegexps"> + <Array type="String"> + <Item>.*\.css</Item> + <Item>.*\.js</Item> + <Item>.*\.png</Item> + <Item>.*\.jpg</Item> + <Item>.*\.gif</Item> + </Array> + </Set> + --> <Set name="referrerPushPeriod">5000</Set> <Set name="maxAssociatedResources">32</Set> </New> @@ -64,7 +79,7 @@ <!-- Add a SPDY/HTTPS Connector. --> <!-- Configure an o.e.j.server.ServerConnector with connection --> <!-- factories for TLS (aka SSL), NPN, SPDY and HTTP to provide --> - <!-- a connector that can accept HTTPS or SPDY connections. --> + <!-- a connector that can accept HTTPS or SPDY connections. --> <!-- --> <!-- All accepted TLS connections are initially wired to a NPN --> <!-- connection, which attempts to use a TLS extension to --> @@ -87,7 +102,7 @@ <Arg name="server"><Ref id="Server" /></Arg> <Arg name="factories"> <Array type="org.eclipse.jetty.server.ConnectionFactory"> - + <!-- SSL Connection factory with NPN as next protocol --> <Item> <New class="org.eclipse.jetty.server.SslConnectionFactory"> @@ -113,7 +128,7 @@ <New class="org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory"> <Arg name="version" type="int">3</Arg> <Arg name="config"><Ref id="tlsHttpConfig" /></Arg> - <Arg name="pushStrategy"><Ref id="pushStrategy"/></Arg> + <Arg name="pushStrategy"><Ref id="pushStrategy"/></Arg> </New> </Item> @@ -133,7 +148,7 @@ </Item> </Array> </Arg> - + <Set name="host"><Property name="jetty.host" /></Set> <Set name="port"><Property name="jetty.tls.port" default="8443" /></Set> <Set name="idleTimeout">30000</Set> diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java index 13dbe6eb0b..9350f7c8f7 100644 --- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java +++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java @@ -33,32 +33,25 @@ import java.util.regex.Pattern; import org.eclipse.jetty.spdy.api.Stream; import org.eclipse.jetty.util.Fields; -import org.eclipse.jetty.util.annotation.Name; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /** - * <p>A SPDY push strategy that auto-populates push metadata based on referrer URLs.</p> - * <p>A typical request for a main resource such as <tt>index.html</tt> is immediately - * followed by a number of requests for associated resources. Associated resource requests - * will have a <tt>Referer</tt> HTTP header that points to <tt>index.html</tt>, which is - * used to link the associated resource to the main resource.</p> - * <p>However, also following a hyperlink generates a HTTP request with a <tt>Referer</tt> - * HTTP header that points to <tt>index.html</tt>; therefore a proper value for {@link #getReferrerPushPeriod()} - * has to be set. If the referrerPushPeriod for a main resource has elapsed, no more - * associated resources will be added for that main resource.</p> - * <p>This class distinguishes associated main resources by their URL path suffix and content - * type. - * CSS stylesheets, images and JavaScript files have recognizable URL path suffixes that - * are classified as associated resources. The suffix regexs can be configured by constructor argument</p> - * <p>When CSS stylesheets refer to images, the CSS image request will have the CSS - * stylesheet as referrer. This implementation will push also the CSS image.</p> - * <p>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.</p> + * <p>A SPDY push strategy that auto-populates push metadata based on referrer URLs.</p> <p>A typical request for a main + * resource such as <tt>index.html</tt> is immediately followed by a number of requests for associated resources. + * Associated resource requests will have a <tt>Referer</tt> HTTP header that points to <tt>index.html</tt>, which is + * used to link the associated resource to the main resource.</p> <p>However, also following a hyperlink generates a + * HTTP request with a <tt>Referer</tt> HTTP header that points to <tt>index.html</tt>; therefore a proper value for + * {@link #setReferrerPushPeriod(int)} has to be set. If the referrerPushPeriod for a main resource has elapsed, + * no more associated resources will be added for that main resource.</p> <p>This class distinguishes associated main + * resources by their URL path suffix and content type. CSS stylesheets, images and JavaScript files have + * recognizable URL path suffixes that are classified as associated resources. The suffix regexs can be configured by + * constructor argument</p> + * <p>When CSS stylesheets refer to images, the CSS image request will have the CSS stylesheet as referrer. This + * implementation will push also the CSS image.</p> <p>The push metadata built by this implementation is limited by the + * number of pages of the application itself, and by the {@link #setMaxAssociatedResources(int)} max associated resources} + * parameter. This parameter limits the number of associated resources per each main resource, so that if a main + * resource has hundreds of associated resources, only up to the number specified by this parameter will be pushed.</p> */ public class ReferrerPushStrategy implements PushStrategy { @@ -67,42 +60,56 @@ public class ReferrerPushStrategy implements PushStrategy private final Set<Pattern> pushRegexps = new HashSet<>(); private final Set<String> pushContentTypes = new HashSet<>(); private final Set<Pattern> allowedPushOrigins = new HashSet<>(); + private final Set<Pattern> userAgentBlacklist = new HashSet<>(); private volatile int maxAssociatedResources = 32; private volatile int referrerPushPeriod = 5000; public ReferrerPushStrategy() { - this(Arrays.asList(".*\\.css", ".*\\.js", ".*\\.png", ".*\\.jpeg", ".*\\.jpg", ".*\\.gif", ".*\\.ico")); - } + List<String> defaultPushRegexps = Arrays.asList(".*\\.css", ".*\\.js", ".*\\.png", ".*\\.jpeg", ".*\\.jpg", + ".*\\.gif", ".*\\.ico"); + addPushRegexps(defaultPushRegexps); - public ReferrerPushStrategy(@Name("pushPatterns") List<String> pushRegexps) - { - this(pushRegexps, Arrays.asList( + List<String> defaultPushContentTypes = 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")); + "image/x-icon", "image/vnd.microsoft.icon"); + this.pushContentTypes.addAll(defaultPushContentTypes); } - public ReferrerPushStrategy(List<String> pushRegexps, List<String> pushContentTypes) + public void setPushRegexps(List<String> pushRegexps) { - this(pushRegexps, pushContentTypes, Collections.<String>emptyList()); + pushRegexps.clear(); + addPushRegexps(pushRegexps); } - public ReferrerPushStrategy(List<String> pushRegexps, List<String> pushContentTypes, List<String> allowedPushOrigins) + private void addPushRegexps(List<String> pushRegexps) { for (String pushRegexp : pushRegexps) this.pushRegexps.add(Pattern.compile(pushRegexp)); - this.pushContentTypes.addAll(pushContentTypes); + } + + public void setPushContentTypes(List<String> pushContentTypes) + { + pushContentTypes.clear(); + pushContentTypes.addAll(pushContentTypes); + } + + public void setAllowedPushOrigins(List<String> allowedPushOrigins) + { + allowedPushOrigins.clear(); for (String allowedPushOrigin : allowedPushOrigins) this.allowedPushOrigins.add(Pattern.compile(allowedPushOrigin.replace(".", "\\.").replace("*", ".*"))); } - public int getMaxAssociatedResources() + public void setUserAgentBlacklist(List<String> userAgentPatterns) { - return maxAssociatedResources; + userAgentBlacklist.clear(); + for (String userAgentPattern : userAgentPatterns) + userAgentBlacklist.add(Pattern.compile(userAgentPattern)); } public void setMaxAssociatedResources(int maxAssociatedResources) @@ -110,11 +117,6 @@ public class ReferrerPushStrategy implements PushStrategy this.maxAssociatedResources = maxAssociatedResources; } - public int getReferrerPushPeriod() - { - return referrerPushPeriod; - } - public void setReferrerPushPeriod(int referrerPushPeriod) { this.referrerPushPeriod = referrerPushPeriod; @@ -125,7 +127,8 @@ public class ReferrerPushStrategy implements PushStrategy { Set<String> result = Collections.<String>emptySet(); short version = stream.getSession().getVersion(); - if (!isIfModifiedSinceHeaderPresent(requestHeaders) && isValidMethod(requestHeaders.get(HTTPSPDYHeader.METHOD.name(version)).value())) + if (!isIfModifiedSinceHeaderPresent(requestHeaders) && isValidMethod(requestHeaders.get(HTTPSPDYHeader.METHOD + .name(version)).value()) && !isUserAgentBlacklisted(requestHeaders)) { String scheme = requestHeaders.get(HTTPSPDYHeader.SCHEME.name(version)).value(); String host = requestHeaders.get(HTTPSPDYHeader.HOST.name(version)).value(); @@ -197,6 +200,16 @@ public class ReferrerPushStrategy implements PushStrategy return !isPushResource(url, responseHeaders); } + public boolean isUserAgentBlacklisted(Fields headers) + { + Fields.Field userAgentHeader = headers.get("user-agent"); + if (userAgentHeader != null) + for (Pattern userAgentPattern : userAgentBlacklist) + if (userAgentPattern.matcher(userAgentHeader.value()).matches()) + return true; + return false; + } + private boolean isPushResource(String url, Fields responseHeaders) { for (Pattern pushRegexp : pushRegexps) @@ -276,10 +289,8 @@ public class ReferrerPushStrategy implements PushStrategy private boolean isPushOriginAllowed(String origin) { for (Pattern allowedPushOrigin : allowedPushOrigins) - { if (allowedPushOrigin.matcher(origin).matches()) return true; - } return false; } } diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java index 430c6bc842..220f5d8174 100644 --- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java +++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.spdy.server.http; import java.io.IOException; import java.io.PrintWriter; import java.net.InetSocketAddress; +import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.servlet.ServletException; @@ -42,12 +43,21 @@ import org.eclipse.jetty.spdy.api.SynInfo; import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory; import org.eclipse.jetty.util.Fields; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest { private final String mainResource = "/index.html"; + private final int referrerPushPeriod = 1000; private final String cssResource = "/style.css"; + private InetSocketAddress serverAddress; + private ReferrerPushStrategy pushStrategy; + private ConnectionFactory defaultFactory; + private Fields mainRequestHeaders; public ReferrerPushStrategyTest(short version) { @@ -57,82 +67,63 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest @Override protected HTTPSPDYServerConnector newHTTPSPDYServerConnector(short version) { - HTTPSPDYServerConnector connector = - new HTTPSPDYServerConnector(server,version,new HttpConfiguration(),new ReferrerPushStrategy()); - return connector; + return new HTTPSPDYServerConnector(server, version, new HttpConfiguration(), new ReferrerPushStrategy()); } - @Test - public void testPushHeadersAreValid() throws Exception + @Before + public void setUp() throws Exception { - InetSocketAddress address = createServer(); - - ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy(); - int referrerPushPeriod = 1000; + serverAddress = createServer(); + pushStrategy = new ReferrerPushStrategy(); pushStrategy.setReferrerPushPeriod(referrerPushPeriod); - ConnectionFactory defaultFactory = new HTTPSPDYServerConnectionFactory(version,new HttpConfiguration(), pushStrategy); + defaultFactory = new HTTPSPDYServerConnectionFactory(version, new HttpConfiguration(), pushStrategy); connector.addConnectionFactory(defaultFactory); - if (connector.getConnectionFactory(NPNServerConnectionFactory.class)!=null) + if (connector.getConnectionFactory(NPNServerConnectionFactory.class) != null) connector.getConnectionFactory(NPNServerConnectionFactory.class).setDefaultProtocol(defaultFactory.getProtocol()); else connector.setDefaultProtocol(defaultFactory.getProtocol()); + mainRequestHeaders = createHeadersWithoutReferrer(mainResource); + } - connector.setDefaultProtocol(defaultFactory.getProtocol()); // TODO I don't think this is right - - Fields 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); + @Test + public void testPushHeadersAreValid() throws Exception + { + sendMainRequestAndCSSRequest(); + run2ndClientRequests(true, true); + } - run2ndClientRequests(address, mainRequestHeaders, true); + @Test + public void testUserAgentBlackList() throws Exception + { + pushStrategy.setUserAgentBlacklist(Arrays.asList(".*(?i)firefox/16.*")); + sendMainRequestAndCSSRequest(); + run2ndClientRequests(false, false); } @Test public void testReferrerPushPeriod() throws Exception { - InetSocketAddress address = createServer(); - - ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy(); - int referrerPushPeriod = 1000; - pushStrategy.setReferrerPushPeriod(referrerPushPeriod); - ConnectionFactory defaultFactory = new HTTPSPDYServerConnectionFactory(version,new HttpConfiguration(), pushStrategy); - connector.addConnectionFactory(defaultFactory); - if (connector.getConnectionFactory(NPNServerConnectionFactory.class)!=null) - connector.getConnectionFactory(NPNServerConnectionFactory.class).setDefaultProtocol(defaultFactory.getProtocol()); - else - connector.setDefaultProtocol(defaultFactory.getProtocol()); - - Fields mainRequestHeaders = createHeadersWithoutReferrer(mainResource); - Session session1 = sendMainRequestAndCSSRequest(address, mainRequestHeaders); + Session session1 = sendMainRequestAndCSSRequest(); // Sleep for pushPeriod This should prevent application.js from being mapped as pushResource - Thread.sleep(referrerPushPeriod+1); - + Thread.sleep(referrerPushPeriod + 1); sendJSRequest(session1); - run2ndClientRequests(address, mainRequestHeaders, false); + run2ndClientRequests(false, true); } @Test public void testMaxAssociatedResources() throws Exception { - InetSocketAddress address = createServer(); - - ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy(); pushStrategy.setMaxAssociatedResources(1); - ConnectionFactory defaultFactory = new HTTPSPDYServerConnectionFactory(version,new HttpConfiguration(), pushStrategy); connector.addConnectionFactory(defaultFactory); connector.setDefaultProtocol(defaultFactory.getProtocol()); // TODO I don't think this is right - Fields mainRequestHeaders = createHeadersWithoutReferrer(mainResource); - Session session1 = sendMainRequestAndCSSRequest(address, mainRequestHeaders); + Session session1 = sendMainRequestAndCSSRequest(); sendJSRequest(session1); - run2ndClientRequests(address, mainRequestHeaders, false); + run2ndClientRequests(false, true); } private InetSocketAddress createServer() throws Exception @@ -155,9 +146,9 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest }); } - private Session sendMainRequestAndCSSRequest(InetSocketAddress address, Fields mainRequestHeaders) throws Exception + private Session sendMainRequestAndCSSRequest() throws Exception { - Session session1 = startClient(version, address, null); + Session session1 = startClient(version, serverAddress, null); final CountDownLatch mainResourceLatch = new CountDownLatch(1); session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() @@ -207,7 +198,8 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest Assert.assertTrue(associatedResourceLatch2.await(5, TimeUnit.SECONDS)); } - private void run2ndClientRequests(InetSocketAddress address, Fields mainRequestHeaders, final boolean validateHeaders) throws Exception + private void run2ndClientRequests(final boolean validateHeaders, + boolean expectPushResource) throws Exception { // Create another client, and perform the same request for the main resource, // we expect the css being pushed, but not the js @@ -215,16 +207,20 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest 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() + Session session2 = startClient(version, serverAddress, new SessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) { - if(validateHeaders) + if (validateHeaders) validateHeaders(synInfo.getHeaders(), pushSynHeadersValid); - Assert.assertTrue(stream.isUnidirectional()); - Assert.assertTrue(synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).value().endsWith(".css")); + assertThat("Stream is unidirectional",stream.isUnidirectional(),is(true)); + assertThat("URI header ends with css", synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)) + .value().endsWith + ("" + + ".css"), + is(true)); return new StreamFrameListener.Adapter() { @Override @@ -243,7 +239,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest @Override public void onReply(Stream stream, ReplyInfo replyInfo) { - Assert.assertFalse(replyInfo.isClose()); + assertThat("replyInfo.isClose() is false", replyInfo.isClose(), is(false)); mainStreamLatch.countDown(); } @@ -256,10 +252,13 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest } }); - 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)); + assertThat("Main request reply and/or data not received", mainStreamLatch.await(5, TimeUnit.SECONDS), is(true)); + if (expectPushResource) + assertThat("Pushed data not received", pushDataLatch.await(5, TimeUnit.SECONDS), is(true)); + else + assertThat("No push data is received", pushDataLatch.await(1, TimeUnit.SECONDS), is(false)); + if (validateHeaders) + assertThat("Push syn headers not valid", pushSynHeadersValid.await(5, TimeUnit.SECONDS), is(true)); } @Test @@ -758,7 +757,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest }); 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)); + 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(Fields headers, CountDownLatch pushSynHeadersValid) @@ -805,12 +804,14 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest private Fields createHeadersWithoutReferrer(String resource) { - Fields associatedRequestHeaders = new Fields(); - 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; + Fields requestHeaders = new Fields(); + requestHeaders.put("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:16.0) " + + "Gecko/20100101 Firefox/16.0"); + requestHeaders.put(HTTPSPDYHeader.METHOD.name(version), "GET"); + requestHeaders.put(HTTPSPDYHeader.URI.name(version), resource); + requestHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1"); + requestHeaders.put(HTTPSPDYHeader.SCHEME.name(version), "http"); + requestHeaders.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + connector.getLocalPort()); + return requestHeaders; } } diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java index 28e7cb11f3..ab8fcb4fe9 100644 --- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java +++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.spdy.server.http; +import java.util.Arrays; import java.util.Set; import org.eclipse.jetty.spdy.api.SPDY; @@ -44,7 +45,7 @@ public class ReferrerPushStrategyUnitTest public static final String METHOD = "GET"; // class under test - private ReferrerPushStrategy referrerPushStrategy; + private ReferrerPushStrategy referrerPushStrategy = new ReferrerPushStrategy(); @Mock Stream stream; @@ -55,7 +56,7 @@ public class ReferrerPushStrategyUnitTest @Before public void setup() { - referrerPushStrategy = new ReferrerPushStrategy(); + referrerPushStrategy.setUserAgentBlacklist(Arrays.asList(".*(?i)firefox/16.*")); } @Test @@ -67,22 +68,45 @@ public class ReferrerPushStrategyUnitTest setMockExpectations(); String referrerUrl = fillPushStrategyCache(requestHeaders); - Set<String> pushResources; - // sleep to pretend that the user manually clicked on a linked resource instead the browser requesting subresources immediately + // sleep to pretend that the user manually clicked on a linked resource instead the browser requesting sub + // resources immediately Thread.sleep(referrerCallTimeout + 1); requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), "image2.jpg"); requestHeaders.put("referer", referrerUrl); - pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields()); + Set<String> pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields()); assertThat("pushResources is empty", pushResources.size(), is(0)); requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), MAIN_URI); pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields()); - // as the image2.jpg request has been a link and not a subresource, we expect that pushResources.size() is still 2 + // as the image2.jpg request has been a link and not a sub resource, we expect that pushResources.size() is + // still 2 assertThat("pushResources contains two elements image.jpg and style.css", pushResources.size(), is(2)); } + @Test + public void testUserAgentFilter() throws InterruptedException + { + Fields requestHeaders = getBaseHeaders(VERSION); + setMockExpectations(); + + fillPushStrategyCache(requestHeaders); + + Set<String> pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields()); + assertThat("pushResources contains two elements image.jpg and style.css as no user-agent header is present", + pushResources.size(), is(2)); + + requestHeaders.put("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4"); + pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields()); + assertThat("pushResources contains two elements image.jpg and style.css as chrome is not blacklisted", + pushResources.size(), is(2)); + + requestHeaders.put("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:16.0) Gecko/20100101 Firefox/16.0"); + pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields()); + assertThat("no resources are returned as we want to filter firefox", pushResources.size(), is(0)); + } + private Fields getBaseHeaders(short version) { Fields requestHeaders = new Fields(); |