From f5d63e86260bf1596090b4351025d8577d56f81a Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 5 Jun 2012 12:32:07 +0200 Subject: [PATCH 1/5] Moved version() method to base test class, and using HTTPSPDYHeader instead of hardcoded strings. --- .../jetty/spdy/http/AbstractHTTPSPDYTest.java | 5 +++++ .../jetty/spdy/http/ConcurrentStreamsTest.java | 16 ++++++++-------- .../spdy/http/PushStrategyBenchmarkTest.java | 6 ------ .../jetty/spdy/http/ServerHTTPSPDYv2Test.java | 6 ------ 4 files changed, 13 insertions(+), 20 deletions(-) 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 b875379f3ba..f6f41776d24 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 @@ -113,4 +113,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..0bbf0e68ff3 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 @@ -73,10 +73,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 +91,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/PushStrategyBenchmarkTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/PushStrategyBenchmarkTest.java index 0ff4893bd09..29b5952d4e5 100644 --- 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 @@ -38,7 +38,6 @@ 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; @@ -62,11 +61,6 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest private final long roundtrip = 100; private final int runs = 10; - protected short version() - { - return SPDY.V2; - } - @Test public void benchmarkPushStrategy() throws Exception { 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 index 3363d124a50..61ebfd127d9 100644 --- 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 @@ -41,7 +41,6 @@ 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.SPDY; import org.eclipse.jetty.spdy.api.Session; import org.eclipse.jetty.spdy.api.Stream; import org.eclipse.jetty.spdy.api.StreamFrameListener; @@ -52,11 +51,6 @@ import org.junit.Test; public class ServerHTTPSPDYv2Test extends AbstractHTTPSPDYTest { - protected short version() - { - return SPDY.V2; - } - @Test public void testSimpleGET() throws Exception { From c7d09af3f255b1fd96632e36e6235e1d7731cfe4 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 5 Jun 2012 12:33:10 +0200 Subject: [PATCH 2/5] Improved ReferrerPushStrategy to check also for content-type of pushed resources. --- .../jetty/spdy/http/ReferrerPushStrategy.java | 49 +++- ...t.java => ReferrerPushStrategyV2Test.java} | 268 ++++++++++++++---- .../spdy/http/ReferrerPushStrategyV3Test.java | 28 ++ 3 files changed, 272 insertions(+), 73 deletions(-) rename jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/{ReferrerPushStrategyTest.java => ReferrerPushStrategyV2Test.java} (57%) create mode 100644 jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV3Test.java 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..bc6e26e00b2 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 @@ -18,7 +18,7 @@ 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; @@ -50,32 +50,41 @@ import org.eclipse.jetty.util.log.Logger; * 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 */ 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 Set pushRegexps = new HashSet<>(); + private final Set pushContentTypes = new HashSet<>(); + private final Set allowedPushOrigins = new HashSet<>(); 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("*", ".*"))); } @@ -84,13 +93,14 @@ public class ReferrerPushStrategy implements PushStrategy 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(); + short version = stream.getSession().getVersion(); + String scheme = requestHeaders.get(HTTPSPDYHeader.SCHEME.name(version)).value(); + String host = requestHeaders.get(HTTPSPDYHeader.HOST.name(version)).value(); String origin = new StringBuilder(scheme).append("://").append(host).toString(); - String url = requestHeaders.get("url").value(); + String url = requestHeaders.get(HTTPSPDYHeader.URI.name(version)).value(); String absoluteURL = new StringBuilder(origin).append(url).toString(); logger.debug("Applying push strategy for {}", absoluteURL); - if (isValidMethod(requestHeaders.get("method").value())) + if (isValidMethod(requestHeaders.get(HTTPSPDYHeader.METHOD.name(version)).value())) { if (isMainResource(url, responseHeaders)) { @@ -129,7 +139,16 @@ public class ReferrerPushStrategy implements PushStrategy for (Pattern pushRegexp : pushRegexps) { if (pushRegexp.matcher(url).matches()) - return true; + { + Headers.Header header = responseHeaders.get("content-type"); + if (header == null) + return true; + + String contentType = header.value().toLowerCase(); + for (String pushContentType : pushContentTypes) + if (contentType.startsWith(pushContentType)) + return true; + } } return false; } diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV2Test.java similarity index 57% rename from jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyTest.java rename to jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV2Test.java index 4215118d5a1..b9b73d30138 100644 --- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyTest.java +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV2Test.java @@ -1,3 +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. + */ + package org.eclipse.jetty.spdy.http; import java.io.IOException; @@ -24,7 +40,7 @@ import org.eclipse.jetty.spdy.api.SynInfo; import org.junit.Assert; import org.junit.Test; -public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest +public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest { @Override protected SPDYServerConnector newHTTPSPDYServerConnector(short version) @@ -38,7 +54,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest @Test public void testAssociatedResourceIsPushed() throws Exception { - InetSocketAddress address = startHTTPServer(new AbstractHandler() + InetSocketAddress address = startHTTPServer(version(), new AbstractHandler() { @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException @@ -52,16 +68,16 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest baseRequest.setHandled(true); } }); - Session session1 = startClient(address, null); + Session session1 = startClient(version(), address, null); final CountDownLatch mainResourceLatch = new CountDownLatch(1); Headers mainRequestHeaders = new Headers(); - mainRequestHeaders.put("method", "GET"); + mainRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); String mainResource = "/index.html"; - mainRequestHeaders.put("url", mainResource); - mainRequestHeaders.put("version", "HTTP/1.1"); - mainRequestHeaders.put("scheme", "http"); - mainRequestHeaders.put("host", "localhost:" + connector.getLocalPort()); + mainRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), mainResource); + mainRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + mainRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + mainRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() { @Override @@ -76,11 +92,11 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest final CountDownLatch associatedResourceLatch = new CountDownLatch(1); Headers associatedRequestHeaders = new Headers(); - associatedRequestHeaders.put("method", "GET"); - associatedRequestHeaders.put("url", "/style.css"); - associatedRequestHeaders.put("version", "HTTP/1.1"); - associatedRequestHeaders.put("scheme", "http"); - associatedRequestHeaders.put("host", "localhost:" + connector.getLocalPort()); + associatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + associatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), "/style.css"); + 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()); associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource); session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter() { @@ -98,7 +114,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest final CountDownLatch mainStreamLatch = new CountDownLatch(2); final CountDownLatch pushDataLatch = new CountDownLatch(1); - Session session2 = startClient(address, new SessionFrameListener.Adapter() + Session session2 = startClient(version(), address, new SessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) @@ -138,10 +154,146 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS)); } + @Test + public void testAssociatedResourceWithWrongContentTypeIsNotPushed() 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")) + { + response.setContentType("text/html"); + output.print("HELLO"); + } + else if (url.equals("/fake.png")) + { + response.setContentType("text/html"); + output.print("IMAGE"); + } + else if (url.endsWith(".css")) + { + response.setContentType("text/css"); + output.print("body { background: #FFF; }"); + } + baseRequest.setHandled(true); + } + }); + Session session1 = startClient(version(), address, null); + + final CountDownLatch mainResourceLatch = new CountDownLatch(1); + Headers mainRequestHeaders = new Headers(); + mainRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + String mainResource = "/index.html"; + mainRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), mainResource); + mainRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + mainRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + mainRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + 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 = new Headers(); + associatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + associatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), "/stylesheet.css"); + 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()); + associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource); + 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 = new Headers(); + fakeAssociatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + fakeAssociatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), "/fake.png"); + fakeAssociatedRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + fakeAssociatedRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + fakeAssociatedRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + fakeAssociatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource); + 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(new AbstractHandler() + InetSocketAddress address = startHTTPServer(version(), new AbstractHandler() { @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException @@ -157,16 +309,16 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest baseRequest.setHandled(true); } }); - Session session1 = startClient(address, null); + Session session1 = startClient(version(), address, null); final CountDownLatch mainResourceLatch = new CountDownLatch(1); Headers mainRequestHeaders = new Headers(); - mainRequestHeaders.put("method", "GET"); + mainRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); String mainResource = "/index.html"; - mainRequestHeaders.put("url", mainResource); - mainRequestHeaders.put("version", "HTTP/1.1"); - mainRequestHeaders.put("scheme", "http"); - mainRequestHeaders.put("host", "localhost:" + connector.getLocalPort()); + mainRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), mainResource); + mainRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + mainRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + mainRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() { @Override @@ -181,12 +333,12 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest final CountDownLatch associatedResourceLatch = new CountDownLatch(1); Headers associatedRequestHeaders = new Headers(); - associatedRequestHeaders.put("method", "GET"); + associatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); String associatedResource = "/style.css"; - associatedRequestHeaders.put("url", associatedResource); - associatedRequestHeaders.put("version", "HTTP/1.1"); - associatedRequestHeaders.put("scheme", "http"); - associatedRequestHeaders.put("host", "localhost:" + connector.getLocalPort()); + associatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), associatedResource); + 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()); associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource); session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter() { @@ -202,11 +354,11 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest final CountDownLatch nestedResourceLatch = new CountDownLatch(1); Headers nestedRequestHeaders = new Headers(); - nestedRequestHeaders.put("method", "GET"); - nestedRequestHeaders.put("url", "/image.gif"); - nestedRequestHeaders.put("version", "HTTP/1.1"); - nestedRequestHeaders.put("scheme", "http"); - nestedRequestHeaders.put("host", "localhost:" + connector.getLocalPort()); + nestedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + nestedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), "/image.gif"); + nestedRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + nestedRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + nestedRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); nestedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + associatedResource); session1.syn(new SynInfo(nestedRequestHeaders, true), new StreamFrameListener.Adapter() { @@ -224,7 +376,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest final CountDownLatch mainStreamLatch = new CountDownLatch(2); final CountDownLatch pushDataLatch = new CountDownLatch(2); - Session session2 = startClient(address, new SessionFrameListener.Adapter() + Session session2 = startClient(version(), address, new SessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) @@ -267,7 +419,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest @Test public void testMainResourceWithReferrerIsNotPushed() throws Exception { - InetSocketAddress address = startHTTPServer(new AbstractHandler() + InetSocketAddress address = startHTTPServer(version(), new AbstractHandler() { @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException @@ -279,16 +431,16 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest baseRequest.setHandled(true); } }); - Session session1 = startClient(address, null); + Session session1 = startClient(version(), address, null); final CountDownLatch mainResourceLatch = new CountDownLatch(1); Headers mainRequestHeaders = new Headers(); - mainRequestHeaders.put("method", "GET"); + mainRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); String mainResource = "/index.html"; - mainRequestHeaders.put("url", mainResource); - mainRequestHeaders.put("version", "HTTP/1.1"); - mainRequestHeaders.put("scheme", "http"); - mainRequestHeaders.put("host", "localhost:" + connector.getLocalPort()); + mainRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), mainResource); + mainRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + mainRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + mainRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() { @Override @@ -303,11 +455,11 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest final CountDownLatch associatedResourceLatch = new CountDownLatch(1); Headers associatedRequestHeaders = new Headers(); - associatedRequestHeaders.put("method", "GET"); - associatedRequestHeaders.put("url", "/home.html"); - associatedRequestHeaders.put("version", "HTTP/1.1"); - associatedRequestHeaders.put("scheme", "http"); - associatedRequestHeaders.put("host", "localhost:" + connector.getLocalPort()); + associatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + associatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), "/home.html"); + 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()); associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource); session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter() { @@ -325,7 +477,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest final CountDownLatch mainStreamLatch = new CountDownLatch(2); final CountDownLatch pushLatch = new CountDownLatch(1); - Session session2 = startClient(address, new SessionFrameListener.Adapter() + Session session2 = startClient(version(), address, new SessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) @@ -359,7 +511,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest @Test public void testRequestWithIfModifiedSinceHeaderPreventsPush() throws Exception { - InetSocketAddress address = startHTTPServer(new AbstractHandler() + InetSocketAddress address = startHTTPServer(version(), new AbstractHandler() { @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException @@ -373,16 +525,16 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest baseRequest.setHandled(true); } }); - Session session1 = startClient(address, null); + Session session1 = startClient(version(), address, null); final CountDownLatch mainResourceLatch = new CountDownLatch(1); Headers mainRequestHeaders = new Headers(); - mainRequestHeaders.put("method", "GET"); + mainRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); String mainResource = "/index.html"; - mainRequestHeaders.put("url", mainResource); - mainRequestHeaders.put("version", "HTTP/1.1"); - mainRequestHeaders.put("scheme", "http"); - mainRequestHeaders.put("host", "localhost:" + connector.getLocalPort()); + mainRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), mainResource); + mainRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + mainRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + mainRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); mainRequestHeaders.put("If-Modified-Since", "Tue, 27 Mar 2012 16:36:52 GMT"); session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter() { @@ -398,11 +550,11 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest final CountDownLatch associatedResourceLatch = new CountDownLatch(1); Headers associatedRequestHeaders = new Headers(); - associatedRequestHeaders.put("method", "GET"); - associatedRequestHeaders.put("url", "/style.css"); - associatedRequestHeaders.put("version", "HTTP/1.1"); - associatedRequestHeaders.put("scheme", "http"); - associatedRequestHeaders.put("host", "localhost:" + connector.getLocalPort()); + associatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + associatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), "/style.css"); + 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()); associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource); session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter() { @@ -421,7 +573,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest final CountDownLatch mainStreamLatch = new CountDownLatch(2); final CountDownLatch pushDataLatch = new CountDownLatch(1); - Session session2 = startClient(address, new SessionFrameListener.Adapter() + Session session2 = startClient(version(), address, new SessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) 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..a722fde9f46 --- /dev/null +++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV3Test.java @@ -0,0 +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. + */ + +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; + } +} From cd05259be4eec537af1f908f89ca758d5114b645 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 5 Jun 2012 13:30:15 +0200 Subject: [PATCH 3/5] Updated ReferrerPushStrategy to limit the number of associated resources. --- .../jetty/spdy/http/ReferrerPushStrategy.java | 45 ++++-- .../spdy/http/ReferrerPushStrategyV2Test.java | 133 ++++++++++++++++++ 2 files changed, 167 insertions(+), 11 deletions(-) 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 bc6e26e00b2..52f1243d734 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 @@ -39,17 +39,18 @@ import org.eclipse.jetty.util.log.Logger; *

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. + *

This class distinguishes associated 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 + *

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 { @@ -58,6 +59,7 @@ public class ReferrerPushStrategy implements PushStrategy private final Set pushRegexps = new HashSet<>(); private final Set pushContentTypes = new HashSet<>(); private final Set allowedPushOrigins = new HashSet<>(); + private volatile int maxAssociatedResources = 32; public ReferrerPushStrategy() { @@ -89,6 +91,16 @@ public class ReferrerPushStrategy implements PushStrategy this.allowedPushOrigins.add(Pattern.compile(allowedPushOrigin.replace(".", "\\.").replace("*", ".*"))); } + public int getMaxAssociatedResources() + { + return maxAssociatedResources; + } + + public void setMaxAssociatedResources(int maxAssociatedResources) + { + this.maxAssociatedResources = maxAssociatedResources; + } + @Override public Set apply(Stream stream, Headers requestHeaders, Headers responseHeaders) { @@ -173,8 +185,19 @@ public class ReferrerPushStrategy implements PushStrategy if (existing != null) pushResources = existing; } - pushResources.add(url); - logger.debug("Built push metadata for {}: {}", referrer, pushResources); + // 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 (pushResources.size() < getMaxAssociatedResources()) + { + pushResources.add(url); + logger.debug("Stored push metadata for {}: {}", referrer, pushResources); + } + else + { + logger.debug("Skipped store of push metadata {} for {}: max associated resources ({}) reached", + url, referrer, maxAssociatedResources); + } } } 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 index b9b73d30138..ce88e712c74 100644 --- 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 @@ -51,6 +51,139 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest return connector; } + @Test + public void testMaxAssociatedResources() throws Exception + { + InetSocketAddress address = startHTTPServer(version(), new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + String url = request.getRequestURI(); + PrintWriter output = response.getWriter(); + if (url.endsWith(".html")) + output.print("HELLO"); + else if (url.endsWith(".css")) + output.print("body { background: #FFF; }"); + else if (url.endsWith(".js")) + output.print("function(){}();"); + baseRequest.setHandled(true); + } + }); + ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy(); + pushStrategy.setMaxAssociatedResources(1); + AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy); + connector.setDefaultAsyncConnectionFactory(defaultFactory); + + Session session1 = startClient(version(), address, null); + + final CountDownLatch mainResourceLatch = new CountDownLatch(1); + Headers mainRequestHeaders = new Headers(); + mainRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + String mainResource = "/index.html"; + mainRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), mainResource); + mainRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + mainRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + mainRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + 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 = new Headers(); + associatedRequestHeaders1.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + associatedRequestHeaders1.put(HTTPSPDYHeader.URI.name(version()), "/style.css"); + associatedRequestHeaders1.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + associatedRequestHeaders1.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + associatedRequestHeaders1.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + associatedRequestHeaders1.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource); + 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)); + + final CountDownLatch associatedResourceLatch2 = new CountDownLatch(1); + Headers associatedRequestHeaders2 = new Headers(); + associatedRequestHeaders2.put(HTTPSPDYHeader.METHOD.name(version()), "GET"); + associatedRequestHeaders2.put(HTTPSPDYHeader.URI.name(version()), "/application.js"); + associatedRequestHeaders2.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1"); + associatedRequestHeaders2.put(HTTPSPDYHeader.SCHEME.name(version()), "http"); + associatedRequestHeaders2.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort()); + associatedRequestHeaders2.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource); + 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)); + + // 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); + 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 testAssociatedResourceIsPushed() throws Exception { From 5b3999a7f49fe0f7353cec67957c08351b1645a5 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Tue, 5 Jun 2012 15:42:28 +0200 Subject: [PATCH 4/5] 381712 Anoint all JspServlets with correct environment --- .../jetty/webapp/StandardDescriptorProcessor.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java index db5ec955afa..51800f4d7c6 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java @@ -239,23 +239,26 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor String servlet_class = node.getString("servlet-class", false, true); // Handle JSP - String jspServletName=null; String jspServletClass=null;; - boolean hasJSP=false; + + //Handle the default jsp servlet instance if (id != null && id.equals("jsp")) { - jspServletName = servlet_name; jspServletClass = servlet_class; try { Loader.loadClass(this.getClass(), servlet_class); - hasJSP = true; } catch (ClassNotFoundException e) { LOG.info("NO JSP Support for {}, did not find {}", context.getContextPath(), servlet_class); jspServletClass = servlet_class = "org.eclipse.jetty.servlet.NoJspServlet"; } + } + + //could be more than 1 declaration of the jsp servlet, so configure them all + if (servlet_class != null && "org.apache.jasper.servlet.JspServlet".equals(servlet_class)) + { if (holder.getInitParameter("scratchdir") == null) { File tmp = context.getTempDirectory(); @@ -316,12 +319,12 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor } } - // Handler JSP file + // Handle JSP file String jsp_file = node.getString("jsp-file", false, true); if (jsp_file != null) { holder.setForcedPath(jsp_file); - holder.setClassName(jspServletClass); + holder.setClassName(jspServletClass); //only use our default instance //set the system classpath explicitly for the holder that will represent the JspServlet instance holder.setInitParameter("com.sun.appserv.jsp.classpath", getSystemClassPath(context)); } From 28c3c3e917d5979d83bca4174473427c55ef571c Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 5 Jun 2012 16:03:08 +0200 Subject: [PATCH 5/5] 381639 - CrossOriginFilter does not support Access-Control-Expose-Headers. --- .../jetty/servlets/CrossOriginFilter.java | 23 +++++++++++++++---- .../jetty/servlets/CrossOriginFilterTest.java | 21 +++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java index bb57608c4b4..eb1b9859959 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java @@ -54,15 +54,18 @@ import org.eclipse.jetty.util.log.Logger; * and any 3 letter top-level domain (.com, .net, .org, etc.). *
  • allowedMethods, a comma separated list of HTTP methods that * are allowed to be used when accessing the resources. Default value is - * GET,POST
  • + * GET,POST,HEAD *
  • allowedHeaders, a comma separated list of HTTP headers that * are allowed to be specified when accessing the resources. Default value - * is X-Requested-With
  • + * is X-Requested-With,Content-Type,Accept,Origin *
  • preflightMaxAge, the number of seconds that preflight requests * can be cached by the client. Default value is 1800 seconds, or 30 * minutes
  • *
  • allowCredentials, a boolean indicating if the resource allows * requests with credentials. Default value is false
  • + *
  • exposeHeaders, a comma separated list of HTTP headers that + * are allowed to be exposed on the client. Default value is the + * empty list
  • *

    *

    A typical configuration could be: *

    @@ -79,8 +82,6 @@ import org.eclipse.jetty.util.log.Logger;
      *     ...
      * </web-app>
      * 

    - * - * @version $Revision$ $Date$ */ public class CrossOriginFilter implements Filter { @@ -96,12 +97,14 @@ public class CrossOriginFilter implements Filter public static final String ACCESS_CONTROL_ALLOW_HEADERS_HEADER = "Access-Control-Allow-Headers"; public static final String ACCESS_CONTROL_MAX_AGE_HEADER = "Access-Control-Max-Age"; public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER = "Access-Control-Allow-Credentials"; + public static final String ACCESS_CONTROL_EXPOSE_HEADERS_HEADER = "Access-Control-Expose-Headers"; // Implementation constants public static final String ALLOWED_ORIGINS_PARAM = "allowedOrigins"; public static final String ALLOWED_METHODS_PARAM = "allowedMethods"; public static final String ALLOWED_HEADERS_PARAM = "allowedHeaders"; 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"; private static final String ANY_ORIGIN = "*"; private static final List SIMPLE_HTTP_METHODS = Arrays.asList("GET", "POST", "HEAD"); @@ -109,6 +112,7 @@ public class CrossOriginFilter implements Filter private List allowedOrigins = new ArrayList(); private List allowedMethods = new ArrayList(); private List allowedHeaders = new ArrayList(); + private List exposedHeaders = new ArrayList(); private int preflightMaxAge = 0; private boolean allowCredentials; @@ -163,6 +167,11 @@ public class CrossOriginFilter implements Filter allowedCredentialsConfig = "true"; allowCredentials = Boolean.parseBoolean(allowedCredentialsConfig); + String exposedHeadersConfig = config.getInitParameter(EXPOSED_HEADERS_PARAM); + if (exposedHeadersConfig == null) + exposedHeadersConfig = ""; + exposedHeaders.addAll(Arrays.asList(exposedHeadersConfig.split(","))); + if (LOG.isDebugEnabled()) { LOG.debug("Cross-origin filter configuration: " + @@ -170,7 +179,9 @@ public class CrossOriginFilter implements Filter ALLOWED_METHODS_PARAM + " = " + allowedMethodsConfig + ", " + ALLOWED_HEADERS_PARAM + " = " + allowedHeadersConfig + ", " + PREFLIGHT_MAX_AGE_PARAM + " = " + preflightMaxAgeConfig + ", " + - ALLOW_CREDENTIALS_PARAM + " = " + allowedCredentialsConfig); + ALLOW_CREDENTIALS_PARAM + " = " + allowedCredentialsConfig + "," + + EXPOSED_HEADERS_PARAM + " = " + exposedHeadersConfig + ); } } @@ -305,6 +316,8 @@ public class CrossOriginFilter implements Filter response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin); if (allowCredentials) response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true"); + if (!exposedHeaders.isEmpty()) + response.setHeader(ACCESS_CONTROL_EXPOSE_HEADERS_HEADER, commify(exposedHeaders)); } private void handlePreflightResponse(HttpServletRequest request, HttpServletResponse response, String origin) 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 fb8d6bba490..bd631ebf37c 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 @@ -371,6 +371,27 @@ public class CrossOriginFilterTest Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); } + @Test + public void testSimpleRequestWithExposedHeaders() throws Exception + { + FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter()); + filterHolder.setInitParameter("exposedHeaders", "Content-Length"); + tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT); + + CountDownLatch latch = new CountDownLatch(1); + tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*"); + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Origin: http://localhost\r\n" + + "\r\n"; + String response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_EXPOSE_HEADERS_HEADER)); + Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); + } + public static class ResourceServlet extends HttpServlet { private static final long serialVersionUID = 1L;