17

(我知道这是一个重复的问题,但最初的发帖人是出于错误的原因提出的。我并不是在暗示我是出于正确的原因提出的,但让我们看看。)

我们有一个在非标准端口号上运行的 Web 服务。尽管用户似乎能够记住端口号,但有时他们会错误地键入 http: 而不是 https:。有人问我们是否可以在该端口上提供 HTTP,然后将它们重定向到同一端口上的 HTTPS。这听起来很邪恶......我喜欢可用性,但感觉也许它应该是浏览器的工作来做到这一点?

我见过的一个解决方案是“在 Jetty 前面编写自己的代理”。这个解决方案会起作用,但我认为它不会很好用,因为我不相信我可以编写一个与 Jetty 本身一样高效的代理。另外,即使代理本身是高效的,所有数据仍然需要额外的一跳,这保证无论如何都会减慢流量。

还有比这更好的方法吗?也许 Jetty 本身有一些地方可以嵌入协议检测逻辑,这将允许利用它们的速度,同时还可以消除代理会引入的额外跃点。

4

6 回答 6

15

更新:有关如何将单个端口重定向到 HTTPS 和 HTTP 侦听器的说明,请参阅此答案。如果出于某种原因您不使用该解决方案,请参见下文:

不可能在同一端口上同时传输来自 http 和 https 的流量。Jetty 使用两个完全不同的连接器来绑定到安全和非安全端口。事实上,我遇到的每个 Web 服务器都将这两个协议绑定到两个完全独立的端口。

出于可用性考虑,我建议的一件事是使用默认端口,这完全对用户隐藏了端口。默认情况下 http 使用端口 80,默认情况下 https 使用端口 443。因此,如果您将连接器配置为分别在端口 80 和端口 443 上运行,那么您的用户不必键入端口,您的开发团队也不需要必须处理在 HTML、CSS、JavaScript 和其他资源的绝对路径中包含端口号。

Jetty 被设计成一个独立的 Web 服务器,与旧版本的 Tomcat不同,Apache 建议在 Apache HTTP 服务器后面运行它。因此,只要您没有其他 HTTP 服务器在运行,并且使用了这些端口,您就可以将 Jetty 配置为在默认端口上运行而不会出现任何问题。这来自经验。我们正是以这种方式运行 Jetty。

最后,一个协议可以绑定到多个端口。因此,如果您当前在端口 8080 上运行 Jetty 用于 http 和 8443 用于 https,您可以保持这些连接器处于活动状态并为端口 80 和端口 443 添加另外两个连接器。这为您的应用程序部分启用了向后兼容性使用端口号,让您有时间继续前进。

<!-- Legacy HTTP connector -->
<Call name="addConnector">
  <Arg>
      <New class="org.mortbay.jetty.nio.SelectChannelConnector">
        <Set name="host"><SystemProperty name="jetty.host" /></Set>
        <Set name="port"><SystemProperty name="jetty.port" default="8080"/></Set>
        <Set name="maxIdleTime">30000</Set>
        <Set name="Acceptors">2</Set>
        <Set name="statsOn">false</Set>
        <Set name="confidentialPort">8443</Set>
        <Set name="lowResourcesConnections">5000</Set>
        <Set name="lowResourcesMaxIdleTime">5000</Set>
      </New>
  </Arg>
</Call>
<!-- Second connector for http on port 80 -->
<Call name="addConnector">
  <Arg>
      <New class="org.mortbay.jetty.nio.SelectChannelConnector">
        <Set name="host"><SystemProperty name="jetty.host" /></Set>
        <Set name="port"><SystemProperty name="jetty.port" default="80"/></Set>
        <Set name="maxIdleTime">30000</Set>
        <Set name="Acceptors">2</Set>
        <Set name="statsOn">false</Set>
        <Set name="confidentialPort">8443</Set>
        <Set name="lowResourcesConnections">5000</Set>
        <Set name="lowResourcesMaxIdleTime">5000</Set>
      </New>
  </Arg>
</Call>

