Add SslCloseCompletionEvent that is fired once a close_notify was received

Motivation:

For the completion of a handshake we already fire a SslHandshakeCompletionEvent which the user can intercept. We should do the same for the receiving of close_notify.

Modifications:

Add SslCloseCompletionEvent and test-case.

Result:

More consistent API.
This commit is contained in:
Norman Maurer 2017-01-19 09:31:35 +01:00
parent cd9008f95b
commit d55c321306
5 changed files with 134 additions and 33 deletions

View File

@ -0,0 +1,37 @@
/*
* 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;
/**
* Event that is fired once the close_notify was received or if an failure happens before it was received.
*/
public final class SslCloseCompletionEvent extends SslCompletionEvent {
public static final SslCloseCompletionEvent SUCCESS = new SslCloseCompletionEvent();
/**
* Creates a new event that indicates a successful receiving of close_notify.
*/
private SslCloseCompletionEvent() { }
/**
* Creates a new event that indicates an close_notify was not received because of an previous error.
* Use {@link #SUCCESS} to indicate a success.
*/
public SslCloseCompletionEvent(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.ObjectUtil;
public abstract class SslCompletionEvent {
private final Throwable cause;
SslCompletionEvent() {
cause = null;
}
SslCompletionEvent(Throwable cause) {
this.cause = ObjectUtil.checkNotNull(cause, "cause");
}
/**
* Return {@code true} if the completion was successful
*/
public final boolean isSuccess() {
return cause == null;
}
/**
* Return the {@link Throwable} if {@link #isSuccess()} returns {@code false}
* and so the completion failed.
*/
public final Throwable cause() {
return cause;
}
@Override
public final String toString() {
final Throwable cause = cause();
return cause == null? getClass().getSimpleName() + "(SUCCESS)" :
getClass().getSimpleName() + '(' + cause + ')';
}
}

View File

@ -772,7 +772,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
setHandshakeFailure(ctx, CHANNEL_CLOSED, !outboundClosed);
// Ensure we always notify the sslClosePromise as well
sslClosePromise.tryFailure(CHANNEL_CLOSED);
notifyClosePromise(CHANNEL_CLOSED);
super.channelInactive(ctx);
}
@ -1133,7 +1133,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
}
if (notifyClosure) {
sslClosePromise.trySuccess(ctx.channel());
notifyClosePromise(null);
}
} finally {
if (decodeOut != null) {
@ -1292,6 +1292,18 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
}
}
private void notifyClosePromise(Throwable cause) {
if (cause == null) {
if (sslClosePromise.trySuccess(ctx.channel())) {
ctx.fireUserEventTriggered(SslCloseCompletionEvent.SUCCESS);
}
} else {
if (sslClosePromise.tryFailure(cause)) {
ctx.fireUserEventTriggered(new SslCloseCompletionEvent(cause));
}
}
}
private void closeOutboundAndChannel(
final ChannelHandlerContext ctx, final ChannelPromise promise, boolean disconnect) throws Exception {
if (!ctx.channel().isActive()) {

View File

@ -20,48 +20,20 @@ package io.netty.handler.ssl;
* Event that is fired once the SSL handshake is complete, which may be because it was successful or there
* was an error.
*/
public final class SslHandshakeCompletionEvent {
public final class SslHandshakeCompletionEvent extends SslCompletionEvent {
public static final SslHandshakeCompletionEvent SUCCESS = new SslHandshakeCompletionEvent();
private final Throwable cause;
/**
* Creates a new event that indicates a successful handshake.
*/
private SslHandshakeCompletionEvent() {
cause = null;
}
private SslHandshakeCompletionEvent() { }
/**
* Creates a new event that indicates an unsuccessful handshake.
* Use {@link #SUCCESS} to indicate a successful handshake.
*/
public SslHandshakeCompletionEvent(Throwable cause) {
if (cause == null) {
throw new NullPointerException("cause");
}
this.cause = cause;
}
/**
* Return {@code true} if the handshake was successful
*/
public boolean isSuccess() {
return cause == null;
}
/**
* Return the {@link Throwable} if {@link #isSuccess()} returns {@code false}
* and so the handshake failed.
*/
public Throwable cause() {
return cause;
}
@Override
public String toString() {
final Throwable cause = cause();
return cause == null? "SslHandshakeCompletionEvent(SUCCESS)" : "SslHandshakeCompletionEvent(" + cause + ')';
super(cause);
}
}

View File

@ -66,6 +66,8 @@ import java.nio.channels.ClosedChannelException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class SslHandlerTest {
@ -384,4 +386,29 @@ public class SslHandlerTest {
assertTrue(handler.handshakeFuture().cause() instanceof ClosedChannelException);
assertTrue(handler.sslCloseFuture().cause() instanceof ClosedChannelException);
}
@Test(timeout = 5000)
public void testEventsFired() throws Exception {
SSLEngine engine = SSLContext.getDefault().createSSLEngine();
final BlockingQueue<SslCompletionEvent> events = new LinkedBlockingQueue<SslCompletionEvent>();
EmbeddedChannel channel = new EmbeddedChannel(new SslHandler(engine), new ChannelInboundHandlerAdapter() {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof SslCompletionEvent) {
events.add((SslCompletionEvent) evt);
}
}
});
assertTrue(events.isEmpty());
assertTrue(channel.finishAndReleaseAll());
SslCompletionEvent evt = events.take();
assertTrue(evt instanceof SslHandshakeCompletionEvent);
assertTrue(evt.cause() instanceof ClosedChannelException);
evt = events.take();
assertTrue(evt instanceof SslCloseCompletionEvent);
assertTrue(evt.cause() instanceof ClosedChannelException);
assertTrue(events.isEmpty());
}
}