Create a NettyRule

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=212806539
This commit is contained in:
guyben 2018-09-13 07:00:49 -07:00 committed by Ben McIlwain
parent cbe628b37c
commit b588c57526
4 changed files with 330 additions and 382 deletions

View file

@ -15,29 +15,11 @@
package google.registry.proxy.handler;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.proxy.Protocol.PROTOCOL_KEY;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import google.registry.proxy.Protocol.BackendProtocol;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
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.EventLoopGroup;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.local.LocalServerChannel;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.util.ReferenceCountUtil;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
@ -46,7 +28,6 @@ import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import javax.net.ssl.SSLSession;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@ -62,133 +43,6 @@ public class SslInitializerTestUtils {
Security.addProvider(new BouncyCastleProvider());
}
/** Sets up a server channel bound to the given local address. */
static void setUpServer(
EventLoopGroup eventLoopGroup,
ChannelInitializer<LocalChannel> serverInitializer,
LocalAddress localAddress) {
ServerBootstrap sb =
new ServerBootstrap()
.group(eventLoopGroup)
.channel(LocalServerChannel.class)
.childHandler(serverInitializer);
ChannelFuture unusedFuture = sb.bind(localAddress).syncUninterruptibly();
}
/** Sets up a client channel connecting to the give local address. */
static Channel setUpClient(
EventLoopGroup eventLoopGroup,
ChannelInitializer<LocalChannel> clientInitializer,
LocalAddress localAddress,
BackendProtocol protocol) {
Bootstrap b =
new Bootstrap()
.group(eventLoopGroup)
.channel(LocalChannel.class)
.handler(clientInitializer)
.attr(PROTOCOL_KEY, protocol);
return b.connect(localAddress).syncUninterruptibly().channel();
}
/**
* A handler that echoes back its inbound message. The message is also saved in a promise for
* inspection later.
*/
static class EchoHandler extends ChannelInboundHandlerAdapter {
private final CountDownLatch latch = new CountDownLatch(1);
private String request;
private Throwable cause;
void waitTillReady() throws InterruptedException {
latch.await();
}
String getRequest() {
return request;
}
Throwable getCause() {
return cause;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// In the test we only send messages of type ByteBuf.
assertThat(msg).isInstanceOf(ByteBuf.class);
request = ((ByteBuf) msg).toString(UTF_8);
// After the message is written back to the client, fulfill the promise.
ChannelFuture unusedFuture = ctx.writeAndFlush(msg).addListener(f -> latch.countDown());
}
/** Saves any inbound error as the cause of the promise failure. */
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
this.cause = cause;
ChannelFuture unusedFuture =
ctx.channel()
.closeFuture()
.addListener(
// Apparently the JDK SSL provider will call #exceptionCaught twice with the same
// exception when the handshake fails. In this case the second listener should not
// set the promise again.
f -> {
if (latch.getCount() == 1) {
latch.countDown();
}
});
}
}
/** A handler that dumps its inbound message to a promise that can be inspected later. */
static class DumpHandler extends ChannelInboundHandlerAdapter {
private final CountDownLatch latch = new CountDownLatch(1);
private String response;
private Throwable cause;
void waitTillReady() throws InterruptedException {
latch.await();
}
String getResponse() {
return response;
}
Throwable getCause() {
return cause;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// In the test we only send messages of type ByteBuf.
assertThat(msg).isInstanceOf(ByteBuf.class);
response = ((ByteBuf) msg).toString(UTF_8);
// There is no more use of this message, we should release its reference count so that it
// can be more effectively garbage collected by Netty.
ReferenceCountUtil.release(msg);
// Save the string in the promise and make it as complete.
latch.countDown();
}
/** Saves any inbound error into the failure cause of the promise. */
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
this.cause = cause;
ctx.channel()
.closeFuture()
.addListener(
f -> {
// Apparently the JDK SSL provider will call #exceptionCaught twice with the same
// exception when the handshake fails. In this case the second listener should not
// set the promise again.
if (latch.getCount() == 1) {
latch.countDown();
}
});
}
}
public static KeyPair getKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
keyPairGenerator.initialize(2048, new SecureRandom());
@ -221,11 +75,9 @@ public class SslInitializerTestUtils {
* @param certs The certificate that the server should provide.
* @return The SSL session in current channel, can be used for further validation.
*/
static SSLSession verifySslChannel(
static SSLSession setUpSslChannel(
Channel channel,
ImmutableList<X509Certificate> certs,
EchoHandler echoHandler,
DumpHandler dumpHandler)
X509Certificate... certs)
throws Exception {
SslHandler sslHandler = channel.pipeline().get(SslHandler.class);
// Wait till the handshake is complete.
@ -236,27 +88,7 @@ public class SslInitializerTestUtils {
assertThat(sslHandler.engine().getSession().isValid()).isTrue();
assertThat(sslHandler.engine().getSession().getPeerCertificates())
.asList()
.containsExactly(certs.toArray());
// Test that message can go through, bound inbound and outbound.
String inputString = "Hello, world!";
// The client writes the message to the server, which echos it back and saves the string in its
// promise. The client receives the echo and saves it in its promise. All these activities
// happens in the I/O thread, and this call itself returns immediately.
ChannelFuture unusedFuture =
channel.writeAndFlush(
Unpooled.wrappedBuffer(inputString.getBytes(StandardCharsets.US_ASCII)));
// Wait for both the server and the client to finish processing.
echoHandler.waitTillReady();
dumpHandler.waitTillReady();
// Checks that the message is transmitted faithfully.
String requestReceived = echoHandler.getRequest();
String responseReceived = dumpHandler.getResponse();
assertThat(inputString).isEqualTo(requestReceived);
assertThat(inputString).isEqualTo(responseReceived);
.containsExactlyElementsIn(certs);
// Returns the SSL session for further assertion.
return sslHandler.engine().getSession();
}