简体   繁体   English

如何从一个端口为Jetty提供https和http?

[英]How do I serve https and http for Jetty from one port?

(I know it's a duplicate question but the original poster asked it for the wrong reason. I'm not implying that I'm asking it for the right reason, but let's see.) (我知道这是一个重复的问题,但原始的海报问错了原因。我并不是说我正在问它是正确的理由,但让我们看看。)

We have a web service which runs on a non-standard port number. 我们有一个运行在非标准端口号上的Web服务。 Even though users seem to be able to remember the port number, occasionally they type http: instead of https: by mistake. 尽管用户似乎能够记住端口号,但偶尔他们会输入http:而不是https:。 Someone is asking whether we can serve HTTP on that port and then redirect them to HTTPS on the same port. 有人问我们是否可以在该端口上提供HTTP,然后在同一端口上将它们重定向到HTTPS。 It sounds evil... I like the usability but it feels like maybe it should be the browser's job to do this? 这听起来很邪恶......我喜欢可用性但感觉浏览器的工作应该是这样做的吗?

The one solution I have seen was "write your own proxy in front of Jetty." 我见过的一个解决方案是“在Jetty面前编写自己的代理”。 This solution would work, but I don't think it would work well as I am not confident that I can write a proxy which is as efficient as Jetty itself. 这个解决方案可行,但我不认为它会运行良好,因为我不相信我能编写一个与Jetty本身一样高效的代理。 Plus, even if the proxy itself is efficient, all the data would still have to go an additional hop, which is guaranteed to slow down the traffic anyway. 此外,即使代理本身有效,所有数据仍然需要额外跳跃,这无论如何都会保证减慢流量。

Is there a better way than this? 有比这更好的方法吗? Perhaps Jetty itself has some place where the protocol detection logic could be wedged which would allow taking advantage of their speed while also removing the additional hop a proxy would introduce. 也许Jetty本身有一些协议检测逻辑可以被楔入的地方,这将允许利用它们的速度,同时还移除代理将引入的额外跳。

Update: See this answer for instructions on how to redirect a single port to both an HTTPS and HTTP listener. 更新:有关如何将单个端口重定向到HTTPS和HTTP侦听器的说明,请参阅此答案 If for whatever reason you don't use that solution, see below: 如果由于某种原因您不使用该解决方案,请参阅以下内容:

It isn't possible to pipe traffic from both http and https on the same port. 无法在同一端口上管理来自http和https的流量。 Jetty uses two completely different connectors to bind to the secure and unsecure ports. Jetty使用两个完全不同的连接器绑定到安全和不安全的端口。 In fact, every web server I've encountered binds the two protocols to two completely separate ports. 事实上,我遇到的每个Web服务器都将这两个协议绑定到两个完全独立的端口。

One thing I would suggest for usability's sake is to use default ports, which completely hides the port from the user. 我建议可用性的一件事是使用默认端口,它完全隐藏了用户的端口。 By default http uses port 80, and by default https uses port 443. So if you configure your connectors to run on port 80 and port 443 respectively, then your users don't have to type a port, and your development team doesn't have to handle including port numbers in absolute paths in HTML, CSS, JavaScript, and other resources. 默认情况下,http使用端口80,默认情况下,https使用端口443.因此,如果您将连接器配置为分别在端口80和端口443上运行,那么您的用户不必键入端口,并且您的开发团队不会必须处理HTML,CSS,JavaScript和其他资源中的绝对路径中的端口号。

Jetty is designed to be a standalone Web server, unlike older versions of Tomcat , which Apache suggests run behind the Apache HTTP server. Jetty被设计为独立的Web服务器,不像旧版本的Tomcat ,Apache建议在Apache HTTP服务器后运行。 Therefore, as long as you have no other HTTP server running, and using those ports so you cannot, you should be able to configure Jetty to run on the default ports without any problem. 因此,只要您没有其他HTTP服务器正在运行,并且使用这些端口而您不能,您就应该能够将Jetty配置为在默认端口上运行而不会出现任何问题。 This comes from experience. 这来自经验。 We run Jetty precisely in this manner. 我们正是以这种方式运行Jetty。

Finally, a protocol can be bound to more than one port. 最后,协议可以绑定到多个端口。 Thus, if you're currently running Jetty on ports 8080 for http and 8443 for https, you can leave those connectors active and add two more connectors for port 80 and port 443. This enabled backwards compatibility for the part of your app that is still using the port numbers and gives you time to walk this forward. 因此,如果您当前在端口8080上为http运行Jetty,在8443上为https运行Jetty,则可以保持这些连接器处于活动状态,并为端口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>

For the 2nd and 4th connectors, the only real differences are the port numbers. 对于第2和第4个连接器,唯一真正的区别是端口号。 In short, you can configure multiple ports per connector/protocol, but you cannot configure multiple protocols/connectors for the same port. 简而言之,您可以为每个连接器/协议配置多个端口,但不能为同一端口配置多个协议/连接器。

Update : As of jetty-9.4.15.v20190215 support for port unification is built into Jetty; 更新 :截至Jetty-9.4.15.v20190215支持端口统一内置于Jetty; see this answer . 看到这个答案

Yes We Can 我们可以

This is possible and we have done it. 这是可能的,我们已经做到了。 The code here works with Jetty 8; 这里的代码适用于Jetty 8; I have not tested with Jetty 9, but this answer has similar code for Jetty 9. 我没有测试过Jetty 9,但这个答案有类似Jetty 9的代码。

By the way this is called port unification , and it is has apparently long been supported in Glassfish using Grizzly . 顺便说一下,这被称为端口统一 ,并且使用Grizzly显然在Glassfish中得到了长期支持。

Outline 大纲

The basic idea is to produce an implementation of org.eclipse.jetty.server.Connector which can look ahead at the first byte of the client's request. 基本思想是生成org.eclipse.jetty.server.Connector的实现,它可以提前查看客户端请求的第一个字节。 Luckily both HTTP and HTTPS have the client start the communication. 幸运的是,HTTP和HTTPS都让客户端开始通信。 For HTTPS (and TLS/SSL generally) the first byte will be 0x16 (TLS), or >= 0x80 (SSLv2). 对于HTTPS(通常为TLS / SSL),第一个字节为0x16 (TLS),或>= 0x80 (SSLv2)。 For HTTP the first byte will be good-old printable 7-bit ASCII. 对于HTTP,第一个字节将是旧的可打印的7位ASCII。 Now, depending on the first byte, the Connector will either produce an SSL connection or a plain connection. 现在,根据第一个字节, Connector将生成SSL连接或普通连接。

In the code here we take advantage of the fact that Jetty's SslSelectChannelConnector itself extends SelectChannelConnector , and has a newPlainConnection() method (calling its superclass to produce a non-SSL connection) as well as a newConnection() method (to produce an SSL connection). 在这里的代码中,我们利用Jetty的SslSelectChannelConnector本身扩展SelectChannelConnector ,并具有newPlainConnection()方法(调用其超类来生成非SSL连接)以及newConnection()方法(以生成SSL连接newConnection()这一newConnection() )。 So our new Connector can extend SslSelectChannelConnector and delegate to one of those methods after observing the first byte from the client. 所以我们的新Connector可以在观察客户端的第一个字节后扩展SslSelectChannelConnector并委托给其中一个方法。

Unfortunately, we will be expected to create an instance of AsyncConnection before the first byte is available. 不幸的是,我们需要第一个字节可用之前创建一个AsyncConnection实例。 Some methods of that instance may even be called before the first byte is available. 甚至可以在第一个字节可用之前调用该实例的某些方法。 So we create a LazyConnection implements AsyncConnection which can figure out later which kind of connection it will delegate to, or even return sensible default responses to some methods before it knows. 因此,我们创建一个LazyConnection implements AsyncConnection ,它可以在以后确定它将委托给哪种连接,或者甚至在它知道之前返回一些方法的合理默认响应。

