├── .gitignore ├── README.md ├── project.clj ├── src └── java │ └── me │ └── shenfeng │ ├── AbstractResponseFuture.java │ ├── PrefixThreadFactory.java │ ├── ResponseFuture.java │ ├── Utils.java │ ├── dns │ ├── DnsClientConstant.java │ └── DnsPrefecher.java │ ├── http │ ├── ConnectionListener.java │ ├── Decoder.java │ ├── HttpClient.java │ ├── HttpClientConfig.java │ ├── HttpClientConstant.java │ ├── HttpResponseFuture.java │ ├── ResponseHandler.java │ └── SocksHandler.java │ └── ssl │ ├── SslContextFactory.java │ ├── SslKeyStore.java │ └── TrustManagerFactory.java └── test ├── java ├── DnsPrefetcherTest.java ├── HttpClientTest.java ├── TestSocket.java ├── UtilsTest.java └── logback-test.xml └── me └── shenfeng └── httpclient_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | .idea/ 3 | target/ 4 | .classpath 5 | .project 6 | /bin 7 | *jar 8 | /lib/ 9 | /classes/ 10 | .settings/ 11 | .lein-deps-sum 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | An Async HTTP Client Based on [Netty](http://netty.io/) 2 | 3 | I write this for my personal part time project 4 | [RSSMiner](http://rssminer.net), for the web crawler and feed fetcher module. 5 | 6 | Please checkout out [http-kit](https://github.com/http-kit/http-kit), it should have better performance. 7 | 8 | ```xml 9 | 10 | clojars.org 11 | http://clojars.org/repo 12 | 13 | 14 | 15 | me.shenfeng 16 | async-http-client 17 | 1.1.0 18 | 19 | ``` 20 | 21 | ```clj 22 | ; You may want to write an wrapper function to make it easier to use in Clojure project 23 | [me.shenfeng/async-http-client "1.1.0"] 24 | ``` 25 | 26 | Features 27 | -------- 28 | 29 | * Asynchronous 30 | * Minimum: just download webpages from Internet efficently. 31 | * Support SOCKS v5, HTTP proxy 32 | * HTTPS(trust all) 33 | * [Configurable](https://github.com/shenfeng/async-http-client/blob/master/src/java/me/shenfeng/http/HttpClientConfig.java) 34 | * [DNS prefetch](https://github.com/shenfeng/async-http-client/blob/master/src/java/me/shenfeng/dns/DnsPrefecher.java), 35 | 36 | 37 | Limitations: 38 | ------------ 39 | * All Content are buffered in memory as byte array=> can not handle 40 | large file. Anyway, it's meant to download webpages(zipped if server 41 | support) 42 | * Dns prefetch is IPV4 only. 43 | 44 | Example 45 | ------- 46 | 47 | ```java 48 | // Http client sample usage 49 | HttpClientConfig config = new HttpClientConfig(); 50 | header = new HashMap(); 51 | HttpClient client = new HttpClient(config); 52 | URI uri = new URI("http://onycloud.com"); 53 | final HttpResponseFuture future = client.execGet(uri, header); 54 | resp.addListener(new Runnable() { 55 | public void run() { 56 | HttpResponse resp = future.get(); // async 57 | } 58 | }); 59 | HttpResponse resp = future.get(); // blocking 60 | ``` 61 | 62 | ### License 63 | 64 | Copyright © 2012 [Feng Shen](http://shenfeng.me/). Distributed under the [Apache License Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). 65 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject me.shenfeng/async-http-client "1.1.0" 2 | :description "HTTP client based on netty" 3 | :java-source-paths ["src/java"] 4 | :url "https://github.com/shenfeng/async-http-client" 5 | :javac-options ["-source" "1.6" "-target" "1.6" "-g"] 6 | :license {:name "Apache License, Version 2.0" 7 | :url "http://www.apache.org/licenses/LICENSE-2.0.html"} 8 | :warn-on-reflection true 9 | :repositories {"JBoss" 10 | "http://repository.jboss.org/nexus/content/groups/public/"} 11 | :dependencies [[io.netty/netty "3.6.3.Final"] 12 | [org.slf4j/slf4j-api "1.7.3"]] 13 | :profiles {:test {:java-source-paths ["test/java" "src/java"]} 14 | :dev {:dependencies [[ring/ring-jetty-adapter "0.3.11"] 15 | [com.google.guava/guava "10.0.1"] 16 | [ring/ring-core "0.3.11"] 17 | [org.clojure/clojure "1.4.0"] 18 | [compojure "1.1.5"] 19 | [ring/ring-jetty-adapter "1.1.8"] 20 | [ring/ring-core "1.1.8"] 21 | [junit/junit "4.8.2"] 22 | [ch.qos.logback/logback-classic "0.9.29"]]}}) 23 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/AbstractResponseFuture.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.CountDownLatch; 6 | import java.util.concurrent.atomic.AtomicReference; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | public abstract class AbstractResponseFuture implements ResponseFuture { 12 | 13 | final static Logger logger = LoggerFactory 14 | .getLogger(AbstractResponseFuture.class); 15 | 16 | protected Runnable mFirstListener; 17 | protected final CountDownLatch mLatch = new CountDownLatch(1); 18 | protected final AtomicReference mResult = new AtomicReference(); 19 | 20 | protected List mOtherListeners; 21 | 22 | public synchronized void addListener(Runnable listener) { 23 | if (mLatch.getCount() == 0) { 24 | notifyListener(listener); 25 | } else { 26 | if (mFirstListener == null) { 27 | mFirstListener = listener; 28 | } else { 29 | if (mOtherListeners == null) { 30 | mOtherListeners = new ArrayList(1); 31 | } 32 | mOtherListeners.add(listener); 33 | } 34 | } 35 | } 36 | 37 | protected void notifyListener(Runnable l) { 38 | try { 39 | l.run();// only called once 40 | } catch (Exception e) { 41 | logger.error(e.getMessage(), e); 42 | } 43 | } 44 | 45 | public boolean cancel(boolean mayInterruptIfRunning) { 46 | return false; 47 | } 48 | 49 | public boolean isCancelled() { 50 | return false; 51 | } 52 | 53 | public boolean isDone() { 54 | return mResult.get() != null; 55 | } 56 | 57 | public synchronized boolean done(V result) { 58 | if (mResult.compareAndSet(null, result)) { 59 | mLatch.countDown(); 60 | if (mFirstListener != null) { 61 | notifyListener(mFirstListener); 62 | mFirstListener = null; 63 | if (mOtherListeners != null) { 64 | for (Runnable r : mOtherListeners) { 65 | notifyListener(r); 66 | } 67 | mOtherListeners = null; 68 | } 69 | } 70 | return true; 71 | } 72 | return false; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/PrefixThreadFactory.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng; 2 | import java.util.concurrent.ThreadFactory; 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | 5 | public class PrefixThreadFactory implements ThreadFactory { 6 | 7 | private final String mPrefix; 8 | private final AtomicInteger mNum = new AtomicInteger(0); 9 | 10 | public PrefixThreadFactory(String prefix) { 11 | mPrefix = prefix; 12 | } 13 | 14 | public Thread newThread(Runnable r) { 15 | Thread t = new Thread(r, mPrefix + "#" + mNum.incrementAndGet()); 16 | return t; 17 | } 18 | } -------------------------------------------------------------------------------- /src/java/me/shenfeng/ResponseFuture.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng; 2 | 3 | import java.util.concurrent.Future; 4 | 5 | public interface ResponseFuture extends Future { 6 | 7 | boolean isTimeout(); 8 | 9 | boolean done(V result); 10 | 11 | void touch(); 12 | 13 | boolean abort(Throwable t); 14 | 15 | void addListener(Runnable listener); 16 | } 17 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/Utils.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng; 2 | 3 | import static org.jboss.netty.buffer.ChannelBuffers.wrappedBuffer; 4 | import static org.jboss.netty.handler.codec.compression.ZlibWrapper.GZIP; 5 | import static org.jboss.netty.handler.codec.compression.ZlibWrapper.ZLIB_OR_NONE; 6 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_ENCODING; 7 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; 8 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.TRANSFER_ENCODING; 9 | import static org.jboss.netty.util.CharsetUtil.UTF_8; 10 | 11 | import java.net.URI; 12 | import java.nio.charset.Charset; 13 | import java.util.List; 14 | 15 | import org.jboss.netty.buffer.ChannelBuffer; 16 | import org.jboss.netty.handler.codec.compression.ZlibDecoder; 17 | import org.jboss.netty.handler.codec.embedder.CodecEmbedderException; 18 | import org.jboss.netty.handler.codec.embedder.DecoderEmbedder; 19 | import org.jboss.netty.handler.codec.http.HttpResponse; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | public class Utils { 24 | static Logger logger = LoggerFactory.getLogger(Utils.class); 25 | 26 | public static byte[] toBytes(int i) { 27 | return new byte[] { (byte) (i >> 8), (byte) (i & 0x00ff) }; 28 | } 29 | 30 | public static int toInt(byte[] bytes) { 31 | return toInt(bytes, 0); 32 | } 33 | 34 | public static int toInt(byte[] bytes, int start) { 35 | return (toInt(bytes[start]) << 8) + toInt(bytes[start + 1]); 36 | } 37 | 38 | public static int toInt(int b) { 39 | if (b < 0) 40 | b += 256; 41 | return b; 42 | } 43 | 44 | public static boolean isIP(String host) { 45 | for (int i = 0; i < host.length(); ++i) { 46 | if (!(Character.isDigit(host.charAt(i)) || host.charAt(i) == '.')) { 47 | return false; 48 | } 49 | } 50 | return true; 51 | } 52 | 53 | private static final String CS = "charset="; 54 | 55 | public static String getPath(URI uri) { 56 | String path = uri.getPath(); 57 | String query = uri.getRawQuery(); 58 | if ("".equals(path)) 59 | path = "/"; 60 | if (query == null) 61 | return path; 62 | else 63 | return path + "?" + query; 64 | } 65 | 66 | public static List getNameServer() { 67 | return sun.net.dns.ResolverConfiguration.open().nameservers(); 68 | } 69 | 70 | public static int getPort(URI uri) { 71 | int port = uri.getPort(); 72 | if (port == -1) { 73 | if ("https".equals(uri.getScheme())) 74 | port = 443; 75 | else 76 | port = 80; 77 | } 78 | return port; 79 | } 80 | 81 | public static Charset parseCharset(String type) { 82 | if (type != null) { 83 | try { 84 | type = type.toLowerCase(); 85 | int i = type.indexOf(CS); 86 | if (i != -1) { 87 | String charset = type.substring(i + CS.length()).trim(); 88 | return Charset.forName(charset); 89 | } 90 | } catch (Exception ignore) { 91 | } 92 | } 93 | return null; 94 | } 95 | 96 | static Charset detectCharset(HttpResponse resp) { 97 | Charset result = parseCharset(resp.getHeader(CONTENT_TYPE)); 98 | if (result == null) { 99 | // decode a little the find charset=??? 100 | byte[] arr = resp.getContent().array(); 101 | String s = new String(arr, 0, Math.min(350, arr.length), UTF_8); 102 | int idx = s.indexOf(CS); 103 | if (idx != -1) { 104 | int start = idx + CS.length(); 105 | int end = s.indexOf('"', start); 106 | if (end != -1) { 107 | try { 108 | result = Charset.forName(s.substring(start, end)); 109 | } catch (Exception ignore) { 110 | } 111 | } 112 | } 113 | } 114 | return result; 115 | } 116 | 117 | public static String bodyStr(HttpResponse m) { 118 | // TODO it's a bug, should not happen, "http://logos.md/2008/" 119 | m.removeHeader(TRANSFER_ENCODING); 120 | 121 | try { 122 | String contentEncoding = m.getHeader(CONTENT_ENCODING); 123 | DecoderEmbedder decoder = null; 124 | if ("gzip".equalsIgnoreCase(contentEncoding) 125 | || "x-gzip".equalsIgnoreCase(contentEncoding)) { 126 | decoder = new DecoderEmbedder(new ZlibDecoder( 127 | GZIP)); 128 | } else if ("deflate".equalsIgnoreCase(contentEncoding) 129 | || "x-deflate".equalsIgnoreCase(contentEncoding)) { 130 | decoder = new DecoderEmbedder(new ZlibDecoder( 131 | ZLIB_OR_NONE)); 132 | } 133 | 134 | ChannelBuffer body = m.getContent(); 135 | 136 | if (decoder != null) { 137 | decoder.offer(body); 138 | ChannelBuffer b = wrappedBuffer(decoder 139 | .pollAll(new ChannelBuffer[decoder.size()])); 140 | if (decoder.finish()) { 141 | ChannelBuffer r = wrappedBuffer(decoder 142 | .pollAll(new ChannelBuffer[decoder.size()])); 143 | body = wrappedBuffer(b, r); 144 | } else { 145 | body = b; 146 | } 147 | m.setContent(body);// for detect charset 148 | } 149 | Charset ch = detectCharset(m); 150 | if (ch == null) 151 | ch = UTF_8; 152 | return new String(body.array(), 0, body.readableBytes(), ch); 153 | } catch (CodecEmbedderException e) { 154 | logger.trace(e.getMessage(), e); // incorrect CRC32 checksum 155 | return ""; 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/dns/DnsClientConstant.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng.dns; 2 | 3 | public interface DnsClientConstant { 4 | static byte[] FLAGS_PARAMS = new byte[] { 1,// message is standard query, 5 | // not truncated, do recursively 6 | 0, // accept non-authenticated data 7 | 0, 1, // 1 question 8 | 0, 0, // 0 answer RRs 9 | 0, 0, // 0 authority RRs 10 | 0, 0 // 0 additional RRs 11 | }; 12 | static byte[] A_IN = new byte[] { 0, 1, // type A(host address) 13 | 0, 1 // class IN 14 | }; 15 | static final String DNS_UNKOWN_HOST = "unknown"; 16 | static final String DNS_TIMEOUT = "timeout"; 17 | static final String TIMER_NAME = "DNS-timer"; 18 | } 19 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/dns/DnsPrefecher.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng.dns; 2 | 3 | import static me.shenfeng.Utils.toBytes; 4 | import static org.jboss.netty.buffer.ChannelBuffers.dynamicBuffer; 5 | 6 | import java.io.IOException; 7 | import java.net.DatagramPacket; 8 | import java.net.DatagramSocket; 9 | import java.net.InetAddress; 10 | import java.net.InetSocketAddress; 11 | import java.net.SocketException; 12 | import java.net.URI; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.concurrent.atomic.AtomicInteger; 16 | 17 | import me.shenfeng.Utils; 18 | 19 | import org.jboss.netty.buffer.ChannelBuffer; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | /** 24 | * Prefetching DNS A record, does not wait for resolution to be completed.
25 | * 26 | * Used when a local DNS cache server is present.
27 | *
28 | * 29 | * -Djava.net.preferIPv4Stack=true 30 | * 31 | * @see InetAddress 32 | * @author feng 33 | * 34 | */ 35 | public class DnsPrefecher implements DnsClientConstant { 36 | final static Logger logger = LoggerFactory.getLogger(DnsPrefecher.class); 37 | private DatagramSocket mSocket; 38 | private final AtomicInteger mId = new AtomicInteger(0); 39 | private final ArrayList mServers; 40 | 41 | public static DnsPrefecher getInstance() { 42 | 43 | return Holder.instance; 44 | } 45 | 46 | private static class Holder { 47 | public static final DnsPrefecher instance = new DnsPrefecher(); 48 | } 49 | 50 | public DnsPrefecher() { 51 | try { 52 | mSocket = new DatagramSocket(); 53 | } catch (SocketException e) { 54 | logger.error("init DnsPrefecher error", e.getMessage()); 55 | } 56 | 57 | List servers = Utils.getNameServer(); 58 | mServers = new ArrayList(servers.size()); 59 | for (String ns : servers) { 60 | mServers.add(new InetSocketAddress(ns, 53)); 61 | } 62 | } 63 | 64 | public void prefetch(URI uri) { 65 | prefetch(uri.getHost()); 66 | } 67 | 68 | public void prefetch(String domain) { 69 | if (Utils.isIP(domain)) 70 | return; 71 | 72 | int id = mId.incrementAndGet(); 73 | if (id > Short.MAX_VALUE) { 74 | id = Short.MAX_VALUE; 75 | mId.set(0); 76 | } 77 | 78 | final ChannelBuffer buffer = dynamicBuffer(64); 79 | buffer.writeBytes(toBytes(id)); 80 | buffer.writeBytes(FLAGS_PARAMS); 81 | 82 | int start = 0; 83 | final int length = domain.length(); 84 | for (int i = 0; i < length; ++i) { 85 | if (domain.charAt(i) == '.') { 86 | buffer.writeByte(i - start); 87 | buffer.writeBytes(domain.substring(start, i).getBytes()); 88 | start = i + 1; 89 | } 90 | } 91 | buffer.writeByte(length - start); 92 | buffer.writeBytes(domain.substring(start).getBytes()); 93 | buffer.writeByte(0); 94 | buffer.writeBytes(A_IN); 95 | 96 | try { 97 | for (InetSocketAddress server : mServers) { 98 | DatagramPacket packet = new DatagramPacket(buffer.array(), 99 | buffer.readerIndex(), buffer.readableBytes(), server); 100 | mSocket.send(packet); 101 | } 102 | } catch (IOException e) { 103 | logger.info("prefetch dns: " + domain, e); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/http/ConnectionListener.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng.http; 2 | 3 | import static org.jboss.netty.buffer.ChannelBuffers.wrappedBuffer; 4 | 5 | import java.net.URI; 6 | 7 | import org.jboss.netty.channel.Channel; 8 | import org.jboss.netty.channel.ChannelFuture; 9 | import org.jboss.netty.channel.ChannelFutureListener; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | public class ConnectionListener implements ChannelFutureListener { 14 | 15 | private static Logger logger = LoggerFactory 16 | .getLogger(ConnectionListener.class); 17 | private boolean mIsSocks; 18 | private HttpResponseFuture mFuture; 19 | private URI mUri; 20 | 21 | public ConnectionListener(HttpResponseFuture future, URI uri, 22 | boolean isSocks) { 23 | mFuture = future; 24 | mIsSocks = isSocks; 25 | mUri = uri; 26 | } 27 | 28 | public void operationComplete(ChannelFuture f) throws Exception { 29 | Channel channel = f.getChannel(); 30 | if (f.isSuccess()) { 31 | channel.getPipeline().getContext(ResponseHandler.class) 32 | .setAttachment(mFuture); 33 | if (mIsSocks) { 34 | channel.getPipeline().addFirst("socks", 35 | new SocksHandler(mFuture, mUri)); 36 | // write version and authen info 37 | channel.write(wrappedBuffer(SocksHandler.VERSION_AUTH)); 38 | } else { 39 | channel.write(mFuture.request).addListener( 40 | new ChannelFutureListener() { 41 | public void operationComplete(ChannelFuture future) 42 | throws Exception { 43 | mFuture.touch(); 44 | } 45 | }); 46 | } 47 | 48 | } else { 49 | channel.close(); 50 | Throwable cause = f.getCause(); 51 | mFuture.abort(cause); 52 | logger.trace(mFuture.request.toString(), cause); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/http/Decoder.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng.http; 2 | 3 | import static me.shenfeng.http.HttpClientConstant.TOO_LARGE; 4 | import static me.shenfeng.http.HttpClientConstant.UNKNOWN_CONTENT; 5 | import static org.jboss.netty.handler.codec.http.HttpHeaders.getContentLength; 6 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; 7 | 8 | import org.jboss.netty.buffer.ChannelBuffer; 9 | import org.jboss.netty.channel.Channel; 10 | import org.jboss.netty.channel.ChannelHandlerContext; 11 | import org.jboss.netty.handler.codec.http.HttpMessage; 12 | import org.jboss.netty.handler.codec.http.HttpResponseDecoder; 13 | 14 | public class Decoder extends HttpResponseDecoder { 15 | final HttpClientConfig conf; 16 | 17 | protected Object decode(ChannelHandlerContext ctx, Channel channel, 18 | ChannelBuffer buffer, State state) throws Exception { 19 | Object o = super.decode(ctx, channel, buffer, state); 20 | 21 | HttpResponseFuture future = (HttpResponseFuture) ctx.getAttachment(); 22 | if (o instanceof HttpMessage && future != null) { 23 | future.touch(); 24 | HttpMessage msg = (HttpMessage) o; 25 | String type = msg.getHeader(CONTENT_TYPE); 26 | if (getContentLength(msg) > conf.maxLength) { 27 | future.done(TOO_LARGE); 28 | } else if (type != null && conf.acceptedContentTypes != null) { 29 | boolean accept = false; 30 | for (String t : conf.acceptedContentTypes) { 31 | if (type.contains(t)) { 32 | accept = true; 33 | break; 34 | } 35 | } 36 | if (!accept) { 37 | future.done(UNKNOWN_CONTENT); 38 | } 39 | } 40 | } 41 | return o; 42 | } 43 | 44 | public Decoder(HttpClientConfig conf) { 45 | super(4096, 8192, conf.maxChunkSize); 46 | this.conf = conf; 47 | } 48 | } -------------------------------------------------------------------------------- /src/java/me/shenfeng/http/HttpClient.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng.http; 2 | 3 | import static java.lang.System.currentTimeMillis; 4 | import static java.util.concurrent.Executors.newCachedThreadPool; 5 | import static me.shenfeng.Utils.getPort; 6 | import static org.jboss.netty.buffer.ChannelBuffers.wrappedBuffer; 7 | import static org.jboss.netty.channel.Channels.pipeline; 8 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.ACCEPT; 9 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.ACCEPT_ENCODING; 10 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH; 11 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; 12 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.HOST; 13 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.USER_AGENT; 14 | import static org.jboss.netty.handler.codec.http.HttpMethod.GET; 15 | import static org.jboss.netty.handler.codec.http.HttpMethod.POST; 16 | import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1; 17 | import static org.jboss.netty.util.CharsetUtil.UTF_8; 18 | import static org.jboss.netty.util.ThreadNameDeterminer.CURRENT; 19 | import static org.jboss.netty.util.ThreadRenamingRunnable.setThreadNameDeterminer; 20 | 21 | import java.io.UnsupportedEncodingException; 22 | import java.net.InetAddress; 23 | import java.net.InetSocketAddress; 24 | import java.net.Proxy; 25 | import java.net.Proxy.Type; 26 | import java.net.URI; 27 | import java.net.URISyntaxException; 28 | import java.net.URLEncoder; 29 | import java.net.UnknownHostException; 30 | import java.util.Iterator; 31 | import java.util.Map; 32 | import java.util.Queue; 33 | import java.util.TreeMap; 34 | import java.util.concurrent.ConcurrentLinkedQueue; 35 | import java.util.concurrent.ExecutorService; 36 | 37 | import javax.net.ssl.SSLEngine; 38 | 39 | import me.shenfeng.PrefixThreadFactory; 40 | import me.shenfeng.Utils; 41 | import me.shenfeng.ssl.SslContextFactory; 42 | 43 | import org.jboss.netty.bootstrap.ClientBootstrap; 44 | import org.jboss.netty.channel.Channel; 45 | import org.jboss.netty.channel.ChannelFuture; 46 | import org.jboss.netty.channel.ChannelPipeline; 47 | import org.jboss.netty.channel.ChannelPipelineFactory; 48 | import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; 49 | import org.jboss.netty.handler.codec.http.DefaultHttpRequest; 50 | import org.jboss.netty.handler.codec.http.HttpChunkAggregator; 51 | import org.jboss.netty.handler.codec.http.HttpMethod; 52 | import org.jboss.netty.handler.codec.http.HttpRequest; 53 | import org.jboss.netty.handler.codec.http.HttpRequestEncoder; 54 | import org.jboss.netty.handler.ssl.SslHandler; 55 | import org.slf4j.Logger; 56 | import org.slf4j.LoggerFactory; 57 | 58 | public class HttpClient implements HttpClientConstant { 59 | 60 | static final Logger logger = LoggerFactory.getLogger(HttpClient.class); 61 | static final Map EMPTY = new TreeMap(); 62 | 63 | private final ClientBootstrap mHttpBootstrap; 64 | private final ClientBootstrap mHttpsBootstrap; 65 | private volatile long mLastCheckTime = currentTimeMillis(); 66 | private final HttpClientConfig mConf; 67 | private final Queue mFutures = new ConcurrentLinkedQueue(); 68 | 69 | public HttpClient() { 70 | this(new HttpClientConfig()); 71 | } 72 | 73 | public HttpClient(HttpClientConfig conf) { 74 | mConf = conf; 75 | setThreadNameDeterminer(CURRENT); 76 | ExecutorService boss = newCachedThreadPool(new PrefixThreadFactory( 77 | conf.bossNamePrefix)); 78 | ExecutorService worker = newCachedThreadPool(new PrefixThreadFactory( 79 | conf.workerNamePrefix)); 80 | NioClientSocketChannelFactory factory = new NioClientSocketChannelFactory( 81 | boss, worker, conf.workerThread); 82 | 83 | mHttpsBootstrap = new ClientBootstrap(factory); 84 | mHttpsBootstrap.setPipelineFactory(new HttpsClientPipelineFactory( 85 | mConf.maxLength, conf)); 86 | 87 | mHttpBootstrap = new ClientBootstrap(factory); 88 | mHttpBootstrap.setPipelineFactory(new HttpClientPipelineFactory( 89 | mConf.maxLength, conf)); 90 | 91 | conf(mHttpBootstrap); 92 | conf(mHttpsBootstrap); 93 | 94 | } 95 | 96 | private HttpRequest buildRequest(HttpMethod method, URI uri, 97 | Map headers, Map params, 98 | Proxy proxy) { 99 | 100 | String path = proxy.type() == Type.HTTP ? uri.toString() : Utils 101 | .getPath(uri); 102 | 103 | HttpRequest request = new DefaultHttpRequest(HTTP_1_1, method, path); 104 | 105 | request.setHeader(HOST, uri.getHost()); 106 | request.setHeader(USER_AGENT, mConf.userAgent); 107 | request.setHeader(ACCEPT, "*/*"); 108 | request.setHeader(ACCEPT_ENCODING, "gzip, deflate"); 109 | 110 | for (Map.Entry entry : headers.entrySet()) { 111 | Object v = entry.getValue(); 112 | if (v == null) { 113 | request.removeHeader(entry.getKey()); 114 | } else { 115 | request.setHeader(entry.getKey(), entry.getValue()); 116 | } 117 | } 118 | 119 | if (params != null) { 120 | StringBuilder sb = new StringBuilder(32); 121 | for (java.util.Map.Entry e : params.entrySet()) { 122 | if (sb.length() > 0) { 123 | sb.append("&"); 124 | } 125 | try { 126 | sb.append(URLEncoder.encode(e.getKey(), "utf8")); 127 | sb.append("="); 128 | sb.append(URLEncoder.encode(e.getValue().toString(), 129 | "utf8")); 130 | } catch (UnsupportedEncodingException ignore) { 131 | } 132 | } 133 | 134 | byte[] data = sb.toString().getBytes(UTF_8); 135 | request.setHeader(CONTENT_TYPE, 136 | "application/x-www-form-urlencoded"); 137 | request.setHeader(CONTENT_LENGTH, data.length); 138 | request.setContent(wrappedBuffer(data)); 139 | } 140 | 141 | return request; 142 | 143 | } 144 | 145 | private void checkTimeoutIfNeeded() { 146 | if (currentTimeMillis() - mLastCheckTime > mConf.timerInterval) { 147 | // thread safe 148 | final Iterator it = mFutures.iterator(); 149 | while (it.hasNext()) { 150 | HttpResponseFuture r = it.next(); 151 | if (r.isDone() || r.isTimeout()) { 152 | it.remove(); 153 | } 154 | } 155 | mLastCheckTime = currentTimeMillis(); 156 | } 157 | } 158 | 159 | public void close() { 160 | mHttpBootstrap.releaseExternalResources(); 161 | mHttpsBootstrap.releaseExternalResources(); 162 | } 163 | 164 | private void conf(ClientBootstrap bootstrap) { 165 | bootstrap.setOption("connectTimeoutMillis", 166 | mConf.connectionTimeOutInMs); 167 | bootstrap.setOption("receiveBufferSize", mConf.receiveBuffer); 168 | bootstrap.setOption("sendBufferSize", mConf.sendBuffer); 169 | bootstrap.setOption("reuseAddress", true); 170 | } 171 | 172 | public HttpResponseFuture execGet(URI uri, Map headers) { 173 | return execGet(uri, headers, Proxy.NO_PROXY); 174 | } 175 | 176 | public HttpResponseFuture execGet(String uri) throws URISyntaxException { 177 | return execGet(new URI(uri), EMPTY, Proxy.NO_PROXY); 178 | } 179 | 180 | public HttpResponseFuture execGet(URI uri, Map headers, 181 | Proxy proxy) { 182 | HttpRequest request = buildRequest(GET, uri, headers, null, proxy); 183 | return execRequest(request, uri, proxy); 184 | } 185 | 186 | public HttpResponseFuture execPost(URI uri, Map headers, 187 | Map params) { 188 | HttpRequest request = buildRequest(POST, uri, headers, params, 189 | Proxy.NO_PROXY); 190 | return execRequest(request, uri, Proxy.NO_PROXY); 191 | } 192 | 193 | public HttpResponseFuture execPost(URI uri, Map headers, 194 | Map params, Proxy proxy) { 195 | HttpRequest request = buildRequest(POST, uri, headers, params, proxy); 196 | return execRequest(request, uri, proxy); 197 | } 198 | 199 | private HttpResponseFuture execRequest(HttpRequest request, URI uri, 200 | Proxy proxy) { 201 | checkTimeoutIfNeeded(); 202 | if (proxy == null) { 203 | proxy = Proxy.NO_PROXY; 204 | } 205 | final HttpResponseFuture future = new HttpResponseFuture( 206 | mConf.requestTimeoutInMs, request); 207 | boolean ssl = "https".equals(uri.getScheme()); 208 | 209 | try { 210 | InetSocketAddress addr = proxy.type() == Type.DIRECT ? new InetSocketAddress( 211 | InetAddress.getByName(uri.getHost()), getPort(uri)) 212 | : (InetSocketAddress) proxy.address(); 213 | 214 | ClientBootstrap bootstrap = ssl ? mHttpsBootstrap 215 | : mHttpBootstrap; 216 | ChannelFuture cf = bootstrap.connect(addr); 217 | Channel ch = cf.getChannel(); 218 | future.setChannel(ch); 219 | ch.getPipeline().getContext(Decoder.class).setAttachment(future); 220 | cf.addListener(new ConnectionListener(future, uri, 221 | proxy.type() == Type.SOCKS)); 222 | mFutures.add(future); 223 | } catch (UnknownHostException e) { 224 | future.done(UNKOWN_HOST); 225 | } 226 | return future; 227 | } 228 | } 229 | 230 | class HttpClientPipelineFactory implements ChannelPipelineFactory { 231 | 232 | final int maxLength; 233 | final HttpClientConfig conf; 234 | 235 | public HttpClientPipelineFactory(int maxLength, HttpClientConfig conf) { 236 | this.maxLength = maxLength; 237 | this.conf = conf; 238 | } 239 | 240 | public ChannelPipeline getPipeline() throws Exception { 241 | ChannelPipeline pipeline = pipeline(); 242 | pipeline.addLast("decoder", new Decoder(conf)); 243 | pipeline.addLast("encoder", new HttpRequestEncoder()); 244 | pipeline.addLast("aggregator", new HttpChunkAggregator(maxLength)); 245 | pipeline.addLast("handler", new ResponseHandler()); 246 | return pipeline; 247 | } 248 | } 249 | 250 | class HttpsClientPipelineFactory implements ChannelPipelineFactory { 251 | 252 | final int maxLength; 253 | final HttpClientConfig conf; 254 | 255 | public HttpsClientPipelineFactory(int maxLength, HttpClientConfig conf) { 256 | this.maxLength = maxLength; 257 | this.conf = conf; 258 | } 259 | 260 | public ChannelPipeline getPipeline() throws Exception { 261 | ChannelPipeline pipeline = pipeline(); 262 | SSLEngine engine = SslContextFactory.getClientContext() 263 | .createSSLEngine(); 264 | engine.setUseClientMode(true); 265 | pipeline.addLast("ssl", new SslHandler(engine)); 266 | 267 | pipeline.addLast("decoder", new Decoder(conf)); 268 | pipeline.addLast("encoder", new HttpRequestEncoder()); 269 | pipeline.addLast("aggregator", new HttpChunkAggregator(maxLength)); 270 | pipeline.addLast("handler", new ResponseHandler()); 271 | return pipeline; 272 | } 273 | 274 | } 275 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/http/HttpClientConfig.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng.http; 2 | 3 | import java.util.List; 4 | 5 | import org.jboss.netty.handler.codec.http.HttpMessageDecoder; 6 | 7 | public class HttpClientConfig { 8 | 9 | protected List acceptedContentTypes; 10 | protected String bossNamePrefix = "Http Boss"; 11 | protected int connectionTimeOutInMs = 4500; 12 | protected int maxChunkSize = 32 * 1024; 13 | protected int maxLength = 1024 * 512; 14 | protected int receiveBuffer = 1024 * 32; 15 | protected int sendBuffer = 1024 * 32; 16 | protected int requestTimeoutInMs = 20000; 17 | protected int timerInterval = 1500; 18 | protected String userAgent = "Async-java/1.0.0"; 19 | 20 | protected String workerNamePrefix = "Http Worker"; 21 | protected int workerThread = 1; 22 | 23 | public HttpClientConfig() { 24 | } 25 | 26 | public void setAcceptedContentTypes(List acceptedContentTypes) { 27 | this.acceptedContentTypes = acceptedContentTypes; 28 | } 29 | 30 | public void setBossNamePrefix(String bossNamePrefix) { 31 | this.bossNamePrefix = bossNamePrefix; 32 | } 33 | 34 | public void setConnectionTimeOutInMs(int connectionTimeOutInMs) { 35 | this.connectionTimeOutInMs = connectionTimeOutInMs; 36 | } 37 | 38 | /** 39 | * @see HttpMessageDecoder 40 | */ 41 | public void setMaxChunkSize(int maxChunkSize) { 42 | this.maxChunkSize = maxChunkSize; 43 | } 44 | 45 | public void setMaxLength(int maxLength) { 46 | this.maxLength = maxLength; 47 | } 48 | 49 | public void setReceiveBuffer(int receiveBuffer) { 50 | this.receiveBuffer = receiveBuffer; 51 | } 52 | 53 | public void setRequestTimeoutInMs(int requestTimeoutInMs) { 54 | this.requestTimeoutInMs = requestTimeoutInMs; 55 | } 56 | 57 | public void setSendBuffer(int sendBuffer) { 58 | this.sendBuffer = sendBuffer; 59 | } 60 | 61 | public void setTimerInterval(int timerInterval) { 62 | this.timerInterval = timerInterval; 63 | } 64 | 65 | public void setUserAgent(String userAgent) { 66 | this.userAgent = userAgent; 67 | } 68 | 69 | public void setWorkerNamePrefix(String workerNamePrefix) { 70 | this.workerNamePrefix = workerNamePrefix; 71 | } 72 | 73 | public void setWorkerThread(int workerThread) { 74 | this.workerThread = workerThread; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/http/HttpClientConstant.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng.http; 2 | 3 | import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1; 4 | 5 | import org.jboss.netty.handler.codec.http.DefaultHttpResponse; 6 | import org.jboss.netty.handler.codec.http.HttpResponse; 7 | import org.jboss.netty.handler.codec.http.HttpResponseStatus; 8 | 9 | /** 10 | * home defined http status 11 | * 12 | * @author feng 13 | * 14 | */ 15 | public interface HttpClientConstant { 16 | 17 | final static HttpResponse UNKOWN_ERROR = new DefaultHttpResponse( 18 | HTTP_1_1, new HttpResponseStatus(150, "unknow error")); 19 | 20 | final static HttpResponse UNKOWN_HOST = new DefaultHttpResponse(HTTP_1_1, 21 | new HttpResponseStatus(160, "unknow host")); 22 | 23 | final static HttpResponse CONN_ERROR = new DefaultHttpResponse(HTTP_1_1, 24 | new HttpResponseStatus(170, "connecton error")); 25 | final static HttpResponse CONN_TIMEOUT = new DefaultHttpResponse( 26 | HTTP_1_1, new HttpResponseStatus(172, "connecton timeout")); 27 | final static HttpResponse CONN_RESET = new DefaultHttpResponse(HTTP_1_1, 28 | new HttpResponseStatus(175, "connecton reset")); 29 | 30 | final static HttpResponse NULL_LOCATION = new DefaultHttpResponse( 31 | HTTP_1_1, new HttpResponseStatus(181, "null location")); 32 | final static HttpResponse BAD_URL = new DefaultHttpResponse(HTTP_1_1, 33 | new HttpResponseStatus(182, "bad url")); 34 | final static HttpResponse IGNORED_URL = new DefaultHttpResponse(HTTP_1_1, 35 | new HttpResponseStatus(183, "ignored url")); 36 | 37 | final static HttpResponse UNKNOWN_CONTENT = new DefaultHttpResponse( 38 | HTTP_1_1, new HttpResponseStatus(190, "unknow content type")); 39 | 40 | final static HttpResponse ABORT = new DefaultHttpResponse(HTTP_1_1, 41 | new HttpResponseStatus(471, "client abort")); 42 | 43 | final static HttpResponse TOO_LARGE = new DefaultHttpResponse(HTTP_1_1, 44 | new HttpResponseStatus(513, "body too large")); 45 | final static HttpResponse TIMEOUT = new DefaultHttpResponse(HTTP_1_1, 46 | new HttpResponseStatus(520, "server timeout")); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/http/HttpResponseFuture.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng.http; 2 | 3 | import static java.lang.System.currentTimeMillis; 4 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 5 | import static me.shenfeng.http.HttpClientConstant.CONN_ERROR; 6 | import static me.shenfeng.http.HttpClientConstant.CONN_RESET; 7 | import static me.shenfeng.http.HttpClientConstant.CONN_TIMEOUT; 8 | import static me.shenfeng.http.HttpClientConstant.TIMEOUT; 9 | import static me.shenfeng.http.HttpClientConstant.TOO_LARGE; 10 | import static me.shenfeng.http.HttpClientConstant.UNKOWN_ERROR; 11 | 12 | import java.io.IOException; 13 | import java.net.ConnectException; 14 | import java.util.concurrent.ExecutionException; 15 | import java.util.concurrent.TimeUnit; 16 | import java.util.concurrent.TimeoutException; 17 | 18 | import me.shenfeng.AbstractResponseFuture; 19 | 20 | import org.jboss.netty.channel.Channel; 21 | import org.jboss.netty.handler.codec.frame.TooLongFrameException; 22 | import org.jboss.netty.handler.codec.http.HttpRequest; 23 | import org.jboss.netty.handler.codec.http.HttpResponse; 24 | 25 | public class HttpResponseFuture extends AbstractResponseFuture { 26 | 27 | private volatile Object mAttachment; 28 | private volatile Channel mChannel; 29 | private volatile long mTouchTime; 30 | private final int mTimeout; 31 | final HttpRequest request; // package private 32 | 33 | public HttpResponseFuture(int timeout, HttpRequest request) { 34 | mTimeout = timeout; 35 | this.request = request; 36 | mTouchTime = currentTimeMillis(); 37 | } 38 | 39 | public boolean isTimeout() { 40 | if (mTimeout + mTouchTime - currentTimeMillis() < 0) { 41 | return done(TIMEOUT); 42 | } 43 | return false; 44 | } 45 | 46 | public boolean done(HttpResponse result) { 47 | if (mChannel != null) 48 | mChannel.close(); 49 | return super.done(result); 50 | } 51 | 52 | public void setAttachment(Object att) { 53 | mAttachment = att; 54 | } 55 | 56 | public Object getAttachment() { 57 | return mAttachment; 58 | } 59 | 60 | public boolean abort(Throwable t) { 61 | HttpResponse resp = UNKOWN_ERROR; 62 | String msg = t.getMessage(); 63 | if (t instanceof TooLongFrameException) { 64 | resp = TOO_LARGE; 65 | } else if (t instanceof ConnectException) { 66 | if (msg != null && msg.indexOf("timed out") != -1) 67 | resp = CONN_TIMEOUT; 68 | else 69 | resp = CONN_ERROR; 70 | } else if (t instanceof IOException && msg != null 71 | && msg.indexOf("reset") != -1) { 72 | resp = CONN_RESET; 73 | } 74 | return done(resp); 75 | } 76 | 77 | public HttpResponse get() throws InterruptedException, ExecutionException { 78 | try { 79 | return get(mTimeout, MILLISECONDS); 80 | } catch (TimeoutException e) { 81 | // ignore 82 | return TIMEOUT; 83 | } 84 | } 85 | 86 | public HttpResponse get(long timeout, TimeUnit unit) 87 | throws InterruptedException, ExecutionException, TimeoutException { 88 | long time = unit.toMillis(timeout); 89 | long wait = time + mTouchTime - currentTimeMillis(); 90 | while (mLatch.getCount() > 0 && wait > 0) { 91 | mLatch.await(wait, MILLISECONDS); 92 | wait = time + mTouchTime - currentTimeMillis(); 93 | } 94 | done(TIMEOUT); 95 | return mResult.get(); 96 | } 97 | 98 | public void setChannel(Channel ch) { 99 | mChannel = ch; 100 | } 101 | 102 | public void touch() { 103 | mTouchTime = currentTimeMillis(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/http/ResponseHandler.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng.http; 2 | 3 | import org.jboss.netty.channel.ChannelHandlerContext; 4 | import org.jboss.netty.channel.ExceptionEvent; 5 | import org.jboss.netty.channel.MessageEvent; 6 | import org.jboss.netty.channel.SimpleChannelUpstreamHandler; 7 | import org.jboss.netty.handler.codec.http.HttpResponse; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | public class ResponseHandler extends SimpleChannelUpstreamHandler { 12 | 13 | private static Logger logger = LoggerFactory 14 | .getLogger(ResponseHandler.class); 15 | 16 | public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) 17 | throws Exception { 18 | ctx.getChannel().close(); 19 | Throwable cause = e.getCause(); 20 | HttpResponseFuture future = (HttpResponseFuture) ctx.getAttachment(); 21 | if (future != null) { 22 | logger.trace(future.request.toString(), cause); 23 | future.abort(cause); 24 | } else { 25 | logger.trace(cause.getMessage(), cause); 26 | } 27 | } 28 | 29 | public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) 30 | throws Exception { 31 | HttpResponseFuture future = (HttpResponseFuture) ctx.getAttachment(); 32 | HttpResponse response = (HttpResponse) e.getMessage(); 33 | ctx.getChannel().close(); 34 | future.done(response); 35 | } 36 | } -------------------------------------------------------------------------------- /src/java/me/shenfeng/http/SocksHandler.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng.http; 2 | 3 | import static java.net.InetAddress.getByName; 4 | import static me.shenfeng.Utils.getPort; 5 | 6 | import java.net.URI; 7 | 8 | import org.jboss.netty.buffer.ChannelBuffer; 9 | import org.jboss.netty.buffer.ChannelBuffers; 10 | import org.jboss.netty.channel.Channel; 11 | import org.jboss.netty.channel.ChannelHandlerContext; 12 | import org.jboss.netty.channel.MessageEvent; 13 | import org.jboss.netty.channel.SimpleChannelUpstreamHandler; 14 | 15 | public class SocksHandler extends SimpleChannelUpstreamHandler { 16 | static final byte PROTO_VER5 = 5; 17 | static final byte CONNECT = 1; 18 | static final byte NO_AUTH = 0; 19 | static final byte IPV4 = 1; 20 | 21 | static final byte[] VERSION_AUTH = new byte[] { PROTO_VER5, 1, NO_AUTH }; 22 | static final byte[] CON = new byte[] { PROTO_VER5, CONNECT, 0, IPV4 }; 23 | 24 | private State state = State.INIT; 25 | private final HttpResponseFuture mFuture; 26 | private final URI mUri; 27 | 28 | static enum State { 29 | INIT, CON_SENT 30 | } 31 | 32 | public SocksHandler(HttpResponseFuture future, URI uri) { 33 | mFuture = future; 34 | mUri = uri; 35 | } 36 | 37 | public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) 38 | throws Exception { 39 | ChannelBuffer buffer = (ChannelBuffer) e.getMessage(); 40 | Channel channel = ctx.getChannel(); 41 | if (state == State.INIT && buffer.readableBytes() == 2) { 42 | buffer.readByte(); 43 | buffer.readByte(); 44 | 45 | ChannelBuffer send = ChannelBuffers.buffer(10); 46 | send.writeBytes(CON); 47 | send.writeBytes(getByName(mUri.getHost()).getAddress()); 48 | send.writeShort(getPort(mUri)); 49 | ctx.getChannel().write(send); 50 | 51 | state = State.CON_SENT; 52 | } else if (state == State.CON_SENT && buffer.readableBytes() == 10) { 53 | byte[] data = new byte[10]; 54 | buffer.readBytes(data); 55 | if (data[1] != 0) { 56 | mFuture.done(HttpClientConstant.UNKOWN_ERROR); 57 | channel.close(); 58 | } else { 59 | channel.write(mFuture.request); 60 | } 61 | } else { 62 | ctx.getPipeline().remove(this); 63 | ctx.getPipeline().sendUpstream(e); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/ssl/SslContextFactory.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng.ssl; 2 | 3 | import javax.net.ssl.SSLContext; 4 | 5 | /** 6 | * copied from netty 7 | * 8 | */ 9 | public class SslContextFactory { 10 | 11 | private static final String PROTOCOL = "TLS"; 12 | private static final SSLContext CLIENT_CONTEXT; 13 | 14 | static { 15 | SSLContext clientContext = null; 16 | 17 | try { 18 | clientContext = SSLContext.getInstance(PROTOCOL); 19 | clientContext.init(null, TrustManagerFactory.getTrustManagers(), 20 | null); 21 | } catch (Exception e) { 22 | throw new Error( 23 | "Failed to initialize the client-side SSLContext", e); 24 | } 25 | 26 | CLIENT_CONTEXT = clientContext; 27 | } 28 | 29 | public static SSLContext getClientContext() { 30 | return CLIENT_CONTEXT; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/ssl/SslKeyStore.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng.ssl; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.InputStream; 5 | 6 | /** 7 | * copied from netty 8 | * 9 | */ 10 | public class SslKeyStore { 11 | private static final short[] DATA = new short[] { 0xfe, 0xed, 0xfe, 0xed, 12 | 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 13 | 0x01, 0x00, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x00, 14 | 0x00, 0x01, 0x1a, 0x9f, 0x57, 0xa5, 0x27, 0x00, 0x00, 0x01, 0x9a, 15 | 0x30, 0x82, 0x01, 0x96, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 16 | 0x04, 0x01, 0x2a, 0x02, 0x11, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 17 | 0x01, 0x82, 0x48, 0x6d, 0xcf, 0x16, 0xb5, 0x50, 0x95, 0x36, 0xbf, 18 | 0x47, 0x27, 0x50, 0x58, 0x0d, 0xa2, 0x52, 0x7e, 0x25, 0xab, 0x14, 19 | 0x1a, 0x26, 0x5e, 0x2d, 0x8a, 0x23, 0x90, 0x60, 0x7f, 0x12, 0x20, 20 | 0x56, 0xd1, 0x43, 0xa2, 0x6b, 0x47, 0x5d, 0xed, 0x9d, 0xd4, 0xe5, 21 | 0x83, 0x28, 0x89, 0xc2, 0x16, 0x4c, 0x76, 0x06, 0xad, 0x8e, 0x8c, 22 | 0x29, 0x1a, 0x9b, 0x0f, 0xdd, 0x60, 0x4b, 0xb4, 0x62, 0x82, 0x9e, 23 | 0x4a, 0x63, 0x83, 0x2e, 0xd2, 0x43, 0x78, 0xc2, 0x32, 0x1f, 0x60, 24 | 0xa9, 0x8a, 0x7f, 0x0f, 0x7c, 0xa6, 0x1d, 0xe6, 0x92, 0x9e, 0x52, 25 | 0xc7, 0x7d, 0xbb, 0x35, 0x3b, 0xaa, 0x89, 0x73, 0x4c, 0xfb, 0x99, 26 | 0x54, 0x97, 0x99, 0x28, 0x6e, 0x66, 0x5b, 0xf7, 0x9b, 0x7e, 0x6d, 27 | 0x8a, 0x2f, 0xfa, 0xc3, 0x1e, 0x71, 0xb9, 0xbd, 0x8f, 0xc5, 0x63, 28 | 0x25, 0x31, 0x20, 0x02, 0xff, 0x02, 0xf0, 0xc9, 0x2c, 0xdd, 0x3a, 29 | 0x10, 0x30, 0xab, 0xe5, 0xad, 0x3d, 0x1a, 0x82, 0x77, 0x46, 0xed, 30 | 0x03, 0x38, 0xa4, 0x73, 0x6d, 0x36, 0x36, 0x33, 0x70, 0xb2, 0x63, 31 | 0x20, 0xca, 0x03, 0xbf, 0x5a, 0xf4, 0x7c, 0x35, 0xf0, 0x63, 0x1a, 32 | 0x12, 0x33, 0x12, 0x58, 0xd9, 0xa2, 0x63, 0x6b, 0x63, 0x82, 0x41, 33 | 0x65, 0x70, 0x37, 0x4b, 0x99, 0x04, 0x9f, 0xdd, 0x5e, 0x07, 0x01, 34 | 0x95, 0x9f, 0x36, 0xe8, 0xc3, 0x66, 0x2a, 0x21, 0x69, 0x68, 0x40, 35 | 0xe6, 0xbc, 0xbb, 0x85, 0x81, 0x21, 0x13, 0xe6, 0xa4, 0xcf, 0xd3, 36 | 0x67, 0xe3, 0xfd, 0x75, 0xf0, 0xdf, 0x83, 0xe0, 0xc5, 0x36, 0x09, 37 | 0xac, 0x1b, 0xd4, 0xf7, 0x2a, 0x23, 0x57, 0x1c, 0x5c, 0x0f, 0xf4, 38 | 0xcf, 0xa2, 0xcf, 0xf5, 0xbd, 0x9c, 0x69, 0x98, 0x78, 0x3a, 0x25, 39 | 0xe4, 0xfd, 0x85, 0x11, 0xcc, 0x7d, 0xef, 0xeb, 0x74, 0x60, 0xb1, 40 | 0xb7, 0xfb, 0x1f, 0x0e, 0x62, 0xff, 0xfe, 0x09, 0x0a, 0xc3, 0x80, 41 | 0x2f, 0x10, 0x49, 0x89, 0x78, 0xd2, 0x08, 0xfa, 0x89, 0x22, 0x45, 42 | 0x91, 0x21, 0xbc, 0x90, 0x3e, 0xad, 0xb3, 0x0a, 0xb4, 0x0e, 0x1c, 43 | 0xa1, 0x93, 0x92, 0xd8, 0x72, 0x07, 0x54, 0x60, 0xe7, 0x91, 0xfc, 44 | 0xd9, 0x3c, 0xe1, 0x6f, 0x08, 0xe4, 0x56, 0xf6, 0x0b, 0xb0, 0x3c, 45 | 0x39, 0x8a, 0x2d, 0x48, 0x44, 0x28, 0x13, 0xca, 0xe9, 0xf7, 0xa3, 46 | 0xb6, 0x8a, 0x5f, 0x31, 0xa9, 0x72, 0xf2, 0xde, 0x96, 0xf2, 0xb1, 47 | 0x53, 0xb1, 0x3e, 0x24, 0x57, 0xfd, 0x18, 0x45, 0x1f, 0xc5, 0x33, 48 | 0x1b, 0xa4, 0xe8, 0x21, 0xfa, 0x0e, 0xb2, 0xb9, 0xcb, 0xc7, 0x07, 49 | 0x41, 0xdd, 0x2f, 0xb6, 0x6a, 0x23, 0x18, 0xed, 0xc1, 0xef, 0xe2, 50 | 0x4b, 0xec, 0xc9, 0xba, 0xfb, 0x46, 0x43, 0x90, 0xd7, 0xb5, 0x68, 51 | 0x28, 0x31, 0x2b, 0x8d, 0xa8, 0x51, 0x63, 0xf7, 0x53, 0x99, 0x19, 52 | 0x68, 0x85, 0x66, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x58, 0x2e, 53 | 0x35, 0x30, 0x39, 0x00, 0x00, 0x02, 0x3a, 0x30, 0x82, 0x02, 0x36, 54 | 0x30, 0x82, 0x01, 0xe0, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x04, 55 | 0x48, 0x59, 0xf1, 0x92, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 56 | 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, 0xa0, 57 | 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 58 | 0x4b, 0x52, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 59 | 0x13, 0x0a, 0x4b, 0x79, 0x75, 0x6e, 0x67, 0x67, 0x69, 0x2d, 0x64, 60 | 0x6f, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 61 | 0x0b, 0x53, 0x65, 0x6f, 0x6e, 0x67, 0x6e, 0x61, 0x6d, 0x2d, 0x73, 62 | 0x69, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 63 | 0x11, 0x54, 0x68, 0x65, 0x20, 0x4e, 0x65, 0x74, 0x74, 0x79, 0x20, 64 | 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x31, 0x18, 0x30, 0x16, 65 | 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0f, 0x45, 0x78, 0x61, 0x6d, 66 | 0x70, 0x6c, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x73, 67 | 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x27, 68 | 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x63, 0x68, 0x61, 0x74, 0x2e, 69 | 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, 70 | 0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65, 0x61, 0x6d, 0x79, 0x6e, 0x6f, 71 | 0x64, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x20, 0x17, 0x0d, 0x30, 72 | 0x38, 0x30, 0x36, 0x31, 0x39, 0x30, 0x35, 0x34, 0x31, 0x33, 0x38, 73 | 0x5a, 0x18, 0x0f, 0x32, 0x31, 0x38, 0x37, 0x31, 0x31, 0x32, 0x34, 74 | 0x30, 0x35, 0x34, 0x31, 0x33, 0x38, 0x5a, 0x30, 0x81, 0xa0, 0x31, 75 | 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4b, 76 | 0x52, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 77 | 0x0a, 0x4b, 0x79, 0x75, 0x6e, 0x67, 0x67, 0x69, 0x2d, 0x64, 0x6f, 78 | 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0b, 79 | 0x53, 0x65, 0x6f, 0x6e, 0x67, 0x6e, 0x61, 0x6d, 0x2d, 0x73, 0x69, 80 | 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 81 | 0x54, 0x68, 0x65, 0x20, 0x4e, 0x65, 0x74, 0x74, 0x79, 0x20, 0x50, 82 | 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x31, 0x18, 0x30, 0x16, 0x06, 83 | 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0f, 0x45, 0x78, 0x61, 0x6d, 0x70, 84 | 0x6c, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x73, 0x31, 85 | 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x27, 0x73, 86 | 0x65, 0x63, 0x75, 0x72, 0x65, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x65, 87 | 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x74, 88 | 0x79, 0x2e, 0x67, 0x6c, 0x65, 0x61, 0x6d, 0x79, 0x6e, 0x6f, 0x64, 89 | 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x5c, 0x30, 0x0d, 0x06, 0x09, 90 | 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 91 | 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41, 0x00, 0xc3, 0xe3, 0x5e, 92 | 0x41, 0xa7, 0x87, 0x11, 0x00, 0x42, 0x2a, 0xb0, 0x4b, 0xed, 0xb2, 93 | 0xe0, 0x23, 0xdb, 0xb1, 0x3d, 0x58, 0x97, 0x35, 0x60, 0x0b, 0x82, 94 | 0x59, 0xd3, 0x00, 0xea, 0xd4, 0x61, 0xb8, 0x79, 0x3f, 0xb6, 0x3c, 95 | 0x12, 0x05, 0x93, 0x2e, 0x9a, 0x59, 0x68, 0x14, 0x77, 0x3a, 0xc8, 96 | 0x50, 0x25, 0x57, 0xa4, 0x49, 0x18, 0x63, 0x41, 0xf0, 0x2d, 0x28, 97 | 0xec, 0x06, 0xfb, 0xb4, 0x9f, 0xbf, 0x02, 0x03, 0x01, 0x00, 0x01, 98 | 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 99 | 0x01, 0x05, 0x05, 0x00, 0x03, 0x41, 0x00, 0x65, 0x6c, 0x30, 0x01, 100 | 0xc2, 0x8e, 0x3e, 0xcb, 0xb3, 0x77, 0x48, 0xe9, 0x66, 0x61, 0x9a, 101 | 0x40, 0x86, 0xaf, 0xf6, 0x03, 0xeb, 0xba, 0x6a, 0xf2, 0xfd, 0xe2, 102 | 0xaf, 0x36, 0x5e, 0x7b, 0xaa, 0x22, 0x04, 0xdd, 0x2c, 0x20, 0xc4, 103 | 0xfc, 0xdd, 0xd0, 0x82, 0x20, 0x1c, 0x3d, 0xd7, 0x9e, 0x5e, 0x5c, 104 | 0x92, 0x5a, 0x76, 0x71, 0x28, 0xf5, 0x07, 0x7d, 0xa2, 0x81, 0xba, 105 | 0x77, 0x9f, 0x2a, 0xd9, 0x44, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 106 | 0x6d, 0x79, 0x6b, 0x65, 0x79, 0x00, 0x00, 0x01, 0x1a, 0x9f, 0x5b, 107 | 0x56, 0xa0, 0x00, 0x00, 0x01, 0x99, 0x30, 0x82, 0x01, 0x95, 0x30, 108 | 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x2a, 0x02, 0x11, 109 | 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 0x01, 0x81, 0x29, 0xa8, 0xb6, 110 | 0x08, 0x0c, 0x85, 0x75, 0x3e, 0xdd, 0xb5, 0xe5, 0x1a, 0x87, 0x68, 111 | 0xd1, 0x90, 0x4b, 0x29, 0x31, 0xee, 0x90, 0xbc, 0x9d, 0x73, 0xa0, 112 | 0x3f, 0xe9, 0x0b, 0xa4, 0xef, 0x30, 0x9b, 0x36, 0x9a, 0xb2, 0x54, 113 | 0x77, 0x81, 0x07, 0x4b, 0xaa, 0xa5, 0x77, 0x98, 0xe1, 0xeb, 0xb5, 114 | 0x7c, 0x4e, 0x48, 0xd5, 0x08, 0xfc, 0x2c, 0x36, 0xe2, 0x65, 0x03, 115 | 0xac, 0xe5, 0xf3, 0x96, 0xb7, 0xd0, 0xb5, 0x3b, 0x92, 0xe4, 0x14, 116 | 0x05, 0x7a, 0x6a, 0x92, 0x56, 0xfe, 0x4e, 0xab, 0xd3, 0x0e, 0x32, 117 | 0x04, 0x22, 0x22, 0x74, 0x47, 0x7d, 0xec, 0x21, 0x99, 0x30, 0x31, 118 | 0x64, 0x46, 0x64, 0x9b, 0xc7, 0x13, 0xbf, 0xbe, 0xd0, 0x31, 0x49, 119 | 0xe7, 0x3c, 0xbf, 0xba, 0xb1, 0x20, 0xf9, 0x42, 0xf4, 0xa9, 0xa9, 120 | 0xe5, 0x13, 0x65, 0x32, 0xbf, 0x7c, 0xcc, 0x91, 0xd3, 0xfd, 0x24, 121 | 0x47, 0x0b, 0xe5, 0x53, 0xad, 0x50, 0x30, 0x56, 0xd1, 0xfa, 0x9c, 122 | 0x37, 0xa8, 0xc1, 0xce, 0xf6, 0x0b, 0x18, 0xaa, 0x7c, 0xab, 0xbd, 123 | 0x1f, 0xdf, 0xe4, 0x80, 0xb8, 0xa7, 0xe0, 0xad, 0x7d, 0x50, 0x74, 124 | 0xf1, 0x98, 0x78, 0xbc, 0x58, 0xb9, 0xc2, 0x52, 0xbe, 0xd2, 0x5b, 125 | 0x81, 0x94, 0x83, 0x8f, 0xb9, 0x4c, 0xee, 0x01, 0x2b, 0x5e, 0xc9, 126 | 0x6e, 0x9b, 0xf5, 0x63, 0x69, 0xe4, 0xd8, 0x0b, 0x47, 0xd8, 0xfd, 127 | 0xd8, 0xe0, 0xed, 0xa8, 0x27, 0x03, 0x74, 0x1e, 0x5d, 0x32, 0xe6, 128 | 0x5c, 0x63, 0xc2, 0xfb, 0x3f, 0xee, 0xb4, 0x13, 0xc6, 0x0e, 0x6e, 129 | 0x74, 0xe0, 0x22, 0xac, 0xce, 0x79, 0xf9, 0x43, 0x68, 0xc1, 0x03, 130 | 0x74, 0x2b, 0xe1, 0x18, 0xf8, 0x7f, 0x76, 0x9a, 0xea, 0x82, 0x3f, 131 | 0xc2, 0xa6, 0xa7, 0x4c, 0xfe, 0xae, 0x29, 0x3b, 0xc1, 0x10, 0x7c, 132 | 0xd5, 0x77, 0x17, 0x79, 0x5f, 0xcb, 0xad, 0x1f, 0xd8, 0xa1, 0xfd, 133 | 0x90, 0xe1, 0x6b, 0xb2, 0xef, 0xb9, 0x41, 0x26, 0xa4, 0x0b, 0x4f, 134 | 0xc6, 0x83, 0x05, 0x6f, 0xf0, 0x64, 0x40, 0xe1, 0x44, 0xc4, 0xf9, 135 | 0x40, 0x2b, 0x3b, 0x40, 0xdb, 0xaf, 0x35, 0xa4, 0x9b, 0x9f, 0xc4, 136 | 0x74, 0x07, 0xe5, 0x18, 0x60, 0xc5, 0xfe, 0x15, 0x0e, 0x3a, 0x25, 137 | 0x2a, 0x11, 0xee, 0x78, 0x2f, 0xb8, 0xd1, 0x6e, 0x4e, 0x3c, 0x0a, 138 | 0xb5, 0xb9, 0x40, 0x86, 0x27, 0x6d, 0x8f, 0x53, 0xb7, 0x77, 0x36, 139 | 0xec, 0x5d, 0xed, 0x32, 0x40, 0x43, 0x82, 0xc3, 0x52, 0x58, 0xc4, 140 | 0x26, 0x39, 0xf3, 0xb3, 0xad, 0x58, 0xab, 0xb7, 0xf7, 0x8e, 0x0e, 141 | 0xba, 0x8e, 0x78, 0x9d, 0xbf, 0x58, 0x34, 0xbd, 0x77, 0x73, 0xa6, 142 | 0x50, 0x55, 0x00, 0x60, 0x26, 0xbf, 0x6d, 0xb4, 0x98, 0x8a, 0x18, 143 | 0x83, 0x89, 0xf8, 0xcd, 0x0d, 0x49, 0x06, 0xae, 0x51, 0x6e, 0xaf, 144 | 0xbd, 0xe2, 0x07, 0x13, 0xd8, 0x64, 0xcc, 0xbf, 0x00, 0x00, 0x00, 145 | 0x01, 0x00, 0x05, 0x58, 0x2e, 0x35, 0x30, 0x39, 0x00, 0x00, 0x02, 146 | 0x34, 0x30, 0x82, 0x02, 0x30, 0x30, 0x82, 0x01, 0xda, 0xa0, 0x03, 147 | 0x02, 0x01, 0x02, 0x02, 0x04, 0x48, 0x59, 0xf2, 0x84, 0x30, 0x0d, 148 | 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 149 | 0x05, 0x00, 0x30, 0x81, 0x9d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 150 | 0x55, 0x04, 0x06, 0x13, 0x02, 0x4b, 0x52, 0x31, 0x13, 0x30, 0x11, 151 | 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x4b, 0x79, 0x75, 0x6e, 152 | 0x67, 0x67, 0x69, 0x2d, 0x64, 0x6f, 0x31, 0x14, 0x30, 0x12, 0x06, 153 | 0x03, 0x55, 0x04, 0x07, 0x13, 0x0b, 0x53, 0x65, 0x6f, 0x6e, 0x67, 154 | 0x6e, 0x61, 0x6d, 0x2d, 0x73, 0x69, 0x31, 0x1a, 0x30, 0x18, 0x06, 155 | 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x54, 0x68, 0x65, 0x20, 0x4e, 156 | 0x65, 0x74, 0x74, 0x79, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 157 | 0x74, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 158 | 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x6f, 159 | 0x72, 0x73, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 160 | 0x13, 0x27, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x63, 0x68, 0x61, 161 | 0x74, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e, 162 | 0x65, 0x74, 0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65, 0x61, 0x6d, 0x79, 163 | 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x20, 0x17, 164 | 0x0d, 0x30, 0x38, 0x30, 0x36, 0x31, 0x39, 0x30, 0x35, 0x34, 0x35, 165 | 0x34, 0x30, 0x5a, 0x18, 0x0f, 0x32, 0x31, 0x38, 0x37, 0x31, 0x31, 166 | 0x32, 0x33, 0x30, 0x35, 0x34, 0x35, 0x34, 0x30, 0x5a, 0x30, 0x81, 167 | 0x9d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 168 | 0x02, 0x4b, 0x52, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 169 | 0x08, 0x13, 0x0a, 0x4b, 0x79, 0x75, 0x6e, 0x67, 0x67, 0x69, 0x2d, 170 | 0x64, 0x6f, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x07, 171 | 0x13, 0x0b, 0x53, 0x65, 0x6f, 0x6e, 0x67, 0x6e, 0x61, 0x6d, 0x2d, 172 | 0x73, 0x69, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 173 | 0x13, 0x11, 0x54, 0x68, 0x65, 0x20, 0x4e, 0x65, 0x74, 0x74, 0x79, 174 | 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x31, 0x15, 0x30, 175 | 0x13, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0c, 0x43, 0x6f, 0x6e, 176 | 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x73, 0x31, 0x30, 177 | 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x27, 0x73, 0x65, 178 | 0x63, 0x75, 0x72, 0x65, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x65, 0x78, 179 | 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x74, 0x79, 180 | 0x2e, 0x67, 0x6c, 0x65, 0x61, 0x6d, 0x79, 0x6e, 0x6f, 0x64, 0x65, 181 | 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 182 | 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 183 | 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41, 0x00, 0x95, 0xb3, 0x47, 0x17, 184 | 0x95, 0x0f, 0x57, 0xcf, 0x66, 0x72, 0x0a, 0x7e, 0x5b, 0x54, 0xea, 185 | 0x8c, 0x6f, 0x79, 0xde, 0x94, 0xac, 0x0b, 0x5a, 0xd4, 0xd6, 0x1b, 186 | 0x58, 0x12, 0x1a, 0x16, 0x3d, 0xfe, 0xdf, 0xa5, 0x2b, 0x86, 0xbc, 187 | 0x64, 0xd4, 0x80, 0x1e, 0x3f, 0xf9, 0xe2, 0x04, 0x03, 0x79, 0x9b, 188 | 0xc1, 0x5c, 0xf0, 0xf1, 0xf3, 0xf1, 0xe3, 0xbf, 0x3f, 0xc0, 0x1f, 189 | 0xdd, 0xdb, 0xc0, 0x5b, 0x21, 0x02, 0x03, 0x01, 0x00, 0x01, 0x30, 190 | 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 191 | 0x05, 0x05, 0x00, 0x03, 0x41, 0x00, 0x02, 0xd7, 0xdd, 0xbd, 0x0c, 192 | 0x8e, 0x21, 0x20, 0xef, 0x9e, 0x4f, 0x1f, 0xf5, 0x49, 0xf1, 0xae, 193 | 0x58, 0x9b, 0x94, 0x3a, 0x1f, 0x70, 0x33, 0xf0, 0x9b, 0xbb, 0xe9, 194 | 0xc0, 0xf3, 0x72, 0xcb, 0xde, 0xb6, 0x56, 0x72, 0xcc, 0x1c, 0xf0, 195 | 0xd6, 0x5a, 0x2a, 0xbc, 0xa1, 0x7e, 0x23, 0x83, 0xe9, 0xe7, 0xcf, 196 | 0x9e, 0xa5, 0xf9, 0xcc, 0xc2, 0x61, 0xf4, 0xdb, 0x40, 0x93, 0x1d, 197 | 0x63, 0x8a, 0x50, 0x4c, 0x11, 0x39, 0xb1, 0x91, 0xc1, 0xe6, 0x9d, 198 | 0xd9, 0x1a, 0x62, 0x1b, 0xb8, 0xd3, 0xd6, 0x9a, 0x6d, 0xb9, 0x8e, 199 | 0x15, 0x51 }; 200 | 201 | public static InputStream asInputStream() { 202 | byte[] data = new byte[DATA.length]; 203 | for (int i = 0; i < data.length; i++) { 204 | data[i] = (byte) DATA[i]; 205 | } 206 | return new ByteArrayInputStream(data); 207 | } 208 | 209 | public static char[] getCertificatePassword() { 210 | return "secret".toCharArray(); 211 | } 212 | 213 | public static char[] getKeyStorePassword() { 214 | return "secret".toCharArray(); 215 | } 216 | 217 | private SslKeyStore() { 218 | // Unused 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/java/me/shenfeng/ssl/TrustManagerFactory.java: -------------------------------------------------------------------------------- 1 | package me.shenfeng.ssl; 2 | 3 | /** 4 | * copy from netty 5 | */ 6 | import java.security.InvalidAlgorithmParameterException; 7 | import java.security.KeyStore; 8 | import java.security.KeyStoreException; 9 | import java.security.cert.CertificateException; 10 | import java.security.cert.X509Certificate; 11 | 12 | import javax.net.ssl.ManagerFactoryParameters; 13 | import javax.net.ssl.TrustManager; 14 | import javax.net.ssl.TrustManagerFactorySpi; 15 | import javax.net.ssl.X509TrustManager; 16 | 17 | public class TrustManagerFactory extends TrustManagerFactorySpi { 18 | 19 | private static final TrustManager DUMMY_TRUST_MANAGER = new X509TrustManager() { 20 | public X509Certificate[] getAcceptedIssuers() { 21 | return new X509Certificate[0]; 22 | } 23 | 24 | public void checkClientTrusted(X509Certificate[] chain, 25 | String authType) throws CertificateException { 26 | // Always trust 27 | } 28 | 29 | public void checkServerTrusted(X509Certificate[] chain, 30 | String authType) throws CertificateException { 31 | // Always trust 32 | } 33 | }; 34 | 35 | public static TrustManager[] getTrustManagers() { 36 | return new TrustManager[] { DUMMY_TRUST_MANAGER }; 37 | } 38 | 39 | @Override 40 | protected TrustManager[] engineGetTrustManagers() { 41 | return getTrustManagers(); 42 | } 43 | 44 | @Override 45 | protected void engineInit(KeyStore keystore) throws KeyStoreException { 46 | // Unused 47 | } 48 | 49 | @Override 50 | protected void engineInit( 51 | ManagerFactoryParameters managerFactoryParameters) 52 | throws InvalidAlgorithmParameterException { 53 | // Unused 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/java/DnsPrefetcherTest.java: -------------------------------------------------------------------------------- 1 | import java.io.IOException; 2 | 3 | import me.shenfeng.dns.DnsPrefecher; 4 | 5 | import org.junit.Test; 6 | 7 | public class DnsPrefetcherTest { 8 | 9 | @Test 10 | public void testPrefetch() throws IOException { 11 | DnsPrefecher prefecher = DnsPrefecher.getInstance(); 12 | prefecher.prefetch("google.com"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/java/HttpClientTest.java: -------------------------------------------------------------------------------- 1 | import java.net.InetSocketAddress; 2 | import java.net.Proxy; 3 | import java.net.Proxy.Type; 4 | import java.net.URI; 5 | import java.net.URISyntaxException; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.concurrent.ExecutionException; 9 | import java.util.concurrent.Semaphore; 10 | 11 | import me.shenfeng.Utils; 12 | import me.shenfeng.http.HttpClient; 13 | import me.shenfeng.http.HttpClientConfig; 14 | import me.shenfeng.http.HttpResponseFuture; 15 | 16 | import org.jboss.netty.handler.codec.http.HttpResponse; 17 | import org.junit.Before; 18 | import org.junit.Test; 19 | 20 | public class HttpClientTest { 21 | 22 | HttpClient client; 23 | Proxy proxy; 24 | Map header; 25 | String urls[] = new String[] { "http://192.168.1.1:8088/", 26 | "http://192.168.1.1:8088/c.html", 27 | "http://192.168.1.1:8088/qunar.html", 28 | "http://192.168.1.1:8088/CHANGES", }; 29 | 30 | @Before 31 | public void setup() { 32 | HttpClientConfig config = new HttpClientConfig(); 33 | header = new HashMap(); 34 | client = new HttpClient(config); 35 | proxy = new Proxy(Type.HTTP, new InetSocketAddress("127.0.0.1", 3128)); 36 | } 37 | 38 | @Test 39 | public void testProxy() throws Exception { 40 | final URI uri = new URI("http://www.facebook.com"); 41 | final HttpResponseFuture resp = client.execGet(uri, header, proxy); 42 | HttpResponse r = resp.get(); 43 | System.out.println(r); 44 | } 45 | 46 | @Test 47 | public void testHttps() throws URISyntaxException, InterruptedException, 48 | ExecutionException { 49 | URI uri = new URI("https://trakrapp.com"); 50 | HttpResponseFuture future = client.execGet(uri, header); 51 | System.out.println(future.get()); 52 | System.out.println(Utils.bodyStr(future.get())); 53 | } 54 | 55 | @Test 56 | public void testHttpAndHttps() throws InterruptedException, 57 | ExecutionException, URISyntaxException { 58 | String[] urls = new String[] { "http://www.baidu.com", 59 | "https://www.google.com", "http://shenfeng.me", 60 | "https://trakrapp.com", "https://github.com" }; 61 | for (String url : urls) { 62 | URI uri = new URI(url); 63 | HttpResponseFuture future = client.execGet(uri, header); 64 | System.out.println(future.get()); 65 | System.out.println(Utils.bodyStr(future.get()).length() + "\t" 66 | + url); 67 | } 68 | } 69 | 70 | @Test 71 | public void testNoProxy() throws Exception { 72 | final URI uri = new URI("http://github.com"); 73 | final HttpResponseFuture resp = client.execGet(uri, header); 74 | HttpResponse r = resp.get(); 75 | String s = Utils.bodyStr(r); 76 | System.out.println(s); 77 | } 78 | 79 | @Test 80 | public void testCheckTimeout() throws Exception { 81 | for (int i = 0; i < 10; i++) { 82 | final URI uri = new URI("http://shenfeng.me"); 83 | final HttpResponseFuture resp = client.execGet(uri, header); 84 | HttpResponse r = resp.get(); 85 | String s = Utils.bodyStr(r); 86 | System.out.println(s.length()); 87 | } 88 | } 89 | 90 | @Test 91 | public void testPotentialError() throws URISyntaxException, 92 | InterruptedException, ExecutionException { 93 | for (int i = 0; i < 1; ++i) { 94 | final URI uri = new URI("http://shenfeng.me"); 95 | final HttpResponseFuture get = client.execGet(uri, header); 96 | get.addListener(new Runnable() { 97 | public void run() { 98 | try { 99 | HttpResponse resp = get.get(); 100 | System.out.println(resp); 101 | // System.out.println(Utils.bodyString(resp)); 102 | } catch (Exception e) { 103 | } 104 | } 105 | }); 106 | get.get(); 107 | } 108 | // Thread.sleep(210000); 109 | } 110 | 111 | @Test 112 | public void testConnectionRefused() throws URISyntaxException, 113 | InterruptedException, ExecutionException { 114 | HttpResponseFuture resp = client.execGet(new URI("http://127.0.0.1"), 115 | header); 116 | 117 | resp.get(); 118 | 119 | // Thread.sleep(10000000); 120 | } 121 | 122 | @Test 123 | public void testGetBigFile() throws URISyntaxException, 124 | InterruptedException, ExecutionException { 125 | 126 | HttpResponseFuture resp = client.execGet(new URI( 127 | "http://192.168.1.1/videos/AdbeRdr940_zh_CN.exe"), header); 128 | 129 | resp.get(); 130 | // Thread.sleep(10000000); 131 | } 132 | 133 | @Test 134 | public void testAsyncGet() throws URISyntaxException, 135 | InterruptedException, ExecutionException { 136 | final Semaphore semaphore = new Semaphore(200); 137 | String[] urls = new String[] { "/", "/browse", "/css/browse.css", 138 | "/js/lib/jquery.js" }; 139 | 140 | while (true) { 141 | for (String url : urls) { 142 | final HttpResponseFuture resp = client.execGet(new URI( 143 | "http://127.0.0.1:8100" + url), header); 144 | resp.addListener(new Runnable() { 145 | public void run() { 146 | try { 147 | // System.out.println(resp.get().getStatus()); 148 | semaphore.release(); 149 | } catch (Exception e) { 150 | e.printStackTrace(); 151 | } 152 | } 153 | }); 154 | semaphore.acquire(); 155 | } 156 | } 157 | } 158 | 159 | @Test 160 | public void testAsyncGet2() throws URISyntaxException, 161 | InterruptedException, ExecutionException { 162 | String urls[] = new String[] { 163 | "http://news.sohu.com/upload/cs/cswb001/beida0909/index.html", 164 | "http://news.sohu.com/photo/", "http://baike.baidu.com/", 165 | "http://tieba.baidu.com/tb/index/v2/niuba.html", 166 | "http://web.qq.com/", "http://im.qq.com/webqq/", 167 | "http://news.sina.com.cn/society/", "http://www.baidu.com/" }; 168 | for (int i = 0; i < 10; ++i) { 169 | for (final String url : urls) { 170 | final HttpResponseFuture future = client.execGet( 171 | new URI(url), header); 172 | future.addListener(new Runnable() { 173 | public void run() { 174 | try { 175 | HttpResponse r = future.get(); 176 | // if (r.getStatus().getCode() == 200) 177 | System.out.println(r.getStatus() + "\t" 178 | + r.getContentLength() + "\t" + url); 179 | } catch (Exception e) { 180 | e.printStackTrace(); 181 | } 182 | } 183 | }); 184 | } 185 | } 186 | Thread.sleep(10000); 187 | } 188 | 189 | @Test 190 | public void testCRCerror() throws URISyntaxException, 191 | InterruptedException, ExecutionException { 192 | HttpResponseFuture f = client.execGet(new URI( 193 | "http://www.allshizuo.com/index.php"), header); 194 | HttpResponse resp = f.get(); 195 | String string = Utils.bodyStr(resp); 196 | System.out.println(string); 197 | } 198 | 199 | @Test 200 | public void testAsyncLoop() throws InterruptedException, 201 | ExecutionException, URISyntaxException { 202 | final Semaphore semaphore = new Semaphore(10); 203 | 204 | while (true) { 205 | for (String s : urls) { 206 | URI uri = new URI(s); 207 | HttpResponseFuture resp = client.execGet(uri, header); 208 | resp.addListener(new Runnable() { 209 | public void run() { 210 | // System.out.println("----------"); 211 | semaphore.release(); 212 | } 213 | }); 214 | semaphore.acquire(); 215 | } 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /test/java/TestSocket.java: -------------------------------------------------------------------------------- 1 | import java.io.IOException; 2 | import java.net.Socket; 3 | import java.net.UnknownHostException; 4 | 5 | import org.junit.Test; 6 | 7 | public class TestSocket { 8 | 9 | @Test 10 | public void testBuffer() throws UnknownHostException, IOException { 11 | Socket socket = new Socket("127.0.0.1", 8100); 12 | int buffer = socket.getSendBufferSize(); 13 | 14 | System.out.println(buffer / 1024); 15 | 16 | buffer = socket.getReceiveBufferSize(); 17 | 18 | System.out.println(buffer / 1024); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/java/UtilsTest.java: -------------------------------------------------------------------------------- 1 | import static org.jboss.netty.util.CharsetUtil.UTF_8; 2 | 3 | import java.net.URI; 4 | import java.net.URISyntaxException; 5 | import java.nio.charset.Charset; 6 | 7 | import me.shenfeng.Utils; 8 | 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | 12 | public class UtilsTest { 13 | 14 | @Test 15 | public void testGetPath() throws URISyntaxException { 16 | URI uri = new URI("http://shenfeng.me?a=b"); 17 | String path = Utils.getPath(uri); 18 | Assert.assertTrue("/?a=b".equals(path)); 19 | uri = new URI( 20 | "http://www.baidu.com/s?wd=%D5%AC%BC%B1%CB%CD&rsv_bp=0&inputT=3664"); 21 | Assert.assertNotSame("should equal", 22 | "s?wd=%D5%AC%BC%B1%CB%CD&rsv_bp=0&inputT=3664", 23 | Utils.getPath(uri)); 24 | } 25 | 26 | @Test 27 | public void testParseCharset() { 28 | Assert.assertEquals("default utf8", UTF_8, Utils.parseCharset(null)); 29 | Assert.assertEquals("parse gbk", Charset.forName("gbk"), 30 | Utils.parseCharset("text/html;charset=gbk")); 31 | Assert.assertEquals("parse gb2312", Charset.forName("gb2312"), 32 | Utils.parseCharset("text/html;charset=gb2312")); 33 | } 34 | 35 | @Test 36 | public void testGetPort() throws URISyntaxException { 37 | Assert.assertEquals(80, Utils.getPort(new URI("http://google.com"))); 38 | Assert.assertEquals(443, Utils.getPort(new URI("https://google.com"))); 39 | } 40 | 41 | @Test 42 | public void testBytes() { 43 | 44 | for (int i = 0; i < 256; ++i) { 45 | System.out.println(i + "\t" + (byte) i); 46 | } 47 | 48 | int[] numbs = new int[] { 0x00000f0f, 0x0000ffff }; 49 | byte[][] bytes = new byte[][] { new byte[] { 15, 15 }, 50 | new byte[] { -1, -1 } }; 51 | 52 | for (int i = 0; i < numbs.length; ++i) { 53 | int num = numbs[i]; 54 | byte[] byts = bytes[i]; 55 | Assert.assertArrayEquals(Utils.toBytes(num), byts); 56 | Assert.assertEquals(num, Utils.toInt(byts)); 57 | } 58 | } 59 | 60 | @Test 61 | public void testIsIP() { 62 | Assert.assertTrue(Utils.isIP("12.1.1.1")); 63 | Assert.assertTrue(!Utils.isIP("shenfeng.me")); 64 | 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /test/java/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/me/shenfeng/httpclient_test.clj: -------------------------------------------------------------------------------- 1 | (ns me.shenfeng.httpclient-test 2 | (:use clojure.test 3 | [ring.adapter.jetty :only [run-jetty]] 4 | (compojure [core :only [defroutes GET PUT PATCH DELETE POST HEAD DELETE ANY context]] 5 | [handler :only [site]] 6 | [route :only [not-found]])) 7 | (:import me.shenfeng.http.HttpClient 8 | java.net.URI 9 | me.shenfeng.http.HttpClientConfig)) 10 | 11 | (defroutes test-routes 12 | (GET "/get" [] "hello world") 13 | (POST "/post" [] "hello world")) 14 | 15 | (use-fixtures :once 16 | (fn [f] 17 | (let [jetty (run-jetty (site test-routes) {:port 14347 18 | :join? false})] 19 | (try (f) (finally (.stop jetty)))))) 20 | 21 | (deftest test-http-client 22 | (let [host "http://127.0.0.1:14347" 23 | client (HttpClient. (HttpClientConfig.))] 24 | (is (= 200 (.getCode (.getStatus (.get (.execGet client (str host "/get"))))))) 25 | (is (= 200 (.getCode (.getStatus (.get (.execPost client (URI. (str host "/post")) 26 | {} {})))))))) 27 | --------------------------------------------------------------------------------