public void run() throws IOException {
    String scheme = uri.getScheme() == null ? "http" : uri.getScheme();

    // Configure the client.
    ClientBootstrap b =
        new ClientBootstrap(
            new HttpTunnelingClientSocketChannelFactory(
                new OioClientSocketChannelFactory(Executors.newCachedThreadPool())));

    b.setPipelineFactory(
        new ChannelPipelineFactory() {
          public ChannelPipeline getPipeline() throws Exception {
            return Channels.pipeline(
                new StringDecoder(),
                new StringEncoder(),
                new LoggingHandler(InternalLogLevel.INFO));
          }
        });

    // Set additional options required by the HTTP tunneling transport.
    b.setOption("serverName", uri.getHost());
    b.setOption("serverPath", uri.getRawPath());

    // Configure SSL if necessary
    if ("https".equals(scheme)) {
      b.setOption("sslContext", SecureChatSslContextFactory.getClientContext());
    } else if (!"http".equals(scheme)) {
      // Only HTTP and HTTPS are supported.
      System.err.println("Only HTTP(S) is supported.");
      return;
    }

    // Make the connection attempt.
    ChannelFuture channelFuture = b.connect(new InetSocketAddress(uri.getHost(), uri.getPort()));
    channelFuture.awaitUninterruptibly();

    // Read commands from the stdin.
    System.out.println("Enter text ('quit' to exit)");
    ChannelFuture lastWriteFuture = null;
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    for (; ; ) {
      String line = in.readLine();
      if (line == null || "quit".equalsIgnoreCase(line)) {
        break;
      }

      // Sends the received line to the server.
      lastWriteFuture = channelFuture.getChannel().write(line);
    }

    // Wait until all messages are flushed before closing the channel.
    if (lastWriteFuture != null) {
      lastWriteFuture.awaitUninterruptibly();
    }

    channelFuture.getChannel().close();
    // Wait until the connection is closed or the connection attempt fails.
    channelFuture.getChannel().getCloseFuture().awaitUninterruptibly();

    // Shut down all threads.
    b.releaseExternalResources();
  }
  @Test
  public void testSslEcho() throws Throwable {
    ServerBootstrap sb =
        new ServerBootstrap(newServerSocketChannelFactory(Executors.newCachedThreadPool()));
    ClientBootstrap cb =
        new ClientBootstrap(newClientSocketChannelFactory(Executors.newCachedThreadPool()));

    EchoHandler sh = new EchoHandler(true);
    EchoHandler ch = new EchoHandler(false);

    SSLEngine sse = SecureChatSslContextFactory.getServerContext().createSSLEngine();
    SSLEngine cse = SecureChatSslContextFactory.getClientContext().createSSLEngine();
    sse.setUseClientMode(false);
    cse.setUseClientMode(true);

    // Workaround for blocking I/O transport write-write dead lock.
    sb.setOption("receiveBufferSize", 1048576);
    sb.setOption("receiveBufferSize", 1048576);

    sb.getPipeline().addFirst("ssl", new SslHandler(sse));
    sb.getPipeline().addLast("handler", sh);
    cb.getPipeline().addFirst("ssl", new SslHandler(cse));
    cb.getPipeline().addLast("handler", ch);
    ExecutorService eventExecutor = null;
    if (isExecutorRequired()) {
      eventExecutor = new OrderedMemoryAwareThreadPoolExecutor(16, 0, 0);
      sb.getPipeline().addFirst("executor", new ExecutionHandler(eventExecutor));
      cb.getPipeline().addFirst("executor", new ExecutionHandler(eventExecutor));
    }

    Channel sc = sb.bind(new InetSocketAddress(0));
    int port = ((InetSocketAddress) sc.getLocalAddress()).getPort();

    ChannelFuture ccf = cb.connect(new InetSocketAddress(TestUtil.getLocalHost(), port));
    ccf.awaitUninterruptibly();
    if (!ccf.isSuccess()) {
      logger.error("Connection attempt failed", ccf.getCause());
      sc.close().awaitUninterruptibly();
    }
    assertTrue(ccf.isSuccess());

    Channel cc = ccf.getChannel();
    ChannelFuture hf = cc.getPipeline().get(SslHandler.class).handshake();
    hf.awaitUninterruptibly();
    if (!hf.isSuccess()) {
      logger.error("Handshake failed", hf.getCause());
      sh.channel.close().awaitUninterruptibly();
      ch.channel.close().awaitUninterruptibly();
      sc.close().awaitUninterruptibly();
    }

    assertTrue(hf.isSuccess());

    for (int i = 0; i < data.length; ) {
      int length = Math.min(random.nextInt(1024 * 64), data.length - i);
      cc.write(ChannelBuffers.wrappedBuffer(data, i, length));
      i += length;
    }

    while (ch.counter < data.length) {
      if (sh.exception.get() != null) {
        break;
      }
      if (ch.exception.get() != null) {
        break;
      }

      try {
        Thread.sleep(1);
      } catch (InterruptedException e) {
        // Ignore.
      }
    }

    while (sh.counter < data.length) {
      if (sh.exception.get() != null) {
        break;
      }
      if (ch.exception.get() != null) {
        break;
      }

      try {
        Thread.sleep(1);
      } catch (InterruptedException e) {
        // Ignore.
      }
    }

    sh.channel.close().awaitUninterruptibly();
    ch.channel.close().awaitUninterruptibly();
    sc.close().awaitUninterruptibly();
    cb.shutdown();
    sb.shutdown();
    cb.releaseExternalResources();
    sb.releaseExternalResources();

    if (eventExecutor != null) {
      eventExecutor.shutdown();
    }
    if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) {
      throw sh.exception.get();
    }
    if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) {
      throw ch.exception.get();
    }
    if (sh.exception.get() != null) {
      throw sh.exception.get();
    }
    if (ch.exception.get() != null) {
      throw ch.exception.get();
    }
  }