sviolet.slate.common.x.net.loadbalance.classic.MultiHostOkHttpClient Maven / Gradle / Ivy
Show all versions of slate-http-client Show documentation
/*
* Copyright (C) 2015-2018 S.Violet
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Project GitHub: https://github.com/shepherdviolet/slate
* Email: [email protected]
*/
package sviolet.slate.common.x.net.loadbalance.classic;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sviolet.slate.common.x.monitor.txtimer.TimerContext;
import sviolet.slate.common.x.monitor.txtimer.noref.NoRefTxTimer;
import sviolet.slate.common.x.monitor.txtimer.noref.NoRefTxTimerFactory;
import sviolet.slate.common.x.net.loadbalance.LoadBalancedHostManager;
import sviolet.thistle.util.common.CloseableUtils;
import sviolet.thistle.util.conversion.ByteUtils;
import sviolet.thistle.util.judge.CheckUtils;
import javax.net.ssl.SSLSocketFactory;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 支持均衡负载的OkHttpClient(简单的示例模板, 建议自行实现)
*
* Java:
*
* {@code
*
* LoadBalancedHostManager hostManager = new LoadBalancedHostManager()
* .setHostArray(new String[]{
* "http://127.0.0.1:8080",
* "http://127.0.0.1:8081"
* });
*
* LoadBalancedInspectManager inspectManager = new LoadBalancedInspectManager()
* .setHostManager(hostManager)
* .setInspectInterval(5000L)
* .setInspector(new TelnetLoadBalanceInspector());
*
* MultiHostOkHttpClient client = new MultiHostOkHttpClient()
* .setHostManager(hostManager)
* .setMaxThreads(256)
* .setMaxThreadsPerHost(256)
* .setPassiveBlockDuration(30000L)
* .setConnectTimeout(3000L)
* .setWriteTimeout(10000L)
* .setReadTimeout(10000L);
*
* }
*
* Spring MVC: 注册了SlateServletContextListener的场合
*
* {@code
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* }
*
* Spring MVC: 没注册SlateServletContextListener的场合, 需要设置destroy-method="close"
*
* {@code
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* }
*
* @author S.Violet
*/
public class MultiHostOkHttpClient {
//普通日志:全开
public static final int LOG_CONFIG_ALL = 0xFFFFFFFF;
//普通日志:全关
public static final int LOG_CONFIG_NONE = 0x00000000;
//普通日志:实际请求的URL(不带参数)
public static final int LOG_CONFIG_REAL_URL = 0x00000001;
//普通日志:阻断日志
public static final int LOG_CONFIG_BLOCK = 0x00000010;
//普通日志:默认
public static final int LOG_CONFIG_DEFAULT = LOG_CONFIG_ALL;
//额外日志:全开
public static final int VERBOSE_LOG_CONFIG_ALL = 0xFFFFFFFF;
//额外日志:全关
public static final int VERBOSE_LOG_CONFIG_NONE = 0x00000000;
//额外日志:输入参数(默认关)
public static final int VERBOSE_LOG_CONFIG_REQUEST_INPUTS = 0x00000001;
//额外日志:请求报文体(最高可读性)
public static final int VERBOSE_LOG_CONFIG_REQUEST_STRING_BODY= 0x00000010;
//额外信息:请求URL(带参数, 且参数未转码)
public static final int VERBOSE_LOG_CONFIG_RAW_URL= 0x00000100;
//额外日志:响应码
public static final int VERBOSE_LOG_CONFIG_RESPONSE_CODE = 0x00001000;
//额外日志:默认
public static final int VERBOSE_LOG_CONFIG_DEFAULT = VERBOSE_LOG_CONFIG_REQUEST_STRING_BODY |
VERBOSE_LOG_CONFIG_RAW_URL |
VERBOSE_LOG_CONFIG_RESPONSE_CODE;
private static final String LOG_PREFIX = "HttpClient | ";
private static final long PASSIVE_BLOCK_DURATION = 30000L;
private static final String MEDIA_TYPE = "application/json;charset=utf-8";
private static final String ENCODE = "utf-8";
private static final String TXTIMER_GROUP_SEND = "MultiHostOkHttpClient-Send-";
private static final String TXTIMER_GROUP_CONNECT = "MultiHostOkHttpClient-Connect-";
private static final Logger logger = LoggerFactory.getLogger(MultiHostOkHttpClient.class);
private static final AtomicInteger requestCounter = new AtomicInteger(0);
private volatile OkHttpClient okHttpClient;
private LoadBalancedHostManager hostManager;
private Settings settings = new Settings();
private volatile boolean refreshSettings = false;
private volatile Exception clientCreateException;
private AtomicBoolean settingsLock = new AtomicBoolean(false);
private SettingsSpinLock settingsSpinLock = new SettingsSpinLock();
private NoRefTxTimer txTimer;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 请求 ///////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* 创建POST请求, 请求创建过程非线程安全, 请勿多线程操作同一个请求
*
* https://github.com/shepherdviolet/slate/blob/master/docs/loadbalance/invoke-sync.md
* https://github.com/shepherdviolet/slate/blob/master/docs/loadbalance/invoke-async.md
*
* @param urlSuffix 请求的url后缀, 不含协议/域名/端口, 例如/user/add.json. 如果是一个完整的URL, 可以借助HttpUrl.parse(url)
* 获取域名端口后面的请求路径(pathSegments)和请求参数(query).
*/
public Request post(String urlSuffix) {
return new Request(this, urlSuffix, true,
settings.requestTraceEnabled ? requestCounter.getAndIncrement() & 0x00000FFF : Integer.MAX_VALUE);
}
/**
* 创建GET请求, 请求创建过程非线程安全, 请勿多线程操作同一个请求
*
* https://github.com/shepherdviolet/slate/blob/master/docs/loadbalance/invoke-sync.md
* https://github.com/shepherdviolet/slate/blob/master/docs/loadbalance/invoke-async.md
*
* @param urlSuffix 请求的url后缀, 不含协议/域名/端口, 例如/user/add.json. 如果是一个完整的URL, 可以借助HttpUrl.parse(url)
* 获取域名端口后面的请求路径(pathSegments)和请求参数(query).
*/
public Request get(String urlSuffix) {
return new Request(this, urlSuffix, false,
settings.requestTraceEnabled ? requestCounter.getAndIncrement() & 0x00000FFF : Integer.MAX_VALUE);
}
/**
* 请求(该对象非线程安全, 请勿多线程操作同一个对象)
*/
public static class Request {
//status
private WeakReference clientReference;
private boolean isSend = false;
private int requestId;
//basic
private String urlSuffix;
private boolean isPost = false;
private Map headers;
private Map urlParams;
//body
private byte[] body;
private Map formBody;
private Object beanBody;
private RequestBody customBody;
//senior
private boolean autoClose = true;
private long passiveBlockDuration = -1;
private String mediaType;
private String encode;
private DataConverter dataConverter;
private Stub stub = new Stub();
private Request(MultiHostOkHttpClient client, String urlSuffix, boolean isPost, int requestId) {
this.clientReference = new WeakReference<>(client);
this.urlSuffix = urlSuffix;
this.isPost = isPost;
this.requestId = requestId;
}
/**
* [配置]URL参数, 即HTTP请求中URL后面跟随的?key=value&key=value
*/
public Request urlParams(Map urlParams) {
this.urlParams = urlParams;
return this;
}
/**
* [配置]添加一个URL参数, 即HTTP请求中URL后面跟随的?key=value&key=value
*/
public Request urlParam(String key, Object value) {
if (this.urlParams == null) {
this.urlParams = new HashMap<>(8);
}
this.urlParams.put(key, value);
return this;
}
/**
* [配置]POST请求专用: 请求报文体, byte[]类型
*/
public Request body(byte[] body) {
if (!isPost) {
throw new IllegalArgumentException("You can not set body in GET request");
}
this.body = body;
this.formBody = null;
this.beanBody = null;
this.customBody = null;
return this;
}
/**
* [配置]POST请求专用: 请求报文体, 表单
*/
public Request formBody(Map formBody) {
if (!isPost) {
throw new IllegalArgumentException("You can not set body in GET request");
}
this.body = null;
this.formBody = formBody;
this.beanBody = null;
this.customBody = null;
return this;
}
/**
* [配置]POST请求专用: 请求报文体, JavaBean
* 注意: 必须配置DataConverter, 否则发送时会报错
*/
public Request beanBody(Object beanBody) {
if (!isPost) {
throw new IllegalArgumentException("You can not set body in GET request");
}
this.body = null;
this.formBody = null;
this.beanBody = beanBody;
this.customBody = null;
return this;
}
/**
*
* [配置]POST请求专用: 请求报文体, OkHttp RequestBody
* 用这个会使得Request.mediaType()无效, 会由Builder().setType()指定.
*
*
*
* //Multipart Form 示例
* //文件
* RequestBody fileBody = RequestBody.create(MediaType.parse("image/png"), fileOrData);
* //Multipart请求体
* RequestBody requestBody = new MultipartBody.Builder()
* .setType(MultipartBody.FORM)
* .addFormDataPart("file", "file_name", fileBody)
* .addFormDataPart("param1", value1)
* .addFormDataPart("param2", value2)
* .build();
*
*/
public Request customBody(RequestBody customBody) {
if (!isPost) {
throw new IllegalArgumentException("You can not set body in GET request");
}
this.body = null;
this.formBody = null;
this.beanBody = null;
this.customBody = customBody;
return this;
}
/**
* [配置]HTTP请求头参数, 客户端配置和此处配置的均生效(此处配置优先)
*/
public Request httpHeaders(Map httpHeaders) {
this.headers = httpHeaders;
return this;
}
/**
* [配置]添加一个HTTP请求头参数, 客户端配置和此处配置的均生效(此处配置优先)
*/
public Request httpHeader(String key, String value) {
if (this.headers == null) {
this.headers = new HashMap<>(8);
}
this.headers.put(key, value);
return this;
}
/**
* [配置]设置被动检测到网络故障时阻断后端的时间, 客户端配置和此处配置的均生效(此处配置优先)
*
* 当请求服务端时, 发生特定的异常或返回特定的响应码(MultiHostOkHttpClient.needBlock方法决定), 客户端会将该
* 后端服务器的IP/PORT标记为暂不可用状态, 而阻断时长是不可用的时长
*
* @param passiveBlockDuration 阻断时长ms
*/
public Request passiveBlockDuration(long passiveBlockDuration) {
this.passiveBlockDuration = passiveBlockDuration;
return this;
}
/**
* [配置]报文体MediaType, 客户端配置和此处配置的均生效(此处配置优先)
*/
public Request mediaType(String mediaType) {
this.mediaType = mediaType;
return this;
}
/**
* [配置]字符编码, 客户端配置和此处配置的均生效(此处配置优先)
*/
public Request encode(String encode) {
this.encode = encode;
return this;
}
/**
* [配置]数据转换器, 用于将beanBody设置的JavaBean转换为byte[], 和将返回报文byte[]转换为JavaBean
* 客户端配置和此处配置的均生效(此处配置优先)
*/
public Request dataConverter(DataConverter dataConverter) {
this.dataConverter = dataConverter;
return this;
}
/**
* [配置]异步请求专用: 配置响应实例(或输入流)是否在回调方法onSucceed结束后自动关闭, 默认true
*
* 注意:同步请求返回的ResponsePackage/InputStream是必须手动关闭的!!!
*
*
* 当autoClose=true时, onSucceed方法回调结束后, ResponsePackage/InputStream会被自动关闭, 无需手动调用close方法. 适用于
* 响应数据在回调方法中处理完的场合.
* 当autoClose=false时, onSucceed方法回调结束后, ResponsePackage/InputStream不会自动关闭, 需要手动调用ResponsePackage.close()关闭,
* 注意!!! 适用于响应数据需要交由其他的线程处理, 或暂时持有的场合使用.
*
*/
public Request autoClose(boolean autoClose){
this.autoClose = autoClose;
return this;
}
/**
* [配置]该次请求的连接超时, 单位ms
*/
public Request connectTimeout(int connectTimeout) {
this.stub.connectTimeout = connectTimeout;
return this;
}
/**
* [配置]该次请求的写数据超时, 单位ms
*/
public Request writeTimeout(int writeTimeout) {
this.stub.writeTimeout = writeTimeout;
return this;
}
/**
* [配置]该次请求的读数据超时, 单位ms
*/
public Request readTimeout(int readTimeout) {
this.stub.readTimeout = readTimeout;
return this;
}
/**
* [请求发送]同步请求并获取Bean返回,
* 如果响应码不为2XX, 会抛出HttpRejectException异常.
* 注意: 必须配置DataConverter, 否则会报错
*
* @return 响应, 可能为null
* @throws NoHostException 当前没有可发送的后端(网络请求发送前的异常, 准备阶段异常)
* @throws RequestBuildException 请求初始化异常(通常是网络请求发送前的异常, 准备阶段异常)
* @throws IOException 网络通讯异常(通常是网络请求发送中的异常)
* @throws HttpRejectException Http请求拒绝异常(网络请求发送后的异常, HTTP响应码不为2XX)
*/
public T sendForBean(Class type) throws NoHostException, RequestBuildException, HttpRejectException, IOException {
MultiHostOkHttpClient client = getClient();
if (client == null) {
throw new RequestBuildException("Missing MultiHostOkHttpClient instance, has been destroyed (cleaned by gc)");
}
NoRefTxTimer txTimer = client.txTimer;
if (txTimer != null) {
try (TimerContext timerContext = txTimer.entry(TXTIMER_GROUP_SEND + client.settings.tag, urlSuffix)) {
return client.responseToBean(client.requestSend(this), type, this);
}
} else {
return client.responseToBean(client.requestSend(this), type, this);
}
}
/**
* [请求发送]同步请求并获取byte[]返回,
* 如果响应码不为2XX, 会抛出HttpRejectException异常
*
* @return 响应, 可能为null
* @throws NoHostException 当前没有可发送的后端(网络请求发送前的异常, 准备阶段异常)
* @throws RequestBuildException 请求初始化异常(通常是网络请求发送前的异常, 准备阶段异常)
* @throws IOException 网络通讯异常(通常是网络请求发送中的异常)
* @throws HttpRejectException Http请求拒绝异常(网络请求发送后的异常, HTTP响应码不为2XX)
*/
public byte[] sendForBytes() throws NoHostException, RequestBuildException, HttpRejectException, IOException {
MultiHostOkHttpClient client = getClient();
if (client == null) {
throw new RequestBuildException("Missing MultiHostOkHttpClient instance, has been destroyed (cleaned by gc)");
}
NoRefTxTimer txTimer = client.txTimer;
if (txTimer != null) {
try (TimerContext timerContext = txTimer.entry(TXTIMER_GROUP_SEND + client.settings.tag, urlSuffix)) {
return client.responseToBytes(client.requestSend(this));
}
} else {
return client.responseToBytes(client.requestSend(this));
}
}
/**
* [请求发送]同步请求并获取InputStream返回,
* 如果响应码不为2XX, 会抛出HttpRejectException异常
*
* @return 响应, 可能为null, InputStream用完后必须手动关闭!!!
* @throws NoHostException 当前没有可发送的后端(网络请求发送前的异常, 准备阶段异常)
* @throws RequestBuildException 请求初始化异常(通常是网络请求发送前的异常, 准备阶段异常)
* @throws IOException 网络通讯异常(通常是网络请求发送中的异常)
* @throws HttpRejectException Http请求拒绝异常(网络请求发送后的异常, HTTP响应码不为2XX)
*/
public InputStream sendForInputStream() throws NoHostException, RequestBuildException, HttpRejectException, IOException {
MultiHostOkHttpClient client = getClient();
if (client == null) {
throw new RequestBuildException("Missing MultiHostOkHttpClient instance, has been destroyed (cleaned by gc)");
}
NoRefTxTimer txTimer = client.txTimer;
if (txTimer != null) {
try (TimerContext timerContext = txTimer.entry(TXTIMER_GROUP_SEND + client.settings.tag, urlSuffix)) {
return client.responseToInputStream(client.requestSend(this));
}
} else {
return client.responseToInputStream(client.requestSend(this));
}
}
/**
* [请求发送]同步请求并获取ResponsePackage返回,
* 如果响应码不为2XX, 会抛出HttpRejectException异常,
* 该方法不会根据maxReadLength限定最大读取长度
* @return 响应, 可能为null, ResponsePackage用完后必须手动关闭!!!
* @throws NoHostException 当前没有可发送的后端(网络请求发送前的异常, 准备阶段异常)
* @throws RequestBuildException 请求初始化异常(通常是网络请求发送前的异常, 准备阶段异常)
* @throws IOException 网络通讯异常(通常是网络请求发送中的异常)
* @throws HttpRejectException Http请求拒绝异常(网络请求发送后的异常, HTTP响应码不为2XX)
*/
public ResponsePackage send() throws NoHostException, RequestBuildException, IOException, HttpRejectException {
MultiHostOkHttpClient client = getClient();
if (client == null) {
throw new RequestBuildException("Missing MultiHostOkHttpClient instance, has been destroyed (cleaned by gc)");
}
NoRefTxTimer txTimer = client.txTimer;
if (txTimer != null) {
try (TimerContext timerContext = txTimer.entry(TXTIMER_GROUP_CONNECT + client.settings.tag, urlSuffix)) {
return client.requestSend(this);
}
} else {
return client.requestSend(this);
}
}
/**
* [请求发送]异步请求,
* 如果响应码不为2XX, 会回调onErrorAfterSend()方法给出HttpRejectException异常
* 注意: 必须配置DataConverter, 否则发送时会报错
* @param callback 回调函数{@link BeanCallback}
*/
public Stub enqueue(BeanCallback> callback) {
enqueue((ResponsePackageCallback)callback);
return stub;
}
/**
* [请求发送]异步请求,
* 如果响应码不为2XX, 会回调onErrorAfterSend()方法给出HttpRejectException异常,
* @param callback 回调函数{@link BytesCallback}
*/
public Stub enqueue(BytesCallback callback) {
enqueue((ResponsePackageCallback)callback);
return stub;
}
/**
* [请求发送]异步请求,
* 如果响应码不为2XX, 会回调onErrorAfterSend()方法给出HttpRejectException异常,
* @param callback 回调函数{@link InputStreamCallback}
*/
public Stub enqueue(InputStreamCallback callback) {
enqueue((ResponsePackageCallback)callback);
return stub;
}
/**
* [请求发送]异步请求,
* 如果响应码不为2XX, 会回调onErrorAfterSend()方法给出HttpRejectException异常,
* 该方法不会根据maxReadLength限定最大读取长度
* @param callback 回调函数{@link BytesCallback}/{@link InputStreamCallback}/{@link ResponsePackageCallback}
*/
public Stub enqueue(ResponsePackageCallback callback) {
MultiHostOkHttpClient client = getClient();
if (client == null) {
callback.onErrorBeforeSend(new RequestBuildException("Missing MultiHostOkHttpClient instance, has been destroyed (cleaned by gc)"));
return stub;
}
client.requestEnqueue(this, callback);
return stub;
}
private MultiHostOkHttpClient getClient(){
MultiHostOkHttpClient client = clientReference.get();
if (client == null) {
logger.error("Missing MultiHostOkHttpClient instance, has been destroyed (cleaned by gc), data:" + this);
}
return client;
}
@Override
public String toString() {
return "Request{" +
"urlSuffix='" + urlSuffix + '\'' +
", isPost=" + isPost +
", headers=" + headers +
", urlParams=" + urlParams +
", body=" + ByteUtils.bytesToHex(body) +
", formBody=" + formBody +
", beanBody=" + beanBody +
", customBody=" + customBody +
", passiveBlockDuration=" + passiveBlockDuration +
", mediaType='" + mediaType + '\'' +
", encode='" + encode + '\'' +
", dataConverter=" + dataConverter +
'}';
}
}
private ResponsePackage requestSend(Request request) throws NoHostException, RequestBuildException, HttpRejectException, IOException {
if (request.isSend) {
throw new IllegalStateException("MultiHostOkHttpClient.Request can only send once!");
}
request.isSend = true;
if (request.isPost) {
return syncPost(request);
} else {
return syncGet(request);
}
}
private void requestEnqueue(Request request, ResponsePackageCallback callback) {
if (request.isSend) {
throw new IllegalStateException("MultiHostOkHttpClient.Request can only send once!");
}
request.isSend = true;
if (request.isPost) {
asyncPost(request, callback);
} else {
asyncGet(request, callback);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Sync ///////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
private T responseToBean(ResponsePackage responsePackage, Class type, Request request) throws NoHostException, RequestBuildException, IOException, HttpRejectException {
DataConverter dataConverter = request.dataConverter != null ? request.dataConverter : settings.dataConverter;
if (dataConverter == null) {
//强制关闭
try {
responsePackage.close();
} catch (Throwable ignore) {
}
throw new RequestConvertException("No DataConverter set, you must set dataConverter before sendForBean()");
}
byte[] responseData = responseToBytes(responsePackage);
try {
return dataConverter.convert(responseData, type);
} catch (Exception e) {
throw new ResponseConvertException("Error while convert byte[] to bean", e);
}
}
private byte[] responseToBytes(ResponsePackage responsePackage) throws NoHostException, RequestBuildException, IOException, HttpRejectException {
//返回空
if (responsePackage == null || responsePackage.body() == null) {
return null;
}
try {
//限定读取长度
if (settings.maxReadLength > 0 && responsePackage.body().contentLength() > settings.maxReadLength){
throw new IOException("Response contentLength is out of limit, contentLength:" + responsePackage.body().contentLength() + ", limit:" + settings.maxReadLength);
}
//返回二进制数据
return responsePackage.body().bytes();
} finally {
//返回byte[]类型时自动关闭
try {
responsePackage.close();
} catch (Throwable ignore) {
}
}
}
private InputStream responseToInputStream(ResponsePackage responsePackage) throws NoHostException, RequestBuildException, IOException, HttpRejectException {
//返回空
if (responsePackage == null || responsePackage.body() == null) {
return null;
}
//限定读取长度
if (settings.maxReadLength > 0 && responsePackage.body().contentLength() > settings.maxReadLength){
try {
responsePackage.body().close();
} catch (Throwable ignore) {
}
throw new IOException("Response contentLength is out of limit, contentLength:" + responsePackage.body().contentLength() + ", limit:" + settings.maxReadLength);
}
//返回二进制数据
return responsePackage.body().byteStream();
}
private ResponsePackage syncPost(Request request) throws NoHostException, RequestBuildException, IOException, HttpRejectException {
//获取远端
LoadBalancedHostManager.Host host = fetchHost();
printPostInputsLog(request, host);
printUrlLog(request, host);
//装配Request
okhttp3.Request okRequest;
try {
okRequest = buildPostRequest(host.getUrl(), request, settings);
} catch (Throwable t) {
throw new RequestBuildException("Error while building request", t);
}
if (okRequest == null) {
throw new RequestBuildException("Null request built");
}
if (logger.isInfoEnabled() && CheckUtils.isFlagMatch(settings.logConfig, LOG_CONFIG_REAL_URL)) {
logger.info(genLogPrefix(settings.tag, request) + "POST: real-url:" + okRequest.url().toString());
}
//请求
return syncCall(host, okRequest, request);
}
private ResponsePackage syncGet(Request request) throws NoHostException, RequestBuildException, IOException, HttpRejectException {
//获取远端
LoadBalancedHostManager.Host host = fetchHost();
printGetInputsLog(request, host);
printUrlLog(request, host);
//装配Request
okhttp3.Request okRequest;
try {
okRequest = buildGetRequest(host.getUrl(), request, settings);
} catch (Throwable t) {
throw new RequestBuildException("Error while building request", t);
}
if (okRequest == null) {
throw new RequestBuildException("Null request built");
}
if (logger.isInfoEnabled() && CheckUtils.isFlagMatch(settings.logConfig, LOG_CONFIG_REAL_URL)) {
logger.info(genLogPrefix(settings.tag, request) + "GET: real-url:" + okRequest.url().toString());
}
//请求
return syncCall(host, okRequest, request);
}
private ResponsePackage syncCall(LoadBalancedHostManager.Host host, okhttp3.Request okRequest, Request request) throws RequestBuildException, IOException, HttpRejectException {
//后端是否健康
boolean isOk = true;
//被动阻断时长
long passiveBlockDuration = request.passiveBlockDuration >= 0 ? request.passiveBlockDuration : settings.passiveBlockDuration;
try {
//同步请求
Response response = getOkHttpClient().newCall(okRequest).execute();
printResponseCodeLog(request, response);
//Http拒绝
if (!isSucceed(response)) {
CloseableUtils.closeQuiet(response);
throw new HttpRejectException(response.code(), response.message());
}
//报文体
return ResponsePackage.newInstance(request, response);
} catch (Throwable t) {
if (needBlock(t, settings)) {
//网络故障阻断后端
isOk = false;
if (logger.isInfoEnabled() && CheckUtils.isFlagMatch(settings.logConfig, LOG_CONFIG_BLOCK)){
logger.info(genLogPrefix(settings.tag, request) + "Bad host " + host.getUrl() + ", block for " + passiveBlockDuration + " ms, passive block, recoveryCoefficient " + settings.recoveryCoefficient);
}
}
if (t instanceof IOException ||
t instanceof HttpRejectException) {
throw t;
} else {
throw new RequestBuildException("Error while request build ?", t);
}
} finally {
//反馈健康状态
host.feedback(isOk, passiveBlockDuration, settings.recoveryCoefficient);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Async //////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void asyncPost(Request request, ResponsePackageCallback callback) {
callback.setContext(settings, request);
try {
//获取远端
LoadBalancedHostManager.Host host = fetchHost();
printPostInputsLog(request, host);
printUrlLog(request, host);
//装配Request
okhttp3.Request okRequest;
try {
okRequest = buildPostRequest(host.getUrl(), request, settings);
} catch (Throwable t) {
throw new RequestBuildException("Error while building request", t);
}
if (okRequest == null) {
throw new RequestBuildException("Null request built");
}
if (logger.isInfoEnabled() && CheckUtils.isFlagMatch(settings.logConfig, LOG_CONFIG_REAL_URL)) {
logger.info(genLogPrefix(settings.tag, request) + "POST: real-url:" + okRequest.url().toString());
}
//请求
asyncCall(host, okRequest, request, callback);
} catch (NoHostException | RequestBuildException e) {
callback.onErrorBeforeSend(e);
}
}
private void asyncGet(Request request, ResponsePackageCallback callback) {
callback.setContext(settings, request);
try {
//获取远端
LoadBalancedHostManager.Host host = fetchHost();
printGetInputsLog(request, host);
printUrlLog(request, host);
//装配Request
okhttp3.Request okRequest;
try {
okRequest = buildGetRequest(host.getUrl(), request, settings);
} catch (Throwable t) {
throw new RequestBuildException("Error while building request", t);
}
if (okRequest == null) {
throw new RequestBuildException("Null request built");
}
if (logger.isInfoEnabled() && CheckUtils.isFlagMatch(settings.logConfig, LOG_CONFIG_REAL_URL)) {
logger.info(genLogPrefix(settings.tag, request) + "GET: real-url:" + okRequest.url().toString());
}
//请求
asyncCall(host, okRequest, request, callback);
} catch (NoHostException | RequestBuildException e) {
callback.onErrorBeforeSend(e);
}
}
private void asyncCall(final LoadBalancedHostManager.Host host, okhttp3.Request okRequest, final Request request, final ResponsePackageCallback callback) {
//异步请求
try {
getOkHttpClient().newCall(okRequest).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
printResponseCodeLog(request, response);
//Http拒绝
if (!isSucceed(response)) {
CloseableUtils.closeQuiet(response);
Exception exception = new HttpRejectException(response.code(), response.message());
tryBlock(exception);
callback.onErrorAfterSend(exception);
return;
}
//反馈健康(反馈健康无需计算阻断时长)
host.feedback(true, 0);
//报文体
try {
callback.onSucceed(ResponsePackage.newInstance(request, response));
//自动关闭
if (request.autoClose) {
CloseableUtils.closeQuiet(response);
}
} catch (Exception e) {
//处理onSucceed
callback.errorOnSucceedProcessing(e);
//强制关闭
CloseableUtils.closeQuiet(response);
}
}
@Override
public void onFailure(Call call, IOException e) {
tryBlock(e);
callback.onErrorAfterSend(e);
}
private void tryBlock(Exception e){
if (needBlock(e, settings)) {
//网络故障阻断后端
long passiveBlockDuration = request.passiveBlockDuration >= 0 ? request.passiveBlockDuration : settings.passiveBlockDuration;
//反馈异常
host.feedback(false, passiveBlockDuration, settings.recoveryCoefficient);
if (logger.isInfoEnabled() && CheckUtils.isFlagMatch(settings.logConfig, LOG_CONFIG_BLOCK)) {
logger.info(genLogPrefix(settings.tag, request) + "Bad host " + host.getUrl() + ", block for " + passiveBlockDuration + " ms, passive block, recoveryCoefficient " + settings.recoveryCoefficient);
}
} else {
//反馈健康(反馈健康无需计算阻断时长)
host.feedback(true, 0);
}
}
});
} catch (Exception t) {
callback.onErrorBeforeSend(new RequestBuildException("Error while request build ?", t));
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 私有逻辑 //////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
private LoadBalancedHostManager.Host fetchHost() throws NoHostException {
LoadBalancedHostManager.Host host = hostManager.nextHost();
if (host == null){
throw new NoHostException("No host");
}
return host;
}
private OkHttpClient getOkHttpClient(){
//客户端创建错误后, 不再重试
if (clientCreateException != null) {
throw new IllegalStateException("Client create error", clientCreateException);
}
//一次检查
while (okHttpClient == null || refreshSettings) {
//自旋锁
if (!settingsLock.get() && settingsLock.compareAndSet(false, true)) {
try {
//二次检查e
if (okHttpClient == null || refreshSettings) {
OkHttpClient client = createOkHttpClient(settings);
okHttpClient = client;
refreshSettings = false;
//跳出循环, 避免因为refreshSettings反复被改为true反复由同一个线程创建客户端
break;
}
} catch (Exception e) {
clientCreateException = e;
throw e;
} finally {
//解锁
settingsLock.set(false);
}
} else {
Thread.yield();
}
}
return okHttpClient;
}
private void printPostInputsLog(Request request, LoadBalancedHostManager.Host host) {
if (!logger.isDebugEnabled() || !CheckUtils.isFlagMatch(settings.verboseLogConfig, VERBOSE_LOG_CONFIG_REQUEST_INPUTS)) {
return;
}
String bodyLog;
if (request.body != null) {
bodyLog = ", body(hex):" + ByteUtils.bytesToHex(request.body);
} else if (request.formBody != null) {
bodyLog = ", formBody:" + request.formBody;
} else if (request.beanBody != null) {
bodyLog = ", beanBody:" + request.beanBody;
} else if (request.customBody != null) {
bodyLog = ", customBody:" + request.customBody;
} else {
bodyLog = ", body: null";
}
logger.debug(genLogPrefix(settings.tag, request) + "POST: url:" + host.getUrl() + ", suffix:" + request.urlSuffix + ", urlParams:" + request.urlParams + bodyLog);
}
private void printPostStringBodyLog(Request request, byte[] parsedData) {
if (settings.verboseLog) {
if (!logger.isInfoEnabled()) {
return;
}
} else {
if (!logger.isDebugEnabled()) {
return;
}
}
if (!CheckUtils.isFlagMatch(settings.verboseLogConfig, VERBOSE_LOG_CONFIG_REQUEST_STRING_BODY)){
return;
}
if (request.body != null) {
try {
logger.info(genLogPrefix(settings.tag, request) + "POST: string-body:" + new String(request.body, settings.encode));
} catch (Exception e) {
logger.warn(genLogPrefix(settings.tag, request) + "Error while printing string body", e);
}
} else if (request.formBody != null) {
logger.info(genLogPrefix(settings.tag, request) + "POST: string-body(form):" + request.formBody);
} else if (request.beanBody != null && parsedData != null) {
try {
logger.info(genLogPrefix(settings.tag, request) + "POST: string-body(bean):" + new String(parsedData, settings.encode));
} catch (Exception e) {
logger.warn(genLogPrefix(settings.tag, request) + "Error while printing string body", e);
}
} else if (request.customBody != null) {
logger.info(genLogPrefix(settings.tag, request) + "POST: string-body: multipart data can not be print");
} else {
logger.info(genLogPrefix(settings.tag, request) + "POST: string-body: null");
}
}
private void printGetInputsLog(Request request, LoadBalancedHostManager.Host host) {
if (!logger.isDebugEnabled() || !CheckUtils.isFlagMatch(settings.verboseLogConfig, VERBOSE_LOG_CONFIG_REQUEST_INPUTS)) {
return;
}
logger.debug(genLogPrefix(settings.tag, request) + "GET: url:" + host.getUrl() + ", suffix:" + request.urlSuffix + ", urlParams:" + request.urlParams);
}
private void printUrlLog(Request request, LoadBalancedHostManager.Host host) {
if (!logger.isDebugEnabled() || !CheckUtils.isFlagMatch(settings.verboseLogConfig, VERBOSE_LOG_CONFIG_RAW_URL)) {
return;
}
StringBuilder stringBuilder = new StringBuilder("raw-url:" + host.getUrl() + request.urlSuffix);
if (request.urlParams != null && request.urlParams.size() > 0) {
stringBuilder.append("?");
int i = 0;
for (Map.Entry entry : request.urlParams.entrySet()) {
if (i++ > 0) {
stringBuilder.append("&");
}
stringBuilder.append(entry.getKey());
stringBuilder.append("=");
stringBuilder.append(entry.getValue());
}
}
logger.debug(genLogPrefix(settings.tag, request) + stringBuilder.toString());
}
private void printResponseCodeLog(Request request, Response response) {
if (settings.verboseLog) {
if (!logger.isInfoEnabled()) {
return;
}
} else {
if (!logger.isDebugEnabled()) {
return;
}
}
if (!CheckUtils.isFlagMatch(settings.verboseLogConfig, VERBOSE_LOG_CONFIG_RESPONSE_CODE)) {
return;
}
logger.info(genLogPrefix(settings.tag, request) + "Response: code:" + response.code() + ", message:" + response.message());
}
private String genLogPrefix(String tag, Request request){
if (request.requestId == Integer.MAX_VALUE) {
return tag;
}
return tag + request.requestId + " ";
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 可复写逻辑 //////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* 初始化OkHttpClient实例(复写本方法实现自定义的逻辑)
* @return OkHttpClient实例
*/
@SuppressWarnings("deprecation")
protected OkHttpClient createOkHttpClient(Settings settings){
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequests(settings.maxThreads);
dispatcher.setMaxRequestsPerHost(settings.maxThreadsPerHost);
ConnectionPool connectionPool = new ConnectionPool(settings.maxIdleConnections, 5, TimeUnit.MINUTES);
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(settings.connectTimeout, TimeUnit.MILLISECONDS)
.writeTimeout(settings.writeTimeout, TimeUnit.MILLISECONDS)
.readTimeout(settings.readTimeout, TimeUnit.MILLISECONDS)
.connectionPool(connectionPool)
.dispatcher(dispatcher);
builder.addInterceptor(new Interceptor(){
@Override
public Response intercept(Chain chain) throws IOException {
okhttp3.Request request = chain.request();
if (request.tag() instanceof Stub) {
Stub stub = (Stub) request.tag();
if (stub.connectTimeout > 0) {
chain = chain.withConnectTimeout(stub.connectTimeout, TimeUnit.MILLISECONDS);
}
if (stub.writeTimeout > 0) {
chain = chain.withWriteTimeout(stub.writeTimeout, TimeUnit.MILLISECONDS);
}
if (stub.readTimeout > 0) {
chain = chain.withReadTimeout(stub.readTimeout, TimeUnit.MILLISECONDS);
}
}
return chain.proceed(request);
}
});
if (settings.cookieJar != null) {
builder.cookieJar(settings.cookieJar);
}
if (settings.proxy != null) {
builder.proxy(settings.proxy);
}
if (settings.dns != null) {
builder.dns(settings.dns);
}
if (settings.sslSocketFactory != null) {
builder.sslSocketFactory(settings.sslSocketFactory);
}
return builder.build();
}
/**
* 根据URL和报文体组POST请求(复写本方法实现自定义的逻辑)
* @param url 由LoadBalancedHostManager选择出的远端url(前缀)
* @param request 请求参数
* @param settings 客户端配置
* @return Request
* @throws RequestBuildException 构建异常
*/
protected okhttp3.Request buildPostRequest(String url, Request request, Settings settings) throws RequestBuildException{
if (request.urlSuffix == null) {
request.urlSuffix = "";
}
HttpUrl httpUrl = HttpUrl.parse(url + request.urlSuffix);
if (httpUrl == null){
throw new RequestBuildException("Invalid url:" + url + request.urlSuffix);
}
String encode = request.encode != null ? request.encode : settings.encode;
if (request.urlParams != null){
HttpUrl.Builder httpUrlBuilder = httpUrl.newBuilder();
for (Map.Entry param : request.urlParams.entrySet()) {
try {
httpUrlBuilder.addEncodedQueryParameter(param.getKey(),
URLEncoder.encode(param.getValue() != null ? param.getValue().toString() : "", encode));
} catch (UnsupportedEncodingException e) {
throw new RequestBuildException("Error while encode urlParams to url format", e);
}
}
httpUrl = httpUrlBuilder.build();
}
RequestBody requestBody;
if (request.body != null) {
//bytes
printPostStringBodyLog(request, null);
requestBody = RequestBody.create(MediaType.parse(request.mediaType != null ? request.mediaType : settings.mediaType), request.body);
} else if (request.formBody != null) {
//form
printPostStringBodyLog(request, null);
FormBody.Builder formBuilder = new FormBody.Builder();
for (Map.Entry param : request.formBody.entrySet()) {
try {
formBuilder.addEncoded(param.getKey(),
URLEncoder.encode(param.getValue() != null ? param.getValue().toString() : "", encode));
} catch (UnsupportedEncodingException e) {
throw new RequestBuildException("Error while encode formBody to url format", e);
}
}
requestBody = formBuilder.build();
} else if (request.beanBody != null) {
//bean
DataConverter dataConverter = request.dataConverter != null ? request.dataConverter : settings.dataConverter;
if (dataConverter == null) {
throw new RequestConvertException("No DataConverter set, you must set dataConverter before send/enqueue a beanBody");
}
byte[] requestBodyBytes;
try {
requestBodyBytes = dataConverter.convert(request.beanBody);
} catch (Exception e) {
throw new RequestConvertException("Error while convert bean to byte[]", e);
}
printPostStringBodyLog(request, requestBodyBytes);
requestBody = RequestBody.create(MediaType.parse(request.mediaType != null ? request.mediaType : settings.mediaType), requestBodyBytes);
} else if (request.customBody != null) {
//custom
printPostStringBodyLog(request, null);
requestBody = request.customBody;
}else {
//null
requestBody = RequestBody.create(MediaType.parse(request.mediaType != null ? request.mediaType : settings.mediaType), new byte[0]);
}
okhttp3.Request.Builder builder = new okhttp3.Request.Builder()
.url(httpUrl)
.post(requestBody)
.tag(request.stub);
Map headers = settings.headers;
if (headers != null){
for (Map.Entry entry : headers.entrySet()){
builder.addHeader(entry.getKey(), entry.getValue());
}
}
if (request.headers != null){
for (Map.Entry entry : request.headers.entrySet()){
builder.addHeader(entry.getKey(), entry.getValue());
}
}
return builder.build();
}
/**
* 根据URL和报文体组GET请求(复写本方法实现自定义的逻辑)
* @param url 由LoadBalancedHostManager选择出的远端url(前缀)
* @param request 请求参数
* @param settings 客户端配置
* @return Request
* @throws RequestBuildException 构建异常
*/
protected okhttp3.Request buildGetRequest(String url, Request request, Settings settings) throws RequestBuildException{
if (request.urlSuffix == null) {
request.urlSuffix = "";
}
HttpUrl httpUrl = HttpUrl.parse(url + request.urlSuffix);
if (httpUrl == null){
throw new RequestBuildException("Invalid url:" + url + request.urlSuffix);
}
String encode = request.encode != null ? request.encode : settings.encode;
if (request.urlParams != null){
HttpUrl.Builder httpUrlBuilder = httpUrl.newBuilder();
for (Map.Entry param : request.urlParams.entrySet()) {
try {
httpUrlBuilder.addEncodedQueryParameter(param.getKey(),
URLEncoder.encode(param.getValue() != null ? param.getValue().toString() : "", encode));
} catch (UnsupportedEncodingException e) {
throw new RequestBuildException("Error while encode to url format", e);
}
}
httpUrl = httpUrlBuilder.build();
}
okhttp3.Request.Builder builder = new okhttp3.Request.Builder()
.url(httpUrl)
.get()
.tag(request.stub);
Map headers = settings.headers;
if (headers != null){
for (Map.Entry entry : headers.entrySet()){
builder.addHeader(entry.getKey(), entry.getValue());
}
}
if (request.headers != null){
for (Map.Entry entry : request.headers.entrySet()){
builder.addHeader(entry.getKey(), entry.getValue());
}
}
return builder.build();
}
/**
* 判断该异常是否需要阻断后端, 返回true阻断
*/
protected boolean needBlock(Throwable t, Settings settings) {
return t instanceof ConnectException ||
t instanceof SocketTimeoutException ||
t instanceof UnknownHostException ||
(t instanceof HttpRejectException && settings.httpCodeNeedBlock.contains(((HttpRejectException) t).getResponseCode()));
}
/**
* 判断HTTP请求是否成功, 返回true成功
*/
protected boolean isSucceed(Response response) {
return response.isSuccessful();
}
public String getTag(){
return settings.rawTag;
}
@Override
public String toString() {
return (hostManager != null ? hostManager.printHostsStatus("Hosts [") + " ] " : "") +
"Settings [ " + settings + " ]";
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 配置 //////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* 客户端配置
*/
public static class Settings {
private long passiveBlockDuration = PASSIVE_BLOCK_DURATION;
private String mediaType = MEDIA_TYPE;
private String encode = ENCODE;
private Map headers;
private boolean verboseLog = false;
private int verboseLogConfig = VERBOSE_LOG_CONFIG_DEFAULT;
private int logConfig = LOG_CONFIG_DEFAULT;
private int recoveryCoefficient = 10;
private int maxIdleConnections = 16;
private int maxThreads = 256;
private int maxThreadsPerHost = 256;
private long connectTimeout = 3000L;
private long writeTimeout = 10000L;
private long readTimeout = 10000L;
private long maxReadLength = 10L * 1024L * 1024L;
private CookieJar cookieJar;
private Proxy proxy;
private Dns dns;
private SSLSocketFactory sslSocketFactory;
private DataConverter dataConverter;
private String tag = LOG_PREFIX;
private String rawTag = "";
private Set httpCodeNeedBlock = new HashSet<>(8);
private boolean requestTraceEnabled = false;
private Settings(){
}
@Override
public String toString() {
return "passiveBlockDuration=" + passiveBlockDuration +
", recoveryCoefficient=" + recoveryCoefficient +
", maxIdleConnections=" + maxIdleConnections +
", maxThreads=" + maxThreads +
", maxThreadsPerHost=" + maxThreadsPerHost +
", connectTimeout=" + connectTimeout +
", writeTimeout=" + writeTimeout +
", readTimeout=" + readTimeout +
", maxReadLength=" + maxReadLength +
", headers=" + headers +
", mediaType='" + mediaType + '\'' +
", encode='" + encode + '\'' +
", verboseLog=" + verboseLog +
", verboseLogConfig=" + verboseLogConfig +
", logConfig=" + logConfig +
", cookieJar=" + cookieJar +
", proxy=" + proxy +
", dns=" + dns +
", sslSocketFactory=" + sslSocketFactory +
", dataConverter=" + dataConverter +
", httpCodeNeedBlock=" + httpCodeNeedBlock;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 响应实例 //////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* 响应包
*/
public static class ResponsePackage implements Closeable {
private int code;
private String message;
private boolean isRedirect;
private ResponseBody body;
private Headers headers;
private String requestId;
private static ResponsePackage newInstance(Request request, Response response) {
if (response == null || response.body() == null) {
return null;
}
return new ResponsePackage(request, response);
}
private ResponsePackage(Request request, Response response) {
code = response.code();
message = response.message();
isRedirect = response.isRedirect();
body = response.body();
headers = response.headers();
requestId = request.requestId == Integer.MAX_VALUE ? "" : String.valueOf(request.requestId);
}
public int code() {
return code;
}
public String message() {
return message;
}
public boolean isRedirect() {
return isRedirect;
}
public ResponseBody body() {
return body;
}
public Headers headers() {
return headers;
}
public String requestId() {
return requestId;
}
@Override
public void close(){
CloseableUtils.closeQuiet(body);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 异步方式的Callback /////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* 请求回调(通用)
*/
public static abstract class ResponsePackageCallback {
/**
* 请求成功
*
* 注意: ResponsePackage实例是需要关闭的(close), 但我们提供autoClose配置, 详见Request#autoClose(boolean)
*
*
* 当autoClose=true时, onSucceed方法回调结束后, ResponsePackage/InputStream会被自动关闭, 无需手动调用close方法. 适用于
* 响应数据在回调方法中处理完的场合.
* 当autoClose=false时, onSucceed方法回调结束后, ResponsePackage/InputStream不会自动关闭, 需要手动调用ResponsePackage.close()关闭,
* 注意!!! 适用于响应数据需要交由其他的线程处理, 或暂时持有的场合使用.
*
*
* @param responsePackage 响应包, 可能为null
*/
protected abstract void onSucceed(ResponsePackage responsePackage) throws Exception;
/**
* 请求前发生异常
* @param e {@link RequestBuildException}:请求前发生异常, {@link NoHostException}:未配置后端地址或所有后端地址均不可用
*/
protected abstract void onErrorBeforeSend(Exception e);
/**
* 请求后发生异常
* @param e {@link HttpRejectException}:后端Http拒绝(返回码不为200), {@link IOException}:通讯异常
*/
protected abstract void onErrorAfterSend(Exception e);
/**
* 回调方法onSucceed执行时如果抛出异常, 会回调该方法处理异常, 默认转交onErrorAfterSend方法处理
*/
protected void errorOnSucceedProcessing(Exception e){
onErrorAfterSend(e);
}
void setContext(Settings settings, Request request) {
//do nothing
}
}
/**
* 请求回调(获得byte[]响应体)
*/
public static abstract class BytesCallback extends ResponsePackageCallback {
private Settings settings;
/**
* 请求成功
*
* @param body 响应, 可能为null
*/
public abstract void onSucceed(byte[] body) throws Exception;
@Override
public final void onSucceed(ResponsePackage responsePackage) throws Exception {
byte[] bytes = null;
try {
if (responsePackage != null && responsePackage.body() != null) {
//限定读取长度
if (settings.maxReadLength > 0 && responsePackage.body().contentLength() > settings.maxReadLength) {
throw new IOException("Response contentLength is out of limit, contentLength:" + responsePackage.body().contentLength() + ", limit:" + settings.maxReadLength);
}
//返回二进制数据
bytes = responsePackage.body().bytes();
}
} catch (IOException e) {
onErrorAfterSend(e);
return;
} finally {
//byte[]类型返回时, 强制关闭(无论autoClose是什么配置)
if (responsePackage != null){
try {
responsePackage.close();
} catch (Throwable ignore) {
}
}
}
onSucceed(bytes);
}
@Override
void setContext(Settings settings, Request request) {
this.settings = settings;
}
}
/**
* 请求回调(获得InputStream响应体)
*/
public static abstract class InputStreamCallback extends ResponsePackageCallback {
private Settings settings;
/**
* 请求成功
*
* 注意: InputStream实例是需要关闭的(close), 但我们提供autoClose配置, 详见Request#autoClose(boolean)
*
*
* 当autoClose=true时, onSucceed方法回调结束后, ResponsePackage/InputStream会被自动关闭, 无需手动调用close方法. 适用于
* 响应数据在回调方法中处理完的场合.
* 当autoClose=false时, onSucceed方法回调结束后, ResponsePackage/InputStream不会自动关闭, 需要手动调用ResponsePackage.close()关闭,
* 注意!!! 适用于响应数据需要交由其他的线程处理, 或暂时持有的场合使用.
*
*
* @param inputStream 响应, 可能为null
*/
public abstract void onSucceed(InputStream inputStream) throws Exception;
@Override
public final void onSucceed(ResponsePackage responsePackage) throws Exception {
//返回空
if (responsePackage == null || responsePackage.body() == null) {
onSucceed((InputStream) null);
return;
}
//限定读取长度
if (settings.maxReadLength > 0 && responsePackage.body().contentLength() > settings.maxReadLength) {
//长度超过限制时, 强制关闭(无论autoClose是什么配置)
try {
responsePackage.close();
} catch (Throwable ignore) {
}
onErrorAfterSend(new IOException("Response contentLength is out of limit, contentLength:" + responsePackage.body().contentLength() + ", limit:" + settings.maxReadLength));
return;
}
//返回二进制数据
onSucceed(responsePackage.body().byteStream());
}
@Override
void setContext(Settings settings, Request request) {
this.settings = settings;
}
}
/**
* 请求回调(获得JavaBean响应体)
*/
public static abstract class BeanCallback extends BytesCallback {
private Request request;
private Settings settings;
/**
* 请求成功
*
* JavaBean的类型有BeanCallback的泛型决定
*
* @param bean 响应, 可能为null
*/
public abstract void onSucceed(T bean) throws Exception;
@Override
public final void onSucceed(byte[] body) throws Exception {
DataConverter dataConverter = request.dataConverter != null ? request.dataConverter : settings.dataConverter;
if (dataConverter == null) {
throw new ResponseConvertException("No DataConverter set, you must set dataConverter before enqueue a beanBody");
}
//当前类的父类(BeanCallback实现类的父类), 即MultiHostOkHttpClient$BeanCallback
Type superType = getClass().getGenericSuperclass();
if (!(superType instanceof ParameterizedType)) {
//MultiHostOkHttpClient$BeanCallback有泛型, 因此这里的superType必然是ParameterizedType实例
//P.S.泛型类的实现类getGenericSuperclass返回ParameterizedType实例
//P.S.非泛型类的实现类getGenericSuperclass返回Class实例
throw new IllegalStateException("FATAL: superType is not an instance of ParameterizedType!");
}
//获取第0个泛型类型, 即T的实际类型
Type generic0Type = ((ParameterizedType)superType).getActualTypeArguments()[0];
//P.S.在getActualTypeArguments返回的类型数组中, 泛型类是ParameterizedType实例, 非泛型类是Class实例
if (generic0Type instanceof ParameterizedType) {
//如果第0个泛型类型(T的实际类型)是泛型类, 则generic0Type是ParameterizedType实例
//使用getRawType方法取原始类型用于类型转换
//例如T为Map时, 只取Map类型
onSucceed(dataConverter.convert(body, (Class) ((ParameterizedType) generic0Type).getRawType()));
} else {
//如果第0个泛型类型(T的实际类型)不是泛型类, 则generic0Type是Class实例, 直接转为Class即可
//例如T类Map时, 直接类型转换为Class