org.eclipse.jetty.server.DetectorConnectionFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jetty-server Show documentation
Show all versions of jetty-server Show documentation
The core jetty server artifact.
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// 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.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.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* A {@link ConnectionFactory} combining multiple {@link Detecting} instances that will upgrade to
* the first one recognizing the bytes in the buffer.
*/
public class DetectorConnectionFactory extends AbstractConnectionFactory implements ConnectionFactory.Detecting
{
private static final Logger LOG = Log.getLogger(DetectorConnectionFactory.class);
private final List _detectingConnectionFactories;
/**
* When the first bytes are not recognized by the {@code detectingConnectionFactories}, the default behavior is to
* upgrade to the protocol returned by {@link #findNextProtocol(Connector)}.
* @param detectingConnectionFactories the {@link Detecting} instances.
*/
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");
// remove protocol duplicates while keeping their ordering -> use LinkedHashSet
LinkedHashSet 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;
}
/**
* Performs a detection using multiple {@link ConnectionFactory.Detecting} instances and returns the aggregated outcome.
* @param buffer the buffer to perform a detection against.
* @return A {@link Detecting.Detection} value with the detection outcome of the {@code detectingConnectionFactories}.
*/
@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;
}
/**
* Utility method that performs an upgrade to the specified connection factory, disposing of the given resources when needed.
* @param connectionFactory the connection factory to upgrade to.
* @param connector the connector.
* @param endPoint the endpoint.
*/
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);
}
/**
* Callback method called when detection was unsuccessful.
* This implementation upgrades to the protocol returned by {@link #findNextProtocol(Connector)}.
* @param connector the connector.
* @param endPoint the endpoint.
* @param buffer the buffer.
*/
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)
{
// Read data
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;
}
// all Detecting instances want more bytes than this buffer can store
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();
}
}
/**
* @return true when upgrade was performed, false otherwise.
*/
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
{
// This DetectingConnectionFactory recognized those bytes -> upgrade to the next one.
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)
{
// It's just bubbling up from a nested Detector, so it's already handled, just rethrow it.
if (LOG.isDebugEnabled())
LOG.debug("Detector {} failed to upgrade, rethrowing", getProtocol(), e);
throw e;
}
catch (Exception e)
{
// Two reasons that can make us end up here:
// 1) detectingConnectionFactory.newConnection() failed? probably because it cannot find the next protocol
// 2) nextConnection is not instanceof UpgradeTo
// -> release the resources before rethrowing as DetectionFailureException
if (LOG.isDebugEnabled())
LOG.debug("Detector {} failed to upgrade", getProtocol());
releaseAndClose();
throw new DetectionFailureException(e);
}
}
notRecognized &= detection == Detecting.Detection.NOT_RECOGNIZED;
}
if (notRecognized)
{
// No DetectingConnectionFactory recognized those bytes -> call unsuccessful detection callback.
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);
}
}
}