package org.httpkit.client; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.Status; import java.io.IOException; import java.io.RandomAccessFile; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.nio.channels.FileChannel.MapMode; import java.security.NoSuchAlgorithmException; import java.util.Iterator; import java.util.Set; public class NBlockingSSL { private static Logger logger = LoggerFactory.getLogger(NBlockingSSL.class); private static final SSLContext CLIENT_CONTEXT; static { try { CLIENT_CONTEXT = SSLContext.getDefault(); } catch (NoSuchAlgorithmException e) { throw new Error("Failed to initialize the client-side SSLContext", e); } } private static Selector selector; public static SSLEngine engine; private static boolean isHandshakeDone = false; private static SelectionKey key; private static ByteBuffer myNetData = ByteBuffer.allocate(32 * 1024); private static ByteBuffer peerNetData = ByteBuffer.allocate(24 * 1024); private static ByteBuffer peerAppData = ByteBuffer.allocate(24 * 1024); private static SocketChannel socketChannel; // private final static String HOST = "d.web2.qq.com"; private final static String HOST = "github.com"; public static void main(String[] args) throws IOException { engine = CLIENT_CONTEXT.createSSLEngine(); engine.setUseClientMode(true); selector = Selector.open(); socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); key = socketChannel.register(selector, SelectionKey.OP_CONNECT); socketChannel.connect(new InetSocketAddress(HOST, 443)); int i = 0; // myNetData.clear(); // peerAppData.clear(); // peerNetData.clear(); while (true) { int select = selector.select(1000); if (select > 0) { Set<SelectionKey> selectionKeys = selector.selectedKeys(); final Iterator<SelectionKey> ite = selectionKeys.iterator(); while (ite.hasNext()) { final SelectionKey key = ite.next(); if (key.isConnectable()) { if (socketChannel.finishConnect()) { key.interestOps(SelectionKey.OP_WRITE); engine.beginHandshake(); } } else if (key.isReadable()) { if (!isHandshakeDone) { doHandshake(); } else { int read = socketChannel.read(peerNetData); if (read > 0) { peerNetData.flip(); SSLEngineResult res = engine.unwrap(peerNetData, peerAppData); if (res.getStatus() == SSLEngineResult.Status.OK) { peerAppData.flip(); byte[] data = new byte[peerAppData.remaining()]; peerAppData.get(data); i++; logger.info("get data length: " + new String(data).length()); key.interestOps(SelectionKey.OP_WRITE); peerAppData.clear(); if (i > 5) { return; } // peerNetData.clear(); } logger.info("read unwrap, " + res); peerNetData.compact(); } } } else if (key.isWritable()) { if (!isHandshakeDone) { doHandshake(); } else { myNetData.clear(); ByteBuffer buffer = ByteBuffer.wrap(("GET / HTTP/1.1\r\nHost: " + HOST + "\r\n\r\n").getBytes()); SSLEngineResult res = engine.wrap(buffer, myNetData); if (res.getStatus() == Status.OK) { myNetData.flip(); socketChannel.write(myNetData); if (!myNetData.hasRemaining()) { key.interestOps(SelectionKey.OP_READ); } } } } ite.remove(); } } else { logger.info("waiting"); } } } private static void doHandshake() throws IOException { SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus(); isHandshakeDone = hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING || hs == SSLEngineResult.HandshakeStatus.FINISHED; loop: while (!isHandshakeDone) { switch (hs) { case NEED_TASK: Runnable runnable; while ((runnable = engine.getDelegatedTask()) != null) { logger.info("get task " + runnable); runnable.run(); } break; case NEED_UNWRAP: int read = socketChannel.read(peerNetData); logger.info("read {} bytes", read); if (read < 0) { logger.info("closed"); // TODO closed } else { peerNetData.flip(); SSLEngineResult res = engine.unwrap(peerNetData, peerAppData); logger.info("hs unwrap, " + res); if(res.getStatus() != Status.OK) { System.out.println("--------------------------"); } peerNetData.compact(); switch (res.getStatus()) { case OK: break; case BUFFER_UNDERFLOW: // need more data from peer logger.info("waiting for more info"); break loop; case BUFFER_OVERFLOW: // need larger peerAppData buffer break; case CLOSED: break; } if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) { key.interestOps(SelectionKey.OP_WRITE); logger.info("for write"); break loop; } } break; case NEED_WRAP: // myNetData.compact(); SSLEngineResult result = engine.wrap(ByteBuffer.allocate(0), myNetData); logger.info("wrap: " + result); myNetData.flip(); socketChannel.write(myNetData); if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) { } if (!myNetData.hasRemaining()) { // write done, so just for read if ((key.interestOps() & SelectionKey.OP_READ) == 0) { key.interestOps(SelectionKey.OP_READ); logger.info("for read"); } myNetData.clear(); // break loop; } else { myNetData.compact(); } break; } hs = engine.getHandshakeStatus(); isHandshakeDone = hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING || hs == SSLEngineResult.HandshakeStatus.FINISHED; if (isHandshakeDone) { logger.info("handshake done"); peerNetData.clear(); ByteBuffer buffer = ByteBuffer .wrap(("GET / HTTP/1.1\r\nHost: " + HOST + "\r\n\r\n").getBytes()); SSLEngineResult res = engine.wrap(buffer, myNetData); RandomAccessFile r = new RandomAccessFile( "/home/feng/workspace/http-kit/blog.access.log", "r"); MappedByteBuffer b = r.getChannel().map(MapMode.READ_ONLY, 0, r.getChannel().size()); ByteBuffer bf = ByteBuffer.allocate(256 * 1024); // even though b is big, bf is small, the two buffer just move // forward SSLEngineResult t = engine.wrap(b, bf); System.out.println(t); if (res.getStatus() == SSLEngineResult.Status.OK) { myNetData.flip(); socketChannel.write(myNetData); if (myNetData.hasRemaining()) { key.interestOps(SelectionKey.OP_WRITE); } } } } } }