Being NIO-based, our Connector will work with a SocketChannel . 基于NIO,我们的Connector将使用SocketChannel Luckily we can extend SocketChannel to create a ReadAheadSocketChannelWrapper which delegates to the "real" SocketChannel but can inspect and store the first bytes of the client's message. 幸运的是,我们可以扩展SocketChannel来创建一个ReadAheadSocketChannelWrapper ,它委托给“真正的” SocketChannel但可以检查和存储客户端消息的第一个字节。

Some Details 一些细节

One very hacky bit. 一个非常hacky位。 One of the methods our Connector must override is customize(Endpoint,Request) . 我们的Connector必须覆盖的方法之一是customize(Endpoint,Request) If we end up with an SSL-based Endpoint we can just pass to our superclass; 如果我们最终得到一个基于SSL的Endpoint我们就可以传递给我们的超类; otherwise the superclass will throw a ClassCastException , but only after both passing to its superclass and setting the scheme on the Request . 否则超类将抛出ClassCastException ,但只有在传递给它的超类Request上设置方案之后。 So we pass to the superclass, but undo setting the scheme when we see the exception. 所以我们传递给超类,但是当我们看到异常时撤消设置方案。

We also override isConfidential() and isIntegral() to ensure that our servlets can properly use HttpServletRequest.isSecure() to figure out whether HTTP or HTTPS was used. 我们还重写了isConfidential()isIntegral()以确保我们的servlet可以正确使用HttpServletRequest.isSecure()来确定是否使用了HTTP或HTTPS。

Attempting to read the first byte from the client may throw an IOException , but we may have to attempt that in a place where an IOException isn't expected, in which case we keep the exception around and throw it later. 尝试从客户端读取第一个字节可能会抛出IOException ,但我们可能必须在不期望IOException的地方尝试,在这种情况下,我们保留异常并稍后抛出它。

Extending SocketChannel looks different in Java >= 7 and Java 6. In the latter case, just comment out the methods that Java 6 SocketChannel doesn't have. 扩展SocketChannel在Java> = 7和Java 6中看起来不同。在后一种情况下,只需注释掉Java 6 SocketChannel没有的方法。

The Code 代码

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();
//        }
    }
}

Based on the Answer "Yes We Can" i build the code that works with current jetty 9.3.11 and i think some would be interested. 基于答案“是的我们可以”我构建了适用于当前码头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});
}

} }

You could implement this by writing a custom Jetty ConnectionFactory. 您可以通过编写自定义Jetty ConnectionFactory来实现此目的。 I would suggest starting by copying and modifying the code of SslConnectionFactory and SslConnection. 我建议首先复制和修改SslConnectionFactory和SslConnection的代码。 You need to inspect the first few bytes of the connection (buffering as necessary) to look for an SSL Client Hello. 您需要检查连接的前几个字节(根据需要进行缓冲)以查找SSL客户端Hello。 With an SSLv2 Hello, you can identify that by two length bytes, followed by 0x01, followed by the version bytes. 使用SSLv2 Hello,您可以通过两个长度字节识别,然后是0x01,后跟版本字节。 An SSLv3 Hello starts with 0x16 followed by the version bytes. SSLv3 Hello以0x16开头,后跟版本字节。 The version byte sequences will be 0x03 0x00 for SSL 3.0, 0x02 0x00 for SSL 2.0, 0x03 0x01 for TLS 1.0, 0x03 0x02 for TLS 1.1, 0x03 0x03 for TLS 1.2. 版本字节序列对于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。 Valid HTTP traffic should never begin with these byte sequences. 有效的HTTP流量不应该以这些字节序列开头。 ( This answer has more details.) If it is SSL, pass it through the SSLEngine; 这个答案有更多细节。)如果是SSL,则将其传递给SSLEngine; if not, pass it directly to the next protocol connector. 如果没有,请将其直接传递给下一个协议连接器。

