package ml.puredark.hviewer.http;
import android.graphics.drawable.BitmapDrawable;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.util.Pair;
import com.facebook.stetho.okhttp3.StethoInterceptor;
import com.lzy.okgo.https.HttpsUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ml.puredark.hviewer.HViewerApplication;
import ml.puredark.hviewer.beans.Site;
import ml.puredark.hviewer.helpers.Logger;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class HViewerHttpClient {
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private static Handler mHandler = new Handler(Looper.getMainLooper());
private static DownloadUtil downloadUtil;
private static HttpsUtils.SSLParams sslParams = HttpsUtils.getSslSocketFactory(null, null, null, null);
private static OkHttpClient mClient = new OkHttpClient.Builder()
.addNetworkInterceptor(new StethoInterceptor())
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.dns(new HttpDns())
.hostnameVerifier((hostname, session) -> true)
.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
.build();
public static DownloadUtil getDownloadUtil(){
if(downloadUtil==null){
downloadUtil = new DownloadUtil(mClient);
}
return downloadUtil;
}
public static MediaType getContentType(String url, List<Pair<String, String>> headers) {
Response response = getResponseHeader(url, headers);
return response.body().contentType();
}
public static long getContentLength(String url, List<Pair<String, String>> headers) {
Response response = getResponseHeader(url, headers);
return (response != null) ? response.body().contentLength() : 0;
}
public static Response getResponseHeader(String url, List<Pair<String, String>> headers) {
if (url == null || !url.startsWith("http")) {
return null;
}
HRequestBuilder builder = new HRequestBuilder();
if (headers != null) {
for (Pair<String, String> header : headers) {
builder.addHeader(header.first, header.second);
}
}
Response response = null;
try {
Request request = builder
.url(url)
.head()
.build();
response = mClient.newCall(request).execute();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
response.close();
}
}
return response;
}
public static Object get(String url, List<Pair<String, String>> headers) {
if (url == null || !url.startsWith("http")) {
Logger.d("HViewerHttpClient", "url = " + url);
return null;
}
if (HViewerApplication.isNetworkAvailable()) {
HRequestBuilder builder = new HRequestBuilder();
if (headers != null) {
for (Pair<String, String> header : headers) {
builder.addHeader(header.first, header.second);
}
}
try {
Request request = builder
.url(url)
.build();
Response response = mClient.newCall(request).execute();
String contentType = response.header("Content-Type");
Object body;
if (contentType != null && contentType.contains("image")) {
body = BitmapDrawable.createFromStream(response.body().byteStream(), null);
} else if(contentType != null && contentType.contains("charset")){
byte[] b = response.body().bytes();
String charset = matchCharset(contentType);
body = new String(b, charset);
}
else {
byte[] b = response.body().bytes();
String charset = getCharset(new String(b));
body = new String(b, charset);
}
response.close();
return body;
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
return null;
}
public static void get(String url, List<Pair<String, String>> headers, final OnResponseListener callback) {
get(url, true, headers, false, null, callback);
}
public static void get(String url, boolean disableHProxy, List<Pair<String, String>> headers, boolean post, final OnResponseListener callback) {
if (!post)
get(url, disableHProxy, headers, false, null, callback);
else {
String jsonParam = Site.getJsonParams(url, 0, "", null);
String params = (jsonParam != null) ? jsonParam : url.substring(url.indexOf('?'));
post(url, disableHProxy, params, (jsonParam != null), headers, callback);
}
}
public static void get(String url, boolean disableHProxy, List<Pair<String, String>> headers, boolean post, RequestBody body, final OnResponseListener callback) {
if (url == null || !url.startsWith("http")) {
Logger.d("HViewerHttpClient", "url = " + url);
callback.onFailure(new HttpError(HttpError.ERROR_WRONG_URL));
return;
}
if (HViewerApplication.isNetworkAvailable()) {
try {
HRequestBuilder builder = new HRequestBuilder(disableHProxy);
if (headers != null) {
for (Pair<String, String> header : headers) {
builder.addHeader(header.first, header.second);
}
}
if (post)
builder.post(body);
builder.url(url);
Request request = builder.build();
mClient.newCall(request).enqueue(new HCallback() {
@Override
void onFailure(IOException e) {
callback.onFailure(new HttpError(HttpError.ERROR_NETWORK));
}
@Override
void onResponse(String contentType, Object body) {
callback.onSuccess(contentType, body);
}
});
} catch (IllegalArgumentException e){
callback.onFailure(new HttpError(HttpError.ERROR_NETWORK));
}
} else {
callback.onFailure(new HttpError(HttpError.ERROR_NETWORK));
}
}
public static void post(String url, boolean disableHProxy, String params, boolean isPostJson, List<Pair<String, String>> headers, final OnResponseListener callback) {
RequestBody requestBody;
if(isPostJson){
requestBody = RequestBody.create(JSON, params);
} else {
String[] paramStrings = params.split("&");
FormBody.Builder formBody = new FormBody.Builder();
for (String paramString : paramStrings) {
String[] pram = paramString.split("=");
if (pram.length != 2) continue;
formBody.add(pram[0], pram[1]);
}
requestBody = formBody.build();
}
get(url, disableHProxy, headers, true, requestBody, callback);
}
/**
* 获得字符集
*/
public static String getCharset(String html) {
Document doc = Jsoup.parse(html);
Elements eles = doc.select("meta[http-equiv=Content-Type],meta[charset]");
Iterator<Element> itor = eles.iterator();
while (itor.hasNext())
return matchCharset(itor.next().toString());
return "utf-8";
}
/**
* 获得页面字符
*/
public static String matchCharset(String content) {
String chs = "utf-8";
Pattern p = Pattern.compile("charset.*?([\\w-]+)", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(content);
if (m.find() && m.groupCount()>0)
return m.group(1);
return chs;
}
public interface OnResponseListener {
void onSuccess(String contentType, Object result);
void onFailure(HttpError error);
}
// UI Thread Handler
public static abstract class HCallback implements Callback {
abstract void onFailure(IOException e);
abstract void onResponse(String contentType, Object body);
@Override
public void onFailure(Call call, final IOException e) {
e.printStackTrace();
mHandler.post(() -> onFailure(e));
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String contentType = response.header("Content-Type");
final Object body;
if (contentType != null && contentType.contains("image")) {
//不经过图片加载库容易导致OOM,宁愿重新加载一次
//body = BitmapFactory.decodeStream(response.body().byteStream());
body = null;
} else if(contentType != null && contentType.contains("charset")){
byte[] b = response.body().bytes();
String charset = matchCharset(contentType);
body = new String(b, charset);
}
else {
byte[] b = response.body().bytes();
String charset = getCharset(new String(b));
body = new String(b, charset);
}
mHandler.post(() -> onResponse(contentType, body));
}
}
// Pre-define error code
public static class HttpError {
// Error code constants
public static final int ERROR_UNKNOWN = 1000; //未知错误
public static final int ERROR_JSON = 1001; //JSON解析错误
public static final int ERROR_NETWORK = 1009; //网络错误
public static final int ERROR_WRONG_URL = 1011; //URL格式错误
private int errorCode;
private String errorString = "";
public HttpError(int errorCode) {
this.errorCode = errorCode;
switch (errorCode) {
case ERROR_UNKNOWN:
errorString = "未知错误";
break;
case ERROR_JSON:
errorString = "JSON解析错误";
break;
case ERROR_NETWORK:
errorString = "网络错误,请重试";
break;
case ERROR_WRONG_URL:
errorString = "URL格式错误";
break;
default:
errorString = "未定义的错误码";
break;
}
}
public int getErrorCode() {
return this.errorCode;
}
public String getErrorString() {
return this.errorString;
}
@Override
public String toString() {
return errorCode + " : " + errorString;
}
}
}