mirror of https://github.com/grpc/grpc-java.git
Add initial Protocol Negotiation tests
This commit is contained in:
parent
2c2d7171ec
commit
dd815bc968
|
|
@ -82,52 +82,61 @@ public final class ProtocolNegotiators {
|
||||||
/**
|
/**
|
||||||
* Create a TLS handler for HTTP/2 capable of using ALPN/NPN.
|
* Create a TLS handler for HTTP/2 capable of using ALPN/NPN.
|
||||||
*/
|
*/
|
||||||
public static ChannelHandler serverTls(SSLEngine sslEngine, final ChannelHandler grpcHandler) {
|
public static ChannelHandler serverTls(SSLEngine sslEngine, ChannelHandler grpcHandler) {
|
||||||
Preconditions.checkNotNull(sslEngine, "sslEngine");
|
Preconditions.checkNotNull(sslEngine, "sslEngine");
|
||||||
|
|
||||||
|
return new TlsChannelInboundHandlerAdapter(new SslHandler(sslEngine, false), grpcHandler);
|
||||||
|
}
|
||||||
|
|
||||||
final SslHandler sslHandler = new SslHandler(sslEngine, false);
|
@VisibleForTesting
|
||||||
return new ChannelInboundHandlerAdapter() {
|
static final class TlsChannelInboundHandlerAdapter extends ChannelInboundHandlerAdapter {
|
||||||
@Override
|
private final ChannelHandler grpcHandler;
|
||||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
private final SslHandler sslHandler;
|
||||||
super.handlerAdded(ctx);
|
|
||||||
ctx.pipeline().addFirst(sslHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
TlsChannelInboundHandlerAdapter(SslHandler sslHandler, ChannelHandler grpcHandler) {
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
this.sslHandler = sslHandler;
|
||||||
fail(ctx, cause);
|
this.grpcHandler = grpcHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||||
if (evt instanceof SslHandshakeCompletionEvent) {
|
super.handlerAdded(ctx);
|
||||||
SslHandshakeCompletionEvent handshakeEvent = (SslHandshakeCompletionEvent) evt;
|
ctx.pipeline().addFirst(sslHandler);
|
||||||
if (handshakeEvent.isSuccess()) {
|
}
|
||||||
if (HTTP2_VERSIONS.contains(sslHandler(ctx).applicationProtocol())) {
|
|
||||||
// Successfully negotiated the protocol. Replace this handler with
|
@Override
|
||||||
// the GRPC handler.
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
ctx.pipeline().replace(this, null, grpcHandler);
|
fail(ctx, cause);
|
||||||
} else {
|
}
|
||||||
fail(ctx, new Exception(
|
|
||||||
"Failed protocol negotiation: Unable to find compatible protocol."));
|
@Override
|
||||||
}
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||||
|
if (evt instanceof SslHandshakeCompletionEvent) {
|
||||||
|
SslHandshakeCompletionEvent handshakeEvent = (SslHandshakeCompletionEvent) evt;
|
||||||
|
if (handshakeEvent.isSuccess()) {
|
||||||
|
if (HTTP2_VERSIONS.contains(sslHandler(ctx).applicationProtocol())) {
|
||||||
|
// Successfully negotiated the protocol. Replace this handler with
|
||||||
|
// the GRPC handler.
|
||||||
|
ctx.pipeline().replace(this, null, grpcHandler);
|
||||||
} else {
|
} else {
|
||||||
fail(ctx, handshakeEvent.cause());
|
fail(ctx, new Exception(
|
||||||
|
"Failed protocol negotiation: Unable to find compatible protocol."));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
fail(ctx, handshakeEvent.cause());
|
||||||
}
|
}
|
||||||
super.userEventTriggered(ctx, evt);
|
|
||||||
}
|
}
|
||||||
|
super.userEventTriggered(ctx, evt);
|
||||||
|
}
|
||||||
|
|
||||||
private void fail(ChannelHandlerContext ctx, Throwable exception) {
|
private void fail(ChannelHandlerContext ctx, Throwable exception) {
|
||||||
logSslEngineDetails(Level.FINE, ctx, "TLS negotiation failed for new client.", exception);
|
logSslEngineDetails(Level.FINE, ctx, "TLS negotiation failed for new client.", exception);
|
||||||
ctx.close();
|
ctx.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SslHandler sslHandler(ChannelHandlerContext ctx) {
|
private SslHandler sslHandler(ChannelHandlerContext ctx) {
|
||||||
return ctx.pipeline().get(SslHandler.class);
|
return ctx.pipeline().get(SslHandler.class);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -235,7 +244,8 @@ public final class ProtocolNegotiators {
|
||||||
return Status.UNAVAILABLE.withDescription(msg).asRuntimeException();
|
return Status.UNAVAILABLE.withDescription(msg).asRuntimeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void logSslEngineDetails(Level level, ChannelHandlerContext ctx, String msg,
|
@VisibleForTesting
|
||||||
|
static void logSslEngineDetails(Level level, ChannelHandlerContext ctx, String msg,
|
||||||
@Nullable Throwable t) {
|
@Nullable Throwable t) {
|
||||||
if (!log.isLoggable(level)) {
|
if (!log.isLoggable(level)) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015, Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are
|
||||||
|
* met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following disclaimer
|
||||||
|
* in the documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* * Neither the name of Google Inc. nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from
|
||||||
|
* this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.netty;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import io.grpc.netty.ProtocolNegotiators.TlsChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.channel.ChannelPipeline;
|
||||||
|
import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
|
import io.netty.handler.ssl.SslHandler;
|
||||||
|
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoRule;
|
||||||
|
|
||||||
|
import java.util.logging.Filter;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class ProtocolNegotiatorsTest {
|
||||||
|
@Rule public final ExpectedException thrown = ExpectedException.none();
|
||||||
|
@Rule public final MockitoRule mocks = MockitoJUnit.rule();
|
||||||
|
|
||||||
|
@Mock private ChannelHandler grpcHandler;
|
||||||
|
|
||||||
|
private EmbeddedChannel channel = new EmbeddedChannel();
|
||||||
|
private ChannelPipeline pipeline = channel.pipeline();
|
||||||
|
private SslHandler sslHandler;
|
||||||
|
private SSLEngine engine;
|
||||||
|
private ChannelHandlerContext channelHandlerCtx;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
engine = SSLContext.getDefault().createSSLEngine();
|
||||||
|
sslHandler = new SslHandler(engine, false) {
|
||||||
|
@Override
|
||||||
|
public String applicationProtocol() {
|
||||||
|
// Just get any of them.
|
||||||
|
return Iterables.getFirst(GrpcSslContexts.HTTP2_VERSIONS, "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tlsHandler_failsOnNullEngine() throws Exception {
|
||||||
|
thrown.expect(NullPointerException.class);
|
||||||
|
thrown.expectMessage("ssl");
|
||||||
|
|
||||||
|
ProtocolNegotiators.serverTls(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tlsAdapter_exceptionClosesChannel() throws Exception {
|
||||||
|
ChannelInboundHandlerAdapter handler =
|
||||||
|
new TlsChannelInboundHandlerAdapter(sslHandler, grpcHandler);
|
||||||
|
|
||||||
|
// Use addFirst due to the funny error handling in EmbeddedChannel.
|
||||||
|
pipeline.addFirst(handler);
|
||||||
|
|
||||||
|
pipeline.fireExceptionCaught(new Exception("bad"));
|
||||||
|
|
||||||
|
assertFalse(channel.isOpen());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tlsHandler_handlerAddedAddsSslHandler() throws Exception {
|
||||||
|
ChannelInboundHandlerAdapter handler =
|
||||||
|
new TlsChannelInboundHandlerAdapter(sslHandler, grpcHandler);
|
||||||
|
|
||||||
|
pipeline.addLast(handler);
|
||||||
|
|
||||||
|
assertEquals(sslHandler, pipeline.first());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tlsHandler_userEventTriggeredNonSslEvent() throws Exception {
|
||||||
|
ChannelInboundHandlerAdapter handler =
|
||||||
|
new TlsChannelInboundHandlerAdapter(sslHandler, grpcHandler);
|
||||||
|
pipeline.addLast(handler);
|
||||||
|
channelHandlerCtx = pipeline.context(handler);
|
||||||
|
Object nonSslEvent = new Object();
|
||||||
|
|
||||||
|
pipeline.fireUserEventTriggered(nonSslEvent);
|
||||||
|
|
||||||
|
// A non ssl event should not cause the grpcHandler to be in the pipeline yet.
|
||||||
|
ChannelHandlerContext grpcHandlerCtx = pipeline.context(grpcHandler);
|
||||||
|
assertNull(grpcHandlerCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tlsHandler_userEventTriggeredSslEvent_unsupportedProtocol() throws Exception {
|
||||||
|
SslHandler badSslHandler = new SslHandler(engine, false) {
|
||||||
|
@Override
|
||||||
|
public String applicationProtocol() {
|
||||||
|
return "badprotocol";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ChannelInboundHandlerAdapter handler =
|
||||||
|
new TlsChannelInboundHandlerAdapter(badSslHandler, grpcHandler);
|
||||||
|
pipeline.addLast(handler);
|
||||||
|
channelHandlerCtx = pipeline.context(handler);
|
||||||
|
Object sslEvent = SslHandshakeCompletionEvent.SUCCESS;
|
||||||
|
|
||||||
|
pipeline.fireUserEventTriggered(sslEvent);
|
||||||
|
|
||||||
|
// No h2 protocol was specified, so this should be closed.
|
||||||
|
assertFalse(channel.isOpen());
|
||||||
|
ChannelHandlerContext grpcHandlerCtx = pipeline.context(grpcHandler);
|
||||||
|
assertNull(grpcHandlerCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tlsHandler_userEventTriggeredSslEvent_handshakeFailure() throws Exception {
|
||||||
|
ChannelInboundHandlerAdapter handler =
|
||||||
|
new TlsChannelInboundHandlerAdapter(sslHandler, grpcHandler);
|
||||||
|
pipeline.addLast(handler);
|
||||||
|
channelHandlerCtx = pipeline.context(handler);
|
||||||
|
Object sslEvent = new SslHandshakeCompletionEvent(new RuntimeException("bad"));
|
||||||
|
|
||||||
|
pipeline.fireUserEventTriggered(sslEvent);
|
||||||
|
|
||||||
|
// No h2 protocol was specified, so this should be closed.
|
||||||
|
assertFalse(channel.isOpen());
|
||||||
|
ChannelHandlerContext grpcHandlerCtx = pipeline.context(grpcHandler);
|
||||||
|
assertNull(grpcHandlerCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tlsHandler_userEventTriggeredSslEvent_supportedProtocol() throws Exception {
|
||||||
|
ChannelInboundHandlerAdapter handler =
|
||||||
|
new TlsChannelInboundHandlerAdapter(sslHandler, grpcHandler);
|
||||||
|
pipeline.addLast(handler);
|
||||||
|
channelHandlerCtx = pipeline.context(handler);
|
||||||
|
Object sslEvent = SslHandshakeCompletionEvent.SUCCESS;
|
||||||
|
|
||||||
|
pipeline.fireUserEventTriggered(sslEvent);
|
||||||
|
|
||||||
|
assertTrue(channel.isOpen());
|
||||||
|
ChannelHandlerContext grpcHandlerCtx = pipeline.context(grpcHandler);
|
||||||
|
assertNotNull(grpcHandlerCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void engineLog() {
|
||||||
|
ChannelInboundHandlerAdapter handler =
|
||||||
|
new TlsChannelInboundHandlerAdapter(sslHandler, grpcHandler);
|
||||||
|
pipeline.addLast(handler);
|
||||||
|
channelHandlerCtx = pipeline.context(handler);
|
||||||
|
|
||||||
|
Logger logger = Logger.getLogger(ProtocolNegotiators.class.getName());
|
||||||
|
Filter oldFilter = logger.getFilter();
|
||||||
|
try {
|
||||||
|
logger.setFilter(new Filter() {
|
||||||
|
@Override
|
||||||
|
public boolean isLoggable(LogRecord record) {
|
||||||
|
// We still want to the log method to be exercised, just not printed to stderr.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ProtocolNegotiators.logSslEngineDetails(
|
||||||
|
Level.INFO, channelHandlerCtx, "message", new Exception("bad"));
|
||||||
|
} finally {
|
||||||
|
logger.setFilter(oldFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue