Implemented ALPNServerConnection and refactored common code with NPN.

This commit is contained in:
Simone Bordet 2014-03-18 16:15:08 +01:00
parent 119718d86f
commit cf1c2451d6
13 changed files with 469 additions and 204 deletions

View File

@ -72,7 +72,7 @@ public class SpdyConnector
new HTTPSPDYServerConnectionFactory(3,https_config,new ReferrerPushStrategy());
// NPN Factory
SPDYServerConnectionFactory.checkNPNAvailable();
SPDYServerConnectionFactory.checkProtocolNegotiationAvailable();
NPNServerConnectionFactory npn =
new NPNServerConnectionFactory(spdy3.getProtocol(),spdy2.getProtocol(),http.getDefaultProtocol());
npn.setDefaultProtocol(http.getDefaultProtocol());

View File

@ -110,7 +110,7 @@ public class SpdyServer
// Spdy Connector
// Make sure that the required NPN implementations are available.
SPDYServerConnectionFactory.checkNPNAvailable();
SPDYServerConnectionFactory.checkProtocolNegotiationAvailable();
// A ReferrerPushStrategy is being initialized.
// See: http://www.eclipse.org/jetty/documentation/current/spdy-configuring-push.html for more details.

View File

@ -91,6 +91,12 @@
<version>${npn.api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.alpn</groupId>
<artifactId>alpn-api</artifactId>
<version>${alpn.api.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,76 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.
// ========================================================================
//
package org.eclipse.jetty.spdy.server;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.alpn.ALPN;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class ALPNServerConnection extends NegotiatingServerConnection implements ALPN.ServerProvider
{
private static final Logger LOG = Log.getLogger(ALPNServerConnection.class);
public ALPNServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol)
{
super(connector, endPoint, engine, protocols, defaultProtocol);
ALPN.put(engine, this);
}
@Override
public void unsupported()
{
select(Collections.<String>emptyList());
}
@Override
public String select(List<String> clientProtocols)
{
List<String> serverProtocols = getProtocols();
String negotiated = null;
for (String clientProtocol : clientProtocols)
{
if (serverProtocols.contains(clientProtocol))
{
negotiated = clientProtocol;
break;
}
}
if (negotiated == null)
{
negotiated = getDefaultProtocol();
}
LOG.debug("{} protocol selected {}", this, negotiated);
setProtocol(negotiated);
ALPN.remove(getSSLEngine());
return negotiated;
}
@Override
public void close()
{
ALPN.remove(getSSLEngine());
super.close();
}
}

View File

@ -0,0 +1,60 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.
// ========================================================================
//
package org.eclipse.jetty.spdy.server;
import java.util.List;
import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.alpn.ALPN;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class ALPNServerConnectionFactory extends NegotiatingServerConnectionFactory
{
private static final Logger LOG = Log.getLogger(ALPNServerConnectionFactory.class);
public ALPNServerConnectionFactory(@Name("protocols") String... protocols)
{
super("alpn", protocols);
try
{
ClassLoader alpnClassLoader = ALPN.class.getClassLoader();
if (alpnClassLoader != null)
{
LOG.warn("ALPN must be in the boot classloader, not in: " + alpnClassLoader);
throw new IllegalStateException("ALPN must be in the boot classloader");
}
}
catch (Throwable x)
{
LOG.warn("ALPN not available", x);
throw new IllegalStateException("ALPN not available", x);
}
}
@Override
protected AbstractConnection newServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol)
{
return new ALPNServerConnection(connector, endPoint, engine, protocols, defaultProtocol);
}
}

View File

@ -18,146 +18,49 @@
package org.eclipse.jetty.spdy.server;
import java.io.IOException;
import java.util.List;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.npn.NextProtoNego;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class NPNServerConnection extends AbstractConnection implements NextProtoNego.ServerProvider
public class NPNServerConnection extends NegotiatingServerConnection implements NextProtoNego.ServerProvider
{
private final Logger LOG = Log.getLogger(getClass());
private final Connector connector;
private final SSLEngine engine;
private final List<String> protocols;
private final String defaultProtocol;
private String nextProtocol; // No need to be volatile: it is modified and read by the same thread
private static final Logger LOG = Log.getLogger(NPNServerConnection.class);
public NPNServerConnection(EndPoint endPoint, SSLEngine engine, Connector connector, List<String> protocols, String defaultProtocol)
{
super(endPoint, connector.getExecutor());
this.connector = connector;
this.protocols = protocols;
this.defaultProtocol = defaultProtocol;
this.engine = engine;
super(connector, endPoint, engine, protocols, defaultProtocol);
NextProtoNego.put(engine, this);
}
@Override
public void onOpen()
{
super.onOpen();
fillInterested();
}
@Override
public void onFillable()
{
int filled = fill();
if (filled == 0)
{
if (nextProtocol == null)
{
if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
{
// Here the SSL handshake is finished, but while the client sent
// the NPN extension, the server application did not select the
// protocol; we need to close as the protocol cannot be negotiated.
LOG.debug("{} missing next protocol. SSLEngine: {}", this, engine);
close();
}
else
{
// Here the SSL handshake is not finished yet but we filled 0 bytes,
// so we need to read more.
fillInterested();
}
}
else
{
ConnectionFactory connectionFactory = connector.getConnectionFactory(nextProtocol);
if (connectionFactory == null)
{
LOG.debug("{} application selected protocol '{}', but no correspondent {} has been configured",
this, nextProtocol, ConnectionFactory.class.getName());
close();
}
else
{
EndPoint endPoint = getEndPoint();
Connection oldConnection = endPoint.getConnection();
Connection newConnection = connectionFactory.newConnection(connector, endPoint);
LOG.debug("{} switching from {} to {}", this, oldConnection, newConnection);
oldConnection.onClose();
endPoint.setConnection(newConnection);
getEndPoint().getConnection().onOpen();
}
}
}
else if (filled < 0)
{
// Something went bad, we need to close.
LOG.debug("{} closing on client close", this);
close();
}
else
{
// Must never happen, since we fill using an empty buffer
throw new IllegalStateException();
}
}
private int fill()
{
try
{
return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
}
catch (IOException x)
{
LOG.debug(x);
close();
return -1;
}
}
@Override
public void unsupported()
{
protocolSelected(defaultProtocol);
protocolSelected(getDefaultProtocol());
}
@Override
public List<String> protocols()
{
return protocols;
return getProtocols();
}
@Override
public void protocolSelected(String protocol)
{
LOG.debug("{} protocol selected {}", this, protocol);
nextProtocol = protocol != null ? protocol : defaultProtocol;
NextProtoNego.remove(engine);
setProtocol(protocol != null ? protocol : getDefaultProtocol());
NextProtoNego.remove(getSSLEngine());
}
@Override
public void close()
{
NextProtoNego.remove(engine);
EndPoint endPoint = getEndPoint();
endPoint.shutdownOutput();
endPoint.close();
NextProtoNego.remove(getSSLEngine());
super.close();
}
}

View File

@ -16,106 +16,45 @@
// ========================================================================
//
package org.eclipse.jetty.spdy.server;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.npn.NextProtoNego;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class NPNServerConnectionFactory extends AbstractConnectionFactory
public class NPNServerConnectionFactory extends NegotiatingServerConnectionFactory
{
private static final Logger LOG = Log.getLogger(NPNServerConnectionFactory.class);
private final List<String> _protocols;
private String _defaultProtocol;
/* ------------------------------------------------------------ */
/**
* @param protocols List of supported protocols in priority order
*/
public NPNServerConnectionFactory(@Name("protocols")String... protocols)
public NPNServerConnectionFactory(@Name("protocols") String... protocols)
{
super("npn");
_protocols=Arrays.asList(protocols);
super("npn", protocols);
try
{
if (NextProtoNego.class.getClassLoader()!=null)
ClassLoader npnClassLoader = NextProtoNego.class.getClassLoader();
if (npnClassLoader != null)
{
LOG.warn("NextProtoNego not from bootloader classloader: "+NextProtoNego.class.getClassLoader());
throw new IllegalStateException("NextProtoNego not on bootloader");
LOG.warn("NPN must be in the boot classloader, not in: " + npnClassLoader);
throw new IllegalStateException("NPN must be in the boot classloader");
}
}
catch(Throwable th)
catch (Throwable x)
{
LOG.warn("NextProtoNego not available: "+th);
throw new IllegalStateException("NextProtoNego not available",th);
LOG.warn("NPN not available: " + x);
throw new IllegalStateException("NPN not available", x);
}
}
public String getDefaultProtocol()
{
return _defaultProtocol;
}
public void setDefaultProtocol(String defaultProtocol)
{
_defaultProtocol = defaultProtocol;
}
public List<String> getProtocols()
{
return _protocols;
}
@Override
public Connection newConnection(Connector connector, EndPoint endPoint)
protected AbstractConnection newServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol)
{
List<String> protocols=_protocols;
if (protocols==null || protocols.size()==0)
{
protocols=connector.getProtocols();
for (Iterator<String> i=protocols.iterator();i.hasNext();)
{
String protocol=i.next();
if (protocol.startsWith("SSL-")||protocol.equals("NPN"))
i.remove();
}
}
String dft=_defaultProtocol;
if (dft==null)
dft=_protocols.get(0);
SSLEngine engine=null;
EndPoint ep=endPoint;
while(engine==null && ep!=null)
{
// TODO make more generic
if (ep instanceof SslConnection.DecryptedEndPoint)
engine=((SslConnection.DecryptedEndPoint)ep).getSslConnection().getSSLEngine();
else
ep=null;
}
return configure(new NPNServerConnection(endPoint, engine, connector,protocols,_defaultProtocol),connector,endPoint);
}
@Override
public String toString()
{
return String.format("%s@%x{%s,%s,%s}",this.getClass().getSimpleName(),hashCode(),getProtocol(),getDefaultProtocol(),getProtocols());
return new NPNServerConnection(endPoint, engine, connector, protocols, defaultProtocol);
}
}

View File

@ -0,0 +1,163 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.
// ========================================================================
//
package org.eclipse.jetty.spdy.server;
import java.io.IOException;
import java.util.List;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public abstract class NegotiatingServerConnection extends AbstractConnection
{
private static final Logger LOG = Log.getLogger(NegotiatingServerConnection.class);
private final Connector connector;
private final SSLEngine engine;
private final List<String> protocols;
private final String defaultProtocol;
private String protocol; // No need to be volatile: it is modified and read by the same thread
public NegotiatingServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol)
{
super(endPoint, connector.getExecutor());
this.connector = connector;
this.protocols = protocols;
this.defaultProtocol = defaultProtocol;
this.engine = engine;
}
protected List<String> getProtocols()
{
return protocols;
}
protected String getDefaultProtocol()
{
return defaultProtocol;
}
protected SSLEngine getSSLEngine()
{
return engine;
}
protected String getProtocol()
{
return protocol;
}
protected void setProtocol(String protocol)
{
this.protocol = protocol;
}
@Override
public void onOpen()
{
super.onOpen();
fillInterested();
}
@Override
public void onFillable()
{
int filled = fill();
if (filled == 0)
{
if (protocol == null)
{
if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
{
// Here the SSL handshake is finished, but the protocol has not been negotiated.
LOG.debug("{} could not negotiate protocol, SSLEngine: {}", this, engine);
close();
}
else
{
// Here the SSL handshake is not finished yet but we filled 0 bytes,
// so we need to read more.
fillInterested();
}
}
else
{
ConnectionFactory connectionFactory = connector.getConnectionFactory(protocol);
if (connectionFactory == null)
{
LOG.debug("{} application selected protocol '{}', but no correspondent {} has been configured",
this, protocol, ConnectionFactory.class.getName());
close();
}
else
{
EndPoint endPoint = getEndPoint();
Connection oldConnection = endPoint.getConnection();
Connection newConnection = connectionFactory.newConnection(connector, endPoint);
LOG.debug("{} switching from {} to {}", this, oldConnection, newConnection);
oldConnection.onClose();
endPoint.setConnection(newConnection);
getEndPoint().getConnection().onOpen();
}
}
}
else if (filled < 0)
{
// Something went bad, we need to close.
LOG.debug("{} closing on client close", this);
close();
}
else
{
// Must never happen, since we fill using an empty buffer
throw new IllegalStateException();
}
}
private int fill()
{
try
{
return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
}
catch (IOException x)
{
LOG.debug(x);
close();
return -1;
}
}
@Override
public void close()
{
// Gentler close for SSL.
getEndPoint().shutdownOutput();
super.close();
}
}

View File

@ -0,0 +1,103 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.
// ========================================================================
//
package org.eclipse.jetty.spdy.server;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.server.Connector;
public abstract class NegotiatingServerConnectionFactory extends AbstractConnectionFactory
{
private final List<String> protocols;
private String defaultProtocol;
public NegotiatingServerConnectionFactory(String protocol, String... protocols)
{
super(protocol);
this.protocols = Arrays.asList(protocols);
}
public String getDefaultProtocol()
{
return defaultProtocol;
}
public void setDefaultProtocol(String defaultProtocol)
{
this.defaultProtocol = defaultProtocol;
}
public List<String> getProtocols()
{
return protocols;
}
@Override
public Connection newConnection(Connector connector, EndPoint endPoint)
{
List<String> protocols = this.protocols;
if (protocols.isEmpty())
{
protocols = connector.getProtocols();
Iterator<String> i = protocols.iterator();
while (i.hasNext())
{
String protocol = i.next();
String prefix = "ssl-";
if (protocol.regionMatches(true, 0, prefix, 0, prefix.length()) || protocol.equalsIgnoreCase("alpn"))
{
i.remove();
}
}
}
String dft = defaultProtocol;
if (dft == null && !protocols.isEmpty())
dft = protocols.get(0);
SSLEngine engine = null;
EndPoint ep = endPoint;
while (engine == null && ep != null)
{
// TODO make more generic
if (ep instanceof SslConnection.DecryptedEndPoint)
engine = ((SslConnection.DecryptedEndPoint)ep).getSslConnection().getSSLEngine();
else
ep = null;
}
return configure(newServerConnection(connector, endPoint, engine, protocols, dft), connector, endPoint);
}
protected abstract AbstractConnection newServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol);
@Override
public String toString()
{
return String.format("%s@%x{%s,%s,%s}", getClass().getSimpleName(), hashCode(), getProtocol(), getDefaultProtocol(), getProtocols());
}
}

View File

@ -47,19 +47,34 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
@ManagedObject("SPDY Server Connection Factory")
public class SPDYServerConnectionFactory extends AbstractConnectionFactory
{
// This method is placed here so as to provide a check for NPN before attempting to load any
// NPN classes.
/**
* @deprecated use {@link #checkProtocolNegotiationAvailable()} instead.
*/
@Deprecated
public static void checkNPNAvailable()
{
checkProtocolNegotiationAvailable();
}
public static void checkProtocolNegotiationAvailable()
{
if (!isAvailableInBootClassPath("org.eclipse.jetty.alpn.ALPN") &&
!isAvailableInBootClassPath("org.eclipse.jetty.npn.NextProtoNego"))
throw new IllegalStateException("No ALPN nor NPN classes available");
}
private static boolean isAvailableInBootClassPath(String className)
{
try
{
Class<?> npn = ClassLoader.getSystemClassLoader().loadClass("org.eclipse.jetty.npn.NextProtoNego");
if (npn.getClassLoader() != null)
throw new IllegalStateException("NextProtoNego must be on JVM boot path");
Class<?> klass = ClassLoader.getSystemClassLoader().loadClass(className);
if (klass.getClassLoader() != null)
throw new IllegalStateException(className + " must be on JVM boot classpath");
return true;
}
catch (ClassNotFoundException e)
catch (ClassNotFoundException x)
{
throw new IllegalStateException("No NextProtoNego on boot path", e);
return false;
}
}

View File

@ -18,7 +18,8 @@
package org.eclipse.jetty.spdy.server;
import org.eclipse.jetty.server.ConnectionFactory;
import java.util.Objects;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
@ -30,23 +31,21 @@ public class SPDYServerConnector extends ServerConnector
{
public SPDYServerConnector(Server server, ServerSessionFrameListener listener)
{
this(server, null, listener);
super(server, (SslContextFactory)null, new SPDYServerConnectionFactory(SPDY.V2, listener));
}
public SPDYServerConnector(Server server, SslContextFactory sslContextFactory, ServerSessionFrameListener listener)
{
super(server,
sslContextFactory,
sslContextFactory==null
?new ConnectionFactory[]{new SPDYServerConnectionFactory(SPDY.V2, listener)}
:new ConnectionFactory[]{
new NPNServerConnectionFactory("spdy/3","spdy/2","http/1.1"),
new HttpConnectionFactory(),
new SPDYServerConnectionFactory(SPDY.V2, listener),
new SPDYServerConnectionFactory(SPDY.V3, listener)});
if (getConnectionFactory(NPNServerConnectionFactory.class)!=null)
getConnectionFactory(NPNServerConnectionFactory.class).setDefaultProtocol("http/1.1");
this(server, sslContextFactory, listener, new NPNServerConnectionFactory("spdy/3", "spdy/2", "http/1.1"));
}
public SPDYServerConnector(Server server, SslContextFactory sslContextFactory, ServerSessionFrameListener listener, NegotiatingServerConnectionFactory negotiator)
{
super(server, Objects.requireNonNull(sslContextFactory),
negotiator,
new SPDYServerConnectionFactory(SPDY.V3, listener),
new SPDYServerConnectionFactory(SPDY.V2, listener),
new HttpConnectionFactory());
negotiator.setDefaultProtocol("http/1.1");
}
}

View File

@ -17,6 +17,7 @@
<slf4j-version>1.6.1</slf4j-version>
<jetty-test-policy-version>1.2</jetty-test-policy-version>
<npn.api.version>1.1.0.v20120525</npn.api.version>
<alpn.api.version>1.0.0-SNAPSHOT</alpn.api.version>
</properties>
<scm>
<connection>scm:git:http://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project.git</connection>

View File

@ -102,7 +102,7 @@ public class TestTransparentProxyServer
// Spdy Connector
SPDYServerConnectionFactory.checkNPNAvailable();
SPDYServerConnectionFactory.checkProtocolNegotiationAvailable();
PushStrategy push = new ReferrerPushStrategy();
HTTPSPDYServerConnectionFactory spdy2 = new HTTPSPDYServerConnectionFactory(2,config,push);
spdy2.setInputBufferSize(8192);