//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

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;

A ConnectionFactory combining multiple Detecting instances that will upgrade to the first one recognizing the bytes in the buffer.
/** * 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 = LoggerFactory.getLogger(DetectorConnectionFactory.class); private final List<Detecting> _detectingConnectionFactories;

When the first bytes are not recognized by the detectingConnectionFactories, the default behavior is to upgrade to the protocol returned by AbstractConnectionFactory.findNextProtocol(Connector).

Params:
  • detectingConnectionFactories – the Detecting instances.
/** * <p>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)}.</p> * @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<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; }
Performs a detection using multiple Detecting instances and returns the aggregated outcome.
Params:
  • buffer – the buffer to perform a detection against.
Returns:A Detection value with the detection outcome of the detectingConnectionFactories.
/** * 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.
Params:
  • connectionFactory – the connection factory to upgrade to.
  • connector – the connector.
  • endPoint – the endpoint.
/** * 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 AbstractConnectionFactory.findNextProtocol(Connector).

Params:
  • connector – the connector.
  • endPoint – the endpoint.
  • buffer – the buffer.
/** * <p>Callback method called when detection was unsuccessful. * This implementation upgrades to the protocol returned by {@link #findNextProtocol(Connector)}.</p> * @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(); } }
Returns:true when upgrade was performed, false otherwise.
/** * @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); } } }