Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse McConnell2012-07-11 15:10:00 +0000
committerJesse McConnell2012-07-11 15:10:00 +0000
commit0fa17c13b1c004951493e2d3199f75b557c5e480 (patch)
treefa9f2448df27984d7891b59ddda80d622e1b0577
parent96fe2d6c3fc1337d359a9900f176363d8c775b6c (diff)
parent5aeca2a138faad875a1a68e56676714b6bc480b9 (diff)
downloadorg.eclipse.jetty.project-0fa17c13b1c004951493e2d3199f75b557c5e480.tar.gz
org.eclipse.jetty.project-0fa17c13b1c004951493e2d3199f75b557c5e480.tar.xz
org.eclipse.jetty.project-0fa17c13b1c004951493e2d3199f75b557c5e480.zip
Merge branch 'master' into jetty-8
-rw-r--r--jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffers.java12
-rw-r--r--jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java12
-rw-r--r--jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java14
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Server.java2
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java8
-rw-r--r--jetty-servlets/src/main/java/org/eclipse/jetty/servlets/BalancerServlet.java417
-rw-r--r--jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ProxyServlet.java29
-rw-r--r--jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractBalancerServletTest.java157
-rw-r--r--jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BalancerServletTest.java129
-rw-r--r--jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java27
-rw-r--r--jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java2
-rw-r--r--jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml52
-rw-r--r--jetty-spdy/spdy-jetty-http/pom.xml5
-rw-r--r--jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java26
-rw-r--r--jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategy.java162
-rw-r--r--jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java57
-rw-r--r--jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngine.java27
-rw-r--r--jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/SPDYProxyEngine.java54
-rw-r--r--jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/PushStrategyBenchmarkTest.java74
-rw-r--r--jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyUnitTest.java106
-rw-r--r--jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV2Test.java281
-rw-r--r--jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/SSLExternalServerTest.java81
-rw-r--r--jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYAsyncConnection.java5
-rw-r--r--jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYServerConnector.java12
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java4
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketHandler.java3
26 files changed, 1483 insertions, 275 deletions
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffers.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffers.java
index b94710af5c..9bc63cd1de 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffers.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffers.java
@@ -1,4 +1,16 @@
package org.eclipse.jetty.io;
+//========================================================================
+//Copyright (c) 2006-2012 Mort Bay Consulting Pty. Ltd.
+//------------------------------------------------------------------------
+//All rights reserved. This program and the accompanying materials
+//are made available under the terms of the Eclipse Public License v1.0
+//and Apache License v2.0 which accompanies this distribution.
+//The Eclipse Public License is available at
+//http://www.eclipse.org/legal/epl-v10.html
+//The Apache License v2.0 is available at
+//http://www.opensource.org/licenses/apache2.0.php
+//You may elect to redistribute this code under either of these licenses.
+//========================================================================
import org.eclipse.jetty.io.nio.DirectNIOBuffer;
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
index ef80cccb2f..e3da77460e 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
@@ -1,4 +1,16 @@
package org.eclipse.jetty.io;
+//========================================================================
+//Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
+//------------------------------------------------------------------------
+//All rights reserved. This program and the accompanying materials
+//are made available under the terms of the Eclipse Public License v1.0
+//and Apache License v2.0 which accompanies this distribution.
+//The Eclipse Public License is available at
+//http://www.eclipse.org/legal/epl-v10.html
+//The Apache License v2.0 is available at
+//http://www.opensource.org/licenses/apache2.0.php
+//You may elect to redistribute this code under either of these licenses.
+//========================================================================
import java.io.IOException;
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java
index a8db4ec100..3ba70b035b 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java
@@ -333,9 +333,10 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
if (l==0 && ( header!=null && header.hasContent() || buffer!=null && buffer.hasContent() || trailer!=null && trailer.hasContent()))
{
synchronized (this)
- {
- if (_dispatched)
- _writable=false;
+ {
+ _writable=false;
+ if (!_dispatched)
+ updateKey();
}
}
else if (l>0)
@@ -358,9 +359,10 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
if (l==0 && buffer!=null && buffer.hasContent())
{
synchronized (this)
- {
- if (_dispatched)
- _writable=false;
+ {
+ _writable=false;
+ if (!_dispatched)
+ updateKey();
}
}
else if (l>0)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
index a6537730df..f477da9c17 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
@@ -354,7 +354,7 @@ public class Server extends HandlerWrapper implements Attributes
{
LOG.debug("REQUEST "+target+" on "+connection);
handle(target, request, request, response);
- LOG.debug("RESPONSE "+target+" "+connection.getResponse().getStatus());
+ LOG.debug("RESPONSE "+target+" "+connection.getResponse().getStatus()+" handled="+request.isHandled());
}
else
handle(target, request, request, response);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java
index dba192ee8f..9feb6f6a25 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java
@@ -233,21 +233,21 @@ public class ConnectHandler extends HandlerWrapper
}
catch (SocketException se)
{
- LOG.info("ConnectHandler: " + se.getMessage());
- response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
+ LOG.info("ConnectHandler: SocketException " + se.getMessage());
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
baseRequest.setHandled(true);
return;
}
catch (SocketTimeoutException ste)
{
- LOG.info("ConnectHandler: " + ste.getMessage());
+ LOG.info("ConnectHandler: SocketTimeoutException" + ste.getMessage());
response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
baseRequest.setHandled(true);
return;
}
catch (IOException ioe)
{
- LOG.info("ConnectHandler: " + ioe.getMessage());
+ LOG.info("ConnectHandler: IOException" + ioe.getMessage());
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
baseRequest.setHandled(true);
return;
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/BalancerServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/BalancerServlet.java
new file mode 100644
index 0000000000..f7ef7db009
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/BalancerServlet.java
@@ -0,0 +1,417 @@
+// ========================================================================
+// Copyright (c) 2012 Intalio, Inc.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.servlets;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.server.Request;
+
+/**
+ * 6
+ */
+public class BalancerServlet extends ProxyServlet
+{
+
+ private static final class BalancerMember
+ {
+
+ private String _name;
+
+ private String _proxyTo;
+
+ private HttpURI _backendURI;
+
+ public BalancerMember(String name, String proxyTo)
+ {
+ super();
+ _name = name;
+ _proxyTo = proxyTo;
+ _backendURI = new HttpURI(_proxyTo);
+ }
+
+ public String getProxyTo()
+ {
+ return _proxyTo;
+ }
+
+ public HttpURI getBackendURI()
+ {
+ return _backendURI;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "BalancerMember [_name=" + _name + ", _proxyTo=" + _proxyTo + "]";
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((_name == null)?0:_name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ BalancerMember other = (BalancerMember)obj;
+ if (_name == null)
+ {
+ if (other._name != null)
+ return false;
+ }
+ else if (!_name.equals(other._name))
+ return false;
+ return true;
+ }
+
+ }
+
+ private static final class RoundRobinIterator implements Iterator<BalancerMember>
+ {
+
+ private BalancerMember[] _balancerMembers;
+
+ private AtomicInteger _index;
+
+ public RoundRobinIterator(Collection<BalancerMember> balancerMembers)
+ {
+ _balancerMembers = (BalancerMember[])balancerMembers.toArray(new BalancerMember[balancerMembers.size()]);
+ _index = new AtomicInteger(-1);
+ }
+
+ public boolean hasNext()
+ {
+ return true;
+ }
+
+ public BalancerMember next()
+ {
+ BalancerMember balancerMember = null;
+ while (balancerMember == null)
+ {
+ int currentIndex = _index.get();
+ int nextIndex = (currentIndex + 1) % _balancerMembers.length;
+ if (_index.compareAndSet(currentIndex,nextIndex))
+ {
+ balancerMember = _balancerMembers[nextIndex];
+ }
+ }
+ return balancerMember;
+ }
+
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+ private static final String BALANCER_MEMBER_PREFIX = "BalancerMember.";
+
+ private static final List<String> FORBIDDEN_CONFIG_PARAMETERS;
+ static
+ {
+ List<String> params = new LinkedList<String>();
+ params.add("HostHeader");
+ params.add("whiteList");
+ params.add("blackList");
+ FORBIDDEN_CONFIG_PARAMETERS = Collections.unmodifiableList(params);
+ }
+
+ private static final List<String> REVERSE_PROXY_HEADERS;
+ static
+ {
+ List<String> params = new LinkedList<String>();
+ params.add("Location");
+ params.add("Content-Location");
+ params.add("URI");
+ REVERSE_PROXY_HEADERS = Collections.unmodifiableList(params);
+ }
+
+ private static final String JSESSIONID = "jsessionid";
+
+ private static final String JSESSIONID_URL_PREFIX = JSESSIONID + "=";
+
+ private boolean _stickySessions;
+
+ private Set<BalancerMember> _balancerMembers = new HashSet<BalancerMember>();
+
+ private boolean _proxyPassReverse;
+
+ private RoundRobinIterator _roundRobinIterator;
+
+ @Override
+ public void init(ServletConfig config) throws ServletException
+ {
+ validateConfig(config);
+ super.init(config);
+ initStickySessions(config);
+ initBalancers(config);
+ initProxyPassReverse(config);
+ postInit();
+ }
+
+ private void validateConfig(ServletConfig config) throws ServletException
+ {
+ @SuppressWarnings("unchecked")
+ List<String> initParameterNames = Collections.list(config.getInitParameterNames());
+ for (String initParameterName : initParameterNames)
+ {
+ if (FORBIDDEN_CONFIG_PARAMETERS.contains(initParameterName))
+ {
+ throw new UnavailableException(initParameterName + " not supported in " + getClass().getName());
+ }
+ }
+ }
+
+ private void initStickySessions(ServletConfig config) throws ServletException
+ {
+ _stickySessions = "true".equalsIgnoreCase(config.getInitParameter("StickySessions"));
+ }
+
+ private void initBalancers(ServletConfig config) throws ServletException
+ {
+ Set<String> balancerNames = getBalancerNames(config);
+ for (String balancerName : balancerNames)
+ {
+ String memberProxyToParam = BALANCER_MEMBER_PREFIX + balancerName + ".ProxyTo";
+ String proxyTo = config.getInitParameter(memberProxyToParam);
+ if (proxyTo == null || proxyTo.trim().length() == 0)
+ {
+ throw new UnavailableException(memberProxyToParam + " parameter is empty.");
+ }
+ _balancerMembers.add(new BalancerMember(balancerName,proxyTo));
+ }
+ }
+
+ private void initProxyPassReverse(ServletConfig config)
+ {
+ _proxyPassReverse = "true".equalsIgnoreCase(config.getInitParameter("ProxyPassReverse"));
+ }
+
+ private void postInit()
+ {
+ _roundRobinIterator = new RoundRobinIterator(_balancerMembers);
+ }
+
+ private Set<String> getBalancerNames(ServletConfig config) throws ServletException
+ {
+ Set<String> names = new HashSet<String>();
+ @SuppressWarnings("unchecked")
+ List<String> initParameterNames = Collections.list(config.getInitParameterNames());
+ for (String initParameterName : initParameterNames)
+ {
+ if (!initParameterName.startsWith(BALANCER_MEMBER_PREFIX))
+ {
+ continue;
+ }
+ int endOfNameIndex = initParameterName.lastIndexOf(".");
+ if (endOfNameIndex <= BALANCER_MEMBER_PREFIX.length())
+ {
+ throw new UnavailableException(initParameterName + " parameter does not provide a balancer member name");
+ }
+ names.add(initParameterName.substring(BALANCER_MEMBER_PREFIX.length(),endOfNameIndex));
+ }
+ return names;
+ }
+
+ @Override
+ protected HttpURI proxyHttpURI(HttpServletRequest request, String uri) throws MalformedURLException
+ {
+ BalancerMember balancerMember = selectBalancerMember(request);
+ try
+ {
+ URI dstUri = new URI(balancerMember.getProxyTo() + "/" + uri).normalize();
+ return new HttpURI(dstUri.toString());
+ }
+ catch (URISyntaxException e)
+ {
+ throw new MalformedURLException(e.getMessage());
+ }
+ }
+
+ private BalancerMember selectBalancerMember(HttpServletRequest request)
+ {
+ BalancerMember balancerMember = null;
+ if (_stickySessions)
+ {
+ String name = getBalancerMemberNameFromSessionId(request);
+ if (name != null)
+ {
+ balancerMember = findBalancerMemberByName(name);
+ if (balancerMember != null)
+ {
+ return balancerMember;
+ }
+ }
+ }
+ return _roundRobinIterator.next();
+ }
+
+ private BalancerMember findBalancerMemberByName(String name)
+ {
+ BalancerMember example = new BalancerMember(name,"");
+ for (BalancerMember balancerMember : _balancerMembers)
+ {
+ if (balancerMember.equals(example))
+ {
+ return balancerMember;
+ }
+ }
+ return null;
+ }
+
+ private String getBalancerMemberNameFromSessionId(HttpServletRequest request)
+ {
+ String name = getBalancerMemberNameFromSessionCookie(request);
+ if (name == null)
+ {
+ name = getBalancerMemberNameFromURL(request);
+ }
+ return name;
+ }
+
+ private String getBalancerMemberNameFromSessionCookie(HttpServletRequest request)
+ {
+ Cookie[] cookies = request.getCookies();
+ String name = null;
+ for (Cookie cookie : cookies)
+ {
+ if (JSESSIONID.equalsIgnoreCase(cookie.getName()))
+ {
+ name = extractBalancerMemberNameFromSessionId(cookie.getValue());
+ break;
+ }
+ }
+ return name;
+ }
+
+ private String getBalancerMemberNameFromURL(HttpServletRequest request)
+ {
+ String name = null;
+ String requestURI = request.getRequestURI();
+ int idx = requestURI.lastIndexOf(";");
+ if (idx != -1)
+ {
+ String requestURISuffix = requestURI.substring(idx);
+ if (requestURISuffix.startsWith(JSESSIONID_URL_PREFIX))
+ {
+ name = extractBalancerMemberNameFromSessionId(requestURISuffix.substring(JSESSIONID_URL_PREFIX.length()));
+ }
+ }
+ return name;
+ }
+
+ private String extractBalancerMemberNameFromSessionId(String sessionId)
+ {
+ String name = null;
+ int idx = sessionId.lastIndexOf(".");
+ if (idx != -1)
+ {
+ String sessionIdSuffix = sessionId.substring(idx + 1);
+ name = (sessionIdSuffix.length() > 0)?sessionIdSuffix:null;
+ }
+ return name;
+ }
+
+ @Override
+ protected String filterResponseHeaderValue(String headerName, String headerValue, HttpServletRequest request)
+ {
+ if (_proxyPassReverse && REVERSE_PROXY_HEADERS.contains(headerName))
+ {
+ HttpURI locationURI = new HttpURI(headerValue);
+ if (isAbsoluteLocation(locationURI) && isBackendLocation(locationURI))
+ {
+ Request jettyRequest = (Request)request;
+ URI reverseUri;
+ try
+ {
+ reverseUri = new URI(jettyRequest.getRootURL().append(locationURI.getCompletePath()).toString()).normalize();
+ return reverseUri.toURL().toString();
+ }
+ catch (Exception e)
+ {
+ _log.warn("Not filtering header response",e);
+ return headerValue;
+ }
+ }
+ }
+ return headerValue;
+ }
+
+ private boolean isBackendLocation(HttpURI locationURI)
+ {
+ for (BalancerMember balancerMember : _balancerMembers)
+ {
+ HttpURI backendURI = balancerMember.getBackendURI();
+ if (backendURI.getHost().equals(locationURI.getHost()) && backendURI.getScheme().equals(locationURI.getScheme())
+ && backendURI.getPort() == locationURI.getPort())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isAbsoluteLocation(HttpURI locationURI)
+ {
+ return locationURI.getHost() != null;
+ }
+
+ @Override
+ public String getHostHeader()
+ {
+ throw new UnsupportedOperationException("HostHeader not supported in " + getClass().getName());
+ }
+
+ @Override
+ public void setHostHeader(String hostHeader)
+ {
+ throw new UnsupportedOperationException("HostHeader not supported in " + getClass().getName());
+ }
+
+ @Override
+ public boolean validateDestination(String host, String path)
+ {
+ return true;
+ }
+
+} \ No newline at end of file
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ProxyServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ProxyServlet.java
index 7fa971838a..6687903a6a 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ProxyServlet.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ProxyServlet.java
@@ -483,13 +483,20 @@ public class ProxyServlet implements Servlet
@Override
protected void onResponseHeader(Buffer name, Buffer value) throws IOException
{
- String s = name.toString().toLowerCase();
+ String nameString = name.toString();
+ String s = nameString.toLowerCase();
if (!_DontProxyHeaders.contains(s) || (HttpHeaders.CONNECTION_BUFFER.equals(name) && HttpHeaderValues.CLOSE_BUFFER.equals(value)))
{
if (debug != 0)
_log.debug(debug + " " + name + ": " + value);
- response.addHeader(name.toString(),value.toString());
+ String filteredHeaderValue = filterResponseHeaderValue(nameString,value.toString(),request);
+ if (filteredHeaderValue != null && filteredHeaderValue.trim().length() > 0)
+ {
+ if (debug != 0)
+ _log.debug(debug + " " + name + ": (filtered): " + filteredHeaderValue);
+ response.addHeader(nameString,filteredHeaderValue);
+ }
}
else if (debug != 0)
_log.debug(debug + " " + name + "! " + value);
@@ -786,8 +793,22 @@ public class ProxyServlet implements Servlet
}
/**
+ * Extension point for remote server response header filtering. The default implementation returns the header value as is. If null is returned, this header
+ * won't be forwarded back to the client.
+ *
+ * @param headerName
+ * @param headerValue
+ * @param request
+ * @return filteredHeaderValue
+ */
+ protected String filterResponseHeaderValue(String headerName, String headerValue, HttpServletRequest request)
+ {
+ return headerValue;
+ }
+
+ /**
* Transparent Proxy.
- *
+ *
* This convenience extension to ProxyServlet configures the servlet as a transparent proxy. The servlet is configured with init parameters:
* <ul>
* <li>ProxyTo - a URI like http://host:80/context to which the request is proxied.
@@ -795,7 +816,7 @@ public class ProxyServlet implements Servlet
* </ul>
* For example, if a request was received at /foo/bar and the ProxyTo was http://host:80/context and the Prefix was /foo, then the request would be proxied
* to http://host:80/context/bar
- *
+ *
*/
public static class Transparent extends ProxyServlet
{
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractBalancerServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractBalancerServletTest.java
new file mode 100644
index 0000000000..e798396adc
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractBalancerServletTest.java
@@ -0,0 +1,157 @@
+package org.eclipse.jetty.servlets;
+
+//========================================================================
+//Copyright (c) 2012 Intalio, Inc.
+//------------------------------------------------------------------------
+//All rights reserved. This program and the accompanying materials
+//are made available under the terms of the Eclipse Public License v1.0
+//and Apache License v2.0 which accompanies this distribution.
+//The Eclipse Public License is available at
+//http://www.eclipse.org/legal/epl-v10.html
+//The Apache License v2.0 is available at
+//http://www.opensource.org/licenses/apache2.0.php
+//You may elect to redistribute this code under either of these licenses.
+//========================================================================
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServlet;
+
+import org.eclipse.jetty.client.ContentExchange;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.server.session.HashSessionIdManager;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.After;
+import org.junit.Before;
+
+
+public abstract class AbstractBalancerServletTest
+{
+
+ private boolean _stickySessions;
+
+ private Server _node1;
+
+ private Server _node2;
+
+ private Server _balancerServer;
+
+ private HttpClient _httpClient;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ _httpClient = new HttpClient();
+ _httpClient.registerListener("org.eclipse.jetty.client.RedirectListener");
+ _httpClient.start();
+ }
+
+ @After
+ public void tearDown() throws Exception
+ {
+ stopServer(_node1);
+ stopServer(_node2);
+ stopServer(_balancerServer);
+ _httpClient.stop();
+ }
+
+ private void stopServer(Server server)
+ {
+ try
+ {
+ server.stop();
+ }
+ catch (Exception e)
+ {
+ // Do nothing
+ }
+ }
+
+ protected void setStickySessions(boolean stickySessions)
+ {
+ _stickySessions = stickySessions;
+ }
+
+ protected void startBalancer(Class<? extends HttpServlet> httpServletClass) throws Exception
+ {
+ _node1 = createServer(new ServletHolder(httpServletClass.newInstance()),"/pipo","/molo/*");
+ setSessionIdManager(_node1,"node1");
+ _node1.start();
+
+ _node2 = createServer(new ServletHolder(httpServletClass.newInstance()),"/pipo","/molo/*");
+ setSessionIdManager(_node2,"node2");
+ _node2.start();
+
+ BalancerServlet balancerServlet = new BalancerServlet();
+ ServletHolder balancerServletHolder = new ServletHolder(balancerServlet);
+ balancerServletHolder.setInitParameter("StickySessions",String.valueOf(_stickySessions));
+ balancerServletHolder.setInitParameter("ProxyPassReverse","true");
+ balancerServletHolder.setInitParameter("BalancerMember." + "node1" + ".ProxyTo","http://localhost:" + getServerPort(_node1));
+ balancerServletHolder.setInitParameter("BalancerMember." + "node2" + ".ProxyTo","http://localhost:" + getServerPort(_node2));
+
+ _balancerServer = createServer(balancerServletHolder,"/pipo","/molo/*");
+ _balancerServer.start();
+ }
+
+ private Server createServer(ServletHolder servletHolder, String appContext, String servletUrlPattern)
+ {
+ Server server = new Server();
+ SelectChannelConnector httpConnector = new SelectChannelConnector();
+ server.addConnector(httpConnector);
+
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+ context.setContextPath(appContext);
+ server.setHandler(context);
+
+ context.addServlet(servletHolder,servletUrlPattern);
+
+ return server;
+ }
+
+ private void setSessionIdManager(Server node, String nodeName)
+ {
+ HashSessionIdManager sessionIdManager = new HashSessionIdManager();
+ sessionIdManager.setWorkerName(nodeName);
+ node.setSessionIdManager(sessionIdManager);
+ }
+
+ private int getServerPort(Server node)
+ {
+ return node.getConnectors()[0].getLocalPort();
+ }
+
+ protected byte[] sendRequestToBalancer(String requestUri) throws IOException, InterruptedException
+ {
+ ContentExchange exchange = new ContentExchange()
+ {
+ @Override
+ protected void onResponseHeader(Buffer name, Buffer value) throws IOException
+ {
+ // Cookie persistence
+ if (name.toString().equals("Set-Cookie"))
+ {
+ String cookieVal = value.toString();
+ if (cookieVal.startsWith("JSESSIONID="))
+ {
+ String jsessionid = cookieVal.split(";")[0].substring("JSESSIONID=".length());
+ _httpClient.getDestination(getAddress(),false).addCookie(new HttpCookie("JSESSIONID",jsessionid));
+ }
+ }
+ }
+ };
+ exchange.setURL("http://localhost:" + getServerPort(_balancerServer) + "/pipo/molo/" + requestUri);
+ exchange.setMethod(HttpMethods.GET);
+
+ _httpClient.send(exchange);
+ exchange.waitForDone();
+
+ return exchange.getResponseContentBytes();
+ }
+
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BalancerServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BalancerServletTest.java
new file mode 100644
index 0000000000..9513895fc6
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BalancerServletTest.java
@@ -0,0 +1,129 @@
+package org.eclipse.jetty.servlets;
+//========================================================================
+//Copyright (c) 2012 Intalio, Inc.
+//------------------------------------------------------------------------
+//All rights reserved. This program and the accompanying materials
+//are made available under the terms of the Eclipse Public License v1.0
+//and Apache License v2.0 which accompanies this distribution.
+//The Eclipse Public License is available at
+//http://www.eclipse.org/legal/epl-v10.html
+//The Apache License v2.0 is available at
+//http://www.opensource.org/licenses/apache2.0.php
+//You may elect to redistribute this code under either of these licenses.
+//========================================================================
+
+import static org.junit.Assert.*;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+
+/**
+ *
+ */
+public class BalancerServletTest extends AbstractBalancerServletTest
+{
+
+ @Test
+ public void testRoundRobinBalancer() throws Exception
+ {
+ setStickySessions(false);
+ startBalancer(CounterServlet.class);
+
+ for (int i = 0; i < 10; i++)
+ {
+ byte[] responseBytes = sendRequestToBalancer("/");
+ String returnedCounter = readFirstLine(responseBytes);
+ // RR : response should increment every other request
+ String expectedCounter = String.valueOf(i / 2);
+ assertEquals(expectedCounter,returnedCounter);
+ }
+ }
+
+ @Test
+ public void testStickySessionsBalancer() throws Exception
+ {
+ setStickySessions(true);
+ startBalancer(CounterServlet.class);
+
+ for (int i = 0; i < 10; i++)
+ {
+ byte[] responseBytes = sendRequestToBalancer("/");
+ String returnedCounter = readFirstLine(responseBytes);
+ // RR : response should increment on each request
+ String expectedCounter = String.valueOf(i);
+ assertEquals(expectedCounter,returnedCounter);
+ }
+ }
+
+ @Test
+ public void testProxyPassReverse() throws Exception
+ {
+ setStickySessions(false);
+ startBalancer(RelocationServlet.class);
+
+ byte[] responseBytes = sendRequestToBalancer("index.html");
+ String msg = readFirstLine(responseBytes);
+ assertEquals("success",msg);
+ }
+
+ private String readFirstLine(byte[] responseBytes) throws IOException
+ {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(responseBytes)));
+ return reader.readLine();
+ }
+
+ @SuppressWarnings("serial")
+ public static final class CounterServlet extends HttpServlet
+ {
+
+ private int counter;
+
+ @Override
+ public void init() throws ServletException
+ {
+ counter = 0;
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ // Force session creation
+ req.getSession();
+ resp.setContentType("text/plain");
+ resp.getWriter().println(counter++);
+ }
+ }
+
+ @SuppressWarnings("serial")
+ public static final class RelocationServlet extends HttpServlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ if (req.getRequestURI().endsWith("/index.html"))
+ {
+ resp.sendRedirect("http://localhost:" + req.getLocalPort() + req.getContextPath() + req.getServletPath() + "/other.html?secret=pipo%20molo");
+ return;
+ }
+ resp.setContentType("text/plain");
+ if ("pipo molo".equals(req.getParameter("secret")))
+ {
+ resp.getWriter().println("success");
+ }
+ else
+ {
+ resp.getWriter().println("failure");
+ }
+ }
+ }
+
+}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
index 446a9103e9..0ed914d709 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
@@ -16,9 +16,11 @@
package org.eclipse.jetty.spdy;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.InterruptedByTimeoutException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@@ -66,10 +68,12 @@ import org.eclipse.jetty.spdy.frames.WindowUpdateFrame;
import org.eclipse.jetty.spdy.generator.Generator;
import org.eclipse.jetty.spdy.parser.Parser;
import org.eclipse.jetty.util.Atomics;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-public class StandardSession implements ISession, Parser.Listener, Handler<StandardSession.FrameBytes>
+public class StandardSession implements ISession, Parser.Listener, Handler<StandardSession.FrameBytes>, Dumpable
{
private static final Logger logger = Log.getLogger(Session.class);
private static final ThreadLocal<Integer> handlerInvocations = new ThreadLocal<Integer>()
@@ -1092,6 +1096,27 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
flowControlStrategy.setWindowSize(this, initialWindowSize);
}
+ public String toString()
+ {
+ return String.format("%s@%x{v%d,queuSize=%d,windowSize=%d,streams=%d}", getClass().getSimpleName(), hashCode(), version, queue.size(), getWindowSize(), streams.size());
+ }
+
+
+ @Override
+ public String dump()
+ {
+ return AggregateLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ AggregateLifeCycle.dumpObject(out,this);
+ AggregateLifeCycle.dump(out,indent,Collections.singletonList(controller),streams.values());
+ }
+
+
+
public interface FrameBytes extends Comparable<FrameBytes>
{
public IStream getStream();
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java
index d173140968..a99536fa1f 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java
@@ -440,7 +440,7 @@ public class StandardStream implements IStream
@Override
public String toString()
{
- return String.format("stream=%d v%d %s", getId(), session.getVersion(), closeState);
+ return String.format("stream=%d v%d windowSize=%db reset=%s %s %s", getId(), session.getVersion(), getWindowSize(), isReset(), openState, closeState);
}
private boolean canSend()
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml b/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml
index 4218d4630e..0d847bcbd4 100644
--- a/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml
+++ b/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml
@@ -11,11 +11,45 @@
<Set name="protocol">TLSv1</Set>
</New>
+ <!-- Uncomment to create a ReferrerPushStrategy that can be added to the Connectors -->
+
+ <!--
+ <New id="pushStrategy" class="org.eclipse.jetty.spdy.http.ReferrerPushStrategy">
+ <Arg type="List">
+ <Array type="String">
+ <Item>.*\.css</Item>
+ <Item>.*\.js</Item>
+ <Item>.*\.png</Item>
+ <Item>.*\.jpg</Item>
+ <Item>.*\.gif</Item>
+ </Array>
+ </Arg>
+ </New>
+ -->
+
<!--<Set class="org.eclipse.jetty.npn.NextProtoNego" name="debug" type="boolean">true</Set>-->
<Call name="addConnector">
<Arg>
<New class="org.eclipse.jetty.spdy.http.HTTPSPDYServerConnector">
+ <!-- uncomment to enable to apply ReferrerPushStrategy for spdy/3
+ if you want to support it in both spdy/2 and spdy/3, just replace the
+ value in the first map entry.
+ -->
+ <!--
+ <Arg name="pushStrategies">
+ <Map>
+ <Entry>
+ <Item type="short">2</Item>
+ <Item><New class="org.eclipse.jetty.spdy.http.PushStrategy$None" /></Item>
+ </Entry>
+ <Entry>
+ <Item type="short">3</Item>
+ <Item><Ref id="pushStrategy" /></Item>
+ </Entry>
+ </Map>
+ </Arg>
+ -->
<Set name="Port">8080</Set>
</New>
</Arg>
@@ -26,6 +60,24 @@
<Arg>
<Ref id="sslContextFactory" />
</Arg>
+ <!-- uncomment to enable to apply ReferrerPushStrategy for spdy/3
+ if you want to support it in both spdy/2 and spdy/3, just replace the
+ value in the first map entry.
+ -->
+ <!--
+ <Arg name="pushStrategies">
+ <Map>
+ <Entry>
+ <Item type="short">2</Item>
+ <Item><New class="org.eclipse.jetty.spdy.http.PushStrategy$None" /></Item>
+ </Entry>
+ <Entry>
+ <Item type="short">3</Item>
+ <Item><Ref id="pushStrategy" /></Item>
+ </Entry>
+ </Map>
+ </Arg>
+ -->
<Set name="Port">8443</Set>
</New>
</Arg>
diff --git a/jetty-spdy/spdy-jetty-http/pom.xml b/jetty-spdy/spdy-jetty-http/pom.xml
index f091eb6b32..dfaf1b1f93 100644
--- a/jetty-spdy/spdy-jetty-http/pom.xml
+++ b/jetty-spdy/spdy-jetty-http/pom.xml
@@ -72,6 +72,11 @@
<version>${slf4j-version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java
index 2cf6e68fd4..389fdb90e2 100644
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java
+++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java
@@ -16,6 +16,9 @@
package org.eclipse.jetty.spdy.http;
+import java.util.Collections;
+import java.util.Map;
+
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -23,32 +26,41 @@ public class HTTPSPDYServerConnector extends AbstractHTTPSPDYServerConnector
{
public HTTPSPDYServerConnector()
{
- this(null, new PushStrategy.None());
+ this(null, Collections.<Short, PushStrategy>emptyMap());
}
- public HTTPSPDYServerConnector(PushStrategy pushStrategy)
+ public HTTPSPDYServerConnector(Map<Short, PushStrategy> pushStrategies)
{
- this(null, pushStrategy);
+ this(null, pushStrategies);
}
public HTTPSPDYServerConnector(SslContextFactory sslContextFactory)
{
- this(sslContextFactory, new PushStrategy.None());
+ this(sslContextFactory, Collections.<Short, PushStrategy>emptyMap());
}
- public HTTPSPDYServerConnector(SslContextFactory sslContextFactory, PushStrategy pushStrategy)
+ public HTTPSPDYServerConnector(SslContextFactory sslContextFactory, Map<Short, PushStrategy> pushStrategies)
{
// We pass a null ServerSessionFrameListener because for
// HTTP over SPDY we need one that references the endPoint
super(null, sslContextFactory);
clearAsyncConnectionFactories();
// The "spdy/3" protocol handles HTTP over SPDY
- putAsyncConnectionFactory("spdy/3", new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V3, getByteBufferPool(), getExecutor(), getScheduler(), this, pushStrategy));
+ putAsyncConnectionFactory("spdy/3", new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V3, getByteBufferPool(), getExecutor(), getScheduler(), this, getPushStrategy(SPDY.V3,pushStrategies)));
// The "spdy/2" protocol handles HTTP over SPDY
- putAsyncConnectionFactory("spdy/2", new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), this, pushStrategy));
+ putAsyncConnectionFactory("spdy/2", new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), this, getPushStrategy(SPDY.V2,pushStrategies)));
// The "http/1.1" protocol handles browsers that support NPN but not SPDY
putAsyncConnectionFactory("http/1.1", new ServerHTTPAsyncConnectionFactory(this));
// The default connection factory handles plain HTTP on non-SSL or non-NPN connections
setDefaultAsyncConnectionFactory(getAsyncConnectionFactory("http/1.1"));
}
+
+ private PushStrategy getPushStrategy(short version, Map<Short, PushStrategy> pushStrategies)
+ {
+ PushStrategy pushStrategy = pushStrategies.get(version);
+ if(pushStrategy == null)
+ pushStrategy = new PushStrategy.None();
+ return pushStrategy;
+ }
+
}
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 52f1243d73..0d7857f931 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
@@ -23,6 +23,8 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import org.eclipse.jetty.spdy.api.Headers;
@@ -37,12 +39,13 @@ import org.eclipse.jetty.util.log.Logger;
* will have a <tt>Referer</tt> HTTP header that points to <tt>index.html</tt>, which we
* use to link the associated resource to the main resource.</p>
* <p>However, also following a hyperlink generates a HTTP request with a <tt>Referer</tt>
- * HTTP header that points to <tt>index.html</tt>; therefore main resources and associated
- * resources must be distinguishable.</p>
- * <p>This class distinguishes associated resources by their URL path suffix and content
+ * HTTP header that points to <tt>index.html</tt>; therefore a proper value for {@link #getReferrerPushPeriod()}
+ * has to be set. If the referrerPushPeriod for a main resource has been passed, no more
+ * associated resources will be added for that main resource.</p>
+ * <p>This class distinguishes associated main resources by their URL path suffix and content
* type.
* CSS stylesheets, images and JavaScript files have recognizable URL path suffixes that
- * are classified as associated resources.</p>
+ * are classified as associated resources. The suffix regexs can be configured by constructor argument</p>
* <p>When CSS stylesheets refer to images, the CSS image request will have the CSS
* stylesheet as referrer. This implementation will push also the CSS image.</p>
* <p>The push metadata built by this implementation is limited by the number of pages
@@ -55,11 +58,12 @@ import org.eclipse.jetty.util.log.Logger;
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 ConcurrentMap<String, MainResource> mainResources = new ConcurrentHashMap<>();
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;
+ private volatile int referrerPushPeriod = 5000;
public ReferrerPushStrategy()
{
@@ -101,22 +105,33 @@ public class ReferrerPushStrategy implements PushStrategy
this.maxAssociatedResources = maxAssociatedResources;
}
+ public int getReferrerPushPeriod()
+ {
+ return referrerPushPeriod;
+ }
+
+ public void setReferrerPushPeriod(int referrerPushPeriod)
+ {
+ this.referrerPushPeriod = referrerPushPeriod;
+ }
+
@Override
public Set<String> apply(Stream stream, Headers requestHeaders, Headers responseHeaders)
{
- Set<String> result = Collections.emptySet();
+ Set<String> result = Collections.<String>emptySet();
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(HTTPSPDYHeader.URI.name(version)).value();
- String absoluteURL = new StringBuilder(origin).append(url).toString();
- logger.debug("Applying push strategy for {}", absoluteURL);
- if (isValidMethod(requestHeaders.get(HTTPSPDYHeader.METHOD.name(version)).value()))
+ if (!isIfModifiedSinceHeaderPresent(requestHeaders) && isValidMethod(requestHeaders.get(HTTPSPDYHeader.METHOD.name(version)).value()))
{
+ String scheme = requestHeaders.get(HTTPSPDYHeader.SCHEME.name(version)).value();
+ String host = requestHeaders.get(HTTPSPDYHeader.HOST.name(version)).value();
+ String origin = scheme + "://" + host;
+ String url = requestHeaders.get(HTTPSPDYHeader.URI.name(version)).value();
+ String absoluteURL = origin + url;
+ logger.debug("Applying push strategy for {}", absoluteURL);
if (isMainResource(url, responseHeaders))
{
- result = pushResources(absoluteURL);
+ MainResource mainResource = getOrCreateMainResource(absoluteURL);
+ result = mainResource.getResources();
}
else if (isPushResource(url, responseHeaders))
{
@@ -124,18 +139,49 @@ public class ReferrerPushStrategy implements PushStrategy
if (referrerHeader != null)
{
String referrer = referrerHeader.value();
- Set<String> pushResources = resources.get(referrer);
- if (pushResources == null || !pushResources.contains(url))
- buildMetadata(origin, url, referrer);
+ MainResource mainResource = mainResources.get(referrer);
+ if (mainResource == null)
+ mainResource = getOrCreateMainResource(referrer);
+
+ Set<String> pushResources = mainResource.getResources();
+ if (!pushResources.contains(url))
+ mainResource.addResource(url, origin, referrer);
else
- result = pushResources(absoluteURL);
+ result = getPushResources(absoluteURL);
}
}
+ logger.debug("Pushing {} resources for {}: {}", result.size(), absoluteURL, result);
}
- logger.debug("Push resources for {}: {}", absoluteURL, result);
return result;
}
+ private Set<String> getPushResources(String absoluteURL)
+ {
+ Set<String> result = Collections.emptySet();
+ if (mainResources.get(absoluteURL) != null)
+ result = mainResources.get(absoluteURL).getResources();
+ return result;
+ }
+
+ private MainResource getOrCreateMainResource(String absoluteURL)
+ {
+ MainResource mainResource = mainResources.get(absoluteURL);
+ if (mainResource == null)
+ {
+ logger.debug("Creating new main resource for {}", absoluteURL);
+ MainResource value = new MainResource(absoluteURL);
+ mainResource = mainResources.putIfAbsent(absoluteURL, value);
+ if (mainResource == null)
+ mainResource = value;
+ }
+ return mainResource;
+ }
+
+ private boolean isIfModifiedSinceHeaderPresent(Headers headers)
+ {
+ return headers.get("if-modified-since") != null;
+ }
+
private boolean isValidMethod(String method)
{
return "GET".equalsIgnoreCase(method);
@@ -165,49 +211,71 @@ public class ReferrerPushStrategy implements PushStrategy
return false;
}
- private Set<String> pushResources(String absoluteURL)
+ private class MainResource
{
- Set<String> pushResources = resources.get(absoluteURL);
- if (pushResources == null)
- return Collections.emptySet();
- return Collections.unmodifiableSet(pushResources);
- }
+ private final String name;
+ private final Set<String> resources = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
+ private final AtomicLong firstResourceAdded = new AtomicLong(-1);
- private void buildMetadata(String origin, String url, String referrer)
- {
- if (referrer.startsWith(origin) || isPushOriginAllowed(origin))
+ private MainResource(String name)
+ {
+ this.name = name;
+ }
+
+ public boolean addResource(String url, String origin, String referrer)
{
- Set<String> pushResources = resources.get(referrer);
- if (pushResources == null)
+ // We start the push period here and not when initializing the main resource, because a browser with a
+ // prefilled cache won't request the subresources. If the browser with warmed up cache now hits the main
+ // resource after a server restart, the push period shouldn't start until the first subresource is
+ // being requested.
+ firstResourceAdded.compareAndSet(-1, System.nanoTime());
+
+ long delay = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - firstResourceAdded.get());
+ if (!referrer.startsWith(origin) && !isPushOriginAllowed(origin))
{
- pushResources = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
- Set<String> existing = resources.putIfAbsent(referrer, pushResources);
- if (existing != null)
- pushResources = existing;
+ logger.debug("Skipped store of push metadata {} for {}: Origin: {} doesn't match or origin not allowed",
+ url, name, origin);
+ return false;
}
+
// This check is not strictly concurrent-safe, but limiting
// the number of associated resources is achieved anyway
// although in rare cases few more resources will be stored
- if (pushResources.size() < getMaxAssociatedResources())
+ if (resources.size() >= maxAssociatedResources)
{
- pushResources.add(url);
- logger.debug("Stored push metadata for {}: {}", referrer, pushResources);
+ logger.debug("Skipped store of push metadata {} for {}: max associated resources ({}) reached",
+ url, name, maxAssociatedResources);
+ return false;
}
- else
+ if (delay > referrerPushPeriod)
{
- logger.debug("Skipped store of push metadata {} for {}: max associated resources ({}) reached",
- url, referrer, maxAssociatedResources);
+ logger.debug("Delay: {}ms longer than referrerPushPeriod: {}ms. Not adding resource: {} for: {}", delay, referrerPushPeriod, url, name);
+ return false;
}
+
+ logger.debug("Adding resource: {} for: {} with delay: {}ms.", url, name, delay);
+ resources.add(url);
+ return true;
}
- }
- private boolean isPushOriginAllowed(String origin)
- {
- for (Pattern allowedPushOrigin : allowedPushOrigins)
+ public Set<String> getResources()
{
- if (allowedPushOrigin.matcher(origin).matches())
- return true;
+ return Collections.unmodifiableSet(resources);
+ }
+
+ public String toString()
+ {
+ return "MainResource: " + name + " associated resources:" + resources.size();
+ }
+
+ private boolean isPushOriginAllowed(String origin)
+ {
+ for (Pattern allowedPushOrigin : allowedPushOrigins)
+ {
+ if (allowedPushOrigin.matcher(origin).matches())
+ return true;
+ }
+ return false;
}
- return false;
}
}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java
index 5fb09f555c..0c3af1bb08 100644
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java
+++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java
@@ -55,6 +55,7 @@ import org.eclipse.jetty.spdy.api.Handler;
import org.eclipse.jetty.spdy.api.Headers;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamStatus;
import org.eclipse.jetty.spdy.api.SynInfo;
@@ -177,6 +178,10 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
logger.debug("HTTP > {} {} {}", m, u, v);
startRequest(new ByteArrayBuffer(m), new ByteArrayBuffer(u), new ByteArrayBuffer(v));
+ Headers.Header schemeHeader = headers.get(HTTPSPDYHeader.SCHEME.name(this.version));
+ if(schemeHeader != null)
+ _request.setScheme(schemeHeader.value());
+
updateState(State.HEADERS);
handle();
break;
@@ -403,7 +408,7 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
if (!stream.isUnidirectional())
stream.reply(replyInfo);
if (replyInfo.getHeaders().get(HTTPSPDYHeader.STATUS.name(version)).value().startsWith("200") &&
- !stream.isClosed() && !isIfModifiedSinceHeaderPresent())
+ !stream.isClosed())
{
// We have a 200 OK with some content to send
@@ -411,19 +416,12 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
Headers.Header host = headers.get(HTTPSPDYHeader.HOST.name(version));
Headers.Header uri = headers.get(HTTPSPDYHeader.URI.name(version));
Set<String> pushResources = pushStrategy.apply(stream, headers, replyInfo.getHeaders());
- String referrer = new StringBuilder(scheme.value()).append("://").append(host.value()).append(uri.value()).toString();
- for (String pushURL : pushResources)
+
+ for (String pushResourcePath : pushResources)
{
- final Headers pushHeaders = new Headers();
- pushHeaders.put(HTTPSPDYHeader.METHOD.name(version), "GET");
- pushHeaders.put(HTTPSPDYHeader.URI.name(version), pushURL);
- pushHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
- pushHeaders.put(scheme);
- pushHeaders.put(host);
- pushHeaders.put("referer", referrer);
- pushHeaders.put("x-spdy-push", "true");
- // Remember support for gzip encoding
- pushHeaders.put(headers.get("accept-encoding"));
+ final Headers requestHeaders = createRequestHeaders(scheme, host, uri, pushResourcePath);
+ final Headers pushHeaders = createPushHeaders(scheme, host, pushResourcePath);
+
stream.syn(new SynInfo(pushHeaders, false), getMaxIdleTime(), TimeUnit.MILLISECONDS, new Handler.Adapter<Stream>()
{
@Override
@@ -431,16 +429,43 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
{
ServerHTTPSPDYAsyncConnection pushConnection =
new ServerHTTPSPDYAsyncConnection(getConnector(), getEndPoint(), getServer(), version, connection, pushStrategy, pushStream);
- pushConnection.beginRequest(pushHeaders, true);
+ pushConnection.beginRequest(requestHeaders, true);
}
});
}
}
}
- private boolean isIfModifiedSinceHeaderPresent()
+ private Headers createRequestHeaders(Headers.Header scheme, Headers.Header host, Headers.Header uri, String pushResourcePath)
{
- return headers.get("if-modified-since") != null;
+ final Headers requestHeaders = new Headers();
+ requestHeaders.put(HTTPSPDYHeader.METHOD.name(version), "GET");
+ requestHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+ requestHeaders.put(scheme);
+ requestHeaders.put(host);
+ requestHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath);
+ String referrer = scheme.value() + "://" + host.value() + uri.value();
+ requestHeaders.put("referer", referrer);
+ // Remember support for gzip encoding
+ requestHeaders.put(headers.get("accept-encoding"));
+ requestHeaders.put("x-spdy-push", "true");
+ return requestHeaders;
+ }
+
+ private Headers createPushHeaders(Headers.Header scheme, Headers.Header host, String pushResourcePath)
+ {
+ final Headers pushHeaders = new Headers();
+ if (version == SPDY.V2)
+ pushHeaders.put(HTTPSPDYHeader.URI.name(version), scheme.value() + "://" + host.value() + pushResourcePath);
+ else
+ {
+ pushHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath);
+ pushHeaders.put(scheme);
+ pushHeaders.put(host);
+ }
+ pushHeaders.put(HTTPSPDYHeader.STATUS.name(version), "200");
+ pushHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+ return pushHeaders;
}
private Buffer consumeContent(long maxIdleTime) throws IOException, InterruptedException
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngine.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngine.java
index 1013430f17..14d053a394 100644
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngine.java
+++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngine.java
@@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.eclipse.jetty.spdy.api.Headers;
+import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.eclipse.jetty.util.log.Log;
@@ -71,19 +72,27 @@ public abstract class ProxyEngine extends ServerSessionFrameListener.Adapter imp
return name;
}
- protected void addRequestProxyHeaders(Headers headers)
+ protected void addRequestProxyHeaders(Stream stream, Headers headers)
{
- String newValue = "";
- Headers.Header header = headers.get("via");
- if (header != null)
- newValue = header.valuesAsString() + ", ";
- newValue += "http/1.1 " + getName();
- headers.put("via", newValue);
+ addViaHeader(headers);
}
- protected void addResponseProxyHeaders(Headers headers)
+ protected void addResponseProxyHeaders(Stream stream, Headers headers)
+ {
+ addViaHeader(headers);
+ }
+
+ private void addViaHeader(Headers headers)
+ {
+ headers.add("Via", "http/1.1 " + getName());
+ }
+
+ protected void customizeRequestHeaders(Stream stream, Headers headers)
+ {
+ }
+
+ protected void customizeResponseHeaders(Stream stream, Headers headers)
{
- // TODO: add Via header
}
public Map<String, ProxyInfo> getProxyInfos()
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/SPDYProxyEngine.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/SPDYProxyEngine.java
index 23b38b0cdb..55cce5d4d3 100644
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/SPDYProxyEngine.java
+++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/SPDYProxyEngine.java
@@ -130,8 +130,6 @@ public class SPDYProxyEngine extends ProxyEngine
return null;
}
- // TODO: give a chance to modify headers and rewrite URI
-
short serverVersion = proxyInfo.getVersion();
InetSocketAddress address = proxyInfo.getAddress();
Session serverSession = produceSession(host, serverVersion, address);
@@ -145,15 +143,13 @@ public class SPDYProxyEngine extends ProxyEngine
Set<Session> sessions = (Set<Session>)serverSession.getAttribute(CLIENT_SESSIONS_ATTRIBUTE);
sessions.add(clientSession);
+ addRequestProxyHeaders(clientStream, headers);
+ customizeRequestHeaders(clientStream, headers);
convert(clientVersion, serverVersion, headers);
- addRequestProxyHeaders(headers);
-
SynInfo serverSynInfo = new SynInfo(headers, clientSynInfo.isClose());
- logger.debug("P -> S {}", serverSynInfo);
-
StreamFrameListener listener = new ProxyStreamFrameListener(clientStream);
- StreamHandler handler = new StreamHandler(clientStream);
+ StreamHandler handler = new StreamHandler(clientStream, serverSynInfo);
clientStream.setAttribute(STREAM_HANDLER_ATTRIBUTE, handler);
serverSession.syn(serverSynInfo, listener, timeout, TimeUnit.MILLISECONDS, handler);
return this;
@@ -254,16 +250,19 @@ public class SPDYProxyEngine extends ProxyEngine
@Override
public void onReply(final Stream stream, ReplyInfo replyInfo)
{
+ logger.debug("S -> P {} on {}", replyInfo, stream);
+
short serverVersion = stream.getSession().getVersion();
Headers headers = new Headers(replyInfo.getHeaders(), false);
+
+ addResponseProxyHeaders(stream, headers);
+ customizeResponseHeaders(stream, headers);
short clientVersion = this.clientStream.getSession().getVersion();
convert(serverVersion, clientVersion, headers);
- addResponseProxyHeaders(headers);
-
this.replyInfo = new ReplyInfo(headers, replyInfo.isClose());
if (replyInfo.isClose())
- reply();
+ reply(stream);
}
@Override
@@ -276,30 +275,39 @@ public class SPDYProxyEngine extends ProxyEngine
@Override
public void onData(final Stream stream, final DataInfo dataInfo)
{
+ logger.debug("S -> P {} on {}", dataInfo, stream);
+
if (replyInfo != null)
{
if (dataInfo.isClose())
replyInfo.getHeaders().put("content-length", String.valueOf(dataInfo.available()));
- reply();
+ reply(stream);
}
- data(dataInfo);
+ data(stream, dataInfo);
}
- private void reply()
+ private void reply(final Stream stream)
{
- clientStream.reply(replyInfo, getTimeout(), TimeUnit.MILLISECONDS, new Handler.Adapter<Void>()
+ final ReplyInfo replyInfo = this.replyInfo;
+ this.replyInfo = null;
+ clientStream.reply(replyInfo, getTimeout(), TimeUnit.MILLISECONDS, new Handler<Void>()
{
@Override
+ public void completed(Void context)
+ {
+ logger.debug("P -> C {} from {} to {}", replyInfo, stream, clientStream);
+ }
+
+ @Override
public void failed(Void context, Throwable x)
{
logger.debug(x);
rst(clientStream);
}
});
- replyInfo = null;
}
- private void data(final DataInfo dataInfo)
+ private void data(final Stream stream, final DataInfo dataInfo)
{
clientStream.data(dataInfo, getTimeout(), TimeUnit.MILLISECONDS, new Handler<Void>()
{
@@ -307,6 +315,7 @@ public class SPDYProxyEngine extends ProxyEngine
public void completed(Void context)
{
dataInfo.consume(dataInfo.length());
+ logger.debug("P -> C {} from {} to {}", dataInfo, stream, clientStream);
}
@Override
@@ -331,16 +340,20 @@ public class SPDYProxyEngine extends ProxyEngine
{
private final Queue<DataInfoHandler> queue = new LinkedList<>();
private final Stream clientStream;
+ private final SynInfo serverSynInfo;
private Stream serverStream;
- private StreamHandler(Stream clientStream)
+ private StreamHandler(Stream clientStream, SynInfo serverSynInfo)
{
this.clientStream = clientStream;
+ this.serverSynInfo = serverSynInfo;
}
@Override
public void completed(Stream serverStream)
{
+ logger.debug("P -> S {} from {} to {}", serverSynInfo, clientStream, serverStream);
+
serverStream.setAttribute(CLIENT_STREAM_ATTRIBUTE, clientStream);
DataInfoHandler dataInfoHandler;
@@ -470,14 +483,15 @@ public class SPDYProxyEngine extends ProxyEngine
Headers headers = new Headers(serverSynInfo.getHeaders(), false);
+ addResponseProxyHeaders(serverStream, headers);
+ customizeResponseHeaders(serverStream, headers);
Stream clientStream = (Stream)serverStream.getAssociatedStream().getAttribute(CLIENT_STREAM_ATTRIBUTE);
convert(serverStream.getSession().getVersion(), clientStream.getSession().getVersion(), headers);
- addResponseProxyHeaders(headers);
-
- StreamHandler handler = new StreamHandler(clientStream);
+ StreamHandler handler = new StreamHandler(clientStream, serverSynInfo);
serverStream.setAttribute(STREAM_HANDLER_ATTRIBUTE, handler);
clientStream.syn(new SynInfo(headers, serverSynInfo.isClose()), getTimeout(), TimeUnit.MILLISECONDS, handler);
+
return this;
}
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 29b5952d4e..4ca4a65e6b 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
@@ -137,39 +137,21 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
for (int i = 0; i < cssResources.length; ++i)
{
String path = "/" + i + ".css";
- exchange = new TestExchange();
- exchange.setMethod("GET");
- exchange.setRequestURI(path);
- exchange.setVersion("HTTP/1.1");
- exchange.setAddress(new Address("localhost", connector.getLocalPort()));
- exchange.setRequestHeader("Host", "localhost:" + connector.getLocalPort());
- exchange.setRequestHeader("referer", referrer);
+ exchange = createExchangeWithReferrer(referrer, path);
++result;
httpClient.send(exchange);
}
for (int i = 0; i < jsResources.length; ++i)
{
String path = "/" + i + ".js";
- exchange = new TestExchange();
- exchange.setMethod("GET");
- exchange.setRequestURI(path);
- exchange.setVersion("HTTP/1.1");
- exchange.setAddress(new Address("localhost", connector.getLocalPort()));
- exchange.setRequestHeader("Host", "localhost:" + connector.getLocalPort());
- exchange.setRequestHeader("referer", referrer);
+ exchange = createExchangeWithReferrer(referrer, path);
++result;
httpClient.send(exchange);
}
for (int i = 0; i < pngResources.length; ++i)
{
String path = "/" + i + ".png";
- exchange = new TestExchange();
- exchange.setMethod("GET");
- exchange.setRequestURI(path);
- exchange.setVersion("HTTP/1.1");
- exchange.setAddress(new Address("localhost", connector.getLocalPort()));
- exchange.setRequestHeader("Host", "localhost:" + connector.getLocalPort());
- exchange.setRequestHeader("referer", referrer);
+ exchange = createExchangeWithReferrer(referrer, path);
++result;
httpClient.send(exchange);
}
@@ -180,6 +162,19 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
return result;
}
+ private ContentExchange createExchangeWithReferrer(String referrer, String path)
+ {
+ ContentExchange exchange;
+ exchange = new TestExchange();
+ exchange.setMethod("GET");
+ exchange.setRequestURI(path);
+ exchange.setVersion("HTTP/1.1");
+ exchange.setAddress(new Address("localhost", connector.getLocalPort()));
+ exchange.setRequestHeader("Host", "localhost:" + connector.getLocalPort());
+ exchange.setRequestHeader("referer", referrer);
+ return exchange;
+ }
+
private void benchmarkSPDY(PushStrategy pushStrategy, Session session) throws Exception
{
@@ -238,13 +233,7 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
String path = "/" + i + ".css";
if (pushedResources.contains(path))
continue;
- headers = new Headers();
- headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
- headers.put(HTTPSPDYHeader.URI.name(version()), path);
- headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
- headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
- headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
- headers.put("referer", referrer);
+ headers = createRequestHeaders(referrer, path);
++result;
session.syn(new SynInfo(headers, true), new DataListener());
}
@@ -253,13 +242,7 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
String path = "/" + i + ".js";
if (pushedResources.contains(path))
continue;
- headers = new Headers();
- headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
- headers.put(HTTPSPDYHeader.URI.name(version()), path);
- headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
- headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
- headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
- headers.put("referer", referrer);
+ headers = createRequestHeaders(referrer, path);
++result;
session.syn(new SynInfo(headers, true), new DataListener());
}
@@ -268,13 +251,7 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
String path = "/" + i + ".png";
if (pushedResources.contains(path))
continue;
- headers = new Headers();
- headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
- headers.put(HTTPSPDYHeader.URI.name(version()), path);
- headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
- headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
- headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
- headers.put("referer", referrer);
+ headers = createRequestHeaders(referrer, path);
++result;
session.syn(new SynInfo(headers, true), new DataListener());
}
@@ -285,6 +262,19 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
return result;
}
+ private Headers createRequestHeaders(String referrer, String path)
+ {
+ Headers headers;
+ headers = new Headers();
+ headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
+ headers.put(HTTPSPDYHeader.URI.name(version()), path);
+ headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
+ headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
+ headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
+ headers.put("referer", referrer);
+ return headers;
+ }
+
private void sleep(long delay) throws ServletException
{
try
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyUnitTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyUnitTest.java
new file mode 100644
index 0000000000..a1df6dcced
--- /dev/null
+++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyUnitTest.java
@@ -0,0 +1,106 @@
+package org.eclipse.jetty.spdy.http;
+
+import java.util.Set;
+
+import org.eclipse.jetty.spdy.api.Headers;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ReferrerPushStrategyUnitTest
+{
+ public static final short VERSION = SPDY.V3;
+ public static final String SCHEME = "http";
+ public static final String HOST = "localhost";
+ public static final String MAIN_URI = "/index.html";
+ public static final String METHOD = "GET";
+
+ // class under test
+ private ReferrerPushStrategy referrerPushStrategy;
+
+ @Mock
+ Stream stream;
+ @Mock
+ Session session;
+
+
+ @Before
+ public void setup()
+ {
+ referrerPushStrategy = new ReferrerPushStrategy();
+ }
+
+ @Test
+ public void testReferrerCallsAfterTimeoutAreNotAddedAsPushResources() throws InterruptedException
+ {
+ Headers requestHeaders = getBaseHeaders(VERSION);
+ int referrerCallTimeout = 1000;
+ referrerPushStrategy.setReferrerPushPeriod(referrerCallTimeout);
+ setMockExpectations();
+
+ String referrerUrl = fillPushStrategyCache(requestHeaders);
+ Set<String> pushResources;
+
+ // sleep to pretend that the user manually clicked on a linked resource instead the browser requesting subresources immediately
+ Thread.sleep(referrerCallTimeout + 1);
+
+ requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), "image2.jpg");
+ requestHeaders.put("referer", referrerUrl);
+ pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers());
+ assertThat("pushResources is empty", pushResources.size(), is(0));
+
+ requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), MAIN_URI);
+ pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers());
+ // as the image2.jpg request has been a link and not a subresource, we expect that pushResources.size() is still 2
+ assertThat("pushResources contains two elements image.jpg and style.css", pushResources.size(), is(2));
+ }
+
+ private Headers getBaseHeaders(short version)
+ {
+ Headers requestHeaders = new Headers();
+ requestHeaders.put(HTTPSPDYHeader.SCHEME.name(version), SCHEME);
+ requestHeaders.put(HTTPSPDYHeader.HOST.name(version), HOST);
+ requestHeaders.put(HTTPSPDYHeader.URI.name(version), MAIN_URI);
+ requestHeaders.put(HTTPSPDYHeader.METHOD.name(version), METHOD);
+ return requestHeaders;
+ }
+
+ private void setMockExpectations()
+ {
+ when(stream.getSession()).thenReturn(session);
+ when(session.getVersion()).thenReturn(VERSION);
+ }
+
+ private String fillPushStrategyCache(Headers requestHeaders)
+ {
+ Set<String> pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers());
+ assertThat("pushResources is empty", pushResources.size(), is(0));
+
+ String origin = SCHEME + "://" + HOST;
+ String referrerUrl = origin + MAIN_URI;
+
+ requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), "image.jpg");
+ requestHeaders.put("referer", referrerUrl);
+ pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers());
+ assertThat("pushResources is empty", pushResources.size(), is(0));
+
+ requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), "style.css");
+ pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers());
+ assertThat("pushResources is empty", pushResources.size(), is(0));
+
+ requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), MAIN_URI);
+ pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers());
+ assertThat("pushResources contains two elements image.jpg and style.css", pushResources.size(), is(2));
+ return referrerUrl;
+ }
+}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV2Test.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV2Test.java
index ce88e712c7..ab24521bea 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
@@ -7,7 +7,7 @@
*
* http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
+ * Unless required by ap‰plicable 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
@@ -32,6 +32,7 @@ import org.eclipse.jetty.spdy.SPDYServerConnector;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.Headers;
import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.SessionFrameListener;
import org.eclipse.jetty.spdy.api.Stream;
@@ -42,6 +43,10 @@ import org.junit.Test;
public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
{
+
+ private final String mainResource = "/index.html";
+ private final String cssResource = "/style.css";
+
@Override
protected SPDYServerConnector newHTTPSPDYServerConnector(short version)
{
@@ -52,9 +57,70 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
}
@Test
+ public void testPushHeadersAreValid() throws Exception
+ {
+ InetSocketAddress address = createServer();
+
+ ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy();
+ int referrerPushPeriod = 1000;
+ pushStrategy.setReferrerPushPeriod(referrerPushPeriod);
+ AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy);
+ connector.setDefaultAsyncConnectionFactory(defaultFactory);
+
+ Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
+ Session session1 = sendMainRequestAndCSSRequest(address, mainRequestHeaders);
+
+ // Sleep for pushPeriod This should prevent application.js from being mapped as pushResource
+ Thread.sleep(referrerPushPeriod + 1);
+
+ sendJSRequest(session1);
+
+ run2ndClientRequests(address, mainRequestHeaders, true);
+ }
+
+ @Test
+ public void testReferrerPushPeriod() throws Exception
+ {
+ InetSocketAddress address = createServer();
+
+ ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy();
+ int referrerPushPeriod = 1000;
+ pushStrategy.setReferrerPushPeriod(referrerPushPeriod);
+ AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy);
+ connector.setDefaultAsyncConnectionFactory(defaultFactory);
+
+ Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
+ Session session1 = sendMainRequestAndCSSRequest(address, mainRequestHeaders);
+
+ // Sleep for pushPeriod This should prevent application.js from being mapped as pushResource
+ Thread.sleep(referrerPushPeriod+1);
+
+ sendJSRequest(session1);
+
+ run2ndClientRequests(address, mainRequestHeaders, false);
+ }
+
+ @Test
public void testMaxAssociatedResources() throws Exception
{
- InetSocketAddress address = startHTTPServer(version(), new AbstractHandler()
+ InetSocketAddress address = createServer();
+
+ ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy();
+ pushStrategy.setMaxAssociatedResources(1);
+ AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy);
+ connector.setDefaultAsyncConnectionFactory(defaultFactory);
+
+ Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
+ Session session1 = sendMainRequestAndCSSRequest(address, mainRequestHeaders);
+
+ sendJSRequest(session1);
+
+ run2ndClientRequests(address, mainRequestHeaders, false);
+ }
+
+ private InetSocketAddress createServer() throws Exception
+ {
+ return startHTTPServer(version(), new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
@@ -70,21 +136,13 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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);
+ }
+ private Session sendMainRequestAndCSSRequest(InetSocketAddress address, Headers mainRequestHeaders) throws Exception
+ {
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
@@ -98,13 +156,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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);
+ Headers associatedRequestHeaders1 = createHeaders(cssResource);
session1.syn(new SynInfo(associatedRequestHeaders1, true), new StreamFrameListener.Adapter()
{
@Override
@@ -116,15 +168,15 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
}
});
Assert.assertTrue(associatedResourceLatch1.await(5, TimeUnit.SECONDS));
+ return session1;
+ }
+
+ private void sendJSRequest(Session session1) throws InterruptedException
+ {
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);
+ String jsResource = "/application.js";
+ Headers associatedRequestHeaders2 = createHeaders(jsResource);
session1.syn(new SynInfo(associatedRequestHeaders2, true), new StreamFrameListener.Adapter()
{
@Override
@@ -136,17 +188,24 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
}
});
Assert.assertTrue(associatedResourceLatch2.await(5, TimeUnit.SECONDS));
+ }
+ private void run2ndClientRequests(InetSocketAddress address, Headers mainRequestHeaders, final boolean validateHeaders) throws Exception
+ {
// Create another client, and perform the same request for the main resource,
// we expect the css being pushed, but not the js
final CountDownLatch mainStreamLatch = new CountDownLatch(2);
final CountDownLatch pushDataLatch = new CountDownLatch(1);
+ final CountDownLatch pushSynHeadersValid = new CountDownLatch(1);
Session session2 = startClient(version(), address, new SessionFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
{
+ if(validateHeaders)
+ validateHeaders(synInfo.getHeaders(), pushSynHeadersValid);
+
Assert.assertTrue(stream.isUnidirectional());
Assert.assertTrue(synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version())).value().endsWith(".css"));
return new StreamFrameListener.Adapter()
@@ -180,8 +239,10 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
}
});
- Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
- Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS));
+ Assert.assertTrue("Main request reply and/or data not received", mainStreamLatch.await(5, TimeUnit.SECONDS));
+ Assert.assertTrue("Pushed data not received", pushDataLatch.await(5, TimeUnit.SECONDS));
+ if(validateHeaders)
+ Assert.assertTrue("Push syn headers not valid", pushSynHeadersValid.await(5, TimeUnit.SECONDS));
}
@Test
@@ -204,13 +265,8 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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());
+ Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
+
session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
@@ -224,13 +280,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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);
+ Headers associatedRequestHeaders = createHeaders(cssResource);
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
@@ -290,6 +340,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
@Test
public void testAssociatedResourceWithWrongContentTypeIsNotPushed() throws Exception
{
+ final String fakeResource = "/fake.png";
InetSocketAddress address = startHTTPServer(version(), new AbstractHandler()
{
@Override
@@ -302,7 +353,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
response.setContentType("text/html");
output.print("<html><head/><body>HELLO</body></html>");
}
- else if (url.equals("/fake.png"))
+ else if (url.equals(fakeResource))
{
response.setContentType("text/html");
output.print("<html><head/><body>IMAGE</body></html>");
@@ -318,13 +369,8 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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());
+ Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
+
session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
@@ -338,13 +384,8 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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);
+ String cssResource = "/stylesheet.css";
+ Headers associatedRequestHeaders = createHeaders(cssResource);
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
@@ -358,13 +399,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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);
+ Headers fakeAssociatedRequestHeaders = createHeaders(fakeResource);
session1.syn(new SynInfo(fakeAssociatedRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
@@ -445,13 +480,8 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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());
+ Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
+
session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
@@ -465,14 +495,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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);
+ Headers associatedRequestHeaders = createHeaders(cssResource);
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
@@ -486,13 +509,9 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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);
+ String imageUrl = "/image.gif";
+ Headers nestedRequestHeaders = createHeaders(imageUrl, cssResource);
+
session1.syn(new SynInfo(nestedRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
@@ -567,13 +586,8 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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());
+ Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
+
session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
@@ -587,13 +601,9 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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);
+ String associatedResource = "/home.html";
+ Headers associatedRequestHeaders = createHeaders(associatedResource);
+
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
@@ -661,13 +671,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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());
+ Headers mainRequestHeaders = createHeaders(mainResource);
mainRequestHeaders.put("If-Modified-Since", "Tue, 27 Mar 2012 16:36:52 GMT");
session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@@ -682,13 +686,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
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);
+ Headers associatedRequestHeaders = createHeaders(cssResource);
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
@@ -745,4 +743,57 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
Assert.assertFalse("We don't expect data to be pushed as the main request contained an if-modified-since header",pushDataLatch.await(1, TimeUnit.SECONDS));
}
+
+ private void validateHeaders(Headers headers, CountDownLatch pushSynHeadersValid)
+ {
+ if (validateHeader(headers, HTTPSPDYHeader.STATUS.name(version()), "200")
+ && validateHeader(headers, HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1")
+ && validateUriHeader(headers))
+ pushSynHeadersValid.countDown();
+ }
+
+ private boolean validateHeader(Headers headers, String name, String expectedValue)
+ {
+ Headers.Header header = headers.get(name);
+ if (header != null && expectedValue.equals(header.value()))
+ return true;
+ System.out.println(name + " not valid! " + headers);
+ return false;
+ }
+
+ private boolean validateUriHeader(Headers headers)
+ {
+ Headers.Header uriHeader = headers.get(HTTPSPDYHeader.URI.name(version()));
+ if (uriHeader != null)
+ if (version() == SPDY.V2 && uriHeader.value().startsWith("http://"))
+ return true;
+ else if (version() == SPDY.V3 && uriHeader.value().startsWith("/")
+ && headers.get(HTTPSPDYHeader.HOST.name(version())) != null && headers.get(HTTPSPDYHeader.SCHEME.name(version())) != null)
+ return true;
+ System.out.println(HTTPSPDYHeader.URI.name(version()) + " not valid!");
+ return false;
+ }
+
+ private Headers createHeaders(String resource)
+ {
+ return createHeaders(resource, mainResource);
+ }
+
+ private Headers createHeaders(String resource, String referrer)
+ {
+ Headers associatedRequestHeaders = createHeadersWithoutReferrer(resource);
+ associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + referrer);
+ return associatedRequestHeaders;
+ }
+
+ private Headers createHeadersWithoutReferrer(String resource)
+ {
+ Headers associatedRequestHeaders = new Headers();
+ associatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
+ associatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), resource);
+ associatedRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
+ associatedRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
+ associatedRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
+ return associatedRequestHeaders;
+ }
}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/SSLExternalServerTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/SSLExternalServerTest.java
new file mode 100644
index 0000000000..d27bf4845e
--- /dev/null
+++ b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/SSLExternalServerTest.java
@@ -0,0 +1,81 @@
+package org.eclipse.jetty.spdy.http;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.spdy.SPDYClient;
+import org.eclipse.jetty.spdy.api.Headers;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+
+public class SSLExternalServerTest extends AbstractHTTPSPDYTest
+{
+ @Override
+ protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
+ {
+ SslContextFactory sslContextFactory = new SslContextFactory();
+ // Force TLSv1
+ sslContextFactory.setIncludeProtocols("TLSv1");
+ return new SPDYClient.Factory(threadPool, sslContextFactory);
+ }
+
+ @Test
+ public void testExternalServer() throws Exception
+ {
+ String host = "encrypted.google.com";
+ int port = 443;
+ InetSocketAddress address = new InetSocketAddress(host, port);
+
+ try
+ {
+ // Test whether there is connectivity to avoid fail the test when offline
+ Socket socket = new Socket();
+ socket.connect(address, 5000);
+ socket.close();
+ }
+ catch (IOException x)
+ {
+ Assume.assumeNoException(x);
+ }
+
+ final short version = SPDY.V2;
+ Session session = startClient(version, address, null);
+ Headers headers = new Headers();
+ headers.put(HTTPSPDYHeader.SCHEME.name(version), "https");
+ headers.put(HTTPSPDYHeader.HOST.name(version), host + ":" + port);
+ headers.put(HTTPSPDYHeader.METHOD.name(version), "GET");
+ headers.put(HTTPSPDYHeader.URI.name(version), "/");
+ headers.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+ final CountDownLatch latch = new CountDownLatch(1);
+ session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+ {
+ @Override
+ public void onReply(Stream stream, ReplyInfo replyInfo)
+ {
+ Headers headers = replyInfo.getHeaders();
+ Headers.Header versionHeader = headers.get(HTTPSPDYHeader.STATUS.name(version));
+ if (versionHeader != null)
+ {
+ Matcher matcher = Pattern.compile("(\\d{3}).*").matcher(versionHeader.value());
+ if (matcher.matches() && Integer.parseInt(matcher.group(1)) < 400)
+ latch.countDown();
+ }
+ }
+ });
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+ }
+}
diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYAsyncConnection.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYAsyncConnection.java
index e6df8fd3fd..3712138a06 100644
--- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYAsyncConnection.java
+++ b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYAsyncConnection.java
@@ -236,4 +236,9 @@ public class SPDYAsyncConnection extends AbstractConnection implements AsyncConn
{
this.session = session;
}
+
+ public String toString()
+ {
+ return String.format("%s@%x{endp=%s@%x}",getClass().getSimpleName(),hashCode(),getEndPoint().getClass().getSimpleName(),getEndPoint().hashCode());
+ }
}
diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYServerConnector.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYServerConnector.java
index 3226ccadea..31a29ca0d0 100644
--- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYServerConnector.java
+++ b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYServerConnector.java
@@ -16,6 +16,7 @@
package org.eclipse.jetty.spdy;
+import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
@@ -41,6 +42,7 @@ import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -312,4 +314,14 @@ public class SPDYServerConnector extends SelectChannelConnector
threadPool.dispatch(command);
}
}
+
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ super.dump(out,indent);
+ AggregateLifeCycle.dump(out, indent, new ArrayList<Session>(sessions));
+ }
+
+
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java
index 917d9ddfc7..4a4f2f6b58 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java
@@ -374,10 +374,10 @@ public class AggregateLifeCycle extends AbstractLifeCycle implements Destroyable
for (Bean b : _beans)
{
i++;
-
+
+ out.append(indent).append(" +- ");
if (b._managed)
{
- out.append(indent).append(" +- ");
if (b._bean instanceof Dumpable)
((Dumpable)b._bean).dump(out,indent+(i==size?" ":" | "));
else
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketHandler.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketHandler.java
index d90780f5c3..5e4bc38582 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketHandler.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketHandler.java
@@ -51,7 +51,10 @@ public abstract class WebSocketHandler extends HandlerWrapper implements WebSock
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (_webSocketFactory.acceptWebSocket(request,response) || response.isCommitted())
+ {
+ baseRequest.setHandled(true);
return;
+ }
super.handle(target,baseRequest,request,response);
}

Back to the top