(我知道这是一个重复的问题,但原始海报问错了原因。我并不是说我要求正确的原因,但让我们看看。)< / p>
我们有一个在非标准端口号上运行的Web服务。尽管用户似乎能够记住端口号,但偶尔他们会输入http:而不是https:。有人问我们是否可以在该端口上提供HTTP,然后在同一端口上将它们重定向到HTTPS。这听起来很邪恶......我喜欢可用性,但感觉浏览器的工作应该是这样做的吗?
我看到的一个解决方案是“在Jetty面前编写自己的代理”。这个解决方案可行,但我不认为它会运行良好,因为我不相信我能编写一个与Jetty本身一样高效的代理。此外,即使代理本身效率很高,所有数据仍然需要额外跳转,这无论如何都可以减缓流量。
有比这更好的方法吗?也许Jetty本身有一些可以楔入协议检测逻辑的地方,这样可以利用它们的速度,同时还可以删除代理引入的额外跳数。
答案 0 :(得分:13)
更新:有关如何将单个端口重定向到HTTPS和HTTP侦听器的说明,请参阅this answer。如果由于某种原因您不使用该解决方案,请参阅以下内容:
无法在同一端口上管理来自http和https的流量。 Jetty使用两个完全不同的连接器绑定到安全和不安全的端口。事实上,我遇到的每个Web服务器都将这两个协议绑定到两个完全独立的端口。
我建议可用性的一件事是使用默认端口,它完全隐藏了用户的端口。默认情况下,http使用端口80,默认情况下,https使用端口443.因此,如果您将连接器配置为分别在端口80和端口443上运行,那么您的用户不必键入端口,并且您的开发团队不会必须处理HTML,CSS,JavaScript和其他资源中的绝对路径中的端口号。
Jetty旨在成为一个独立的Web服务器,不像早期版本的Tomcat ,Apache建议在Apache HTTP服务器后运行。因此,只要您没有其他HTTP服务器正在运行,并且使用这些端口而您不能,您就应该能够将Jetty配置为在默认端口上运行而不会出现任何问题。 这来自经验。我们正是以这种方式运行Jetty。
最后,协议可以绑定到多个端口。因此,如果您当前在端口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>
对于第2和第4个连接器,唯一真正的区别是端口号。简而言之,您可以为每个连接器/协议配置多个端口,但不能为同一端口配置多个协议/连接器。
答案 1 :(得分:7)
更新:截至Jetty-9.4.15.v20190215,端口统一支持内置于Jetty;见this answer。
这是可能的,我们已经做到了。这里的代码适用于Jetty 8;我没有使用Jetty 9进行测试,但是this answer有类似Jetty 9的代码。
顺便说一下,这称为端口统一,Glassfish使用Grizzly显然支持它。
基本思想是生成org.eclipse.jetty.server.Connector
的实现,它可以提前查看客户端请求的第一个字节。幸运的是,HTTP和HTTPS都让客户端开始通信。对于HTTPS(通常为TLS / SSL),第一个字节为0x16
(TLS)或>= 0x80
(SSLv2)。对于HTTP,第一个字节将是旧的可打印的7位ASCII。现在,根据第一个字节,Connector
将生成SSL连接或普通连接。
在这里的代码中,我们利用Jetty的SslSelectChannelConnector
本身扩展SelectChannelConnector
的事实,并且有一个newPlainConnection()
方法(调用它的超类来生成非SSL连接)以及newConnection()
方法(用于生成SSL连接)。所以我们的新Connector
可以扩展SslSelectChannelConnector
并在观察客户端的第一个字节后委托给其中一个方法。
不幸的是,我们需要在第一个字节可用之前创建AsyncConnection
的实例。甚至可以在第一个字节可用之前调用该实例的某些方法。所以我们创建了一个LazyConnection implements AsyncConnection
,它可以在以后知道它将委托给哪种连接,甚至可以在它知道之前为某些方法返回合理的默认响应。
基于NIO,我们的Connector
将使用SocketChannel
。幸运的是,我们可以扩展SocketChannel
以创建一个ReadAheadSocketChannelWrapper
,代表真实的&#34; SocketChannel
但可以检查并存储客户端消息的第一个字节。
一个非常hacky位。我们Connector
必须覆盖的方法之一是customize(Endpoint,Request)
。如果我们最终得到基于SSL的Endpoint
,我们就可以传递给我们的超类;否则超类将抛出ClassCastException
,但只有在传递给其超类和并在Request
上设置方案之后。所以我们传递给超类,但是当我们看到异常时撤消设置方案。
我们还会覆盖isConfidential()
和isIntegral()
,以确保我们的servlet可以正确使用HttpServletRequest.isSecure()
来确定是否使用了HTTP或HTTPS。
尝试从客户端读取第一个字节可能会抛出IOException
,但我们可能不得不在IOException
未预期的地方尝试这种情况,在这种情况下我们会保留周围的例外并稍后抛出。
扩展SocketChannel
在Java&gt; = 7和Java 6中看起来有所不同。在后一种情况下,只需注释掉Java 6 SocketChannel
没有的方法。
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();
// }
}
}
答案 2 :(得分: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});
}
}
答案 3 :(得分: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流量不应该以这些字节序列开头。 (This answer有更多细节。)如果是SSL,则将其传递给SSLEngine;如果没有,请将其直接传递给下一个协议连接器。
答案 4 :(得分:1)
从jetty-9.4.15.v20190215开始,Jetty通过类OptionalSslConnectionFactory内置了对端口统一的支持。
这是一个示例类,运行时将启动一个服务器,该服务器侦听单个端口8000,并将响应HTTP或HTTPS。 (这基于单独的HTTP和HTTPS连接器here的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.jar
和jetty-io-9.4.15.v20190215.jar
。
答案 5 :(得分:-2)
即使将Jetty排除在外,这实际上也是不可能的,因为服务器必须检测传入连接是HTTP还是SSL / TLS。 TLS协议不是为支持这种用法而设计的,因此任何实现都是黑客攻击(我找不到任何一种)。
确实存在SSL-SSH multiplexer,可以区分传入连接是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