com.ejlchina.okhttps.HttpTask Maven / Gradle / Ivy
package com.ejlchina.okhttps;
import com.ejlchina.okhttps.HttpResult.State;
import com.ejlchina.okhttps.internal.*;
import com.ejlchina.okhttps.internal.HttpClient.TagTask;
import okhttp3.*;
import okhttp3.internal.http.HttpMethod;
import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Created by 周旭(Troy.Zhou) on 2020/3/11.
*/
@SuppressWarnings("unchecked")
public abstract class HttpTask> implements Cancelable {
private static final String PATH_PARAM_REGEX = "[A-Za-z0-9_\\-/]*\\{[A-Za-z0-9_\\-]+\\}[A-Za-z0-9_\\-/]*";
protected HttpClient httpClient;
protected boolean nothrow;
protected boolean nextOnIO = false;
private String urlPath;
private String tag;
private Map headers;
private Map pathParams;
private Map urlParams;
private Map bodyParams;
private Map files;
private Object requestBody;
private String dateFormat;
private String bodyType;
private OnCallback onProcess;
private boolean processOnIO;
private long stepBytes = 0;
private double stepRate = -1;
private Object object;
private TagTask tagTask;
private Cancelable canceler;
private Charset charset;
protected boolean skipPreproc = false;
protected boolean skipSerialPreproc = false;
public HttpTask(HttpClient httpClient, String url) {
this.urlPath = url;
this.httpClient = httpClient;
this.charset = httpClient.charset();
this.bodyType = httpClient.bodyType();
}
/**
* 获取请求任务的URL地址
* @return URL地址
*/
public String getUrl() {
return urlPath;
}
/**
* @return 是否是 Websocket 通讯
*/
public boolean isWebsocket() {
return false;
}
/**
* @since 2.2.0
* @return 是否是 同步 Http 请求
*/
public boolean isSyncHttp() {
return false;
}
/**
* @since 2.2.0
* @return 是否是 异步 Http 请求
*/
public boolean isAsyncHttp() {
return false;
}
/**
* 获取请求任务的标签
* @return 标签
*/
public String getTag() {
return tag;
}
public String getBodyType() {
return bodyType;
}
/**
* 标签匹配
* 判断任务标签与指定的标签是否匹配(包含指定的标签)
* @param tag 标签
* @return 是否匹配
*/
public boolean isTagged(String tag) {
if (this.tag != null && tag != null) {
return this.tag.contains(tag);
}
return false;
}
/**
* 获取请求任务的头信息
* @return 头信息
*/
public Map getHeaders() {
return headers;
}
/**
* 获得被绑定的对象
* @return Object
*/
public Object getBound() {
return object;
}
/**
* 设置在发生异常时不向上抛出,设置后:
* 异步请求可以在异常回调内捕获异常,同步请求在返回结果中找到该异常
* @return HttpTask 实例
*/
public C nothrow() {
this.nothrow = true;
return (C) this;
}
/**
* 指定该请求跳过任何预处理器(包括串行和并行)
* @return HttpTask 实例
*/
public C skipPreproc() {
this.skipPreproc = true;
return (C) this;
}
/**
* 指定该请求跳过任何串行预处理器
* @return HttpTask 实例
*/
public C skipSerialPreproc() {
this.skipSerialPreproc = true;
return (C) this;
}
/**
* @since 2.0.0.RC
* 为请求任务添加标签
* @param tag 标签
* @return HttpTask 实例
*/
public C tag(String tag) {
if (tag != null) {
if (this.tag != null) {
this.tag = this.tag + "." + tag;
} else {
this.tag = tag;
}
updateTagTask();
}
return (C) this;
}
/**
* @since 2.0.0
* 设置该请求的编码格式
* @param charset 编码格式
* @return HttpTask 实例
*/
public C charset(Charset charset) {
if (charset != null) {
this.charset = charset;
}
return (C) this;
}
/**
* @since 2.0.0
* 设置请求体的类型,如:form、json、xml、protobuf 等
* @param type 请求类型
* @return HttpTask 实例
*/
public C bodyType(String type) {
if (type != null) {
this.bodyType = type;
}
return (C) this;
}
/**
* 下一个回调在IO线程执行
* @return HttpTask 实例
*/
public C nextOnIO() {
nextOnIO = true;
return (C) this;
}
/**
* 绑定一个对象
* @param object 对象
* @return HttpTask 实例
*/
public C bind(Object object) {
this.object = object;
return (C) this;
}
/**
* 添加请求头
* @param name 请求头名
* @param value 请求头值
* @return HttpTask 实例
*/
public C addHeader(String name, String value) {
if (name != null && value != null) {
if (headers == null) {
headers = new HashMap<>();
}
headers.put(name, value);
}
return (C) this;
}
/**
* 添加请求头
* @param headers 请求头集合
* @return HttpTask 实例
*/
public C addHeader(Map headers) {
if (headers != null) {
if (this.headers == null) {
this.headers = new HashMap<>();
}
this.headers.putAll(headers);
}
return (C) this;
}
/**
* 设置Range头信息
* 表示接收报文体时跳过的字节数,用于断点续传
* @param rangeStart 表示从 rangeStart 个字节处开始接收,通常是已经下载的字节数,即上次的断点)
* @return HttpTask 实例
*/
public C setRange(long rangeStart) {
return addHeader("Range", "bytes=" + rangeStart + "-");
}
/**
* 设置Range头信息
* 设置接收报文体时接收的范围,用于分块下载
* @param rangeStart 表示从 rangeStart 个字节处开始接收
* @param rangeEnd 表示接收到 rangeEnd 个字节处
* @return HttpTask 实例
*/
public C setRange(long rangeStart, long rangeEnd) {
return addHeader("Range", "bytes=" + rangeStart + "-" + rangeEnd);
}
/**
* 设置报文体发送进度回调
* @param onProcess 进度回调函数
* @return HttpTask 实例
*/
public C setOnProcess(OnCallback onProcess) {
this.onProcess = onProcess;
processOnIO = nextOnIO;
nextOnIO = false;
return (C) this;
}
/**
* 设置进度回调的步进字节,默认 8K(8192)
* 表示每接收 stepBytes 个字节,执行一次进度回调
* @param stepBytes 步进字节
* @return HttpTask 实例
*/
public C stepBytes(long stepBytes) {
this.stepBytes = stepBytes;
return (C) this;
}
/**
* 设置进度回调的步进比例
* 表示每接收 stepRate 比例,执行一次进度回调
* @param stepRate 步进比例
* @return HttpTask 实例
*/
public C stepRate(double stepRate) {
this.stepRate = stepRate;
return (C) this;
}
/**
* 路径参数:替换URL里的{name}
* @param name 参数名
* @param value 参数值
* @return HttpTask 实例
**/
public C addPathPara(String name, Object value) {
if (name != null && value != null) {
if (pathParams == null) {
pathParams = new HashMap<>();
}
pathParams.put(name, value.toString());
}
return (C) this;
}
/**
* 路径参数:替换URL里的{name}
* @param params 参数集合
* @return HttpTask 实例
**/
public C addPathPara(Map params) {
if (pathParams == null) {
pathParams = new HashMap<>();
}
doAddParams(pathParams, params);
return (C) this;
}
/**
* URL参数:拼接在URL后的参数
* @param name 参数名
* @param value 参数值
* @return HttpTask 实例
**/
public C addUrlPara(String name, Object value) {
if (name != null && value != null) {
if (urlParams == null) {
urlParams = new HashMap<>();
}
urlParams.put(name, value.toString());
}
return (C) this;
}
/**
* URL参数:拼接在URL后的参数
* @param params 参数集合
* @return HttpTask 实例
**/
public C addUrlPara(Map params) {
if (urlParams == null) {
urlParams = new HashMap<>();
}
doAddParams(urlParams, params);
return (C) this;
}
/**
* Body参数:放在Body里的参数
* @param name 参数名
* @param value 参数值
* @return HttpTask 实例
**/
public C addBodyPara(String name, Object value) {
if (name != null && value != null) {
if (bodyParams == null) {
bodyParams = new HashMap<>();
}
bodyParams.put(name, value.toString());
}
return (C) this;
}
/**
* Body参数:放在Body里的参数
* @param params 参数集合
* @return HttpTask 实例
**/
public C addBodyPara(Map params) {
if (bodyParams == null) {
bodyParams = new HashMap<>();
}
doAddParams(bodyParams, params);
return (C) this;
}
private void doAddParams(Map taskParams, Map params) {
if (params != null) {
for (String name : params.keySet()) {
Object value = params.get(name);
if (name != null && value != null) {
taskParams.put(name, value.toString());
}
}
}
}
/**
* 设置 json 请求体
* @param body 请求体,字节数组、字符串 或 Java对象(由 MsgConvertor 来序列化)
* @return HttpTask 实例
**/
public C setBodyPara(Object body) {
this.requestBody = body;
return (C) this;
}
/**
* 添加文件参数
* @param name 参数名
* @param filePath 文件路径
* @return HttpTask 实例
*/
public C addFilePara(String name, String filePath) {
return addFilePara(name, new File(filePath));
}
/**
* 添加文件参数
* @param name 参数名
* @param file 文件
* @return HttpTask 实例
*/
public C addFilePara(String name, File file) {
if (name != null && file != null && file.exists()) {
String fileName = file.getName();
String type = fileName.substring(fileName.lastIndexOf(".") + 1);
if (files == null) {
files = new HashMap<>();
}
files.put(name, new FilePara(type, fileName, file));
}
return (C) this;
}
/**
* 添加文件参数
* @param name 参数名
* @param type 文件类型: 如 png、jpg、jpeg 等
* @param content 文件内容
* @return HttpTask 实例
*/
public C addFilePara(String name, String type, byte[] content) {
return addFilePara(name, type, null, content);
}
/**
* 添加文件参数
* @param name 参数名
* @param type 文件类型: 如 png、jpg、jpeg 等
* @param fileName 文件名
* @param content 文件内容
* @return HttpTask 实例
*/
public C addFilePara(String name, String type, String fileName, byte[] content) {
if (name != null && content != null) {
if (files == null) {
files = new HashMap<>();
}
files.put(name, new FilePara(type, fileName, content));
}
return (C) this;
}
@Override
public boolean cancel() {
if (canceler != null) {
return canceler.cancel();
}
return false;
}
static class FilePara {
String type;
String fileName;
byte[] content;
File file;
FilePara(String type, String fileName, byte[] content) {
this.type = type;
this.fileName = fileName;
this.content = content;
}
FilePara(String type, String fileName, File file) {
this.type = type;
this.fileName = fileName;
this.file = file;
}
}
protected void registeTagTask(Cancelable canceler) {
if (tag != null && tagTask == null) {
tagTask = httpClient.addTagTask(tag, canceler, this);
}
this.canceler = canceler;
}
private void updateTagTask() {
if (tagTask != null) {
tagTask.setTag(tag);
} else
if (canceler != null) {
registeTagTask(canceler);
}
}
protected void removeTagTask() {
if (tag != null) {
httpClient.removeTagTask(this);
}
}
protected Call prepareCall(String method) {
Request request = prepareRequest(method);
return httpClient.request(request);
}
protected Request prepareRequest(String method) {
boolean bodyCanUsed = HttpMethod.permitsRequestBody(method);
assertNotConflict(!bodyCanUsed);
Request.Builder builder = new Request.Builder()
.url(buildUrlPath());
buildHeaders(builder);
if (bodyCanUsed) {
RequestBody reqBody = buildRequestBody();
if (onProcess != null) {
long contentLength = contentLength(reqBody);
if (stepRate > 0 && stepRate <= 1) {
stepBytes = (long) (contentLength * stepRate);
}
if (stepBytes <= 0) {
stepBytes = Process.DEFAULT_STEP_BYTES;
}
reqBody = new ProcessRequestBody(reqBody, onProcess,
httpClient.executor().getExecutor(processOnIO),
contentLength, stepBytes);
}
builder.method(method, reqBody);
} else {
builder.method(method, null);
}
if (tag != null) {
builder.tag(String.class, tag);
}
return builder.build();
}
private long contentLength(RequestBody reqBody) {
try {
return reqBody.contentLength();
} catch (IOException e) {
throw new HttpException("无法获取请求体长度", e);
}
}
private void buildHeaders(Request.Builder builder) {
if (headers != null) {
for (String name : headers.keySet()) {
String value = headers.get(name);
if (value != null) {
builder.addHeader(name, value);
}
}
}
}
protected State toState(IOException e) {
if (e instanceof SocketTimeoutException) {
return State.TIMEOUT;
} else if (e instanceof UnknownHostException || e instanceof ConnectException) {
return State.NETWORK_ERROR;
}
String msg = e.getMessage();
if (msg != null && ("Canceled".equals(msg) || e instanceof SocketException
&& (msg.startsWith("Socket operation on nonsocket") || "Socket closed".equals(msg)))) {
return State.CANCELED;
}
return State.EXCEPTION;
}
private RequestBody buildRequestBody() {
if (files != null) {
MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
if (bodyParams != null) {
for (String name : bodyParams.keySet()) {
byte[] value = bodyParams.get(name).getBytes(charset);
RequestBody body = RequestBody.create(null, value);
builder.addPart(MultipartBody.Part.createFormData(name, null, body));
}
}
for (String name : files.keySet()) {
FilePara file = files.get(name);
MediaType type = httpClient.mediaType(file.type);
RequestBody bodyPart;
if (file.file != null) {
bodyPart = RequestBody.create(type, file.file);
} else {
bodyPart = RequestBody.create(type, file.content);
}
builder.addFormDataPart(name, file.fileName, bodyPart);
}
return builder.build();
}
if (requestBody != null) {
return toRequestBody(requestBody);
}
if (bodyParams == null) {
return new FormBody.Builder(charset).build();
}
if (OkHttps.FORM.equalsIgnoreCase(bodyType)) {
FormBody.Builder builder = new FormBody.Builder(charset);
for (String name : bodyParams.keySet()) {
String value = bodyParams.get(name);
builder.add(name, value);
}
return builder.build();
}
return toRequestBody(bodyParams);
}
private RequestBody toRequestBody(Object object) {
if (object instanceof byte[] || object instanceof String) {
String mediaType = httpClient.executor().doMsgConvert(bodyType, null).mediaType;
byte[] body = object instanceof byte[] ? (byte[]) object : ((String) object).getBytes(charset);
return RequestBody.create(MediaType.parse(mediaType + "; charset=" + charset.name()), body);
}
TaskExecutor.Data data = httpClient.executor()
.doMsgConvert(bodyType, (MsgConvertor c) -> c.serialize(object, dateFormat, charset));
return RequestBody.create(MediaType.parse(data.mediaType + "; charset=" + charset.name()), data.data);
}
private String buildUrlPath() {
String url = urlPath;
if (url == null || url.trim().isEmpty()) {
throw new HttpException("url 不能为空!");
}
if (pathParams != null) {
for (String name : pathParams.keySet()) {
String target = "{" + name + "}";
if (url.contains(target)) {
url = url.replace(target, pathParams.get(name));
} else {
throw new HttpException("pathParameter [ " + name + " ] 不存在于 url [ " + urlPath + " ]");
}
}
}
if (url.matches(PATH_PARAM_REGEX)) {
throw new HttpException("url 里有 pathParameter 没有设置,你必须先调用 addPathParam 为其设置!");
}
if (urlParams != null) {
url = buildUrl(url.trim());
}
return url;
}
private String buildUrl(String url) {
StringBuilder sb = new StringBuilder(url);
if (url.contains("?")) {
if (!url.endsWith("?")) {
if (url.lastIndexOf("=") < url.lastIndexOf("?") + 2) {
throw new HttpException("url 格式错误,'?' 后没有发现 '='");
}
if (!url.endsWith("&")) {
sb.append('&');
}
}
} else {
sb.append('?');
}
for (String name : urlParams.keySet()) {
sb.append(name).append('=').append(urlParams.get(name)).append('&');
}
sb.delete(sb.length() - 1, sb.length());
return sb.toString();
}
protected void assertNotConflict(boolean bodyCantUsed) {
if (bodyCantUsed) {
if (requestBody != null) {
throw new HttpException("GET | HEAD 请求 不能调用 setBodyPara 方法!");
}
if (bodyParams != null) {
throw new HttpException("GET | HEAD 请求 不能调用 addBodyPara 方法!");
}
if (files != null) {
throw new HttpException("GET | HEAD 请求 不能调用 addFilePara 方法!");
}
}
if (requestBody != null) {
if (bodyParams != null) {
throw new HttpException("方法 addBodyPara 与 setBodyPara 不能同时调用!");
}
if (files != null) {
throw new HttpException("方法 addFilePara 与 setBodyPara 不能同时调用!");
}
}
}
/**
* @param latch CountDownLatch
* @return 是否未超时:false 表示已超时
*/
protected boolean timeoutAwait(CountDownLatch latch) {
try {
return latch.await(httpClient.preprocTimeoutMillis(),
TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new HttpException("超时", e);
}
}
protected HttpResult timeoutResult() {
if (nothrow) {
return new RealHttpResult(this, State.TIMEOUT);
}
throw new HttpException(State.TIMEOUT, "执行超时");
}
public Charset charset(Response response) {
ResponseBody b = response.body();
MediaType type = b != null ? b.contentType() : null;
return type != null ? type.charset(charset) : charset;
}
protected void execute(Runnable command, boolean onIo) {
httpClient.executor().execute(command, onIo);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy