package org.littleshoot.proxy.mitm.example.handshakeFailesWithUnrecognizedName; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoop; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import java.io.File; import java.net.URI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * An example client with handling for reconnect after a handshake failure * unrecognized_name occurs. * * FIXME state in member variables is save for only one request, I think. It * should be handled within Netty instead. */ public class RetryClient { private static final Logger LOG = LoggerFactory .getLogger(RetryClient.class); private EventLoopGroup group = new NioEventLoopGroup(); private URI uri; private SslContext sslCtx; public static void main(String[] args) throws Exception { new RetryClient().get("https://wiki.gnome.org/"); } public File get(String url) throws Exception { URI uri = new URI(url); String scheme = uri.getScheme() == null ? "http" : uri.getScheme(); if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) { LOG.info("Only HTTP(S) is supported."); return null; } String host = uri.getHost() == null ? "127.0.0.1" : uri.getHost(); int port; if (uri.getPort() == -1) { if ("http".equalsIgnoreCase(scheme)) { port = 80; } else if ("https".equalsIgnoreCase(scheme)) { port = 443; } else { port = -1; } } else { port = uri.getPort(); } this.uri = new URI(scheme, uri.getUserInfo(), host, port, uri.getPath(), uri.getQuery(), uri.getFragment()); final boolean ssl = "https".equalsIgnoreCase(scheme); if (ssl) { sslCtx = SslContext .newClientContext(InsecureTrustManagerFactory.INSTANCE); } else { sslCtx = null; } connect(false, group); return null; } private void connect(final boolean retry, EventLoopGroup loop) throws InterruptedException { Bootstrap b = new Bootstrap(); b.group(loop); b.channel(NioSocketChannel.class); b.option(ChannelOption.SO_KEEPALIVE, true); b.handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline(); if (sslCtx != null) { if (retry) { p.addLast(sslCtx.newHandler(ch.alloc())); } else { p.addLast(sslCtx.newHandler(ch.alloc(), uri.getHost(), uri.getPort())); } } p.addLast(new HttpClientCodec()); p.addLast(new RetryClientHandler(RetryClient.this)); } }); Channel ch = b.connect(uri.getHost(), uri.getPort()).sync().channel(); HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.toASCIIString()); request.headers().set(HttpHeaders.Names.HOST, uri.getHost()); request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE); ch.writeAndFlush(request); ch.closeFuture().sync(); } void retry(EventLoop loop) throws InterruptedException { // FIXME the loop from the first connect is blocking (?) so use the // group here // connect(true, group); } void stop() { group.shutdownGracefully(); } } class RetryClientHandler extends SimpleChannelInboundHandler<Object> { private static final Logger LOG = LoggerFactory .getLogger(RetryClientHandler.class); private RetryClient client; public RetryClientHandler(RetryClient client) { this.client = client; } private boolean unrecognizedName; @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { LOG.info(">>> userEventTriggered " + evt); if (evt instanceof SslHandshakeCompletionEvent) { SslHandshakeCompletionEvent hce = (SslHandshakeCompletionEvent) evt; if (!hce.isSuccess() && hce.cause().getMessage().contains("unrecognized_name")) { LOG.info(">>> unrecognized_name"); ctx.close(); unrecognizedName = true; return; } } super.userEventTriggered(ctx, evt); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (!unrecognizedName) { super.exceptionCaught(ctx, cause); } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { LOG.info(">>> channelUnregistered"); if (unrecognizedName) { LOG.info(">>> unrecognizedName retry"); final EventLoop loop = ctx.channel().eventLoop(); loop.execute(new Runnable() { @Override public void run() { try { client.retry(loop); } catch (InterruptedException e) { LOG.info(">>> retry interrupted, shutdown"); client.stop(); } } }); } else { LOG.info(">>> shutdown sucessfully"); client.stop(); } } @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { LOG.info(String.valueOf(msg)); if (msg instanceof LastHttpContent) { LOG.info(">>> close sucessfully"); ctx.close(); } } }