As of jetty-9.4.15.v20190215 support for port unification is built into Jetty, via the class OptionalSslConnectionFactory . 截至Jetty-9.4.15.v20190215,端口统一的支持通过类OptionalSslConnectionFactory构建到Jetty中。

Here's an example class which, when run, will start up a server that listens on a single port 8000 and will respond to either HTTP or HTTPS. 这是一个示例类,在运行时,将启动一个侦听单个端口8000的服务器,并将响应HTTP或HTTPS。 (This is based on the Jetty example code for separate HTTP and HTTPS connectors here .) (这是基于此处单独的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();
    }
}

To run it you'll need 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.jar , and jetty-io-9.4.15.v20190215.jar . 要运行它,你需要javax.servlet-api-3.1.0.jarjetty-server-9.4.15.v20190215.jarjetty-util-9.4.15.v20190215.jarjetty-http-9.4.15.v20190215.jarjetty-io-9.4.15.v20190215.jar

Even taking Jetty out the picture, this isn't really possible, because the server would have to detect whether the incoming connection is HTTP or SSL/TLS. 即使将Jetty排除在外,这实际上也是不可能的,因为服务器必须检测传入连接是HTTP还是SSL / TLS。 The TLS protocol isn't designed to support this usage, and so any implementation would be a hack (and I can't find any either). TLS协议不是为支持这种用法而设计的,因此任何实现都是黑客攻击(我找不到任何实现)。

There does exist an SSL-SSH multiplexer which can distinguish whether an incoming connection is TLS or SSH, and OpenVPN has a "port share" feature where it proxies non-OpenVPN connections to another port. 确实存在一个SSL-SSH多路复用器 ,它可以区分传入连接是TLS还是SSH,而OpenVPN具有“端口共享”功能,它可以将非OpenVPN连接代理到另一个端口。

One possible approach is to use iptables rules which match strings inside the packets. 一种可能的方法是使用匹配数据包内的字符串的iptables规则。 The first packet of an HTTP request should include "HTTP/", whereas a TLS ClientHello packet would not. HTTP请求的第一个数据包应包含“HTTP /”,而TLS ClientHello数据包则不包含。 The connection could then be redirected to a different port which doesn't use TLS. 然后可以将连接重定向到不使用TLS的其他端口。 Note that this would incur additional overhead due to string searches in entire packets, and is quite a hacky solution. 请注意,由于在整个数据包中进行字符串搜索,这会产生额外的开销,这是一个非常糟糕的解决方案。

iptables --table nat --append PREROUTING --protocol tcp --dport 10433 --match string --string "HTTP/" --REDIRECT 1080

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在与 Jetty 相同的端口上将 HTTP 重定向到 HTTPS — 识别 http 与 Z5E0561Z1050A1C4BADEA77 - Redirect HTTP to HTTPS on the same port with Jetty — identifying http vs. https from the Request 如何从 Java 中的端口提供 HTTP 内容 - How to serve HTTP content from a port in Java 码头:如何从请求中获取无缓冲的InputStream或Reader(通过HTTP“流”事件)? - Jetty: how do I get unbuffered InputStream or Reader from request (to “stream” events over HTTP)? 如何从jetty服务器中的java servlet发送大(超过64k)http响应? - How do I send large (over 64k) http responses from a java servlet in a jetty server? 如何从 http 或 https 请求获取带有端口的主机名 - How to get host name with port from a http or https request 如何通过除443以外的其他端口通过HTTPS使用嵌入式Jetty? - How to user embedded Jetty with HTTPS over port other than 443? 如何修改 https(http) URL 的端口? - How to modify the port of https(http) URL? tomcat如何为区域设置请求提供Http服务,为远程请求提供https - tomcat how to serve Http for locale requests, https for remote requests 如何在运行时从Java代码获取server.xml中已配置的HTTP和HTTPS端口号 - How to get the configured HTTP and HTTPS port numbers in server.xml from Java code at runtime 如何验证 URL,例如 Java 中的“http://mylocalhost:port”? - How do I validate a URL such as "http://mylocalhost:port "in Java?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM