package com.app.mvc.http; import com.app.mvc.config.GlobalConfig; import com.app.mvc.config.GlobalConfigKey; import com.app.mvc.http.ext.AuthSSLProtocolSocketFactory; import com.app.mvc.http.ext.EasySSLProtocolSocketFactory; import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HostConfiguration; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.HttpMethodRetryHandler; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.cookie.CookiePolicy; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.commons.httpclient.protocol.Protocol; import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; import javax.annotation.concurrent.NotThreadSafe; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.List; import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; /** * 封装了apache的HttpClient,简化同步,异步,https,代理设置,get/post请求 * Created by jimin on 16/03/10. */ @Slf4j @NotThreadSafe public abstract class AbstractHttpClient { static class DefaultConfig { final static String DEFAULT_USER_AGENT = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; InfoPath.2)"; final static String DEFAULT_CHARSET = "UTF-8"; final static int DEFAULT_SOCKET_TIME = 60000; final static int DEFAULT_CONN_TIME = 60000; final static HttpCallback DEFAULT_CALLBACK = new CallbackAdaptor(); final static String DEFAULT_COOKIE_POLICY = CookiePolicy.BROWSER_COMPATIBILITY; final static int DEFAULT_HTTPS_PORT = 443; } protected final HttpClient httpClient = new HttpClient(); protected HttpCallback callBack = DefaultConfig.DEFAULT_CALLBACK; protected Map<String, String> headers = Maps.newHashMap(); protected HttpMethodRetryHandler retryHandler; protected List<NameValuePair> parameters = Lists.newArrayList(); protected URL keyStoreUrl; protected String keyStorePwd; protected URL trustStoreUrl; protected String trustStorePwd; protected String charset = DefaultConfig.DEFAULT_CHARSET; protected String cookie; protected String cookiePolicy; protected int httpsPort = DefaultConfig.DEFAULT_HTTPS_PORT; protected boolean keepAlive; static { /* escape the warning */ ProtocolSocketFactory sslFactory = new EasySSLProtocolSocketFactory(); Protocol https = new Protocol("https", sslFactory, 443); Protocol.registerProtocol("https", https); } AbstractHttpClient() { connectionTimeout(GlobalConfig.getIntValue(GlobalConfigKey.HTTP_DEFAULT_CONNECTION_TIMEOUT, DefaultConfig.DEFAULT_CONN_TIME)); socketTimeout(GlobalConfig.getIntValue(GlobalConfigKey.HTTP_DEFAULT_SOCKET_TIMEOUT, DefaultConfig.DEFAULT_SOCKET_TIME)); } /** * 连接超时时间 * * @param millisecond int * @return AbstractHttpClient */ public AbstractHttpClient connectionTimeout(int millisecond) { checkArgument(millisecond >= 0, "connection time 必须大于等于0"); httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(millisecond); return this; } /** * 设置socket timeout * * @param millisecond int * @return AbstractHttpClient * @deprecated use socketTimeout */ @Deprecated public AbstractHttpClient soTimeout(int millisecond) { checkArgument(millisecond >= 0, "socket time 必须大于等于0"); httpClient.getHttpConnectionManager().getParams().setSoTimeout(millisecond); return this; } /** * 设置socket timeout * * @param millisecond int * @return AbstractHttpClient */ public AbstractHttpClient socketTimeout(int millisecond) { checkArgument(millisecond >= 0, "socket time 必须大于等于0"); httpClient.getHttpConnectionManager().getParams().setSoTimeout(millisecond); return this; } /** * 设置https的端口号,默认为https通用的443 * * @param httpsPort int * @return AbstractHttpClient */ public AbstractHttpClient httpsPort(int httpsPort) { checkArgument(httpsPort > 0 && httpsPort < 65536, "端口号必须在(0,65535]之间"); this.httpsPort = httpsPort; return this; } /** * 设置字符编码,默认为utf-8 * * @param charset Charset * @return AbstractHttpClient */ public AbstractHttpClient charset(Charset charset) { checkNotNull(charset); this.charset = charset.name(); return this; } /** * 设置代理host和端口号 * * @param host String * @param port int * @return AbstractHttpClient */ public AbstractHttpClient proxy(String host, int port) { checkArgument(!isNullOrEmpty(host), "host值错误"); checkArgument(port > 0 && port < 65535, "端口号需要在(0,65535)之间"); HostConfiguration config = httpClient.getHostConfiguration(); config.setProxy(host, port); config.getParams().setParameter("http.default-headers", Lists.newArrayList(new Header("User-Agent", DefaultConfig.DEFAULT_USER_AGENT))); return this; } /** * 设置cookie,默认的cookie策略:BROWSER_COMPATIBILITY * * @param cookie String * @return AbstractHttpClient */ public AbstractHttpClient cookie(String cookie) { return cookie(cookie, DefaultConfig.DEFAULT_COOKIE_POLICY); } /** * 设置cookie以及cookie策略 * {@link org.apache.commons.httpclient.cookie.CookiePolicy} * * @param cookie String * @param policy String * @return AbstractHttpClient */ public AbstractHttpClient cookie(String cookie, String policy) { checkArgument(!isNullOrEmpty(cookie), "cookie值错误"); checkArgument(!isNullOrEmpty(policy), "policy值错误"); this.cookie = cookie; this.cookiePolicy = policy; return this; } /** * 设置cookie策略 * {@link org.apache.commons.httpclient.cookie.CookiePolicy} * * @param policy String * @return AbstractHttpClient */ public AbstractHttpClient cookiePolicy(String policy) { checkArgument(!isNullOrEmpty(policy), "policy值错误"); this.cookiePolicy = policy; return this; } /** * 增加request header * * @param headerName String * @param headerValue String * @return AbstractHttpClient */ public AbstractHttpClient addHeader(String headerName, String headerValue) { checkArgument(!isNullOrEmpty(headerName), "headerName值错误"); checkArgument(!isNullOrEmpty(headerValue), "headerValue值错误"); headers.put(headerName, headerValue); return this; } /** * 注入出错重试处理器 * * @param retryHandler HttpMethodRetryHandler * @return AbstractHttpClient */ public AbstractHttpClient retry(HttpMethodRetryHandler retryHandler) { checkNotNull(retryHandler, "retryHandler不能为null"); this.retryHandler = retryHandler; return this; } /** * 注入回调 * * @param callBack HttpCallback * @return AbstractHttpClient */ public AbstractHttpClient callback(HttpCallback callBack) { checkNotNull(callBack, "callback不能为null"); this.callBack = callBack; return this; } /** * 增加请求参数,根据get或者post能够自动组装 * * @param key String * @param value String * @return AbstractHttpClient */ public AbstractHttpClient addParameter(String key, String value) { checkNotNull(key, "key不能为null"); checkNotNull(value, "value不能为null"); parameters.add(new NameValuePair(key, value)); return this; } /** * 增加请求参数,根据get或者post能够自动组装 * * @param parameters Map * @return AbstractHttpClient */ public AbstractHttpClient addParameter(Map<String, String> parameters) { checkNotNull(parameters, "parameters不能为null"); for (Map.Entry<String, String> entry : parameters.entrySet()) { this.parameters.add(new NameValuePair(entry.getKey(), entry.getValue())); } return this; } /** * 设置是否keepAlive,目前都是短连接 * * @return AbstractHttpClient */ public AbstractHttpClient keepAlive() { this.keepAlive = true; return this; } /** * 设定key store的url和密码 * * @param url String * @param password String * @return AbstractHttpClient */ public AbstractHttpClient keyStore(String url, String password) { checkArgument(!Strings.isNullOrEmpty(url), "key store的url不能为空"); checkArgument(!Strings.isNullOrEmpty(password), "key store的密码不能为空"); try { this.keyStoreUrl = new URL(url); this.keyStorePwd = password; } catch (MalformedURLException e) { Throwables.propagate(e); } return this; } /** * 设定 trust store的url和密码 * * @param url String * @param password String * @return AbstractHttpClient */ public AbstractHttpClient trustStore(String url, String password) { checkArgument(!Strings.isNullOrEmpty(url), "trust store的url不能为空"); checkArgument(!Strings.isNullOrEmpty(password), "trust store的密码不能为空"); try { this.trustStoreUrl = new URL(url); this.trustStorePwd = password; } catch (MalformedURLException e) { Throwables.propagate(e); } return this; } protected PostMethod buildPostMethod(String uri, String content) { PostMethod method = new PostMethod(uri); buildCommons(method); try { if (!parameters.isEmpty()) { //parameters first method.setRequestBody(parameters.toArray(new NameValuePair[0])); } if (content != null) { method.setRequestEntity(new StringRequestEntity(content, "application/x-www-form-urlencoded", charset)); } } catch (UnsupportedEncodingException e) { //we swallow this exp Throwables.propagate(e); } return method; } protected GetMethod buildGetMethod(String uri) { if (!parameters.isEmpty()) { StringBuilder sb = new StringBuilder(uri); sb.append("?"); for (int i = 0; i < parameters.size(); i++) { NameValuePair kv = parameters.get(i); if (i != 0) { sb.append("&"); } sb.append(urlEncode(kv.getName())).append("=").append(urlEncode(kv.getValue())); } uri = sb.toString(); } GetMethod method = new GetMethod(uri); buildCommons(method); return method; } private String urlEncode(String queryString) { try { return URLEncoder.encode(queryString, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } //post and get方法都需要构建的信息 private void buildCommons(HttpMethod method) { trySetDefaults(method); appendHeaders(method); } private void trySetDefaults(HttpMethod method) { HttpMethodParams params = method.getParams(); params.setContentCharset(charset); if (retryHandler != null) { params.setParameter(HttpMethodParams.RETRY_HANDLER, retryHandler); } if (!headers.containsKey("Accept-Language")) { addHeader("Accept-Language", "zh-cn"); } if (!headers.containsKey("User-Agent")) { addHeader("User-Agent", DefaultConfig.DEFAULT_USER_AGENT); } if (!headers.containsKey("Connection") && keepAlive) { addHeader("Connection", " Keep-Alive"); } if (!isNullOrEmpty(cookie)) { method.setRequestHeader("cookie", cookie); } if (!isNullOrEmpty(cookiePolicy)) { params.setCookiePolicy(DefaultConfig.DEFAULT_COOKIE_POLICY); } } private void appendHeaders(HttpMethod method) { for (Map.Entry<String, String> entry : headers.entrySet()) { method.setRequestHeader(entry.getKey(), entry.getValue()); } } private boolean trySetAuthSSLFactory(String host, String path) { checkArgument(!isNullOrEmpty(host), "host不能为空"); checkArgument(!isNullOrEmpty(path), "path不能为空"); if (this.trustStorePwd != null && this.trustStoreUrl != null && this.keyStorePwd != null && this.keyStoreUrl != null) { ProtocolSocketFactory factory = new AuthSSLProtocolSocketFactory(keyStoreUrl, keyStorePwd, trustStoreUrl, trustStorePwd); httpClient.getHostConfiguration().setHost(host, httpsPort, new Protocol("https", factory, httpsPort)); return true; } log.info("未设定trust store和keystroe,将按照默认证书校验处理"); return false; } private String combineHttpsURI(String host, String path) { if (path.startsWith("/")) { return "https://" + host + path; } else { return "https://" + host + "/" + path; } } /** * 支持https的同步get * * @param host 主机名如:user.qunar.com * @param path 地址路径如:/passport/login.jsp * @return ResponseWrapper */ public ResponseWrapper httpsGet(String host, String path) { if (!trySetAuthSSLFactory(host, path)) { return get(combineHttpsURI(host, path)); } return get(path); } /** * 支持https的异步get * * @param host 主机名如:user.qunar.com * @param path 地址路径如:/passport/login.jsp * @return ListenableFuture */ public ListenableFuture<ResponseWrapper> httpsAsyncGet(String host, String path) { if (!trySetAuthSSLFactory(host, path)) { return asyncGet(combineHttpsURI(host, path)); } return asyncGet(path); } /** * 支持https的同步post * * @param host 主机名如:user.qunar.com * @param path 地址路径如:/passport/login.jsp * @return ResponseWrapper */ public ResponseWrapper httpsPost(String host, String path) { if (!trySetAuthSSLFactory(host, path)) { return post(combineHttpsURI(host, path)); } return post(path); } /** * 支持https的同步post * * @param host 主机名如:user.qunar.com * @param path 地址路径如:/passport/login.jsp * @param content request body * @return ResponseWrapper */ public ResponseWrapper httpsPost(String host, String path, String content) { if (!trySetAuthSSLFactory(host, path)) { return post(combineHttpsURI(host, path), content); } return post(path, content); } /** * 支持https的异步post * * @param host 主机名如:user.qunar.com * @param path 地址路径如:/passport/login.jsp * @return ListenableFuture */ public ListenableFuture<ResponseWrapper> httpsAsyncPost(String host, String path) { if (!trySetAuthSSLFactory(host, path)) { return asyncPost(combineHttpsURI(host, path)); } return asyncPost(path); } /** * 支持https的异步post * * @param host 主机名如:user.qunar.com * @param path 地址路径如:/passport/login.jsp * @param content request body * @return ListenableFuture */ public ListenableFuture<ResponseWrapper> httpsAsyncPost(String host, String path, String content) { if (!trySetAuthSSLFactory(host, path)) { return asyncPost(combineHttpsURI(host, path), content); } return asyncPost(path, content); } /** * 同步get * * @param uri String * @return ResponseWrapper */ public abstract ResponseWrapper get(String uri); /** * 同步post * * @param uri String * @param content String * @return ResponseWrapper */ public abstract ResponseWrapper post(String uri, String content); /** * 同步post * * @param uri String * @return ResponseWrapper */ public abstract ResponseWrapper post(String uri); /** * 异步get * * @param uri String * @return ListenableFuture */ public abstract ListenableFuture<ResponseWrapper> asyncGet(String uri); /** * 异步post * * @param uri String * @param content String * @return ListenableFuture */ public abstract ListenableFuture<ResponseWrapper> asyncPost(String uri, String content); /** * 异步post * * @param uri String * @return ListenableFuture */ public abstract ListenableFuture<ResponseWrapper> asyncPost(String uri); }