<!-- Legacy SSL Connector for https port 8443 -->
<Call name="addConnector">
 <Arg>
  <New class="org.mortbay.jetty.security.SslSocketConnector">
    <Set name="Port">8443</Set>
    <Set name="maxIdleTime">30000</Set>
    <Set name="handshakeTimeout">2000</Set>
    <Set name="keystore"><SystemProperty name="jetty.home" default="." />/etc/keystore</Set>
    <Set name="password">xxxxxx</Set>
    <Set name="keyPassword">xxxxxx</Set>
    <Set name="truststore"><SystemProperty name="jetty.home" default="." />/etc/keystore</Set>
    <Set name="trustPassword">OBF:xxxxx</Set>
    <Set name="handshakeTimeout">2000</Set>
    <!-- Set name="ThreadPool">
      <New class="org.mortbay.thread.BoundedThreadPool">
        <Set name="minThreads">10</Set>
        <Set name="maxThreads">250</Set>
     </New>
    </Set -->
  </New>
 </Arg>
</Call>



<!-- Default SSL Connector for https port 443 -->
<Call name="addConnector">
 <Arg>
  <New class="org.mortbay.jetty.security.SslSocketConnector">
    <Set name="Port">443</Set>
    <Set name="maxIdleTime">30000</Set>
    <Set name="handshakeTimeout">2000</Set>
    <Set name="keystore"><SystemProperty name="jetty.home" default="." />/etc/keystore</Set>
    <Set name="password">xxxxxx</Set>
    <Set name="keyPassword">xxxxxx</Set>
    <Set name="truststore"><SystemProperty name="jetty.home" default="." />/etc/keystore</Set>
    <Set name="trustPassword">OBF:xxxxx</Set>
    <Set name="handshakeTimeout">2000</Set>
    <!-- Set name="ThreadPool">
      <New class="org.mortbay.thread.BoundedThreadPool">
        <Set name="minThreads">10</Set>
        <Set name="maxThreads">250</Set>
     </New>
    </Set -->
  </New>
 </Arg>
</Call>

对于第 2 和第 4 连接器,唯一真正的区别是端口号。简而言之,您可以为每个连接器/协议配置多个端口,但不能为同一端口配置多个协议/连接器。

于 2012-06-25T01:07:32.723 回答
10

更新:从 jetty-9.4.15.v20190215 起,Jetty 内置了对端口统一的支持;看到这个答案

我们可以

这是可能的,我们已经做到了。这里的代码适用于 Jetty 8;我没有用 Jetty 9 测试过,但是这个答案有类似的 Jetty 9 代码。

顺便说一句,这被称为端口统一,而且很明显, Glassfish使用Grizzly很早就支持它。

大纲

基本思想是产生一个org.eclipse.jetty.server.Connector可以提前查看客户端请求的第一个字节的实现。幸运的是,HTTP 和 HTTPS 都让客户端开始通信。对于 HTTPS(通常是 TLS/SSL),第一个字节将是0x16(TLS) 或>= 0x80(SSLv2)。对于 HTTP,第一个字节将是旧的可打印 7 位 ASCII。现在,根据第一个字节,Connector将产生 SSL 连接或普通连接。

在这里的代码中,我们利用了 JettySslSelectChannelConnector本身 extends的事实SelectChannelConnector,并且有一个newPlainConnection()方法(调用它的超类来产生一个非 SSL 连接)以及一个newConnection()方法(产生一个 SSL 连接)。因此,我们的 newConnector可以在观察到来自客户端的第一个字节后扩展SslSelectChannelConnector并委托给其中一个方法。

不幸的是,我们需要在第一个字节可用AsyncConnection 之前创建一个实例。该实例的某些方法甚至可能在第一个字节可用之前被调用。因此,我们创建了一个LazyConnection implements AsyncConnection它,它可以在以后确定它将委托给哪种连接,甚至在它知道之前将合理的默认响应返回给某些方法。

基于 NIO,我们Connector将使用SocketChannel. 幸运的是,我们可以扩展SocketChannel创建一个ReadAheadSocketChannelWrapper委托给“真实”SocketChannel但可以检查和存储客户端消息的第一个字节。

一些细节

一个非常hacky的位。我们Connector必须重写的方法之一是customize(Endpoint,Request). 如果我们最终得到一个基于 SSL 的,Endpoint我们可以传递给我们的超类;否则超类将抛出一个ClassCastException,但只有在传递给它的超类Request. 所以我们传递给超类,但是当我们看到异常时撤消设置方案。

我们还覆盖isConfidential()isIntegral()确保我们的 servlet 可以正确使用HttpServletRequest.isSecure()来确定是否使用了 HTTP 或 HTTPS。

尝试从客户端读取第一个字节可能会抛出一个,但我们可能不得不在一个不期望IOException出现的地方尝试这样做,在这种情况下,我们会保留异常并稍后抛出它。IOException

在 Java >= 7 和 Java 6 中扩展SocketChannel看起来不同。在后一种情况下,只需注释掉 Java 6SocketChannel没有的方法。

编码

public class PortUnificationSelectChannelConnector extends SslSelectChannelConnector {
    public PortUnificationSelectChannelConnector() {
        super();
    }

    public PortUnificationSelectChannelConnector(SslContextFactory sslContextFactory) {
        super(sslContextFactory);
    }

    @Override
    protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException {
        return super.newEndPoint(new ReadAheadSocketChannelWrapper(channel, 1), selectSet, key);
    }

