package org.eclipse.jetty.server;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DetectorConnectionFactory extends AbstractConnectionFactory implements ConnectionFactory.Detecting
{
private static final Logger LOG = LoggerFactory.getLogger(DetectorConnectionFactory.class);
private final List<Detecting> _detectingConnectionFactories;
public DetectorConnectionFactory(Detecting... detectingConnectionFactories)
{
super(toProtocolString(detectingConnectionFactories));
_detectingConnectionFactories = Arrays.asList(detectingConnectionFactories);
for (Detecting detectingConnectionFactory : detectingConnectionFactories)
{
addBean(detectingConnectionFactory);
}
}
private static String toProtocolString(Detecting... detectingConnectionFactories)
{
if (detectingConnectionFactories.length == 0)
throw new IllegalArgumentException("At least one detecting instance is required");
LinkedHashSet<String> protocols = Arrays.stream(detectingConnectionFactories).map(ConnectionFactory::getProtocol).collect(Collectors.toCollection(LinkedHashSet::new));
String protocol = protocols.stream().collect(Collectors.joining("|", "[", "]"));
if (LOG.isDebugEnabled())
LOG.debug("Detector generated protocol name : {}", protocol);
return protocol;
}
@Override
public Detecting.Detection detect(ByteBuffer buffer)
{
if (LOG.isDebugEnabled())
LOG.debug("Detector {} detecting from buffer {} using {}", getProtocol(), BufferUtil.toHexString(buffer), _detectingConnectionFactories);
boolean needMoreBytes = true;
for (Detecting detectingConnectionFactory : _detectingConnectionFactories)
{
Detecting.Detection detection = detectingConnectionFactory.detect(buffer);
if (detection == Detecting.Detection.RECOGNIZED)
{
if (LOG.isDebugEnabled())
LOG.debug("Detector {} recognized bytes using {}", getProtocol(), detection);
return Detecting.Detection.RECOGNIZED;
}
needMoreBytes &= detection == Detection.NEED_MORE_BYTES;
}
if (LOG.isDebugEnabled())
LOG.debug("Detector {} {}", getProtocol(), (needMoreBytes ? "requires more bytes" : "failed to recognize bytes"));
return needMoreBytes ? Detection.NEED_MORE_BYTES : Detection.NOT_RECOGNIZED;
}
protected static void upgradeToConnectionFactory(ConnectionFactory connectionFactory, Connector connector, EndPoint endPoint) throws IllegalStateException
{
if (LOG.isDebugEnabled())
LOG.debug("Upgrading to connection factory {}", connectionFactory);
if (connectionFactory == null)
throw new IllegalStateException("Cannot upgrade: connection factory must not be null for " + endPoint);
Connection nextConnection = connectionFactory.newConnection(connector, endPoint);
if (!(nextConnection instanceof Connection.UpgradeTo))
throw new IllegalStateException("Cannot upgrade: " + nextConnection + " does not implement " + Connection.UpgradeTo.class.getName() + " for " + endPoint);
endPoint.upgrade(nextConnection);
if (LOG.isDebugEnabled())
LOG.debug("Upgraded to connection factory {} and released buffer", connectionFactory);
}
protected void nextProtocol(Connector connector, EndPoint endPoint, ByteBuffer buffer) throws IllegalStateException
{
String nextProtocol = findNextProtocol(connector);
if (LOG.isDebugEnabled())
LOG.debug("Detector {} detection unsuccessful, found '{}' as the next protocol to upgrade to", getProtocol(), nextProtocol);
if (nextProtocol == null)
throw new IllegalStateException("Cannot find protocol following '" + getProtocol() + "' in connector's protocol list " + connector.getProtocols() + " for " + endPoint);
upgradeToConnectionFactory(connector.getConnectionFactory(nextProtocol), connector, endPoint);
}
@Override
public Connection newConnection(Connector connector, EndPoint endPoint)
{
return configure(new DetectorConnection(endPoint, connector), connector, endPoint);
}
private class DetectorConnection extends AbstractConnection implements Connection.UpgradeFrom, Connection.UpgradeTo
{
private final Connector _connector;
private final ByteBuffer _buffer;
private DetectorConnection(EndPoint endp, Connector connector)
{
super(endp, connector.getExecutor());
_connector = connector;
_buffer = connector.getByteBufferPool().acquire(getInputBufferSize(), true);
}
@Override
public void onUpgradeTo(ByteBuffer buffer)
{
if (LOG.isDebugEnabled())
LOG.debug("Detector {} copying unconsumed buffer {}", getProtocol(), BufferUtil.toDetailString(buffer));
BufferUtil.append(_buffer, buffer);
}
@Override
public ByteBuffer onUpgradeFrom()
{
if (_buffer.hasRemaining())
{
ByteBuffer unconsumed = ByteBuffer.allocateDirect(_buffer.remaining());
unconsumed.put(_buffer);
unconsumed.flip();
_connector.getByteBufferPool().release(_buffer);
return unconsumed;
}
return null;
}
@Override
public void onOpen()
{
super.onOpen();
if (!detectAndUpgrade())
fillInterested();
}
@Override
public void onFillable()
{
try
{
while (BufferUtil.space(_buffer) > 0)
{
int fill = getEndPoint().fill(_buffer);
if (LOG.isDebugEnabled())
LOG.debug("Detector {} filled buffer with {} bytes", getProtocol(), fill);
if (fill < 0)
{
_connector.getByteBufferPool().release(_buffer);
getEndPoint().shutdownOutput();
return;
}
if (fill == 0)
{
fillInterested();
return;
}
if (detectAndUpgrade())
return;
}
LOG.warn("Detector {} failed to detect upgrade target on {} for {}", getProtocol(), _detectingConnectionFactories, getEndPoint());
releaseAndClose();
}
catch (Throwable x)
{
LOG.warn("Detector {} error for {}", getProtocol(), getEndPoint(), x);
releaseAndClose();
}
}
private boolean detectAndUpgrade()
{
if (BufferUtil.isEmpty(_buffer))
{
if (LOG.isDebugEnabled())
LOG.debug("Detector {} skipping detection on an empty buffer", getProtocol());
return false;
}
if (LOG.isDebugEnabled())
LOG.debug("Detector {} performing detection with {} bytes", getProtocol(), _buffer.remaining());
boolean notRecognized = true;
for (Detecting detectingConnectionFactory : _detectingConnectionFactories)
{
Detecting.Detection detection = detectingConnectionFactory.detect(_buffer);
if (LOG.isDebugEnabled())
LOG.debug("Detector {} performed detection from {} with {} which returned {}", getProtocol(), BufferUtil.toDetailString(_buffer), detectingConnectionFactory, detection);
if (detection == Detecting.Detection.RECOGNIZED)
{
try
{
Connection nextConnection = detectingConnectionFactory.newConnection(_connector, getEndPoint());
if (!(nextConnection instanceof UpgradeTo))
throw new IllegalStateException("Cannot upgrade: " + nextConnection + " does not implement " + UpgradeTo.class.getName());
getEndPoint().upgrade(nextConnection);
if (LOG.isDebugEnabled())
LOG.debug("Detector {} upgraded to {}", getProtocol(), nextConnection);
return true;
}
catch (DetectionFailureException e)
{
if (LOG.isDebugEnabled())
LOG.debug("Detector {} failed to upgrade, rethrowing", getProtocol(), e);
throw e;
}
catch (Exception e)
{
if (LOG.isDebugEnabled())
LOG.debug("Detector {} failed to upgrade", getProtocol());
releaseAndClose();
throw new DetectionFailureException(e);
}
}
notRecognized &= detection == Detecting.Detection.NOT_RECOGNIZED;
}
if (notRecognized)
{
if (LOG.isDebugEnabled())
LOG.debug("Detector {} failed to detect a known protocol, falling back to nextProtocol()", getProtocol());
nextProtocol(_connector, getEndPoint(), _buffer);
if (LOG.isDebugEnabled())
LOG.debug("Detector {} call to nextProtocol() succeeded, assuming upgrade performed", getProtocol());
return true;
}
return false;
}
private void releaseAndClose()
{
if (LOG.isDebugEnabled())
LOG.debug("Detector {} releasing buffer and closing", getProtocol());
_connector.getByteBufferPool().release(_buffer);
close();
}
}
private static class DetectionFailureException extends RuntimeException
{
public DetectionFailureException(Throwable cause)
{
super(cause);
}
}
}