Add SniCompletionEvent which allows to easily retrieve the hostname that was used to select the SslContext.
Motivation: At the moment its a bit "hacky" to retrieve the hostname that was used during SNI as you need to hold a reference to SniHandler and then call hostname() once the selection is done. It would be better to fire an event to let the user know we did the selection. Modifications: Add a SniCompletionEvent that can be used to get the hostname that was used to do the selection and was included in the SNI extension. Result: Easier usage of SNI.
This commit is contained in:
parent
ca1e1fcddf
commit
aabb73a9d2
@ -80,7 +80,7 @@ public abstract class AbstractSniHandler<T> extends ByteToMessageDecoder impleme
|
||||
NotSslRecordException e = new NotSslRecordException(
|
||||
"not an SSL/TLS record: " + ByteBufUtil.hexDump(in));
|
||||
in.skipBytes(in.readableBytes());
|
||||
|
||||
ctx.fireUserEventTriggered(new SniCompletionEvent(e));
|
||||
SslUtils.notifyHandshakeFailure(ctx, e, true);
|
||||
throw e;
|
||||
}
|
||||
@ -222,6 +222,7 @@ public abstract class AbstractSniHandler<T> extends ByteToMessageDecoder impleme
|
||||
private void select(final ChannelHandlerContext ctx, final String hostname) throws Exception {
|
||||
Future<T> future = lookup(ctx, hostname);
|
||||
if (future.isDone()) {
|
||||
fireSniCompletionEvent(ctx, hostname, future);
|
||||
onLookupComplete(ctx, hostname, future);
|
||||
} else {
|
||||
suppressRead = true;
|
||||
@ -231,6 +232,7 @@ public abstract class AbstractSniHandler<T> extends ByteToMessageDecoder impleme
|
||||
try {
|
||||
suppressRead = false;
|
||||
try {
|
||||
fireSniCompletionEvent(ctx, hostname, future);
|
||||
onLookupComplete(ctx, hostname, future);
|
||||
} catch (DecoderException err) {
|
||||
ctx.fireExceptionCaught(err);
|
||||
@ -250,6 +252,15 @@ public abstract class AbstractSniHandler<T> extends ByteToMessageDecoder impleme
|
||||
}
|
||||
}
|
||||
|
||||
private void fireSniCompletionEvent(ChannelHandlerContext ctx, String hostname, Future<T> future) {
|
||||
Throwable cause = future.cause();
|
||||
if (cause == null) {
|
||||
ctx.fireUserEventTriggered(new SniCompletionEvent(hostname));
|
||||
} else {
|
||||
ctx.fireUserEventTriggered(new SniCompletionEvent(hostname, cause));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks off a lookup for the given SNI value and returns a {@link Future} which in turn will
|
||||
* notify the {@link #onLookupComplete(ChannelHandlerContext, String, Future)} on completion.
|
||||
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2017 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.ssl;
|
||||
|
||||
import io.netty.util.internal.UnstableApi;
|
||||
|
||||
/**
|
||||
* Event that is fired once we did a selection of a {@link SslContext} based on the {@code SNI hostname},
|
||||
* which may be because it was successful or there was an error.
|
||||
*/
|
||||
@UnstableApi
|
||||
public final class SniCompletionEvent extends SslCompletionEvent {
|
||||
private final String hostname;
|
||||
|
||||
SniCompletionEvent(String hostname) {
|
||||
this.hostname = hostname;
|
||||
}
|
||||
|
||||
SniCompletionEvent(String hostname, Throwable cause) {
|
||||
super(cause);
|
||||
this.hostname = hostname;
|
||||
}
|
||||
|
||||
SniCompletionEvent(Throwable cause) {
|
||||
this(null, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SNI hostname send by the client if we were able to parse it, {@code null} otherwise.
|
||||
*/
|
||||
public String hostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final Throwable cause = cause();
|
||||
return cause == null ? getClass().getSimpleName() + "(SUCCESS='" + hostname + "'\")":
|
||||
getClass().getSimpleName() + '(' + cause + ')';
|
||||
}
|
||||
}
|
@ -45,7 +45,7 @@ public abstract class SslCompletionEvent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
public String toString() {
|
||||
final Throwable cause = cause();
|
||||
return cause == null? getClass().getSimpleName() + "(SUCCESS)" :
|
||||
getClass().getSimpleName() + '(' + cause + ')';
|
||||
|
@ -24,6 +24,7 @@ import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.DefaultEventLoopGroup;
|
||||
@ -56,15 +57,13 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
@ -157,8 +156,18 @@ public class SniHandlerTest {
|
||||
.add("chat4.leancloud.cn", leanContext2)
|
||||
.build();
|
||||
|
||||
final AtomicReference<SniCompletionEvent> evtRef = new AtomicReference<SniCompletionEvent>();
|
||||
SniHandler handler = new SniHandler(mapping);
|
||||
EmbeddedChannel ch = new EmbeddedChannel(handler);
|
||||
EmbeddedChannel ch = new EmbeddedChannel(handler, new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt instanceof SniCompletionEvent) {
|
||||
assertTrue(evtRef.compareAndSet(null, (SniCompletionEvent) evt));
|
||||
} else {
|
||||
ctx.fireUserEventTriggered(evt);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// hex dump of a client hello packet, which contains hostname "CHAT4.LEANCLOUD.CN"
|
||||
@ -180,6 +189,12 @@ public class SniHandlerTest {
|
||||
|
||||
assertThat(handler.hostname(), is("chat4.leancloud.cn"));
|
||||
assertThat(handler.sslContext(), is(leanContext));
|
||||
|
||||
SniCompletionEvent evt = evtRef.get();
|
||||
assertNotNull(evt);
|
||||
assertEquals("chat4.leancloud.cn", evt.hostname());
|
||||
assertTrue(evt.isSuccess());
|
||||
assertNull(evt.cause());
|
||||
} finally {
|
||||
ch.finishAndReleaseAll();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user