    @Override
    protected AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endPoint) {
        return new LazyConnection((ReadAheadSocketChannelWrapper)channel, endPoint);
    }

    @Override
    public void customize(EndPoint endpoint, Request request) throws IOException {
        String scheme = request.getScheme();
        try {
            super.customize(endpoint, request);
        } catch (ClassCastException e) {
            request.setScheme(scheme);
        }
    }

    @Override
    public boolean isConfidential(Request request) {
        if (request.getAttribute("javax.servlet.request.cipher_suite") != null) return true;
        else return isForwarded() && request.getScheme().equalsIgnoreCase(HttpSchemes.HTTPS);
    }

    @Override
    public boolean isIntegral(Request request) {
        return isConfidential(request);
    }

    class LazyConnection implements AsyncConnection {
        private final ReadAheadSocketChannelWrapper channel;
        private final AsyncEndPoint endPoint;
        private final long timestamp;
        private AsyncConnection connection;

        public LazyConnection(ReadAheadSocketChannelWrapper channel, AsyncEndPoint endPoint) {
            this.channel = channel;
            this.endPoint = endPoint;
            this.timestamp = System.currentTimeMillis();
            this.connection = determineNewConnection(channel, endPoint, false);
        }

        public Connection handle() throws IOException {
            if (connection == null) {
                connection = determineNewConnection(channel, endPoint, false);
                channel.throwPendingException();
            }
            if (connection != null) return connection.handle();
            else return this;
        }

        public long getTimeStamp() {
            return timestamp;
        }

        public void onInputShutdown() throws IOException {
            if (connection == null) connection = determineNewConnection(channel, endPoint, true);
            connection.onInputShutdown();
        }

        public boolean isIdle() {
            if (connection == null) connection = determineNewConnection(channel, endPoint, false);
            if (connection != null) return connection.isIdle();
            else return false;
        }

        public boolean isSuspended() {
            if (connection == null) connection = determineNewConnection(channel, endPoint, false);
            if (connection != null) return connection.isSuspended();
            else return false;
        }

        public void onClose() {
            if (connection == null) connection = determineNewConnection(channel, endPoint, true);
            connection.onClose();
        }

        public void onIdleExpired(long l) {
            if (connection == null) connection = determineNewConnection(channel, endPoint, true);
            connection.onIdleExpired(l);
        }

        AsyncConnection determineNewConnection(ReadAheadSocketChannelWrapper channel, AsyncEndPoint endPoint, boolean force) {
            byte[] bytes = channel.getBytes();
            if ((bytes == null || bytes.length == 0) && !force) return null;
            if (looksLikeSsl(bytes)) {
                return PortUnificationSelectChannelConnector.super.newConnection(channel, endPoint);
            } else {
                return PortUnificationSelectChannelConnector.super.newPlainConnection(channel, endPoint);
            }
        }

        // TLS first byte is 0x16
        // SSLv2 first byte is >= 0x80
        // HTTP is guaranteed many bytes of ASCII
        private boolean looksLikeSsl(byte[] bytes) {
            if (bytes == null || bytes.length == 0) return false; // force HTTP
            byte b = bytes[0];
            return b >= 0x7F || (b < 0x20 && b != '\n' && b != '\r' && b != '\t');
        }
    }

    static class ReadAheadSocketChannelWrapper extends SocketChannel {
        private final SocketChannel channel;
        private final ByteBuffer start;
        private byte[] bytes;
        private IOException pendingException;
        private int leftToRead;

        public ReadAheadSocketChannelWrapper(SocketChannel channel, int readAheadLength) throws IOException {
            super(channel.provider());
            this.channel = channel;
            start = ByteBuffer.allocate(readAheadLength);
            leftToRead = readAheadLength;
            readAhead();
        }

        public synchronized void readAhead() throws IOException {
            if (leftToRead > 0) {
                int n = channel.read(start);
                if (n == -1) {
                    leftToRead = -1;
                } else {
                    leftToRead -= n;
                }
                if (leftToRead <= 0) {
                    start.flip();
                    bytes = new byte[start.remaining()];
                    start.get(bytes);
                    start.rewind();
                }
            }
        }

        public byte[] getBytes() {
            if (pendingException == null) {
                try {
                    readAhead();
                } catch (IOException e) {
                    pendingException = e;
                }
            }
            return bytes;
        }

        public void throwPendingException() throws IOException {
            if (pendingException != null) {
                IOException e = pendingException;
                pendingException = null;
                throw e;
            }
        }

        private int readFromStart(ByteBuffer dst) throws IOException {
            int sr = start.remaining();
            int dr = dst.remaining();
            if (dr == 0) return 0;
            int n = Math.min(dr, sr);
            dst.put(bytes, start.position(), n);
            start.position(start.position() + n);
            return n;
        }

        public synchronized int read(ByteBuffer dst) throws IOException {
            throwPendingException();
            readAhead();
            if (leftToRead > 0) return 0;
            int sr = start.remaining();
            if (sr > 0) {
                int n = readFromStart(dst);
                if (n < sr) return n;
            }
            return sr + channel.read(dst);
        }

        public synchronized long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
            throwPendingException();
            if (offset + length > dsts.length || length < 0 || offset < 0) {
                throw new IndexOutOfBoundsException();
            }
            readAhead();
            if (leftToRead > 0) return 0;
            int sr = start.remaining();
            int newOffset = offset;
            if (sr > 0) {
                int accum = 0;
                for (; newOffset < offset + length; newOffset++) {
                    accum += readFromStart(dsts[newOffset]);
                    if (accum == sr) break;
                }
                if (accum < sr) return accum;
            }
            return sr + channel.read(dsts, newOffset, length - newOffset + offset);
        }

        public int hashCode() {
            return channel.hashCode();
        }

        public boolean equals(Object obj) {
            return channel.equals(obj);
        }

        public String toString() {
            return channel.toString();
        }

        public Socket socket() {
            return channel.socket();
        }

        public boolean isConnected() {
            return channel.isConnected();
        }

        public boolean isConnectionPending() {
            return channel.isConnectionPending();
        }

        public boolean connect(SocketAddress remote) throws IOException {
            return channel.connect(remote);
        }

        public boolean finishConnect() throws IOException {
            return channel.finishConnect();
        }

        public int write(ByteBuffer src) throws IOException {
            return channel.write(src);
        }

        public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
            return channel.write(srcs, offset, length);
        }

        @Override
        protected void implCloseSelectableChannel() throws IOException {
            channel.close();
        }

        @Override
        protected void implConfigureBlocking(boolean block) throws IOException {
            channel.configureBlocking(block);
        }

//        public SocketAddress getLocalAddress() throws IOException {
//            return channel.getLocalAddress();
//        }
//
//        public <T> T getOption(java.net.SocketOption<T> name) throws IOException {
//            return channel.getOption(name);
//        }
//
//        public Set<java.net.SocketOption<?>> supportedOptions() {
//            return channel.supportedOptions();
//        }
//
//        public SocketChannel bind(SocketAddress local) throws IOException {
//            return channel.bind(local);
//        }
//
//        public SocketAddress getRemoteAddress() throws IOException {
//            return channel.getRemoteAddress();
//        }
//
//        public <T> SocketChannel setOption(java.net.SocketOption<T> name, T value) throws IOException {
//            return channel.setOption(name, value);
//        }
//
//        public SocketChannel shutdownInput() throws IOException {
//            return channel.shutdownInput();
//        }
//
//        public SocketChannel shutdownOutput() throws IOException {
//            return channel.shutdownOutput();
//        }
    }
}
于 2014-07-22T15:05:47.000 回答
5

基于答案“是的,我们可以”,我构建了适用于当前码头 9.3.11 的代码,我认为有些人会感兴趣。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ReadPendingException;
import java.nio.channels.WritePendingException;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;

public class MyReadAheadEndpoint implements EndPoint {
/** real endpoint we are wrapping    */ private final EndPoint endPoint;
/** buffer used to read start bytes  */ private final ByteBuffer start     ;
/** how many N start bytes to read   */ private       int        leftToRead;
/** first  N bytes                   */ private final byte[]     bytes     ;
/** buffered exception to throw next */ private IOException pendingException = null;
@Override public InetSocketAddress getLocalAddress            () { return endPoint.getLocalAddress(); }
@Override public InetSocketAddress getRemoteAddress           () { return endPoint.getRemoteAddress(); }
@Override public boolean           isOpen                     () { return endPoint.isOpen(); }
@Override public long              getCreatedTimeStamp        () { return endPoint.getCreatedTimeStamp(); }
@Override public boolean           isOutputShutdown           () { return endPoint.isOutputShutdown(); }
@Override public boolean           isInputShutdown            () { return endPoint.isInputShutdown(); }
@Override public void              shutdownOutput             () { endPoint.shutdownOutput(); }
@Override public void              close                      () { endPoint.close(); }
@Override public Object            getTransport               () { return endPoint.getTransport(); }
@Override public long              getIdleTimeout             () { return endPoint.getIdleTimeout(); }
@Override public Connection        getConnection              () { return endPoint.getConnection(); }
@Override public void              onOpen                     () { endPoint.onOpen(); }
@Override public void              onClose                    () { endPoint.onClose(); }
@Override public boolean           isOptimizedForDirectBuffers() { return endPoint.isOptimizedForDirectBuffers(); }
@Override public boolean           isFillInterested           () { return endPoint.isFillInterested(); }
@Override public boolean           flush                      (final ByteBuffer... v) throws IOException { return endPoint.flush(v); }
@Override public void              setIdleTimeout             (final long          v) { endPoint.setIdleTimeout(v); }
@Override public void              write                      (final Callback      v, final ByteBuffer... b) throws WritePendingException { endPoint.write(v, b); }
@Override public void              setConnection              (final Connection    v) { endPoint.setConnection(v); }
@Override public void              upgrade                    (final Connection    v) { endPoint.upgrade(v); }
@Override public void              fillInterested  (final Callback   v) throws ReadPendingException { endPoint.fillInterested(v); }
@Override public int               hashCode() { return endPoint.hashCode(); }
@Override public boolean           equals(final Object obj) { return endPoint.equals(obj); }
@Override public String            toString() { return endPoint.toString(); }
public byte[] getBytes() { if (pendingException == null) { try { readAhead(); } catch (final IOException e) { pendingException = e; } } return bytes; }
private void throwPendingException() throws IOException { if (pendingException != null) { final IOException e = pendingException; pendingException = null; throw e; } }

public MyReadAheadEndpoint(final EndPoint channel, final int readAheadLength){
    this.endPoint = channel;
    start = ByteBuffer.wrap(bytes = new byte[readAheadLength]);
    start.flip();
    leftToRead = readAheadLength;
}

private synchronized void readAhead() throws IOException {
    if (leftToRead > 0) {
        final int n = endPoint.fill(start);
        if (n == -1) { leftToRead = -1; }
        else         {  leftToRead -= n; }
        if (leftToRead <= 0) start.rewind();
    }
}

private int readFromStart(final ByteBuffer dst) throws IOException {
    final int n = Math.min(dst.remaining(), start.remaining());
    if (n > 0)  {
        dst.put(bytes, start.position(), n);
        start.position(start.position() + n);
        dst.flip();
    }
    return n;
}

@Override public synchronized int fill(final ByteBuffer dst) throws IOException {
    throwPendingException();
    if (leftToRead > 0) readAhead();
    if (leftToRead > 0) return 0;
    final int sr = start.remaining();
    if (sr > 0) {
        dst.compact();
        final int n = readFromStart(dst);
        if (n < sr) return n;
    }
    return sr + endPoint.fill(dst);
}

}

import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.annotation.Name;
public class MySslConnectionFactory extends AbstractConnectionFactory {
private final SslContextFactory _sslContextFactory;
private final String _nextProtocol;

public MySslConnectionFactory() { this(HttpVersion.HTTP_1_1.asString()); }

public MySslConnectionFactory(@Name("next") final String nextProtocol) { this((SslContextFactory)null, nextProtocol); }

public MySslConnectionFactory(@Name("sslContextFactory") final SslContextFactory factory, @Name("next") final String nextProtocol) {
    super("SSL");
    this._sslContextFactory = factory == null?new SslContextFactory():factory;
    this._nextProtocol = nextProtocol;
    this.addBean(this._sslContextFactory);
}

public SslContextFactory getSslContextFactory() { return this._sslContextFactory; }

@Override protected void doStart() throws Exception {
    super.doStart();
    final SSLEngine engine = this._sslContextFactory.newSSLEngine();
    engine.setUseClientMode(false);
    final SSLSession session = engine.getSession();
    if(session.getPacketBufferSize() > this.getInputBufferSize()) this.setInputBufferSize(session.getPacketBufferSize());
}

@Override public Connection newConnection(final Connector connector, final EndPoint realEndPoint) {
    final MyReadAheadEndpoint aheadEndpoint = new MyReadAheadEndpoint(realEndPoint, 1);
    final byte[] bytes = aheadEndpoint.getBytes();
    final boolean isSSL;
    if (bytes == null || bytes.length == 0) {
        System.out.println("NO-Data in newConnection : "+aheadEndpoint.getRemoteAddress());
        isSSL = true;
    } else {
        final byte b = bytes[0];    // TLS first byte is 0x16 , SSLv2 first byte is >= 0x80 , HTTP is guaranteed many bytes of ASCII
        isSSL = b >= 0x7F || (b < 0x20 && b != '\n' && b != '\r' && b != '\t');
        if(!isSSL) System.out.println("newConnection["+isSSL+"] : "+aheadEndpoint.getRemoteAddress());
    }
    final EndPoint      plainEndpoint;
    final SslConnection sslConnection;
    if (isSSL) {
        final SSLEngine engine = this._sslContextFactory.newSSLEngine(aheadEndpoint.getRemoteAddress());
        engine.setUseClientMode(false);
        sslConnection = this.newSslConnection(connector, aheadEndpoint, engine);
        sslConnection.setRenegotiationAllowed(this._sslContextFactory.isRenegotiationAllowed());
        this.configure(sslConnection, connector, aheadEndpoint);
        plainEndpoint = sslConnection.getDecryptedEndPoint();
    } else {
        sslConnection = null;
        plainEndpoint = aheadEndpoint;
    }
    final ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);
    final Connection connection = next.newConnection(connector, plainEndpoint);
    plainEndpoint.setConnection(connection);
    return sslConnection == null ? connection : sslConnection;
}

protected SslConnection newSslConnection(final Connector connector, final EndPoint endPoint, final SSLEngine engine) {
    return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine);
}

@Override public String toString() {
    return String.format("%s@%x{%s->%s}", new Object[]{this.getClass().getSimpleName(), Integer.valueOf(this.hashCode()), this.getProtocol(), this._nextProtocol});
}

}

于 2016-10-16T22:15:21.347 回答
3

您可以通过编写自定义 Jetty ConnectionFactory 来实现这一点。我建议从复制和修改 SslConnectionFactory 和 SslConnection 的代码开始。您需要检查连接的前几个字节(必要时进行缓冲)以查找 SSL 客户端 Hello。使用 SSLv2 Hello,您可以通过两个长度字节来识别它,然后是 0x01,然后是版本字节。SSLv3 Hello 以 0x16 开头,后跟版本字节。版本字节序列将为 SSL 3.0 的 0x03 0x00、SSL 2.0 的 0x02 0x00、TLS 1.0 的 0x03 0x01、TLS 1.1 的 0x03 0x02、TLS 1.2 的 0x03 0x03。有效的 HTTP 流量不应以这些字节序列开头。(这个答案有更多细节。)如果是 SSL,通过 SSLEngine 传递它;如果没有,则直接将其传递给下一个协议连接器。

于 2013-08-13T05:05:24.870 回答
3

从 jetty-9.4.15.v20190215 开始,对端口统一的支持通过类OptionalSslConnectionFactory内置在 Jetty 中。

这是一个示例类,它在运行时将启动一个侦听单个端口 8000 并响应 HTTP 或 HTTPS 的服务器。(这基于此处用于单独 HTTP 和 HTTPS 连接器的 Jetty 示例代码。)

import java.io.*;
import javax.servlet.http.*;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;

public class Jetty9PortUnification {

    public static void main(String[] args) throws Exception {
        // Use example keystore and keys from Jetty distribution
        String keystorePath = "jetty-distribution/demo-base/etc/keystore";
        File keystoreFile = new File(keystorePath);
        if (!keystoreFile.exists()) {
            throw new FileNotFoundException(keystoreFile.getAbsolutePath());
        }

        Server server = new Server();

        HttpConfiguration httpConfig = new HttpConfiguration();
        httpConfig.setSecureScheme("https");
        httpConfig.setSecurePort(8000);

        SecureRequestCustomizer src = new SecureRequestCustomizer();
        httpConfig.addCustomizer(src);

        HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfig);

        SslContextFactory sslContextFactory = new SslContextFactory();
        sslContextFactory.setKeyStorePath(keystoreFile.getAbsolutePath());
        sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
        sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");

        SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString());

        ServerConnector portUnified = new ServerConnector(server,
            new OptionalSslConnectionFactory(sslConnectionFactory, HttpVersion.HTTP_1_1.asString()),
            sslConnectionFactory,
            httpConnectionFactory);
        portUnified.setPort(8000);

        server.addConnector(portUnified);

        server.setHandler(new AbstractHandler() {
            @Override
            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
                response.setContentType("text/plain");
                response.getWriter().println("Hello");
                baseRequest.setHandled(true);
            }
        });

        server.start();
        server.join();
    }
}

要运行它,您需要javax.servlet-api-3.1.0.jar, jetty-server-9.4.15.v20190215.jar, jetty-util-9.4.15.v20190215.jar,jetty-http-9.4.15.v20190215.jarjetty-io-9.4.15.v20190215.jar.

于 2019-02-27T22:23:01.943 回答
-2

即使将 Jetty 排除在外,这也是不可能的,因为服务器必须检测传入连接是 HTTP 还是 SSL/TLS。TLS 协议并非旨在支持这种用法,因此任何实现都将是一个 hack(我也找不到任何实现)。

确实存在一个SSL-SSH 多路复用器,它可以区分传入连接是 TLS 还是 SSH,并且OpenVPN具有“端口共享”功能,它将非 OpenVPN 连接代理到另一个端口。

一种可能的方法是使用与数据包内的字符串匹配的 iptables 规则。HTTP 请求的第一个数据包应该包含“HTTP/”,而 TLS ClientHello 数据包不会。然后可以将连接重定向到不使用 TLS 的不同端口。请注意,由于在整个数据包中进行字符串搜索,这会产生额外的开销,并且是一个非常老套的解决方案。

iptables --table nat --append PREROUTING --protocol tcp --dport 10433 --match string --string "HTTP/" --REDIRECT 1080
于 2012-06-25T05:24:49.367 回答