更新: jetty-9.4.15.v20190215 の時点で、ポート統合のサポートが Jetty に組み込まれています。この回答を参照してください。
はい、できます
これは可能であり、私たちはそれを実行しました。このコードは Jetty 8 で動作します。Jetty 9 ではテストしていませんが、この回答には Jetty 9 の同様のコードがあります。
ちなみにこれはポート統合と呼ばれ、 Grizzlyを使ったGlassfishでは以前からサポートされているようです。
概要
org.eclipse.jetty.server.Connector
基本的な考え方は、クライアントの要求の最初のバイトを先読みできる実装を作成することです。幸いなことに、HTTP と HTTPS の両方でクライアントが通信を開始します。HTTPS (および TLS/SSL 一般) の場合、最初のバイトは0x16
(TLS) または>= 0x80
(SSLv2) になります。HTTP の場合、最初のバイトは古き良き印刷可能な 7 ビット ASCII になります。ここで、最初のバイトに応じてConnector
、SSL 接続またはプレーン接続のいずれかが生成されます。
ここのコードでは、JettySslSelectChannelConnector
自体が を拡張SelectChannelConnector
し、newPlainConnection()
メソッド (非 SSL 接続を生成するためにスーパークラスを呼び出す) とnewConnection()
メソッド (SSL 接続を生成する) を持っているという事実を利用しています。そのため、クライアントからの最初のバイトを観察した後、新しいメソッドをConnector
拡張してこれらのメソッドの 1 つに委譲できます。SslSelectChannelConnector
残念ながら、最初のバイトが利用可能になるAsyncConnection
前にのインスタンスを作成する必要があります。そのインスタンスの一部のメソッドは、最初のバイトが使用可能になる前に呼び出されることさえあります。LazyConnection implements AsyncConnection
そのため、デリゲートする接続の種類を後で把握できるを作成したり、認識される前にいくつかのメソッドに適切なデフォルトの応答を返すことさえできます。
NIO ベースでConnector
あるため、SocketChannel
. 幸いなことに、「本物」に委譲するSocketChannel
を作成するように拡張できますが、クライアントのメッセージの最初のバイトを検査して保存することができます。ReadAheadSocketChannelWrapper
SocketChannel
いくつかの詳細
非常にハックなビット。Connector
オーバーライドする必要があるメソッドの 1 つはcustomize(Endpoint,Request)
. 最終的に SSL ベースEndpoint
になった場合は、スーパークラスに渡すだけです。それ以外の場合、スーパークラスは をスローしますが、それはそのスーパークラスに渡してにスキームを設定した後ClassCastException
でのみです。そのため、スーパークラスに渡しますが、例外が発生したときにスキームの設定を元に戻します。Request
また、サーブレットがHTTP と HTTPS のどちらが使用されたかを適切に判断できるように、isConfidential()
とをオーバーライドします。isIntegral()
HttpServletRequest.isSecure()
クライアントから最初のバイトを読み込もうとすると がスローされる可能性がありますが、 が予期されないIOException
場所で試行する必要がIOException
ある場合があります。その場合、例外を保持して後でスローします。
拡張は、Java >= 7 と Java 6 では異なるように見えます。後者の場合、Java 6にないSocketChannel
メソッドをコメントアウトするだけです。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();
// }
}
}