针对上一篇文章的接口设计,提供了三种实现URLConnection、ApacheHttpClient、OkHttp3。
现给出三种实现的继承树。
HTTPURLConnection
ApacheHttp
OkHttp3
首先每种实现都需要实现模板方法HttpTemplate。
URLConnection:
package cn.zytx.common.http.base;
import cn.zytx.common.http.Header;
import cn.zytx.common.http.HttpConstants;
import cn.zytx.common.http.HttpStatus;
import cn.zytx.common.http.Method;
import cn.zytx.common.http.base.ssl.DefaultTrustManager;
import cn.zytx.common.http.base.ssl.SSLSocketFactoryBuilder;
import cn.zytx.common.http.base.ssl.TrustAnyHostnameVerifier;
import cn.zytx.common.http.smart.Request;
import cn.zytx.common.http.smart.SSLRequest;
import cn.zytx.common.utils.ArrayListMultimap;
import cn.zytx.common.utils.IoUtil;
import javax.net.ssl.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.util.HashMap;
import java.util.Set;
/**
* 使用URLConnection实现的Http公共父类
* @author 熊诗言2018/6/6
*/
public abstract class AbstractNativeHttp implements HttpTemplate<HttpURLConnection> {
@Override
public <R> R template(Request request, Method method, ContentCallback<HttpURLConnection> contentCallback , ResultCallback<R> resultCallback) throws IOException {
HttpURLConnection connect = null;
InputStream inputStream = null;
try {
//1.获取连接
connect = (HttpURLConnection)new java.net.URL(request.getUrl()).openConnection();
//2.处理header
setConnectProperty(connect, method, request.getContentType(), request.getHeaders(),request.getConnectionTimeout(),request.getReadTimeout());
ssl处理///
if(connect instanceof HttpsURLConnection){
//默认设置这些
HttpsURLConnection con = (HttpsURLConnection)connect;
initSSL(con , getDefaultHostnameVerifier() , getDefaultSSLSocketFactory());
if(request instanceof SSLRequest){
//客户给了就用给客户的
SSLRequest sslRequest = (SSLRequest) request;
initSSL(con , sslRequest.getHostnameVerifier(), sslRequest.getSslSocketFactory());
}
}
ssl处理///
//3.留给子类复写的机会:给connection设置更多参数
doWithConnection(connect);
//5.写入内容,只对post有效
if(contentCallback != null){
contentCallback.doWriteWith(connect);
}
//4.连接
connect.connect();
//6.获取返回值
int statusCode = connect.getResponseCode();
inputStream = getStreamFrom(connect , statusCode);
return resultCallback.convert(statusCode , inputStream, request.getResultCharset(), request.isIncludeHeaders() ? connect.getHeaderFields() : new HashMap<>(0));
///返回Response
//return Response.with(statusCode , inputStream , request.getResultCharset() , request.isIncludeHeaders() ? connect.getHeaderFields() : new HashMap<>(0));
} catch (IOException e) {
throw e;
} catch (Exception e){
throw new RuntimeException(e);
} finally {
//关闭顺序不能改变,否则服务端可能出现这个异常 严重: java.io.IOException: 远程主机强迫关闭了一个现有的连接
//1 . 关闭连接
disconnectQuietly(connect);
//2 . 关闭流
IoUtil.close(inputStream);
}
}
@Override
public <R> R template(String url, Method method, String contentType, ContentCallback<HttpURLConnection> contentCallback, ArrayListMultimap<String, String> headers, int connectTimeout, int readTimeout, String resultCharset , boolean includeHeaders , ResultCallback<R> resultCallback) throws IOException {
//默认的https校验
// 后面会处理的,这里就不需要了 initDefaultSSL(sslVer);
HttpURLConnection connect = null;
InputStream inputStream = null;
try {
//1.获取连接
connect = (HttpURLConnection)new java.net.URL(url).openConnection();
//2.处理header
setConnectProperty(connect, method, contentType, headers,connectTimeout,readTimeout);
ssl处理///
if(connect instanceof HttpsURLConnection){
//默认设置这些
HttpsURLConnection con = (HttpsURLConnection)connect;
initSSL(con , getDefaultHostnameVerifier() , getDefaultSSLSocketFactory());
}
ssl处理///
//3.留给子类复写的机会:给connection设置更多参数
doWithConnection(connect);
//5.写入内容,只对post有效
if(contentCallback != null){
contentCallback.doWriteWith(connect);
}
//4.连接
connect.connect();
//6.获取返回值
int statusCode = connect.getResponseCode();
///保留起非200抛异常的方式
// if( HttpStatus.HTTP_OK == statusCode){
// //6.1获取body
// /*InputStream is = connect.getInputStream();
// byte[] result = IoUtil.stream2Bytes(is);
// is.close();
//
// String s = new String(result, resultCharset);*/
//
// //6.2获取header
//
// inputStream = connect.getInputStream();
//
// return resultCallback.convert(inputStream, resultCharset, includeHeaders ? connect.getHeaderFields() : new HashMap<>(0));
//
// } else{
// String err = errMessage(connect);
// throw new HttpException(statusCode,err,connect.getHeaderFields());
// }
inputStream = getStreamFrom(connect , statusCode);
return resultCallback.convert(statusCode , inputStream, resultCharset, includeHeaders ? connect.getHeaderFields() : new HashMap<>(0));
} catch (IOException e) {
throw e;
} catch (Exception e){
throw new RuntimeException(e);
} finally {
//关闭顺序不能改变,否则服务端可能出现这个异常 严重: java.io.IOException: 远程主机强迫关闭了一个现有的连接
//1 . 关闭连接
disconnectQuietly(connect);
//2 . 关闭流
IoUtil.close(inputStream);
}
}
private InputStream getStreamFrom(HttpURLConnection connect , int statusCode) throws IOException{
InputStream inputStream;
if(HttpStatus.HTTP_OK == statusCode){
inputStream = connect.getInputStream();
}else {
inputStream = connect.getErrorStream();
}
if(null == inputStream){
inputStream = new ByteArrayInputStream(new byte[]{});
}
return inputStream;
}
protected HostnameVerifier getDefaultHostnameVerifier(){
return new TrustAnyHostnameVerifier();
}
protected SSLSocketFactory getDefaultSSLSocketFactory(){
return SSLSocketFactoryBuilder.create().build();
}
/**子类复写需要首先调用此方法,保证http的功能*/
protected void doWithConnection(HttpURLConnection connect) throws Exception{
}
/**
* @see SSLSocketFactoryBuilder#build()
* @see SSLSocketFactoryBuilder#build(String, String)
*/
protected void initSSL(HttpsURLConnection con , HostnameVerifier hostnameVerifier , SSLSocketFactory sslSocketFactory) {
// 验证域
if(null != hostnameVerifier){
con.setHostnameVerifier(hostnameVerifier);
}
if(null != sslSocketFactory){
con.setSSLSocketFactory(sslSocketFactory);
}
}
protected void setConnectProperty(HttpURLConnection connect, Method method, String contentType, ArrayListMultimap<String,String> headers, int connectTimeout, int readTimeout) throws ProtocolException {
connect.setRequestMethod(method.name());
connect.setDoOutput(true);
connect.setUseCaches(false);
if(null != headers) {
Set<String> keySet = headers.keySet();
keySet.forEach((k)->headers.get(k).forEach((v)->connect.addRequestProperty(k,v)));
}
if(null != contentType){
connect.setRequestProperty(Header.CONTENT_TYPE.toString(), contentType);
}
connect.setConnectTimeout(connectTimeout);
connect.setReadTimeout(readTimeout);
}
protected void writeContent(HttpURLConnection connect, String data, String bodyCharset) throws IOException {
if (null == data || null == bodyCharset) {
return;
}
OutputStream outputStream = connect.getOutputStream();
outputStream.write(data.getBytes(bodyCharset));
outputStream.close();
/* PrintWriter out = new PrintWriter(connect.getOutputStream());
if (null != data) {
out.print(data);
out.flush();
}
out.close();*/
}
/**
* 获取输出流中的错误信息,针对HttpURLConnection
* @param connect HttpURLConnection
* @return 错误信息
* @see HttpURLConnection
* @throws IOException IO异常
*/
protected String errMessage(HttpURLConnection connect) throws IOException {
//如果服务器返回的HTTP状态不是HTTP_OK,则表示发生了错误,此时可以通过如下方法了解错误原因。
// 通过getErrorStream了解错误的详情
InputStream is = connect.getErrorStream();
if(null==is){return "";}
InputStreamReader isr = new InputStreamReader(is, HttpConstants.DEFAULT_CHARSET);
BufferedReader in = new BufferedReader(isr);
String inputLine;
StringWriter bw = new StringWriter();
while ((inputLine = in.readLine()) != null)
{
bw.write(inputLine);
bw.write("\n");
}
bw.close();
in.close();
//disconnectQuietly(connect);
return bw.getBuffer().toString();
}
public static void disconnectQuietly(HttpURLConnection connect) {
if(null != connect){
try {
connect.disconnect();
} catch (Exception e) {}
}
}
protected void initDefaultSSL(String sslVer) {
try {
TrustManager[] tmCerts = new TrustManager[1];
tmCerts[0] = new DefaultTrustManager();
SSLContext sslContext = SSLContext.getInstance(sslVer);
sslContext.init(null, tmCerts, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HostnameVerifier hostnameVerifier = new TrustAnyHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
} catch (Exception e) {
}
}
}
ApcheHttpClient:
package cn.zytx.common.http.base;
import cn.zytx.common.http.Method;
import cn.zytx.common.http.ParamUtil;
import cn.zytx.common.http.base.ssl.SSLSocketFactoryBuilder;
import cn.zytx.common.http.base.ssl.TrustAnyHostnameVerifier;
import cn.zytx.common.http.smart.Request;
import cn.zytx.common.http.smart.SSLRequest;
import cn.zytx.common.utils.ArrayListMultimap;
import cn.zytx.common.utils.IoUtil;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 使用Apache SmartHttpClient 实现的Http请求类
* @author 熊诗言2018/6/6
*/
public abstract class AbstractApacheHttp implements HttpTemplate<HttpEntityEnclosingRequest> {
protected int _maxRetryTimes = 3;
@Override
public <R> R template(Request request, Method method , ContentCallback<HttpEntityEnclosingRequest> contentCallback , ResultCallback<R> resultCallback) throws IOException {
HttpUriRequest httpUriRequest = (Method.POST == method) ? new HttpPost(request.getUrl()) : new HttpGet(request.getUrl());
//2.设置请求头
setRequestHeaders(httpUriRequest, request.getContentType(), request.getHeaders());
//3.设置请求参数
setRequestProperty((HttpRequestBase) httpUriRequest, request.getConnectionTimeout(), request.getReadTimeout());
//4.创建请求内容,如果有的话
if(httpUriRequest instanceof HttpEntityEnclosingRequest){
if(contentCallback != null){
contentCallback.doWriteWith((HttpEntityEnclosingRequest)httpUriRequest);
}
}
CloseableHttpClient httpClient = null;
CloseableHttpResponse response = null;
HttpEntity entity = null;
try {
ssl处理///
HostnameVerifier hostnameVerifier = null;
SSLContext sslContext = null;
//https默认设置这些
if(ParamUtil.isHttps(request.getUrl())){
hostnameVerifier = getDefaultHostnameVerifier();
sslContext = getDefaultSSLContext();
//客户传过来就用客户的
if(request instanceof SSLRequest){
SSLRequest sslRequest = (SSLRequest)request;
hostnameVerifier = sslRequest.getHostnameVerifier();
sslContext = sslRequest.getSslContext();
}
}
ssl处理///
httpClient = getCloseableHttpClient(request.getUrl() , hostnameVerifier , sslContext);
//6.发送请求
response = httpClient.execute(httpUriRequest , HttpClientContext.create());
int statusCode = response.getStatusLine().getStatusCode();
entity = response.getEntity();
InputStream inputStream = entity.getContent();
if(null == inputStream){
inputStream = new ByteArrayInputStream(new byte[]{});
}
R convert = resultCallback.convert(statusCode , inputStream, request.getResultCharset(), request.isIncludeHeaders() ? parseHeaders(response) : new HashMap<>(0));
IoUtil.close(inputStream);
return convert;
///
/*Response convert = Response.with(statusCode , inputStream , request.getResultCharset() , request.isIncludeHeaders() ? parseHeaders(response) : new HashMap<>(0));
IoUtil.close(inputStream);
return convert;*/
} catch (IOException e) {
throw e;
} catch (Exception e){
throw new RuntimeException(e);
}finally {
EntityUtils.consumeQuietly(entity);
IoUtil.close(response);
IoUtil.close(httpClient);
}
}
@Override
public <R> R template(String url, Method method , String contentType, ContentCallback<HttpEntityEnclosingRequest> contentCallback, ArrayListMultimap<String, String> headers, int connectTimeout, int readTimeout, String resultCharset , boolean includeHeader , ResultCallback<R> resultCallback) throws IOException {
//1.创建请求
///*URIBuilder builder = new URIBuilder(url);
//URI uri = builder.build();*/
HttpUriRequest httpUriRequest = (Method.POST == method) ? new HttpPost(url) : new HttpGet(url);
//2.设置请求头
setRequestHeaders(httpUriRequest, contentType, headers);
//3.设置请求参数
setRequestProperty((HttpRequestBase) httpUriRequest, connectTimeout, readTimeout);
//4.创建请求内容,如果有的话
if(httpUriRequest instanceof HttpEntityEnclosingRequest){
if(contentCallback != null){
contentCallback.doWriteWith((HttpEntityEnclosingRequest)httpUriRequest);
}
}
CloseableHttpClient httpClient = null;
CloseableHttpResponse response = null;
HttpEntity entity = null;
try {
//5.创建http客户端
//CloseableHttpClient httpClient = HttpClients.createDefault();
httpClient = getCloseableHttpClient(url ,getDefaultHostnameVerifier() , getDefaultSSLContext());
//6.发送请求
response = httpClient.execute(httpUriRequest , HttpClientContext.create());
int statusCode = response.getStatusLine().getStatusCode();
/*String resultString = EntityUtils.toString(response.getEntity(), resultCharset);*/
entity = response.getEntity();
// if (HttpStatus.HTTP_OK == statusCode) {
// //7.封装返回参数
// InputStream inputStream = entity.getContent();
// R convert = resultCallback.convert(inputStream, resultCharset, includeHeader ? parseHeaders(response) : new HashMap<>(0));
// IoUtil.close(inputStream);
// return convert;
// }else {
// String errorMsg = EntityUtils.toString(entity, resultCharset);
// throw new HttpException(statusCode,errorMsg,parseHeaders(response));
// }
InputStream inputStream = entity.getContent();
// R convert;
// if (HttpStatus.HTTP_OK == statusCode) {
// //7.封装返回参数
// convert = resultCallback.convert(HttpStatus.HTTP_OK , inputStream, resultCharset, includeHeader ? parseHeaders(response) : new HashMap<>(0));
// }else {
// convert = resultCallback.convert(statusCode , inputStream , resultCharset , parseHeaders(response));
// }
if(null == inputStream){
inputStream = new ByteArrayInputStream(new byte[]{});
}
R convert = resultCallback.convert(statusCode , inputStream, resultCharset, includeHeader ? parseHeaders(response) : new HashMap<>(0));
IoUtil.close(inputStream);
return convert;
} catch (IOException e) {
throw e;
} catch (Exception e){
throw new RuntimeException(e);
}finally {
EntityUtils.consumeQuietly(entity);
IoUtil.close(response);
IoUtil.close(httpClient);
}
}
protected HostnameVerifier getDefaultHostnameVerifier(){
return new TrustAnyHostnameVerifier();
}
protected SSLContext getDefaultSSLContext(){
return SSLSocketFactoryBuilder.create().getSSLContext();
}
/**
* https://ss.xx.xx.ss:8080/dsda
*/
private CloseableHttpClient getCloseableHttpClient(String url, HostnameVerifier hostnameVerifier , SSLContext sslContext) throws Exception{
return createHttpClient(200, 40, 100, url , hostnameVerifier , sslContext);
}
private boolean retryIf(IOException exception, int executionCount, HttpContext context) {
if (executionCount >= _maxRetryTimes) {
return false;
}
// 如果服务器丢掉了连接,那么就重试
if (exception instanceof NoHttpResponseException) {
return true;
}
// 不要重试SSL握手异常
if (exception instanceof SSLHandshakeException) {
return false;
}
// 超时
if (exception instanceof InterruptedIOException) {
return false;
}
// 目标服务器不可达
if (exception instanceof UnknownHostException) {
return false;
}
// 连接被拒绝
if (exception instanceof ConnectException) {
return false;
}
// SSL握手异常
if (exception instanceof SSLException) {
return false;
}
HttpClientContext clientContext = HttpClientContext
.adapt(context);
HttpRequest request = clientContext.getRequest();
// 如果请求是幂等的,就再次尝试
if (!(request instanceof HttpEntityEnclosingRequest)) {
return true;
}
return false;
}
protected CloseableHttpClient createHttpClient(int maxTotal, int maxPerRoute, int maxRoute, String url , HostnameVerifier hostnameVerifier , SSLContext sslContext) throws Exception{
String hostname = url.split("/")[2];
boolean isHttps = ParamUtil.isHttps(url);
int port = isHttps ? 443 : 80;
if (hostname.contains(":")) {
String[] arr = hostname.split(":");
hostname = arr[0];
port = Integer.parseInt(arr[1]);
}
ConnectionSocketFactory csf = PlainConnectionSocketFactory
.getSocketFactory();
LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory
.getSocketFactory();
Registry<ConnectionSocketFactory> registry = RegistryBuilder
.<ConnectionSocketFactory> create().register("http", csf)
.register("https", sslsf).build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
// 将最大连接数增加
cm.setMaxTotal(maxTotal);
// 将每个路由基础的连接增加
cm.setDefaultMaxPerRoute(maxPerRoute);
// 设置不活动的连接1000ms之后Validate
cm.setValidateAfterInactivity(1000);
HttpHost httpHost = new HttpHost(hostname, port);
// 将目标主机的最大连接数增加
cm.setMaxPerRoute(new HttpRoute(httpHost), maxRoute);
// 请求重试处理
HttpRequestRetryHandler httpRequestRetryHandler = this::retryIf ;
HttpClientBuilder httpClientBuilder = HttpClients.custom()
.setConnectionManager(cm)
.setRetryHandler(httpRequestRetryHandler);
if(isHttps){
initSSL(httpClientBuilder , hostnameVerifier , sslContext);
}
//给子类复写的机会
doWithClient(httpClientBuilder , isHttps);
return httpClientBuilder.build();
}
protected void doWithClient(HttpClientBuilder httpClientBuilder , boolean isHttps) throws Exception{
}
/**
* @see SSLSocketFactoryBuilder#getSSLContext()
* @see SSLSocketFactoryBuilder#getSSLContext(String, String)
*/
protected void initSSL(HttpClientBuilder httpClientBuilder , HostnameVerifier hostnameVerifier , SSLContext sslContext) {
// 验证域
if(null != hostnameVerifier){
httpClientBuilder.setSSLHostnameVerifier(hostnameVerifier);
}
if(null != sslContext){
httpClientBuilder.setSSLContext(sslContext);
}
}
protected void setRequestBody(HttpEntityEnclosingRequest request, String body, String bodyCharset) {
if(body == null || bodyCharset == null){return;}
StringEntity entity = new StringEntity(body, bodyCharset);
entity.setContentEncoding(bodyCharset);
request.setEntity(entity);
}
protected void setRequestProperty(HttpRequestBase request, int connectTimeout, int readTimeout) {
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(connectTimeout)
.setConnectionRequestTimeout(readTimeout)
.setSocketTimeout(readTimeout)
//.setStaleConnectionCheckEnabled(true)
.build();
request.setConfig(config);
}
protected void setRequestHeaders(HttpUriRequest request, String contentType, ArrayListMultimap<String, String> headers) {
if(null != headers) {
Set<String> keySet = headers.keySet();
keySet.forEach((k)->headers.get(k).forEach((v)->request.addHeader(k,v)));
}
if(null != contentType){
request.setHeader(cn.zytx.common.http.Header.CONTENT_TYPE.toString(), contentType);
}
}
private Map<String , List<String>> parseHeaders(CloseableHttpResponse response) {
Header[] allHeaders = response.getAllHeaders();
ArrayListMultimap<String,String> arrayListMultimap = new ArrayListMultimap<>(allHeaders.length);
for (Header header : allHeaders) {
arrayListMultimap.put(header.getName() , header.getValue());
}
return arrayListMultimap.getMap();
}
}
OkHttp3:
package cn.zytx.common.http.base;
import cn.zytx.common.http.Header;
import cn.zytx.common.http.HttpStatus;
import cn.zytx.common.http.Method;
import cn.zytx.common.http.ParamUtil;
import cn.zytx.common.http.base.ssl.DefaultTrustManager2;
import cn.zytx.common.http.base.ssl.SSLSocketFactoryBuilder;
import cn.zytx.common.http.base.ssl.TrustAnyHostnameVerifier;
import cn.zytx.common.http.smart.SSLRequest;
import cn.zytx.common.utils.ArrayListMultimap;
import cn.zytx.common.utils.IoUtil;
import okhttp3.*;
import okio.BufferedSink;
import okio.Okio;
import okio.Source;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author xiongshiyan at 2018/6/6
*/
public abstract class AbstractOkHttp3 implements HttpTemplate<Request.Builder> {
@Override
public <R> R template(cn.zytx.common.http.smart.Request request, Method method ,ContentCallback<Request.Builder> contentCallback , ResultCallback<R> resultCallback) throws IOException {
Response response = null;
InputStream inputStream = null;
try {
//1.构造OkHttpClient
OkHttpClient.Builder clientBuilder = new OkHttpClient().newBuilder()
.connectTimeout(request.getConnectionTimeout(), TimeUnit.MILLISECONDS)
.readTimeout(request.getReadTimeout(), TimeUnit.MILLISECONDS);
ssl处理///
if(ParamUtil.isHttps(request.getUrl())){
//默认设置这些
initSSL(clientBuilder , getDefaultHostnameVerifier() , getDefaultSSLSocketFactory() , getDefaultX509TrustManager());
if(request instanceof SSLRequest){
//客户给了就用给客户的
SSLRequest sslRequest = (SSLRequest) request;
initSSL(clientBuilder , sslRequest.getHostnameVerifier(), sslRequest.getSslSocketFactory() , sslRequest.getX509TrustManager());
}
}
ssl处理///
//给子类复写的机会
doWithBuilder(clientBuilder , ParamUtil.isHttps(request.getUrl()));
OkHttpClient client = clientBuilder.build();
//2.1设置URL
Request.Builder builder = new Request.Builder().url(request.getUrl());
ArrayListMultimap<String, String> headers = request.getHeaders();
//2.2设置headers
if(null != headers) {
Set<String> keySet = headers.keySet();
keySet.forEach((k)->headers.get(k).forEach((v)->builder.addHeader(k,v)));
}
if(null != request.getContentType()){
builder.addHeader(Header.CONTENT_TYPE.toString() , request.getContentType());
}
//2.3处理请求体
if(null != contentCallback && Method.POST == method){
contentCallback.doWriteWith(builder);
}
//3.构造请求
Request okRequest = builder.build();
//4.执行请求
response = client.newCall(okRequest).execute();
//5.获取响应
inputStream = getStreamFrom(response);
int statusCode = response.code();
return resultCallback.convert(statusCode , inputStream, request.getResultCharset(), request.isIncludeHeaders() ? parseHeaders(response) : new HashMap<>(0));
/*return cn.zytx.common.http.smart.Response.with(statusCode , inputStream , request.getResultCharset() ,
request.isIncludeHeaders() ? parseHeaders(response) : new HashMap<>(0));*/
} catch (IOException e) {
throw e;
} catch (Exception e){
throw new RuntimeException(e);
} finally {
IoUtil.close(inputStream);
if(null != response){
try {
response.close();
} catch (Exception e) {}
}
}
}
private InputStream getStreamFrom(Response response) {
ResponseBody body = response.body();
InputStream inputStream = (body != null) ? body.byteStream() : emptyInputStream();
if(null == inputStream){
inputStream = emptyInputStream();
}
return inputStream;
}
/**
* 获取一个空的,防止空指针
*/
private ByteArrayInputStream emptyInputStream() {
return new ByteArrayInputStream(new byte[]{});
}
@Override
public <R> R template(String url, Method method , String contentType , ContentCallback<Request.Builder> contentCallback , ArrayListMultimap<String, String> headers, int connectTimeout, int readTimeout, String resultCharset , boolean includeHeaders , ResultCallback<R> resultCallback) throws IOException{
Objects.requireNonNull(url);
Response response = null;
InputStream inputStream = null;
try {
//1.构造OkHttpClient
OkHttpClient.Builder clientBuilder = new OkHttpClient().newBuilder()
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.readTimeout(readTimeout, TimeUnit.MILLISECONDS);
ssl处理///
if(ParamUtil.isHttps(url)){
//默认设置这些
initSSL(clientBuilder , getDefaultHostnameVerifier() , getDefaultSSLSocketFactory() , getDefaultX509TrustManager());
}
ssl处理///
//给子类复写的机会
doWithBuilder(clientBuilder , ParamUtil.isHttps(url));
OkHttpClient client = clientBuilder.build();
//2.1设置URL
Request.Builder builder = new Request.Builder().url(url);
//2.2设置headers
if(null != headers) {
Set<String> keySet = headers.keySet();
keySet.forEach((k)->headers.get(k).forEach((v)->builder.addHeader(k,v)));
}
if(null != contentType){
builder.addHeader(Header.CONTENT_TYPE.toString() , contentType);
}
//2.3处理请求体
if(null != contentCallback && Method.POST == method){
contentCallback.doWriteWith(builder);
}
//3.构造请求
Request request = builder.build();
//4.执行请求
response = client.newCall(request).execute();
//5.获取响应
// return getReturnMsg(response , resultCharset , includeHeaders , resultCallback);
inputStream = getStreamFrom(response);
int statusCode = response.code();
return resultCallback.convert(statusCode , inputStream, resultCharset, includeHeaders ? parseHeaders(response) : new HashMap<>(0));
///保留起
/*if (HttpStatus.HTTP_OK == statusCode) {
convert = resultCallback.convert(HttpStatus.HTTP_OK , inputStream, resultCharset, includeHeaders ? parseHeaders(response) : new HashMap<>(0));
}else {
convert = resultCallback.convert(statusCode , inputStream , resultCharset , parseHeaders(response));
}
return convert;*/
} catch (IOException e) {
throw e;
} catch (Exception e){
throw new RuntimeException(e);
} finally {
IoUtil.close(inputStream);
if(null != response){
try {
response.close();
} catch (Exception e) {}
}
}
}
protected HostnameVerifier getDefaultHostnameVerifier(){
return new TrustAnyHostnameVerifier();
}
protected SSLSocketFactory getDefaultSSLSocketFactory(){
return SSLSocketFactoryBuilder.create().build();
}
protected X509TrustManager getDefaultX509TrustManager(){
return new DefaultTrustManager2();
}
protected void doWithBuilder(OkHttpClient.Builder builder , boolean isHttps) throws Exception{
}
/**
* @see SSLSocketFactoryBuilder#build()
* @see SSLSocketFactoryBuilder#build(String, String)
*/
protected void initSSL(OkHttpClient.Builder builder , HostnameVerifier hostnameVerifier , SSLSocketFactory sslSocketFactory , X509TrustManager trustManager) {
// 验证域
if(null != hostnameVerifier){
builder.hostnameVerifier(hostnameVerifier);
}
if(null != sslSocketFactory) {
builder.sslSocketFactory(sslSocketFactory , trustManager);
}
}
protected void setRequestBody(Request.Builder builder , Method method , RequestBody requestBody){
builder.method(method.name() , requestBody);
}
protected RequestBody stringBody(String body , String contentType){
return RequestBody.create(MediaType.parse(contentType),body);
}
protected RequestBody inputStreamBody(String contentType , InputStream inputStream , long length){
return new InputStreamRequestBody(contentType , inputStream , length);
}
protected <R> R getReturnMsg(Response response , String resultCharset , boolean includeHeaders , ResultCallback<R> resultParser) throws IOException {
int statusCode = response.code();
/*String body = new String(response.body().bytes() , resultCharset);*/
// if (HttpStatus.HTTP_OK == statusCode) {
// InputStream inputStream = response.body().byteStream();
// R convert = resultParser.convert(inputStream, resultCharset, includeHeaders ? parseHeaders(response) : new HashMap<>(0));
// IoUtil.close(inputStream);
// return convert;
//
// }else {
// //获取错误body,而不是仅仅状态行信息的message response.message()
// String errorMsg = new String(response.body().bytes() , resultCharset);
// throw new HttpException(statusCode,errorMsg,parseHeaders(response));
// }
InputStream inputStream = getStreamFrom(response);
R convert;
if (HttpStatus.HTTP_OK == statusCode) {
convert = resultParser.convert(HttpStatus.HTTP_OK , inputStream, resultCharset, includeHeaders ? parseHeaders(response) : new HashMap<>(0));
}else {
convert = resultParser.convert(statusCode , inputStream , resultCharset , parseHeaders(response));
}
IoUtil.close(inputStream);
return convert;
}
/**
* 获取响应中的headers
*/
private Map<String, List<String>> parseHeaders(Response response) {
Headers resHeaders = response.headers();
ArrayListMultimap<String , String> headers = new ArrayListMultimap<>(resHeaders.size());
resHeaders.names().forEach((key)-> headers.put(key,resHeaders.get(key)) );
return headers.getMap();
}
/**
* 根据inputStream生成requestBody的工具类
* @see RequestBody#create(MediaType, File)
*/
private static class InputStreamRequestBody extends RequestBody {
private String contentType;
private long len;
private InputStream stream;
public InputStreamRequestBody(String contentType, InputStream stream, long len) {
this.contentType = contentType;
this.stream = stream;
this.len = len;
}
@Override
public MediaType contentType() {
return MediaType.parse(contentType);
}
@Override
public long contentLength() throws IOException {
return len;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
Source source = Okio.source(stream);
sink.writeAll(source);
IoUtil.close(stream);
IoUtil.close(source);
}
}
}
具体的实现就是调用相应的模板方法,传入不同的参数即可:
URLConnection:
package cn.zytx.common.http.basic;
import cn.zytx.common.http.*;
import cn.zytx.common.http.base.AbstractNativeHttp;
import cn.zytx.common.utils.ArrayListMultimap;
import cn.zytx.common.utils.IoUtil;
import java.io.*;
import java.net.HttpURLConnection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 使用URLConnection实现的Http请求类
* @author 熊诗言2017/11/24
*/
public class NativeHttpClient extends AbstractNativeHttp implements HttpClient {
private static final String END = "\r\n";
private static final String TWO_HYPHENS = "--";
private static final String BOUNDARY = "*****xsyloveyou******";
/**
* 数据开始标志
*/
private static final String PART_BEGIN_LINE = TWO_HYPHENS + BOUNDARY + END;
/**
* 数据结束标志
*/
private static final String END_LINE = TWO_HYPHENS + BOUNDARY + TWO_HYPHENS + END;
@Override
public String get(String actionName, Map<String, String> params, Map<String, String> headers, int connectTimeout, int readTimeout, String resultCharset) throws IOException {
return template(HttpUtil.contactUrlParams(actionName , params), Method.GET, null, null, ArrayListMultimap.fromMap(headers), connectTimeout, readTimeout,
resultCharset, false , (b,r,h)-> IoUtil.read(b , r));
}
@Override
public String post(String url, String body, String contentType, Map<String, String> headers, int connectTimeout, int readTimeout, String bodyCharset, String resultCharset) throws IOException {
return template(url, Method.POST, contentType, connect -> writeContent(connect , body , bodyCharset),
ArrayListMultimap.fromMap(headers), connectTimeout, readTimeout, resultCharset, false, (b, r, h) -> IoUtil.read(b, r));
}
@Override
public byte[] getAsBytes(String url, ArrayListMultimap<String, String> headers,int connectTimeout,int readTimeout) throws IOException {
return template(url, Method.GET, null, null, headers,
connectTimeout, readTimeout, null, false ,
(b,r,h)-> IoUtil.stream2Bytes(b));
}
@Override
public File getAsFile(String url, ArrayListMultimap<String, String> headers, File file, int connectTimeout, int readTimeout) throws IOException {
return template(url, Method.GET, null, null, headers,
connectTimeout, readTimeout, null, false ,
(b,r,h)-> IoUtil.copy2File(b, file));
}
/**
* 上传文件
*/
@Override
public String upload(String url , ArrayListMultimap<String,String> headers , int connectTimeout , int readTimeout , String resultCharset ,FormFile... files) throws IOException{
ArrayListMultimap<String, String> multimap = mergeHeaders(headers);
return template(url, Method.POST, null, connect -> this.upload0(connect , files), multimap ,
connectTimeout, readTimeout, resultCharset, false,
(b, r, h) -> IoUtil.read(b, r));
}
/**
* 上传文件和params参数传递,form-data类型的完全支持
*/
@Override
public String upload(String url, ArrayListMultimap<String, String> params, ArrayListMultimap<String, String> headers, int connectTimeout, int readTimeout, String resultCharset , FormFile... files) throws IOException {
ArrayListMultimap<String, String> multimap = mergeHeaders(headers);
return template(url, Method.POST, null, connect -> this.upload0(connect , params , files), multimap ,
connectTimeout, readTimeout, resultCharset, false,
(b, r, h) -> IoUtil.read(b, r));
}
protected void upload0(HttpURLConnection connection , FormFile... files) throws IOException{
connection.addRequestProperty("Content-Length" , String.valueOf(getFormFilesLen(files) + END_LINE.length()));
// 设置DataOutputStream
DataOutputStream ds = new DataOutputStream(connection.getOutputStream());
for (int i = 0; i < files.length; i++) {
writeOneFile(ds, files[i]);
}
ds.writeBytes(END_LINE);
/* close streams */
ds.flush();
//ds.close();
}
protected void upload0(HttpURLConnection connection , ArrayListMultimap<String, String> params, FormFile... files) throws IOException{
int fileDataLength = getFormFilesLen(files);
String textEntity = getTextEntity(params);
// 计算传输给服务器的实体数据总长度
int dataLength = textEntity.getBytes().length + fileDataLength + END_LINE.length();
connection.addRequestProperty("Content-Length" , String.valueOf(dataLength));
DataOutputStream ds = new DataOutputStream(connection.getOutputStream());
//写params数据
ds.writeBytes(textEntity);
//写文件
for (int i = 0; i < files.length; i++) {
writeOneFile(ds, files[i]);
}
//写末尾行
ds.writeBytes(END_LINE);
/* close streams */
ds.flush();
ds.close();
}
/**
* 写一个文件 , 必须保证和getFormFilesLen的内容一致
* @see NativeHttpClient#getFormFilesLen(FormFile...)
*/
private void writeOneFile(DataOutputStream ds, FormFile formFile) throws IOException {
ds.writeBytes(PART_BEGIN_LINE);
ds.writeBytes("Content-Disposition: form-data; name=\"" + formFile.getParameterName() + "\"; filename=\"" + formFile.getFilName() + "\"" + END);
ds.writeBytes("Content-Type: " + formFile.getContentType() + END + END);
InputStream inStream = formFile.getInStream();
IoUtil.copy(inStream, ds);
ds.writeBytes(END);
IoUtil.close(inStream);
}
/**
* 计算需要传输的字节数
* @see NativeHttpClient#writeOneFile(DataOutputStream, FormFile)
* @param files FormFile
* @return 总的字节数
*/
protected int getFormFilesLen(FormFile... files){
int fileDataLength = 0;
for (FormFile formFile : files) {
StringBuilder fileExplain = new StringBuilder();
fileExplain.append(PART_BEGIN_LINE);
fileExplain.append("Content-Disposition: form-data; name=\"" + formFile.getParameterName() + "\";filename=\"" + formFile.getFilName() + "\"" + END);
fileExplain.append("Content-Type: " + formFile.getContentType() + END + END);
fileDataLength += fileExplain.length();
fileDataLength += formFile.getFileLen();
fileDataLength += END.length();
}
return fileDataLength;
}
private String getTextEntity(ArrayListMultimap<String, String> params) {
StringBuilder textEntity = new StringBuilder();
// 构造文本类型参数的实体数据
if(null != params){
Set<String> keySet = params.keySet();
for(String key : keySet){
List<String> list = params.get(key);
for(String value : list){
textEntity.append(PART_BEGIN_LINE);
textEntity.append("Content-Disposition: form-data; name=\"" + key + "\"" + END + END);
textEntity.append(value).append(END);
}
}
}
return textEntity.toString();
}
protected ArrayListMultimap<String, String> mergeHeaders(ArrayListMultimap<String, String> headers) {
if(null == headers){
headers = new ArrayListMultimap<>();
}
//headers.put("Connection" , "Keep-Alive");
headers.put("Charset" , "UTF-8");
headers.put("Content-Type" , "multipart/form-data; boundary=" + BOUNDARY);
return headers;
}
/**
* form-data的格式为:
*/
/*
--*****xsyloveyou******
Content-Disposition: form-data; name="k1"
v1
--*****xsyloveyou******
Content-Disposition: form-data; name="k2"
v2
--*****xsyloveyou******
Content-Disposition: form-data; name="filedata2"; filename="BugReport.png"
Content-Type: application/octet-stream
我是文本类容文件
--*****xsyloveyou******
Content-Disposition: form-data; name="filedata"; filename="838586397836550106.jpg"
Content-Type: application/octet-stream
我是文件内容2
--*****xsyloveyou******--
*/
}
ApacheHttpClient:
package cn.zytx.common.http.basic;
import cn.zytx.common.http.*;
import cn.zytx.common.http.base.AbstractApacheHttp;
import cn.zytx.common.utils.ArrayListMultimap;
import cn.zytx.common.utils.IoUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.util.CharsetUtils;
import java.io.*;
import java.util.Map;
/**
* 使用Apache SmartHttpClient 实现的Http请求类
* @author 熊诗言2017/12/01
*/
public class ApacheHttpClient extends AbstractApacheHttp implements HttpClient {
@Override
public String get(String url, Map<String, String> params, Map<String, String> headers, int connectTimeout, int readTimeout, String resultCharset) throws IOException{
return template(HttpUtil.contactUrlParams(url , params), Method.GET,null,null,
ArrayListMultimap.fromMap(headers),
connectTimeout,readTimeout , resultCharset,false,(b,r,h)-> IoUtil.read(b ,r));
}
@Override
public String post(String url, String body, String contentType, Map<String, String> headers, int connectTimeout, int readTimeout, String bodyCharset, String resultCharset) throws IOException {
return template(url,Method.POST, contentType, (request -> setRequestBody(request , body ,bodyCharset)),
ArrayListMultimap.fromMap(headers),
connectTimeout, readTimeout , resultCharset,false, (b,r,h)-> IoUtil.read(b ,r));
}
@Override
public byte[] getAsBytes(String url, ArrayListMultimap<String, String> headers, int connectTimeout, int readTimeout) throws IOException {
return template(url, Method.GET,null,null, headers,
connectTimeout,readTimeout , null,false,
(b,r,h)-> IoUtil.stream2Bytes(b));
}
@Override
public File getAsFile(String url, ArrayListMultimap<String, String> headers, File file, int connectTimeout, int readTimeout) throws IOException {
return template(url, Method.GET,null,null, headers ,
connectTimeout,readTimeout , null,false,
(b,r,h)-> IoUtil.copy2File(b, file));
}
@Override
public String upload(String url, ArrayListMultimap<String,String> headers, int connectTimeout, int readTimeout, String resultCharset, FormFile... files) throws IOException{
return template(url,Method.POST, null, (request -> addFormFiles(request, files)),
headers, connectTimeout, readTimeout , resultCharset,false, (b,r,h)-> IoUtil.read(b ,r));
}
protected void addFormFiles(HttpEntityEnclosingRequest request, FormFile[] files) throws UnsupportedEncodingException {
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create()
.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
.setCharset(CharsetUtils.get(DEFAULT_CHARSET));
for (FormFile formFile : files) {
multipartEntityBuilder.addBinaryBody(formFile.getParameterName(), formFile.getInStream() , ContentType.parse(formFile.getContentType()) , formFile.getFilName());
}
HttpEntity reqEntity = multipartEntityBuilder.build();
request.setEntity(reqEntity);
}
@Override
public String upload(String url, ArrayListMultimap<String, String> params, ArrayListMultimap<String, String> headers, int connectTimeout, int readTimeout, String resultCharset, FormFile... files) throws IOException {
return template(url,Method.POST, null, (request -> addFormFiles(request, params ,files)),
headers, connectTimeout, readTimeout , resultCharset,false, (b,r,h)-> IoUtil.read(b ,r));
}
protected void addFormFiles(HttpEntityEnclosingRequest request, ArrayListMultimap<String, String> params ,FormFile[] files) throws UnsupportedEncodingException {
final MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create()
.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
.setCharset(CharsetUtils.get(DEFAULT_CHARSET));
if(null != params){
params.keySet().forEach(key->params.get(key).forEach(value->multipartEntityBuilder.addTextBody(key , value)));
}
for (FormFile formFile : files) {
multipartEntityBuilder.addBinaryBody(formFile.getParameterName(), formFile.getInStream() , ContentType.parse(formFile.getContentType()) , formFile.getFilName());
}
HttpEntity reqEntity = multipartEntityBuilder.build();
request.setEntity(reqEntity);
}
}
OkHttp3:
package cn.zytx.common.http.basic;
import cn.zytx.common.http.base.AbstractOkHttp3;
import cn.zytx.common.http.HttpUtil;
import cn.zytx.common.http.Method;
import cn.zytx.common.utils.ArrayListMultimap;
import cn.zytx.common.utils.IoUtil;
import okhttp3.Headers;
import okhttp3.MultipartBody;
import java.io.File;
import java.io.IOException;
import java.util.Map;
/**
* @author xiongshiyan at 2018/1/11
*/
public class OkHttp3Client extends AbstractOkHttp3 implements HttpClient {
@Override
public String get(String url, Map<String, String> params, Map<String, String> headers, int connectTimeout, int readTimeout, String resultCharset) throws IOException {
return template(HttpUtil.contactUrlParams(url , params),Method.GET,null,null,
ArrayListMultimap.fromMap(headers),
connectTimeout,readTimeout,resultCharset,false,(b,r,h)-> IoUtil.read(b ,r));
}
/**
* @param bodyCharset 请求体的编码,不支持,需要在contentType中指定
*/
@Override
public String post(String url, String body, String contentType, Map<String, String> headers, int connectTimeout, int readTimeout, String bodyCharset, String resultCharset) throws IOException {
return template(url, Method.POST, contentType, d->setRequestBody(d , Method.POST , stringBody(body , contentType)) ,
ArrayListMultimap.fromMap(headers),
connectTimeout,readTimeout,resultCharset,false,
(b,r,h)-> IoUtil.read(b ,r));
}
@Override
public byte[] getAsBytes(String url, ArrayListMultimap<String, String> headers, int connectTimeout, int readTimeout) throws IOException {
return template(url,Method.GET,null,null, headers ,
connectTimeout,readTimeout,null,false,
(b,r,h)-> IoUtil.stream2Bytes(b));
}
@Override
public File getAsFile(String url, ArrayListMultimap<String, String> headers, File file, int connectTimeout, int readTimeout) throws IOException {
return template(url,Method.GET,null,null, headers ,
connectTimeout,readTimeout,null,false,
(b,r,h)-> IoUtil.copy2File(b, file));
}
@Override
public String upload(String url, ArrayListMultimap<String, String> headers, int connectTimeout, int readTimeout, String resultCharset, FormFile... files) throws IOException {
MultipartBody requestBody = getFilesBody(files);
return template(url, Method.POST, null , d->setRequestBody(d, Method.POST , requestBody), headers,
connectTimeout,readTimeout,resultCharset,false,
(b,r,h)-> IoUtil.read(b ,r));
}
protected MultipartBody getFilesBody(FormFile... files) {
MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
for (FormFile formFile : files) {
builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + formFile.getParameterName() + "\"; filename=\"" + formFile.getFilName() + "\"") ,
inputStreamBody(formFile.getContentType() , formFile.getInStream() , formFile.getFileLen()));
}
return builder.build();
}
@Override
public String upload(String url, ArrayListMultimap<String, String> params, ArrayListMultimap<String, String> headers, int connectTimeout, int readTimeout, String resultCharset , FormFile... files) throws IOException {
MultipartBody requestBody = getFilesBody(params , files);
return template(url, Method.POST, null , d->setRequestBody(d, Method.POST , requestBody), headers,
connectTimeout,readTimeout,resultCharset,false,
(b,r,h)-> IoUtil.read(b ,r));
}
protected MultipartBody getFilesBody(ArrayListMultimap<String, String> params , FormFile... files) {
MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
if(null != params){
params.keySet().forEach(key->params.get(key).forEach(value->builder.addFormDataPart(key , value)));
}
for (FormFile formFile : files) {
builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + formFile.getParameterName() + "\"; filename=\"" + formFile.getFilName() + "\"") ,
inputStreamBody(formFile.getContentType() , formFile.getInStream() , formFile.getFileLen()));
}
return builder.build();
}
}
其中关于SSL处理的一些工具类【感谢looly等】
package cn.zytx.common.http.base.ssl;
import cn.zytx.common.utils.ArrayUtil;
import cn.zytx.common.utils.StrUtil;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Objects;
/**
* SSLSocketFactory构建器
* @author Looly
* @author xiongshiyan 增加证书相关方法
*/
public class SSLSocketFactoryBuilder {
/** Supports some version of SSL; may support other versions */
public static final String SSL = "SSL";
/** Supports SSL version 2 or later; may support other versions */
public static final String SSLv2 = "SSLv2";
/** Supports SSL version 3; may support other versions */
public static final String SSLv3 = "SSLv3";
/** Supports some version of TLS; may support other versions */
public static final String TLS = "TLS";
/** Supports RFC 2246: TLS version 1.0 ; may support other versions */
public static final String TLSv1 = "TLSv1";
/** Supports RFC 4346: TLS version 1.1 ; may support other versions */
public static final String TLSv11 = "TLSv1.1";
/** Supports RFC 5246: TLS version 1.2 ; may support other versions */
public static final String TLSv12 = "TLSv1.2";
/**
* 证书类型
*/
public static final String JKS = "JKS";
public static final String PKCS12 = "PKCS12";
private String protocol = TLS;
private String certType = PKCS12;
private KeyManager[] keyManagers;
private TrustManager[] trustManagers = {new DefaultTrustManager()};
private SecureRandom secureRandom = new SecureRandom();
/**
* 创建 SSLSocketFactoryBuilder
* @return SSLSocketFactoryBuilder
*/
public static SSLSocketFactoryBuilder create(){
return new SSLSocketFactoryBuilder();
}
/**
* 设置协议
* @param protocol 协议
* @return 自身
*/
public SSLSocketFactoryBuilder setProtocol(String protocol){
if(StrUtil.isNotBlank(protocol)){
this.protocol = protocol;
}
return this;
}
/**
* 设置证书类型
* @param certType 证书类型
*/
public SSLSocketFactoryBuilder setCertType(String certType) {
if(StrUtil.isNotBlank(certType)){
this.certType = certType;
}
return this;
}
/**
* 设置信任信息
* @param trustManagers TrustManager列表
* @return 自身
*/
public SSLSocketFactoryBuilder setTrustManagers(TrustManager... trustManagers) {
if (ArrayUtil.isNotEmpty(trustManagers)) {
this.trustManagers = trustManagers;
}
return this;
}
public TrustManager[] getTrustManagers() {
return trustManagers;
}
/**
* 设置 JSSE key managers
* @param keyManagers JSSE key managers
* @return 自身
*/
public SSLSocketFactoryBuilder setKeyManagers(KeyManager... keyManagers) {
if (ArrayUtil.isNotEmpty(keyManagers)) {
this.keyManagers = keyManagers;
}
return this;
}
/**
* 设置 SecureRandom
* @param secureRandom SecureRandom
* @return 自己
*/
public SSLSocketFactoryBuilder setSecureRandom(SecureRandom secureRandom){
if(null != secureRandom){
this.secureRandom = secureRandom;
}
return this;
}
/**
* 构建SSLSocketFactory
* @return SSLSocketFactory
*/
public SSLSocketFactory build() {
return getSSLContext().getSocketFactory();
}
/**
* 用于ssl双向认证,例如微信退款应用中
* 用证书和密码构建SSLSocketFactory
* @param certPath 证书路径
* @param certPass 证书密码
*/
public SSLSocketFactory build(String certPath, String certPass){
return getSSLContext(certPath , certPass).getSocketFactory();
}
public SSLSocketFactory buildWithClassPathCert(Class<?> clazz , String certPath, String certPass){
return getClassPathSSLContext(clazz , certPath , certPass).getSocketFactory();
}
public SSLSocketFactory build(InputStream inputStream, String certPass){
return getSSLContext(inputStream , certPass).getSocketFactory();
}
public SSLContext getSSLContext(){
try {
SSLContext sslContext = SSLContext.getInstance(protocol);
sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom);
return sslContext;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 使用FileInputStream来加载资源
* @param certPath 证书路径
* @param certPass 证书密码
*/
public SSLContext getSSLContext(String certPath, String certPass){
try {
InputStream inputStream = new FileInputStream(certPath);
return getSSLContext(inputStream , certPass);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 使用class来加载资源
* @param clazz 用于加载资源 /开头的话就从classpath目录下,否则从class包下
* @see Class#getResourceAsStream(String)
* @param certPath classpath 证书路径
* @param certPass 证书密码
*/
public SSLContext getClassPathSSLContext(Class<?> clazz , String certPath, String certPass){
try {
InputStream inputStream = clazz.getResourceAsStream(certPath);
return getSSLContext(inputStream , certPass);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @param certStream 证书流,本方法会自动关闭
* @param certPass 密码
*/
public SSLContext getSSLContext(InputStream certStream, String certPass){
InputStream inputStream = Objects.requireNonNull(certStream);
try {
KeyStore clientStore = KeyStore.getInstance(certType);
char[] passArray = certPass.toCharArray();
clientStore.load(inputStream, passArray);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientStore, passArray);
KeyManager[] kms = kmf.getKeyManagers();
//"TLSv1"
SSLContext sslContext = SSLContext.getInstance(protocol);
sslContext.init(kms, this.trustManagers, this.secureRandom);
inputStream.close();
return sslContext;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package cn.zytx.common.http.base.ssl;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
/**
* https 域名校验
* @author Looly
*/
public class TrustAnyHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
// 直接返回true
return true;
}
}
package cn.zytx.common.http.base.ssl;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
/**
* 证书管理
* @author Looly
*
*/
public class DefaultTrustManager implements X509TrustManager {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
}
package cn.zytx.common.http.base.ssl;
import java.security.cert.X509Certificate;
/**
* 防止OKHttp3的空指针
* @author xiongshiyan
*/
public class DefaultTrustManager2 extends DefaultTrustManager {
@Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] certificates = {};
return certificates;
}
}
操作更简便的SmartHttpClient的实现类
HttpURLConnection
package cn.zytx.common.http.smart;
import cn.zytx.common.http.*;
import cn.zytx.common.http.basic.NativeHttpClient;
import cn.zytx.common.utils.ArrayListMultimap;
import cn.zytx.common.utils.IoUtil;
import java.io.*;
/**
* 使用URLConnection实现的Http请求类
* @author 熊诗言2017/11/24
*/
public class NativeSmartHttpClient extends NativeHttpClient implements SmartHttpClient {
@Override
public Response get(Request req) throws IOException {
Request request = beforeTemplate(req);
/*Response response = template(ParamUtil.contactUrlParams(request.getUrl(), request.getParams() , request.getBodyCharset()), Method.GET,
request.getContentType(), null, request.getHeaders(), request.getConnectionTimeout(), request.getReadTimeout(),
request.getResultCharset(), request.isIncludeHeaders(), Response::with);*/
Response response = template(request.setUrl(ParamUtil.contactUrlParams(request.getUrl(), request.getParams(), request.getBodyCharset())) ,
Method.GET , null , Response::with);
return afterTemplate(request , response);
}
@Override
public Response post(Request req) throws IOException {
Request request = beforeTemplate(req);
/*Response response = template(request.getUrl(), Method.POST, request.getContentType(),
connection -> writeContent(connection, request.getBody(), request.getBodyCharset()),
request.getHeaders(), request.getConnectionTimeout(), request.getReadTimeout(),
request.getResultCharset(), request.isIncludeHeaders(), Response::with);*/
Response response = template(request, Method.POST , connection -> writeContent(connection, request.getBody(), request.getBodyCharset()) , Response::with);
return afterTemplate(request , response);
}
@Override
public byte[] getAsBytes(Request req) throws IOException {
Request request = beforeTemplate(req);
/*return template(request.getUrl(), Method.GET, request.getContentType(), null,request.getHeaders(),
request.getConnectionTimeout(), request.getReadTimeout(), request.getResultCharset(), request.isIncludeHeaders() ,
(s,b,r,h)-> IoUtil.stream2Bytes(b));*/
return template(request , Method.GET , null , (s, b, r, h)-> IoUtil.stream2Bytes(b));
}
@Override
public File getAsFile(Request req) throws IOException {
Request request = beforeTemplate(req);
/*return template(request.getUrl(), Method.GET, request.getContentType(), null,request.getHeaders(),
request.getConnectionTimeout(), request.getReadTimeout(), request.getResultCharset(), request.isIncludeHeaders() ,
(s,b,r,h)-> IoUtil.copy2File(b, request.getFile()));*/
return template(request , Method.GET , null , (s, b, r, h)-> IoUtil.copy2File(b, request.getFile()));
}
@Override
public Response upload(Request req) throws IOException {
Request request = beforeTemplate(req);
/*ArrayListMultimap<String, String> headers = mergeHeaders(request.getHeaders(), request.getFormFiles());
Response response = template(request.getUrl(), Method.POST, null, connect -> this.upload0(connect, request.getFormFiles()),
headers, request.getConnectionTimeout(), request.getReadTimeout(), request.getResultCharset(), request.isIncludeHeaders(),
Response::with);*/
/*使用更全的 ,支持文件和参数一起上传 */
ArrayListMultimap<String, String> headers = mergeHeaders(request.getHeaders());
/*Response response = template(request.getUrl(), Method.POST, null, connect -> this.upload0(connect, request.getParams() ,request.getFormFiles()),
headers, request.getConnectionTimeout(), request.getReadTimeout(), request.getResultCharset(), request.isIncludeHeaders(),
Response::with);*/
Response response = template(request.setHeaders(headers), Method.POST ,
connect -> this.upload0(connect, request.getParams(), request.getFormFiles()) , Response::with);
return afterTemplate(request , response);
}
@Override
public Response afterTemplate(Request request, Response response) throws IOException{
if(request.isRedirectable() && response.needRredirect()){
return get(Request.of(response.getRedirectUrl()));
}
return response;
}
}
ApacheHttpClient
package cn.zytx.common.http.smart;
import cn.zytx.common.http.*;
import cn.zytx.common.http.basic.ApacheHttpClient;
import cn.zytx.common.utils.IoUtil;
import java.io.File;
import java.io.IOException;
/**
* 使用Apache SmartHttpClient 实现的Http请求类
* @author 熊诗言2017/12/01
*/
public class ApacheSmartHttpClient extends ApacheHttpClient implements SmartHttpClient {
@Override
public Response get(Request req) throws IOException {
Request request = beforeTemplate(req);
/*Response response = template(ParamUtil.contactUrlParams(request.getUrl(), request.getParams() , request.getBodyCharset()), Method.GET,
request.getContentType(), null, request.getHeaders(),
request.getConnectionTimeout(), request.getReadTimeout(),
request.getResultCharset(), request.isIncludeHeaders(),
Response::with);*/
Response response = template(request.setUrl(ParamUtil.contactUrlParams(request.getUrl(), request.getParams(), request.getBodyCharset())) ,
Method.GET , null , Response::with);
return afterTemplate(request , response);
}
@Override
public Response post(Request req) throws IOException {
Request request = beforeTemplate(req);
/*Response response = template(request.getUrl(), Method.POST, request.getContentType(),
r -> setRequestBody(r, request.getBody(), request.getBodyCharset()), request.getHeaders(),
request.getConnectionTimeout(), request.getReadTimeout(), request.getResultCharset(), request.isIncludeHeaders(),
Response::with);*/
Response response = template(request, Method.POST ,
r -> setRequestBody(r, request.getBody(), request.getBodyCharset()) , Response::with);
return afterTemplate(request , response);
}
@Override
public byte[] getAsBytes(Request req) throws IOException {
Request request = beforeTemplate(req);
/*return template(request.getUrl(), Method.GET, request.getContentType(),null, request.getHeaders(),
request.getConnectionTimeout(),request.getReadTimeout() , request.getResultCharset(),request.isIncludeHeaders(),
(s,b,r,h)-> IoUtil.stream2Bytes(b));*/
return template(request , Method.GET , null , (s, b, r, h)-> IoUtil.stream2Bytes(b));
}
@Override
public File getAsFile(Request req) throws IOException {
Request request = beforeTemplate(req);
/*return template(request.getUrl(), Method.GET, request.getContentType(),null, request.getHeaders() ,
request.getConnectionTimeout(),request.getReadTimeout() , request.getResultCharset(),request.isIncludeHeaders(),
(s,b,r,h)-> IoUtil.copy2File(b, request.getFile()));*/
return template(request , Method.GET, null , (s, b, r, h)-> IoUtil.copy2File(b, request.getFile()));
}
@Override
public Response upload(Request req) throws IOException {
Request request = beforeTemplate(req);
/*Response response = template(request.getUrl(), Method.POST, request.getContentType(), r -> addFormFiles(r, request.getFormFiles()),
request.getHeaders(), request.getConnectionTimeout(), request.getReadTimeout(), request.getResultCharset(), request.isIncludeHeaders(),
Response::with);*/
/*使用更全的 ,支持文件和参数一起上传 */
/*Response response = template(request.getUrl(), Method.POST, request.getContentType(), r -> addFormFiles(r, request.getParams() , request.getFormFiles()),
request.getHeaders(), request.getConnectionTimeout(), request.getReadTimeout(), request.getResultCharset(), request.isIncludeHeaders(),
Response::with);*/
Response response = template(request , Method.POST ,
r -> addFormFiles(r, request.getParams(), request.getFormFiles()) , Response::with);
return afterTemplate(request , response);
}
@Override
public Response afterTemplate(Request request, Response response) throws IOException{
if(request.isRedirectable() && response.needRredirect()){
return get(Request.of(response.getRedirectUrl()));
}
return response;
}
}
OkHttp3
package cn.zytx.common.http.smart;
import cn.zytx.common.http.ParamUtil;
import cn.zytx.common.http.Method;
import cn.zytx.common.http.basic.OkHttp3Client;
import cn.zytx.common.utils.IoUtil;
import okhttp3.MultipartBody;
import java.io.File;
import java.io.IOException;
/**
* @author xiongshiyan at 2018/1/11
*/
public class OkHttp3SmartHttpClient extends OkHttp3Client implements SmartHttpClient {
@Override
public Response get(Request req) throws IOException {
Request request = beforeTemplate(req);
/*Response response = template(ParamUtil.contactUrlParams(request.getUrl(), request.getParams() , request.getBodyCharset()), Method.GET, request.getContentType(), null, request.getHeaders(),
request.getConnectionTimeout(), request.getReadTimeout(), request.getResultCharset(), request.isIncludeHeaders(),
Response::with);
return afterTemplate(request , response);*/
Response response = template(request.setUrl(ParamUtil.contactUrlParams(request.getUrl(), request.getParams(), request.getBodyCharset())) , Method.GET , null , Response::with);
return afterTemplate(request , response);
}
/**
* @param req 请求体的编码,不支持,需要在contentType中指定
*/
@Override
public Response post(Request req) throws IOException {
Request request = beforeTemplate(req);
/*Response response = template(request.getUrl(), Method.POST, request.getContentType(), d -> setRequestBody(d, Method.POST, stringBody(request.getBody(), request.getContentType())),
request.getHeaders(), request.getConnectionTimeout(), request.getReadTimeout(), request.getResultCharset(), request.isIncludeHeaders(),
Response::with);*/
Response response = template(request, Method.POST , d -> setRequestBody(d, Method.POST, stringBody(request.getBody(), request.getContentType())) , Response::with);
return afterTemplate(request , response);
}
@Override
public byte[] getAsBytes(Request req) throws IOException {
Request request = beforeTemplate(req);
/*return template(request.getUrl(),Method.GET,request.getContentType() ,null, request.getHeaders() ,
request.getConnectionTimeout(),request.getReadTimeout(),request.getResultCharset(),request.isIncludeHeaders(),
(s,b,r,h)-> IoUtil.stream2Bytes(b));*/
return template(request , Method.GET , null , (s, b, r, h)-> IoUtil.stream2Bytes(b));
}
@Override
public File getAsFile(Request req) throws IOException {
Request request = beforeTemplate(req);
/*return template(request.getUrl(),Method.GET,request.getContentType(),null, request.getHeaders() ,
request.getConnectionTimeout(),request.getConnectionTimeout(),request.getResultCharset(),request.isIncludeHeaders(),
(s,b,r,h)-> IoUtil.copy2File(b, request.getFile()));*/
return template(request , Method.GET , null , (s, b, r, h)-> IoUtil.copy2File(b, request.getFile()));
}
@Override
public Response upload(Request req) throws IOException {
Request request = beforeTemplate(req);
//MultipartBody requestBody = getFilesBody(request.getFormFiles());
/*使用更全的 ,支持文件和参数一起上传 */
MultipartBody requestBody = getFilesBody(request.getParams() , request.getFormFiles());
/*Response response = template(request.getUrl(), Method.POST, request.getContentType(), d -> setRequestBody(d, Method.POST, requestBody), request.getHeaders(),
request.getConnectionTimeout(), request.getConnectionTimeout(), request.getResultCharset(), request.isIncludeHeaders(),
Response::with);*/
Response response = template(request, Method.POST , d -> setRequestBody(d, Method.POST, requestBody) , Response::with);
return afterTemplate(request , response);
}
@Override
public Response afterTemplate(Request request, Response response) throws IOException{
if(request.isRedirectable() && response.needRredirect()){
return get(Request.of(response.getRedirectUrl()));
}
return response;
}
}
直接将结果封装为Bean的实现,使用的时候需要给这些类设置一个Converter的实现类(String->Bean)。
HTTPURLConnection
package cn.zytx.common.http.withconverter;
import cn.zytx.common.http.smart.NativeSmartHttpClient;
import java.util.Objects;
/**
* 返回结果String转换为<R>
* @author 熊诗言2017/11/24
*/
public class ConverterNativeSmartHttpClient extends NativeSmartHttpClient implements ConverterSmartHttpClient {
private Converter converter;
public ConverterNativeSmartHttpClient(Converter converter){
this.converter = Objects.requireNonNull(converter);
}
public ConverterNativeSmartHttpClient(){}
@Override
public ConverterSmartHttpClient setConverter(Converter converter) {
this.converter = Objects.requireNonNull(converter);
return this;
}
@Override
public <R> R convert(String src, Class<R> clazz) {
if(converter == null){
throw new IllegalStateException("converter is null,please use setConverter() to setting a converter");
}
return converter.convert(src , clazz);
}
}
ApacheHttp
package cn.zytx.common.http.withconverter;
import cn.zytx.common.http.smart.ApacheSmartHttpClient;
import java.util.Objects;
/**
* 返回结果String转换为<R>
* @author 熊诗言2017/12/01
*/
public class ConverterApacheSmartHttpClient extends ApacheSmartHttpClient implements ConverterSmartHttpClient {
private Converter converter;
public ConverterApacheSmartHttpClient(Converter converter){
this.converter = Objects.requireNonNull(converter);
}
public ConverterApacheSmartHttpClient(){}
@Override
public ConverterSmartHttpClient setConverter(Converter converter) {
this.converter = Objects.requireNonNull(converter);
return this;
}
@Override
public <R> R convert(String src, Class<R> clazz) {
if(converter == null){
throw new IllegalStateException("converter is null,please use setConverter() to setting a converter");
}
return converter.convert(src , clazz);
}
}
OkHttp3
package cn.zytx.common.http.withconverter;
import cn.zytx.common.http.smart.OkHttp3SmartHttpClient;
import java.util.Objects;
/**
* 返回结果String转换为<R>
* @author xiongshiyan at 2018/1/11
*/
public class ConverterOkHttp3SmartHttpClient extends OkHttp3SmartHttpClient implements ConverterSmartHttpClient {
private Converter converter;
public ConverterOkHttp3SmartHttpClient(Converter converter){
this.converter = Objects.requireNonNull(converter);
}
public ConverterOkHttp3SmartHttpClient(){}
@Override
public ConverterSmartHttpClient setConverter(Converter converter) {
this.converter = Objects.requireNonNull(converter);
return this;
}
@Override
public <R> R convert(String src, Class<R> clazz) {
if(converter == null){
throw new IllegalStateException("converter is null,please use setConverter() to setting a converter");
}
return converter.convert(src , clazz);
}
}
其中的一个实现Json2BeanConverter
package cn.zytx.common.http.withconverter;
import cn.zytx.common.json.JsonObject;
import cn.zytx.common.json.impl.JSONObject;
/**
* Json String -> JavaBean
* 此类也示范了统一json的好处
* cn.zytx.common.json.JsonObject
* cn.zytx.common.json.impl.JSONObject
* <!包名不变!>,只需要在build.gradle中修改引用就能改变json的实现
* @author xiongshiyan at 2018/7/16
*/
public class Json2BeanConverter implements Converter{
@Override
public <R> R convert(String src, Class<R> clazz){
JsonObject json = new JSONObject();
return json.deserialize(src, clazz);
}
}
json的实现参见我的另外两篇博文(完美统一Json的使用):
https://blog.csdn.net/xxssyyyyssxx/article/details/80696560
https://blog.csdn.net/xxssyyyyssxx/article/details/80696822
使用的时候可以使用HttpUtil工具里的getSmartHttpClient获取实现类,可以通过setSmartHttpClient修改默认的实现(默认是基于HTTPURLConnection)。
还可以通过 delegate()方法获取自动的实现,默认加载顺序是OkHttp3、ApacheHttpClient、HTTPURLConnection,即根据jar包自动加载。
package cn.zytx.common.http;
import cn.zytx.common.http.basic.HttpClient;
import cn.zytx.common.http.basic.NativeHttpClient;
import cn.zytx.common.http.converter.json.ConverterSmartHttpClient;
import cn.zytx.common.http.smart.NativeSmartHttpClient;
import cn.zytx.common.http.smart.SmartHttpClient;
import cn.zytx.common.utils.ClassUtil;
/**
* @author xiongshiyan at 2017/12/11
*/
public class HttpUtil {
private HttpUtil(){}
/**
* 静态绑定
*/
private static HttpClient BASIC_HTTP_CLIENT = new NativeHttpClient();
private static SmartHttpClient SMART_HTTP_CLIENT = new NativeSmartHttpClient();
public static HttpClient getBasicHttpClient() {
return BASIC_HTTP_CLIENT;
}
public static void setBasicHttpClient(HttpClient httpClient){BASIC_HTTP_CLIENT = httpClient;}
public static SmartHttpClient getSmartHttpClient() {
return SMART_HTTP_CLIENT;
}
public static void setSmartHttpClient(SmartHttpClient smartHttpClient){SMART_HTTP_CLIENT = smartHttpClient;}
// http请求工具代理对象
private static final ConverterSmartHttpClient delegate;
public static ConverterSmartHttpClient delegate() {
return delegate;
}
static {
//根据类路径的jar加载默认顺序是 OKHttp3、ApacheHttpClient、URLConnection
ConverterSmartHttpClient delegateToUse = null;
// okhttp3.OkHttpClient ?
if (ClassUtil.isPresent(HttpUtil.class.getClassLoader() ,"okhttp3.OkHttpClient" , "okio.Okio")) {
delegateToUse = new cn.zytx.common.http.converter.json.ConverterOkHttp3SmartHttpClient();
}
// org.apache.http.impl.client.CloseableHttpClient ?
else if (ClassUtil.isPresent(HttpUtil.class.getClassLoader() ,
"org.apache.http.impl.client.CloseableHttpClient","org.apache.http.impl.client.HttpClientBuilder")) {
delegateToUse = new cn.zytx.common.http.converter.json.ConverterApacheSmartHttpClient();
}
// java.net.URLConnection
else if (ClassUtil.isPresent(HttpUtil.class.getClassLoader() ,"java.net.URLConnection")) {
delegateToUse = new cn.zytx.common.http.converter.json.ConverterNativeSmartHttpClient();
}
delegate = delegateToUse;
}
}
在springboot环境中,通过注入bean的方式使用,可以灵活地定制实现类及做一些额外的设置。
@Configuration
public class HttpConfig {
@Bean("smartHttpClient")
public SmartHttpClient smartHttpClient(){
//如果要更换http的实现或者做更多的事情,可以对此bean进行配置
return new NativeSmartHttpClient();
}
}
详见:
https://gitee.com/xxssyyyyssxx/unihttp-all/tree/master/src/test/java/top/jfunc/http