Merge branch 'master' of ssh://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project

This commit is contained in:
Greg Wilkins 2012-06-05 17:31:44 +02:00
commit 1801c4ebe8
11 changed files with 905 additions and 520 deletions

View File

@ -54,15 +54,18 @@ import org.eclipse.jetty.util.log.Logger;
* and any 3 letter top-level domain (.com, .net, .org, etc.).</li>
* <li><b>allowedMethods</b>, a comma separated list of HTTP methods that
* are allowed to be used when accessing the resources. Default value is
* <b>GET,POST</b></li>
* <b>GET,POST,HEAD</b></li>
* <li><b>allowedHeaders</b>, a comma separated list of HTTP headers that
* are allowed to be specified when accessing the resources. Default value
* is <b>X-Requested-With</b></li>
* is <b>X-Requested-With,Content-Type,Accept,Origin</b></li>
* <li><b>preflightMaxAge</b>, the number of seconds that preflight requests
* can be cached by the client. Default value is <b>1800</b> seconds, or 30
* minutes</li>
* <li><b>allowCredentials</b>, a boolean indicating if the resource allows
* requests with credentials. Default value is <b>false</b></li>
* <li><b>exposeHeaders</b>, a comma separated list of HTTP headers that
* are allowed to be exposed on the client. Default value is the
* <b>empty list</b></li>
* </ul></p>
* <p>A typical configuration could be:
* <pre>
@ -79,8 +82,6 @@ import org.eclipse.jetty.util.log.Logger;
* ...
* &lt;/web-app&gt;
* </pre></p>
*
* @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<String> SIMPLE_HTTP_METHODS = Arrays.asList("GET", "POST", "HEAD");
@ -109,6 +112,7 @@ public class CrossOriginFilter implements Filter
private List<String> allowedOrigins = new ArrayList<String>();
private List<String> allowedMethods = new ArrayList<String>();
private List<String> allowedHeaders = new ArrayList<String>();
private List<String> exposedHeaders = new ArrayList<String>();
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)

View File

@ -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;

View File

@ -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;
@ -39,58 +39,80 @@ import org.eclipse.jetty.util.log.Logger;
* <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 main resources and associated
* resources must be distinguishable.</p>
* <p>This class distinguishes associated resources by their URL path suffix.
* <p>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.</p>
* <p>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.</p>
*
* 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
* <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>
*/
public class ReferrerPushStrategy implements PushStrategy
{
private static final Logger logger = Log.getLogger(ReferrerPushStrategy.class);
private final ConcurrentMap<String, Set<String>> resources = new ConcurrentHashMap<>();
private final Set<Pattern> pushRegexps = new LinkedHashSet<>();
private final Set<Pattern> allowedPushOrigins = new LinkedHashSet<>();
private final Set<Pattern> pushRegexps = new HashSet<>();
private final Set<String> pushContentTypes = new HashSet<>();
private final Set<Pattern> allowedPushOrigins = new HashSet<>();
private volatile int maxAssociatedResources = 32;
public ReferrerPushStrategy()
{
this(Arrays.asList(".*\\.css", ".*\\.js", ".*\\.png", ".*\\.jpg", ".*\\.gif"));
this(Arrays.asList(".*\\.css", ".*\\.js", ".*\\.png", ".*\\.jpeg", ".*\\.jpg", ".*\\.gif", ".*\\.ico"));
}
public ReferrerPushStrategy(List<String> pushRegexps)
{
this(pushRegexps, Collections.<String>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<String> pushRegexps, List<String> allowedPushOrigins)
public ReferrerPushStrategy(List<String> pushRegexps, List<String> pushContentTypes)
{
this(pushRegexps, pushContentTypes, Collections.<String>emptyList());
}
public ReferrerPushStrategy(List<String> pushRegexps, List<String> pushContentTypes, List<String> 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;
}
@Override
public Set<String> apply(Stream stream, Headers requestHeaders, Headers responseHeaders)
{
Set<String> 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 +151,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;
}
@ -154,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);
}
}
}

View File

@ -113,4 +113,9 @@ public abstract class AbstractHTTPSPDYTest
server.join();
}
}
protected short version()
{
return SPDY.V2;
}
}

View File

@ -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()
{

View File

@ -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
{

View File

@ -1,463 +0,0 @@
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.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 ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
{
@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 testAssociatedResourceIsPushed() throws Exception
{
InetSocketAddress address = startHTTPServer(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("<html><head/><body>HELLO</body></html>");
else if (url.endsWith(".css"))
output.print("body { background: #FFF; }");
baseRequest.setHandled(true);
}
});
Session session1 = startClient(address, null);
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
Headers mainRequestHeaders = new Headers();
mainRequestHeaders.put("method", "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());
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("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("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));
// 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(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 testNestedAssociatedResourceIsPushed() throws Exception
{
InetSocketAddress address = startHTTPServer(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("<html><head/><body>HELLO</body></html>");
else if (url.endsWith(".css"))
output.print("body { background: #FFF; }");
else if (url.endsWith(".gif"))
output.print("\u0000");
baseRequest.setHandled(true);
}
});
Session session1 = startClient(address, null);
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
Headers mainRequestHeaders = new Headers();
mainRequestHeaders.put("method", "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());
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("method", "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("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 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("referer", "http://localhost:" + connector.getLocalPort() + associatedResource);
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(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(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("<html><head/><body>HELLO</body></html>");
baseRequest.setHandled(true);
}
});
Session session1 = startClient(address, null);
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
Headers mainRequestHeaders = new Headers();
mainRequestHeaders.put("method", "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());
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("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("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));
// 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(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(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("<html><head/><body>HELLO</body></html>");
else if (url.endsWith(".css"))
output.print("body { background: #FFF; }");
baseRequest.setHandled(true);
}
});
Session session1 = startClient(address, null);
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
Headers mainRequestHeaders = new Headers();
mainRequestHeaders.put("method", "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("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 = 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("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));
// 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(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));
}
}

View File

@ -0,0 +1,748 @@
/*
* 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;
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.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
{
@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 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("<html><head/><body>HELLO</body></html>");
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
{
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("<html><head/><body>HELLO</body></html>");
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 = 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()), "/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()
{
@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
{
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("<html><head/><body>HELLO</body></html>");
}
else if (url.equals("/fake.png"))
{
response.setContentType("text/html");
output.print("<html><head/><body>IMAGE</body></html>");
}
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(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("<html><head/><body>HELLO</body></html>");
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 = 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");
String associatedResource = "/style.css";
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()
{
@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);
Headers nestedRequestHeaders = new Headers();
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()
{
@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("<html><head/><body>HELLO</body></html>");
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()), "/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()
{
@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("<html><head/><body>HELLO</body></html>");
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 = 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());
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 = new Headers();
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()
{
@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));
}
}

View File

@ -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;
}
}

View File

@ -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
{

View File

@ -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));
}