Merge branch 'master' into jetty-8
This commit is contained in:
commit
0fa17c13b1
|
@ -1,4 +1,16 @@
|
||||||
package org.eclipse.jetty.io;
|
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.DirectNIOBuffer;
|
||||||
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
|
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
|
||||||
|
|
|
@ -1,4 +1,16 @@
|
||||||
package org.eclipse.jetty.io;
|
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;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
|
@ -334,8 +334,9 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
|
||||||
{
|
{
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
if (_dispatched)
|
|
||||||
_writable=false;
|
_writable=false;
|
||||||
|
if (!_dispatched)
|
||||||
|
updateKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (l>0)
|
else if (l>0)
|
||||||
|
@ -359,8 +360,9 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
|
||||||
{
|
{
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
if (_dispatched)
|
|
||||||
_writable=false;
|
_writable=false;
|
||||||
|
if (!_dispatched)
|
||||||
|
updateKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (l>0)
|
else if (l>0)
|
||||||
|
|
|
@ -354,7 +354,7 @@ public class Server extends HandlerWrapper implements Attributes
|
||||||
{
|
{
|
||||||
LOG.debug("REQUEST "+target+" on "+connection);
|
LOG.debug("REQUEST "+target+" on "+connection);
|
||||||
handle(target, request, request, response);
|
handle(target, request, request, response);
|
||||||
LOG.debug("RESPONSE "+target+" "+connection.getResponse().getStatus());
|
LOG.debug("RESPONSE "+target+" "+connection.getResponse().getStatus()+" handled="+request.isHandled());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
handle(target, request, request, response);
|
handle(target, request, request, response);
|
||||||
|
|
|
@ -233,21 +233,21 @@ public class ConnectHandler extends HandlerWrapper
|
||||||
}
|
}
|
||||||
catch (SocketException se)
|
catch (SocketException se)
|
||||||
{
|
{
|
||||||
LOG.info("ConnectHandler: " + se.getMessage());
|
LOG.info("ConnectHandler: SocketException " + se.getMessage());
|
||||||
response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
|
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||||
baseRequest.setHandled(true);
|
baseRequest.setHandled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch (SocketTimeoutException ste)
|
catch (SocketTimeoutException ste)
|
||||||
{
|
{
|
||||||
LOG.info("ConnectHandler: " + ste.getMessage());
|
LOG.info("ConnectHandler: SocketTimeoutException" + ste.getMessage());
|
||||||
response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
|
response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
|
||||||
baseRequest.setHandled(true);
|
baseRequest.setHandled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch (IOException ioe)
|
catch (IOException ioe)
|
||||||
{
|
{
|
||||||
LOG.info("ConnectHandler: " + ioe.getMessage());
|
LOG.info("ConnectHandler: IOException" + ioe.getMessage());
|
||||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||||
baseRequest.setHandled(true);
|
baseRequest.setHandled(true);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -483,13 +483,20 @@ public class ProxyServlet implements Servlet
|
||||||
@Override
|
@Override
|
||||||
protected void onResponseHeader(Buffer name, Buffer value) throws IOException
|
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 (!_DontProxyHeaders.contains(s) || (HttpHeaders.CONNECTION_BUFFER.equals(name) && HttpHeaderValues.CLOSE_BUFFER.equals(value)))
|
||||||
{
|
{
|
||||||
if (debug != 0)
|
if (debug != 0)
|
||||||
_log.debug(debug + " " + name + ": " + value);
|
_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)
|
else if (debug != 0)
|
||||||
_log.debug(debug + " " + name + "! " + value);
|
_log.debug(debug + " " + name + "! " + value);
|
||||||
|
@ -785,6 +792,20 @@ 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.
|
* Transparent Proxy.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,9 +16,11 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.spdy;
|
package org.eclipse.jetty.spdy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.InterruptedByTimeoutException;
|
import java.nio.channels.InterruptedByTimeoutException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
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.generator.Generator;
|
||||||
import org.eclipse.jetty.spdy.parser.Parser;
|
import org.eclipse.jetty.spdy.parser.Parser;
|
||||||
import org.eclipse.jetty.util.Atomics;
|
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.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
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 Logger logger = Log.getLogger(Session.class);
|
||||||
private static final ThreadLocal<Integer> handlerInvocations = new ThreadLocal<Integer>()
|
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);
|
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 interface FrameBytes extends Comparable<FrameBytes>
|
||||||
{
|
{
|
||||||
public IStream getStream();
|
public IStream getStream();
|
||||||
|
|
|
@ -440,7 +440,7 @@ public class StandardStream implements IStream
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
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()
|
private boolean canSend()
|
||||||
|
|
|
@ -11,11 +11,45 @@
|
||||||
<Set name="protocol">TLSv1</Set>
|
<Set name="protocol">TLSv1</Set>
|
||||||
</New>
|
</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>-->
|
<!--<Set class="org.eclipse.jetty.npn.NextProtoNego" name="debug" type="boolean">true</Set>-->
|
||||||
|
|
||||||
<Call name="addConnector">
|
<Call name="addConnector">
|
||||||
<Arg>
|
<Arg>
|
||||||
<New class="org.eclipse.jetty.spdy.http.HTTPSPDYServerConnector">
|
<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>
|
<Set name="Port">8080</Set>
|
||||||
</New>
|
</New>
|
||||||
</Arg>
|
</Arg>
|
||||||
|
@ -26,6 +60,24 @@
|
||||||
<Arg>
|
<Arg>
|
||||||
<Ref id="sslContextFactory" />
|
<Ref id="sslContextFactory" />
|
||||||
</Arg>
|
</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>
|
<Set name="Port">8443</Set>
|
||||||
</New>
|
</New>
|
||||||
</Arg>
|
</Arg>
|
||||||
|
|
|
@ -72,6 +72,11 @@
|
||||||
<version>${slf4j-version}</version>
|
<version>${slf4j-version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.spdy.http;
|
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.spdy.api.SPDY;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
|
@ -23,32 +26,41 @@ public class HTTPSPDYServerConnector extends AbstractHTTPSPDYServerConnector
|
||||||
{
|
{
|
||||||
public HTTPSPDYServerConnector()
|
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)
|
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
|
// We pass a null ServerSessionFrameListener because for
|
||||||
// HTTP over SPDY we need one that references the endPoint
|
// HTTP over SPDY we need one that references the endPoint
|
||||||
super(null, sslContextFactory);
|
super(null, sslContextFactory);
|
||||||
clearAsyncConnectionFactories();
|
clearAsyncConnectionFactories();
|
||||||
// The "spdy/3" protocol handles HTTP over SPDY
|
// 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
|
// 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
|
// The "http/1.1" protocol handles browsers that support NPN but not SPDY
|
||||||
putAsyncConnectionFactory("http/1.1", new ServerHTTPAsyncConnectionFactory(this));
|
putAsyncConnectionFactory("http/1.1", new ServerHTTPAsyncConnectionFactory(this));
|
||||||
// The default connection factory handles plain HTTP on non-SSL or non-NPN connections
|
// The default connection factory handles plain HTTP on non-SSL or non-NPN connections
|
||||||
setDefaultAsyncConnectionFactory(getAsyncConnectionFactory("http/1.1"));
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.eclipse.jetty.spdy.api.Headers;
|
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
|
* 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>
|
* 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>
|
* <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
|
* HTTP header that points to <tt>index.html</tt>; therefore a proper value for {@link #getReferrerPushPeriod()}
|
||||||
* resources must be distinguishable.</p>
|
* has to be set. If the referrerPushPeriod for a main resource has been passed, no more
|
||||||
* <p>This class distinguishes associated resources by their URL path suffix and content
|
* 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.
|
* type.
|
||||||
* CSS stylesheets, images and JavaScript files have recognizable URL path suffixes that
|
* 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
|
* <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>
|
* 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
|
* <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
|
public class ReferrerPushStrategy implements PushStrategy
|
||||||
{
|
{
|
||||||
private static final Logger logger = Log.getLogger(ReferrerPushStrategy.class);
|
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<Pattern> pushRegexps = new HashSet<>();
|
||||||
private final Set<String> pushContentTypes = new HashSet<>();
|
private final Set<String> pushContentTypes = new HashSet<>();
|
||||||
private final Set<Pattern> allowedPushOrigins = new HashSet<>();
|
private final Set<Pattern> allowedPushOrigins = new HashSet<>();
|
||||||
private volatile int maxAssociatedResources = 32;
|
private volatile int maxAssociatedResources = 32;
|
||||||
|
private volatile int referrerPushPeriod = 5000;
|
||||||
|
|
||||||
public ReferrerPushStrategy()
|
public ReferrerPushStrategy()
|
||||||
{
|
{
|
||||||
|
@ -101,22 +105,33 @@ public class ReferrerPushStrategy implements PushStrategy
|
||||||
this.maxAssociatedResources = maxAssociatedResources;
|
this.maxAssociatedResources = maxAssociatedResources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getReferrerPushPeriod()
|
||||||
|
{
|
||||||
|
return referrerPushPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReferrerPushPeriod(int referrerPushPeriod)
|
||||||
|
{
|
||||||
|
this.referrerPushPeriod = referrerPushPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> apply(Stream stream, Headers requestHeaders, Headers responseHeaders)
|
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();
|
short version = stream.getSession().getVersion();
|
||||||
|
if (!isIfModifiedSinceHeaderPresent(requestHeaders) && isValidMethod(requestHeaders.get(HTTPSPDYHeader.METHOD.name(version)).value()))
|
||||||
|
{
|
||||||
String scheme = requestHeaders.get(HTTPSPDYHeader.SCHEME.name(version)).value();
|
String scheme = requestHeaders.get(HTTPSPDYHeader.SCHEME.name(version)).value();
|
||||||
String host = requestHeaders.get(HTTPSPDYHeader.HOST.name(version)).value();
|
String host = requestHeaders.get(HTTPSPDYHeader.HOST.name(version)).value();
|
||||||
String origin = new StringBuilder(scheme).append("://").append(host).toString();
|
String origin = scheme + "://" + host;
|
||||||
String url = requestHeaders.get(HTTPSPDYHeader.URI.name(version)).value();
|
String url = requestHeaders.get(HTTPSPDYHeader.URI.name(version)).value();
|
||||||
String absoluteURL = new StringBuilder(origin).append(url).toString();
|
String absoluteURL = origin + url;
|
||||||
logger.debug("Applying push strategy for {}", absoluteURL);
|
logger.debug("Applying push strategy for {}", absoluteURL);
|
||||||
if (isValidMethod(requestHeaders.get(HTTPSPDYHeader.METHOD.name(version)).value()))
|
|
||||||
{
|
|
||||||
if (isMainResource(url, responseHeaders))
|
if (isMainResource(url, responseHeaders))
|
||||||
{
|
{
|
||||||
result = pushResources(absoluteURL);
|
MainResource mainResource = getOrCreateMainResource(absoluteURL);
|
||||||
|
result = mainResource.getResources();
|
||||||
}
|
}
|
||||||
else if (isPushResource(url, responseHeaders))
|
else if (isPushResource(url, responseHeaders))
|
||||||
{
|
{
|
||||||
|
@ -124,18 +139,49 @@ public class ReferrerPushStrategy implements PushStrategy
|
||||||
if (referrerHeader != null)
|
if (referrerHeader != null)
|
||||||
{
|
{
|
||||||
String referrer = referrerHeader.value();
|
String referrer = referrerHeader.value();
|
||||||
Set<String> pushResources = resources.get(referrer);
|
MainResource mainResource = mainResources.get(referrer);
|
||||||
if (pushResources == null || !pushResources.contains(url))
|
if (mainResource == null)
|
||||||
buildMetadata(origin, url, referrer);
|
mainResource = getOrCreateMainResource(referrer);
|
||||||
|
|
||||||
|
Set<String> pushResources = mainResource.getResources();
|
||||||
|
if (!pushResources.contains(url))
|
||||||
|
mainResource.addResource(url, origin, referrer);
|
||||||
else
|
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;
|
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)
|
private boolean isValidMethod(String method)
|
||||||
{
|
{
|
||||||
return "GET".equalsIgnoreCase(method);
|
return "GET".equalsIgnoreCase(method);
|
||||||
|
@ -165,40 +211,61 @@ public class ReferrerPushStrategy implements PushStrategy
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<String> pushResources(String absoluteURL)
|
private class MainResource
|
||||||
{
|
{
|
||||||
Set<String> pushResources = resources.get(absoluteURL);
|
private final String name;
|
||||||
if (pushResources == null)
|
private final Set<String> resources = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
|
||||||
return Collections.emptySet();
|
private final AtomicLong firstResourceAdded = new AtomicLong(-1);
|
||||||
return Collections.unmodifiableSet(pushResources);
|
|
||||||
|
private MainResource(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildMetadata(String origin, String url, String referrer)
|
public boolean addResource(String url, String origin, String referrer)
|
||||||
{
|
{
|
||||||
if (referrer.startsWith(origin) || isPushOriginAllowed(origin))
|
// 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))
|
||||||
{
|
{
|
||||||
Set<String> pushResources = resources.get(referrer);
|
logger.debug("Skipped store of push metadata {} for {}: Origin: {} doesn't match or origin not allowed",
|
||||||
if (pushResources == null)
|
url, name, origin);
|
||||||
{
|
return false;
|
||||||
pushResources = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
|
|
||||||
Set<String> existing = resources.putIfAbsent(referrer, pushResources);
|
|
||||||
if (existing != null)
|
|
||||||
pushResources = existing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This check is not strictly concurrent-safe, but limiting
|
// This check is not strictly concurrent-safe, but limiting
|
||||||
// the number of associated resources is achieved anyway
|
// the number of associated resources is achieved anyway
|
||||||
// although in rare cases few more resources will be stored
|
// 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);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
logger.debug("Skipped store of push metadata {} for {}: max associated resources ({}) reached",
|
logger.debug("Skipped store of push metadata {} for {}: max associated resources ({}) reached",
|
||||||
url, referrer, maxAssociatedResources);
|
url, name, maxAssociatedResources);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
if (delay > referrerPushPeriod)
|
||||||
|
{
|
||||||
|
logger.debug("Delay: {}ms longer than referrerPushPeriod: {}ms. Not adding resource: {} for: {}", delay, referrerPushPeriod, url, name);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug("Adding resource: {} for: {} with delay: {}ms.", url, name, delay);
|
||||||
|
resources.add(url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getResources()
|
||||||
|
{
|
||||||
|
return Collections.unmodifiableSet(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "MainResource: " + name + " associated resources:" + resources.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPushOriginAllowed(String origin)
|
private boolean isPushOriginAllowed(String origin)
|
||||||
|
@ -211,3 +278,4 @@ public class ReferrerPushStrategy implements PushStrategy
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ import org.eclipse.jetty.spdy.api.Handler;
|
||||||
import org.eclipse.jetty.spdy.api.Headers;
|
import org.eclipse.jetty.spdy.api.Headers;
|
||||||
import org.eclipse.jetty.spdy.api.ReplyInfo;
|
import org.eclipse.jetty.spdy.api.ReplyInfo;
|
||||||
import org.eclipse.jetty.spdy.api.RstInfo;
|
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.Stream;
|
||||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||||
|
@ -177,6 +178,10 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
|
||||||
logger.debug("HTTP > {} {} {}", m, u, v);
|
logger.debug("HTTP > {} {} {}", m, u, v);
|
||||||
startRequest(new ByteArrayBuffer(m), new ByteArrayBuffer(u), new ByteArrayBuffer(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);
|
updateState(State.HEADERS);
|
||||||
handle();
|
handle();
|
||||||
break;
|
break;
|
||||||
|
@ -403,7 +408,7 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
|
||||||
if (!stream.isUnidirectional())
|
if (!stream.isUnidirectional())
|
||||||
stream.reply(replyInfo);
|
stream.reply(replyInfo);
|
||||||
if (replyInfo.getHeaders().get(HTTPSPDYHeader.STATUS.name(version)).value().startsWith("200") &&
|
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
|
// 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 host = headers.get(HTTPSPDYHeader.HOST.name(version));
|
||||||
Headers.Header uri = headers.get(HTTPSPDYHeader.URI.name(version));
|
Headers.Header uri = headers.get(HTTPSPDYHeader.URI.name(version));
|
||||||
Set<String> pushResources = pushStrategy.apply(stream, headers, replyInfo.getHeaders());
|
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();
|
final Headers requestHeaders = createRequestHeaders(scheme, host, uri, pushResourcePath);
|
||||||
pushHeaders.put(HTTPSPDYHeader.METHOD.name(version), "GET");
|
final Headers pushHeaders = createPushHeaders(scheme, host, pushResourcePath);
|
||||||
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"));
|
|
||||||
stream.syn(new SynInfo(pushHeaders, false), getMaxIdleTime(), TimeUnit.MILLISECONDS, new Handler.Adapter<Stream>()
|
stream.syn(new SynInfo(pushHeaders, false), getMaxIdleTime(), TimeUnit.MILLISECONDS, new Handler.Adapter<Stream>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -431,16 +429,43 @@ public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implem
|
||||||
{
|
{
|
||||||
ServerHTTPSPDYAsyncConnection pushConnection =
|
ServerHTTPSPDYAsyncConnection pushConnection =
|
||||||
new ServerHTTPSPDYAsyncConnection(getConnector(), getEndPoint(), getServer(), version, connection, pushStrategy, pushStream);
|
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
|
private Buffer consumeContent(long maxIdleTime) throws IOException, InterruptedException
|
||||||
|
|
|
@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
import org.eclipse.jetty.spdy.api.Headers;
|
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.StreamFrameListener;
|
||||||
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
|
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
@ -71,19 +72,27 @@ public abstract class ProxyEngine extends ServerSessionFrameListener.Adapter imp
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addRequestProxyHeaders(Headers headers)
|
protected void addRequestProxyHeaders(Stream stream, Headers headers)
|
||||||
{
|
{
|
||||||
String newValue = "";
|
addViaHeader(headers);
|
||||||
Headers.Header header = headers.get("via");
|
|
||||||
if (header != null)
|
|
||||||
newValue = header.valuesAsString() + ", ";
|
|
||||||
newValue += "http/1.1 " + getName();
|
|
||||||
headers.put("via", newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
public Map<String, ProxyInfo> getProxyInfos()
|
||||||
|
|
|
@ -130,8 +130,6 @@ public class SPDYProxyEngine extends ProxyEngine
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: give a chance to modify headers and rewrite URI
|
|
||||||
|
|
||||||
short serverVersion = proxyInfo.getVersion();
|
short serverVersion = proxyInfo.getVersion();
|
||||||
InetSocketAddress address = proxyInfo.getAddress();
|
InetSocketAddress address = proxyInfo.getAddress();
|
||||||
Session serverSession = produceSession(host, serverVersion, address);
|
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);
|
Set<Session> sessions = (Set<Session>)serverSession.getAttribute(CLIENT_SESSIONS_ATTRIBUTE);
|
||||||
sessions.add(clientSession);
|
sessions.add(clientSession);
|
||||||
|
|
||||||
|
addRequestProxyHeaders(clientStream, headers);
|
||||||
|
customizeRequestHeaders(clientStream, headers);
|
||||||
convert(clientVersion, serverVersion, headers);
|
convert(clientVersion, serverVersion, headers);
|
||||||
|
|
||||||
addRequestProxyHeaders(headers);
|
|
||||||
|
|
||||||
SynInfo serverSynInfo = new SynInfo(headers, clientSynInfo.isClose());
|
SynInfo serverSynInfo = new SynInfo(headers, clientSynInfo.isClose());
|
||||||
logger.debug("P -> S {}", serverSynInfo);
|
|
||||||
|
|
||||||
StreamFrameListener listener = new ProxyStreamFrameListener(clientStream);
|
StreamFrameListener listener = new ProxyStreamFrameListener(clientStream);
|
||||||
StreamHandler handler = new StreamHandler(clientStream);
|
StreamHandler handler = new StreamHandler(clientStream, serverSynInfo);
|
||||||
clientStream.setAttribute(STREAM_HANDLER_ATTRIBUTE, handler);
|
clientStream.setAttribute(STREAM_HANDLER_ATTRIBUTE, handler);
|
||||||
serverSession.syn(serverSynInfo, listener, timeout, TimeUnit.MILLISECONDS, handler);
|
serverSession.syn(serverSynInfo, listener, timeout, TimeUnit.MILLISECONDS, handler);
|
||||||
return this;
|
return this;
|
||||||
|
@ -254,16 +250,19 @@ public class SPDYProxyEngine extends ProxyEngine
|
||||||
@Override
|
@Override
|
||||||
public void onReply(final Stream stream, ReplyInfo replyInfo)
|
public void onReply(final Stream stream, ReplyInfo replyInfo)
|
||||||
{
|
{
|
||||||
|
logger.debug("S -> P {} on {}", replyInfo, stream);
|
||||||
|
|
||||||
short serverVersion = stream.getSession().getVersion();
|
short serverVersion = stream.getSession().getVersion();
|
||||||
Headers headers = new Headers(replyInfo.getHeaders(), false);
|
Headers headers = new Headers(replyInfo.getHeaders(), false);
|
||||||
|
|
||||||
|
addResponseProxyHeaders(stream, headers);
|
||||||
|
customizeResponseHeaders(stream, headers);
|
||||||
short clientVersion = this.clientStream.getSession().getVersion();
|
short clientVersion = this.clientStream.getSession().getVersion();
|
||||||
convert(serverVersion, clientVersion, headers);
|
convert(serverVersion, clientVersion, headers);
|
||||||
|
|
||||||
addResponseProxyHeaders(headers);
|
|
||||||
|
|
||||||
this.replyInfo = new ReplyInfo(headers, replyInfo.isClose());
|
this.replyInfo = new ReplyInfo(headers, replyInfo.isClose());
|
||||||
if (replyInfo.isClose())
|
if (replyInfo.isClose())
|
||||||
reply();
|
reply(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -276,19 +275,29 @@ public class SPDYProxyEngine extends ProxyEngine
|
||||||
@Override
|
@Override
|
||||||
public void onData(final Stream stream, final DataInfo dataInfo)
|
public void onData(final Stream stream, final DataInfo dataInfo)
|
||||||
{
|
{
|
||||||
|
logger.debug("S -> P {} on {}", dataInfo, stream);
|
||||||
|
|
||||||
if (replyInfo != null)
|
if (replyInfo != null)
|
||||||
{
|
{
|
||||||
if (dataInfo.isClose())
|
if (dataInfo.isClose())
|
||||||
replyInfo.getHeaders().put("content-length", String.valueOf(dataInfo.available()));
|
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
|
@Override
|
||||||
public void failed(Void context, Throwable x)
|
public void failed(Void context, Throwable x)
|
||||||
{
|
{
|
||||||
|
@ -296,10 +305,9 @@ public class SPDYProxyEngine extends ProxyEngine
|
||||||
rst(clientStream);
|
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>()
|
clientStream.data(dataInfo, getTimeout(), TimeUnit.MILLISECONDS, new Handler<Void>()
|
||||||
{
|
{
|
||||||
|
@ -307,6 +315,7 @@ public class SPDYProxyEngine extends ProxyEngine
|
||||||
public void completed(Void context)
|
public void completed(Void context)
|
||||||
{
|
{
|
||||||
dataInfo.consume(dataInfo.length());
|
dataInfo.consume(dataInfo.length());
|
||||||
|
logger.debug("P -> C {} from {} to {}", dataInfo, stream, clientStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -331,16 +340,20 @@ public class SPDYProxyEngine extends ProxyEngine
|
||||||
{
|
{
|
||||||
private final Queue<DataInfoHandler> queue = new LinkedList<>();
|
private final Queue<DataInfoHandler> queue = new LinkedList<>();
|
||||||
private final Stream clientStream;
|
private final Stream clientStream;
|
||||||
|
private final SynInfo serverSynInfo;
|
||||||
private Stream serverStream;
|
private Stream serverStream;
|
||||||
|
|
||||||
private StreamHandler(Stream clientStream)
|
private StreamHandler(Stream clientStream, SynInfo serverSynInfo)
|
||||||
{
|
{
|
||||||
this.clientStream = clientStream;
|
this.clientStream = clientStream;
|
||||||
|
this.serverSynInfo = serverSynInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void completed(Stream serverStream)
|
public void completed(Stream serverStream)
|
||||||
{
|
{
|
||||||
|
logger.debug("P -> S {} from {} to {}", serverSynInfo, clientStream, serverStream);
|
||||||
|
|
||||||
serverStream.setAttribute(CLIENT_STREAM_ATTRIBUTE, clientStream);
|
serverStream.setAttribute(CLIENT_STREAM_ATTRIBUTE, clientStream);
|
||||||
|
|
||||||
DataInfoHandler dataInfoHandler;
|
DataInfoHandler dataInfoHandler;
|
||||||
|
@ -470,14 +483,15 @@ public class SPDYProxyEngine extends ProxyEngine
|
||||||
|
|
||||||
Headers headers = new Headers(serverSynInfo.getHeaders(), false);
|
Headers headers = new Headers(serverSynInfo.getHeaders(), false);
|
||||||
|
|
||||||
|
addResponseProxyHeaders(serverStream, headers);
|
||||||
|
customizeResponseHeaders(serverStream, headers);
|
||||||
Stream clientStream = (Stream)serverStream.getAssociatedStream().getAttribute(CLIENT_STREAM_ATTRIBUTE);
|
Stream clientStream = (Stream)serverStream.getAssociatedStream().getAttribute(CLIENT_STREAM_ATTRIBUTE);
|
||||||
convert(serverStream.getSession().getVersion(), clientStream.getSession().getVersion(), headers);
|
convert(serverStream.getSession().getVersion(), clientStream.getSession().getVersion(), headers);
|
||||||
|
|
||||||
addResponseProxyHeaders(headers);
|
StreamHandler handler = new StreamHandler(clientStream, serverSynInfo);
|
||||||
|
|
||||||
StreamHandler handler = new StreamHandler(clientStream);
|
|
||||||
serverStream.setAttribute(STREAM_HANDLER_ATTRIBUTE, handler);
|
serverStream.setAttribute(STREAM_HANDLER_ATTRIBUTE, handler);
|
||||||
clientStream.syn(new SynInfo(headers, serverSynInfo.isClose()), getTimeout(), TimeUnit.MILLISECONDS, handler);
|
clientStream.syn(new SynInfo(headers, serverSynInfo.isClose()), getTimeout(), TimeUnit.MILLISECONDS, handler);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,39 +137,21 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
|
||||||
for (int i = 0; i < cssResources.length; ++i)
|
for (int i = 0; i < cssResources.length; ++i)
|
||||||
{
|
{
|
||||||
String path = "/" + i + ".css";
|
String path = "/" + i + ".css";
|
||||||
exchange = new TestExchange();
|
exchange = createExchangeWithReferrer(referrer, path);
|
||||||
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);
|
|
||||||
++result;
|
++result;
|
||||||
httpClient.send(exchange);
|
httpClient.send(exchange);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < jsResources.length; ++i)
|
for (int i = 0; i < jsResources.length; ++i)
|
||||||
{
|
{
|
||||||
String path = "/" + i + ".js";
|
String path = "/" + i + ".js";
|
||||||
exchange = new TestExchange();
|
exchange = createExchangeWithReferrer(referrer, path);
|
||||||
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);
|
|
||||||
++result;
|
++result;
|
||||||
httpClient.send(exchange);
|
httpClient.send(exchange);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < pngResources.length; ++i)
|
for (int i = 0; i < pngResources.length; ++i)
|
||||||
{
|
{
|
||||||
String path = "/" + i + ".png";
|
String path = "/" + i + ".png";
|
||||||
exchange = new TestExchange();
|
exchange = createExchangeWithReferrer(referrer, path);
|
||||||
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);
|
|
||||||
++result;
|
++result;
|
||||||
httpClient.send(exchange);
|
httpClient.send(exchange);
|
||||||
}
|
}
|
||||||
|
@ -180,6 +162,19 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
|
||||||
return result;
|
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
|
private void benchmarkSPDY(PushStrategy pushStrategy, Session session) throws Exception
|
||||||
{
|
{
|
||||||
|
@ -238,13 +233,7 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
|
||||||
String path = "/" + i + ".css";
|
String path = "/" + i + ".css";
|
||||||
if (pushedResources.contains(path))
|
if (pushedResources.contains(path))
|
||||||
continue;
|
continue;
|
||||||
headers = new Headers();
|
headers = createRequestHeaders(referrer, path);
|
||||||
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);
|
|
||||||
++result;
|
++result;
|
||||||
session.syn(new SynInfo(headers, true), new DataListener());
|
session.syn(new SynInfo(headers, true), new DataListener());
|
||||||
}
|
}
|
||||||
|
@ -253,13 +242,7 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
|
||||||
String path = "/" + i + ".js";
|
String path = "/" + i + ".js";
|
||||||
if (pushedResources.contains(path))
|
if (pushedResources.contains(path))
|
||||||
continue;
|
continue;
|
||||||
headers = new Headers();
|
headers = createRequestHeaders(referrer, path);
|
||||||
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);
|
|
||||||
++result;
|
++result;
|
||||||
session.syn(new SynInfo(headers, true), new DataListener());
|
session.syn(new SynInfo(headers, true), new DataListener());
|
||||||
}
|
}
|
||||||
|
@ -268,13 +251,7 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
|
||||||
String path = "/" + i + ".png";
|
String path = "/" + i + ".png";
|
||||||
if (pushedResources.contains(path))
|
if (pushedResources.contains(path))
|
||||||
continue;
|
continue;
|
||||||
headers = new Headers();
|
headers = createRequestHeaders(referrer, path);
|
||||||
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);
|
|
||||||
++result;
|
++result;
|
||||||
session.syn(new SynInfo(headers, true), new DataListener());
|
session.syn(new SynInfo(headers, true), new DataListener());
|
||||||
}
|
}
|
||||||
|
@ -285,6 +262,19 @@ public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
|
||||||
return result;
|
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
|
private void sleep(long delay) throws ServletException
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* 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,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* 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.DataInfo;
|
||||||
import org.eclipse.jetty.spdy.api.Headers;
|
import org.eclipse.jetty.spdy.api.Headers;
|
||||||
import org.eclipse.jetty.spdy.api.ReplyInfo;
|
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.Session;
|
||||||
import org.eclipse.jetty.spdy.api.SessionFrameListener;
|
import org.eclipse.jetty.spdy.api.SessionFrameListener;
|
||||||
import org.eclipse.jetty.spdy.api.Stream;
|
import org.eclipse.jetty.spdy.api.Stream;
|
||||||
|
@ -42,6 +43,10 @@ import org.junit.Test;
|
||||||
|
|
||||||
public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private final String mainResource = "/index.html";
|
||||||
|
private final String cssResource = "/style.css";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected SPDYServerConnector newHTTPSPDYServerConnector(short version)
|
protected SPDYServerConnector newHTTPSPDYServerConnector(short version)
|
||||||
{
|
{
|
||||||
|
@ -51,10 +56,71 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
return connector;
|
return connector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPushHeadersAreValid() throws Exception
|
||||||
|
{
|
||||||
|
InetSocketAddress address = createServer();
|
||||||
|
|
||||||
|
ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy();
|
||||||
|
int referrerPushPeriod = 1000;
|
||||||
|
pushStrategy.setReferrerPushPeriod(referrerPushPeriod);
|
||||||
|
AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy);
|
||||||
|
connector.setDefaultAsyncConnectionFactory(defaultFactory);
|
||||||
|
|
||||||
|
Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
|
||||||
|
Session session1 = sendMainRequestAndCSSRequest(address, mainRequestHeaders);
|
||||||
|
|
||||||
|
// Sleep for pushPeriod This should prevent application.js from being mapped as pushResource
|
||||||
|
Thread.sleep(referrerPushPeriod + 1);
|
||||||
|
|
||||||
|
sendJSRequest(session1);
|
||||||
|
|
||||||
|
run2ndClientRequests(address, mainRequestHeaders, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReferrerPushPeriod() throws Exception
|
||||||
|
{
|
||||||
|
InetSocketAddress address = createServer();
|
||||||
|
|
||||||
|
ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy();
|
||||||
|
int referrerPushPeriod = 1000;
|
||||||
|
pushStrategy.setReferrerPushPeriod(referrerPushPeriod);
|
||||||
|
AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy);
|
||||||
|
connector.setDefaultAsyncConnectionFactory(defaultFactory);
|
||||||
|
|
||||||
|
Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
|
||||||
|
Session session1 = sendMainRequestAndCSSRequest(address, mainRequestHeaders);
|
||||||
|
|
||||||
|
// Sleep for pushPeriod This should prevent application.js from being mapped as pushResource
|
||||||
|
Thread.sleep(referrerPushPeriod+1);
|
||||||
|
|
||||||
|
sendJSRequest(session1);
|
||||||
|
|
||||||
|
run2ndClientRequests(address, mainRequestHeaders, false);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMaxAssociatedResources() throws Exception
|
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
|
@Override
|
||||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
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);
|
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);
|
Session session1 = startClient(version(), address, null);
|
||||||
|
|
||||||
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
|
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()
|
session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -98,13 +156,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
final CountDownLatch associatedResourceLatch1 = new CountDownLatch(1);
|
final CountDownLatch associatedResourceLatch1 = new CountDownLatch(1);
|
||||||
Headers associatedRequestHeaders1 = new Headers();
|
Headers associatedRequestHeaders1 = createHeaders(cssResource);
|
||||||
associatedRequestHeaders1.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
|
|
||||||
associatedRequestHeaders1.put(HTTPSPDYHeader.URI.name(version()), "/style.css");
|
|
||||||
associatedRequestHeaders1.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
|
|
||||||
associatedRequestHeaders1.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
|
|
||||||
associatedRequestHeaders1.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
|
|
||||||
associatedRequestHeaders1.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource);
|
|
||||||
session1.syn(new SynInfo(associatedRequestHeaders1, true), new StreamFrameListener.Adapter()
|
session1.syn(new SynInfo(associatedRequestHeaders1, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -116,15 +168,15 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Assert.assertTrue(associatedResourceLatch1.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(associatedResourceLatch1.await(5, TimeUnit.SECONDS));
|
||||||
|
return session1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void sendJSRequest(Session session1) throws InterruptedException
|
||||||
|
{
|
||||||
final CountDownLatch associatedResourceLatch2 = new CountDownLatch(1);
|
final CountDownLatch associatedResourceLatch2 = new CountDownLatch(1);
|
||||||
Headers associatedRequestHeaders2 = new Headers();
|
String jsResource = "/application.js";
|
||||||
associatedRequestHeaders2.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
|
Headers associatedRequestHeaders2 = createHeaders(jsResource);
|
||||||
associatedRequestHeaders2.put(HTTPSPDYHeader.URI.name(version()), "/application.js");
|
|
||||||
associatedRequestHeaders2.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
|
|
||||||
associatedRequestHeaders2.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
|
|
||||||
associatedRequestHeaders2.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
|
|
||||||
associatedRequestHeaders2.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource);
|
|
||||||
session1.syn(new SynInfo(associatedRequestHeaders2, true), new StreamFrameListener.Adapter()
|
session1.syn(new SynInfo(associatedRequestHeaders2, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -136,17 +188,24 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Assert.assertTrue(associatedResourceLatch2.await(5, TimeUnit.SECONDS));
|
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,
|
// Create another client, and perform the same request for the main resource,
|
||||||
// we expect the css being pushed, but not the js
|
// we expect the css being pushed, but not the js
|
||||||
|
|
||||||
final CountDownLatch mainStreamLatch = new CountDownLatch(2);
|
final CountDownLatch mainStreamLatch = new CountDownLatch(2);
|
||||||
final CountDownLatch pushDataLatch = new CountDownLatch(1);
|
final CountDownLatch pushDataLatch = new CountDownLatch(1);
|
||||||
|
final CountDownLatch pushSynHeadersValid = new CountDownLatch(1);
|
||||||
Session session2 = startClient(version(), address, new SessionFrameListener.Adapter()
|
Session session2 = startClient(version(), address, new SessionFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||||
{
|
{
|
||||||
|
if(validateHeaders)
|
||||||
|
validateHeaders(synInfo.getHeaders(), pushSynHeadersValid);
|
||||||
|
|
||||||
Assert.assertTrue(stream.isUnidirectional());
|
Assert.assertTrue(stream.isUnidirectional());
|
||||||
Assert.assertTrue(synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version())).value().endsWith(".css"));
|
Assert.assertTrue(synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version())).value().endsWith(".css"));
|
||||||
return new StreamFrameListener.Adapter()
|
return new StreamFrameListener.Adapter()
|
||||||
|
@ -180,8 +239,10 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue("Main request reply and/or data not received", mainStreamLatch.await(5, TimeUnit.SECONDS));
|
||||||
Assert.assertTrue(pushDataLatch.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
|
@Test
|
||||||
|
@ -204,13 +265,8 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
Session session1 = startClient(version(), address, null);
|
Session session1 = startClient(version(), address, null);
|
||||||
|
|
||||||
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
|
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
|
||||||
Headers mainRequestHeaders = new Headers();
|
Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
|
||||||
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()
|
session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -224,13 +280,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
|
final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
|
||||||
Headers associatedRequestHeaders = new Headers();
|
Headers associatedRequestHeaders = createHeaders(cssResource);
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), "/style.css");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
|
|
||||||
associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource);
|
|
||||||
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
|
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -290,6 +340,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
@Test
|
@Test
|
||||||
public void testAssociatedResourceWithWrongContentTypeIsNotPushed() throws Exception
|
public void testAssociatedResourceWithWrongContentTypeIsNotPushed() throws Exception
|
||||||
{
|
{
|
||||||
|
final String fakeResource = "/fake.png";
|
||||||
InetSocketAddress address = startHTTPServer(version(), new AbstractHandler()
|
InetSocketAddress address = startHTTPServer(version(), new AbstractHandler()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -302,7 +353,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
response.setContentType("text/html");
|
response.setContentType("text/html");
|
||||||
output.print("<html><head/><body>HELLO</body></html>");
|
output.print("<html><head/><body>HELLO</body></html>");
|
||||||
}
|
}
|
||||||
else if (url.equals("/fake.png"))
|
else if (url.equals(fakeResource))
|
||||||
{
|
{
|
||||||
response.setContentType("text/html");
|
response.setContentType("text/html");
|
||||||
output.print("<html><head/><body>IMAGE</body></html>");
|
output.print("<html><head/><body>IMAGE</body></html>");
|
||||||
|
@ -318,13 +369,8 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
Session session1 = startClient(version(), address, null);
|
Session session1 = startClient(version(), address, null);
|
||||||
|
|
||||||
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
|
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
|
||||||
Headers mainRequestHeaders = new Headers();
|
Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
|
||||||
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()
|
session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -338,13 +384,8 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
|
final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
|
||||||
Headers associatedRequestHeaders = new Headers();
|
String cssResource = "/stylesheet.css";
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
|
Headers associatedRequestHeaders = createHeaders(cssResource);
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), "/stylesheet.css");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
|
|
||||||
associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource);
|
|
||||||
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
|
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -358,13 +399,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
final CountDownLatch fakeAssociatedResourceLatch = new CountDownLatch(1);
|
final CountDownLatch fakeAssociatedResourceLatch = new CountDownLatch(1);
|
||||||
Headers fakeAssociatedRequestHeaders = new Headers();
|
Headers fakeAssociatedRequestHeaders = createHeaders(fakeResource);
|
||||||
fakeAssociatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
|
|
||||||
fakeAssociatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), "/fake.png");
|
|
||||||
fakeAssociatedRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
|
|
||||||
fakeAssociatedRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
|
|
||||||
fakeAssociatedRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
|
|
||||||
fakeAssociatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource);
|
|
||||||
session1.syn(new SynInfo(fakeAssociatedRequestHeaders, true), new StreamFrameListener.Adapter()
|
session1.syn(new SynInfo(fakeAssociatedRequestHeaders, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -445,13 +480,8 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
Session session1 = startClient(version(), address, null);
|
Session session1 = startClient(version(), address, null);
|
||||||
|
|
||||||
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
|
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
|
||||||
Headers mainRequestHeaders = new Headers();
|
Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
|
||||||
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()
|
session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -465,14 +495,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
|
final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
|
||||||
Headers associatedRequestHeaders = new Headers();
|
Headers associatedRequestHeaders = createHeaders(cssResource);
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
|
|
||||||
String associatedResource = "/style.css";
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), associatedResource);
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
|
|
||||||
associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource);
|
|
||||||
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
|
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -486,13 +509,9 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
final CountDownLatch nestedResourceLatch = new CountDownLatch(1);
|
final CountDownLatch nestedResourceLatch = new CountDownLatch(1);
|
||||||
Headers nestedRequestHeaders = new Headers();
|
String imageUrl = "/image.gif";
|
||||||
nestedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
|
Headers nestedRequestHeaders = createHeaders(imageUrl, cssResource);
|
||||||
nestedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), "/image.gif");
|
|
||||||
nestedRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
|
|
||||||
nestedRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
|
|
||||||
nestedRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
|
|
||||||
nestedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + associatedResource);
|
|
||||||
session1.syn(new SynInfo(nestedRequestHeaders, true), new StreamFrameListener.Adapter()
|
session1.syn(new SynInfo(nestedRequestHeaders, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -567,13 +586,8 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
Session session1 = startClient(version(), address, null);
|
Session session1 = startClient(version(), address, null);
|
||||||
|
|
||||||
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
|
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
|
||||||
Headers mainRequestHeaders = new Headers();
|
Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
|
||||||
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()
|
session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -587,13 +601,9 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
|
final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
|
||||||
Headers associatedRequestHeaders = new Headers();
|
String associatedResource = "/home.html";
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
|
Headers associatedRequestHeaders = createHeaders(associatedResource);
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), "/home.html");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
|
|
||||||
associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource);
|
|
||||||
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
|
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -661,13 +671,7 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
Session session1 = startClient(version(), address, null);
|
Session session1 = startClient(version(), address, null);
|
||||||
|
|
||||||
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
|
final CountDownLatch mainResourceLatch = new CountDownLatch(1);
|
||||||
Headers mainRequestHeaders = new Headers();
|
Headers mainRequestHeaders = createHeaders(mainResource);
|
||||||
mainRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
|
|
||||||
String mainResource = "/index.html";
|
|
||||||
mainRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), mainResource);
|
|
||||||
mainRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
|
|
||||||
mainRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
|
|
||||||
mainRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
|
|
||||||
mainRequestHeaders.put("If-Modified-Since", "Tue, 27 Mar 2012 16:36:52 GMT");
|
mainRequestHeaders.put("If-Modified-Since", "Tue, 27 Mar 2012 16:36:52 GMT");
|
||||||
session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
|
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));
|
Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
|
final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
|
||||||
Headers associatedRequestHeaders = new Headers();
|
Headers associatedRequestHeaders = createHeaders(cssResource);
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), "/style.css");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
|
|
||||||
associatedRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
|
|
||||||
associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + mainResource);
|
|
||||||
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
|
session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -745,4 +743,57 @@ public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
|
||||||
Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
|
||||||
Assert.assertFalse("We don't expect data to be pushed as the main request contained an if-modified-since header",pushDataLatch.await(1, TimeUnit.SECONDS));
|
Assert.assertFalse("We don't expect data to be pushed as the main request contained an if-modified-since header",pushDataLatch.await(1, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateHeaders(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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -236,4 +236,9 @@ public class SPDYAsyncConnection extends AbstractConnection implements AsyncConn
|
||||||
{
|
{
|
||||||
this.session = session;
|
this.session = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return String.format("%s@%x{endp=%s@%x}",getClass().getSimpleName(),hashCode(),getEndPoint().getClass().getSimpleName(),getEndPoint().hashCode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.spdy;
|
package org.eclipse.jetty.spdy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
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.SPDY;
|
||||||
import org.eclipse.jetty.spdy.api.Session;
|
import org.eclipse.jetty.spdy.api.Session;
|
||||||
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
|
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.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
@ -312,4 +314,14 @@ public class SPDYServerConnector extends SelectChannelConnector
|
||||||
threadPool.dispatch(command);
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -375,9 +375,9 @@ public class AggregateLifeCycle extends AbstractLifeCycle implements Destroyable
|
||||||
{
|
{
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
|
out.append(indent).append(" +- ");
|
||||||
if (b._managed)
|
if (b._managed)
|
||||||
{
|
{
|
||||||
out.append(indent).append(" +- ");
|
|
||||||
if (b._bean instanceof Dumpable)
|
if (b._bean instanceof Dumpable)
|
||||||
((Dumpable)b._bean).dump(out,indent+(i==size?" ":" | "));
|
((Dumpable)b._bean).dump(out,indent+(i==size?" ":" | "));
|
||||||
else
|
else
|
||||||
|
|
|
@ -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
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
{
|
{
|
||||||
if (_webSocketFactory.acceptWebSocket(request,response) || response.isCommitted())
|
if (_webSocketFactory.acceptWebSocket(request,response) || response.isCommitted())
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
super.handle(target,baseRequest,request,response);
|
super.handle(target,baseRequest,request,response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue