/*
* Created by LuaView.
* Copyright (c) 2017, Alibaba Group. All rights reserved.
*
* This source code is licensed under the MIT.
* For the full copyright and license information,please view the LICENSE file in the root directory of this source tree.
*/
package com.taobao.luaview.userdata.net;
import android.os.Build;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import com.taobao.luaview.userdata.base.BaseUserdata;
import com.taobao.luaview.util.IOUtil;
import com.taobao.luaview.util.LogUtil;
import com.taobao.luaview.util.LuaUtil;
import com.taobao.luaview.util.LuaViewUtil;
import org.luaj.vm2.Globals;
import org.luaj.vm2.LuaFunction;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* Http 用户数据封装
*
* @author song
* @date 15/9/6
*/
public class UDHttp extends BaseUserdata {
private LuaFunction mCallback;
public static final String METHOD_GET = "GET";
public static final String METHOD_POST = "POST";
public static final int DEFAULT_RETRY_TIMES = 3;
public static final int DEFAULT_TIMEOUT = 30;
private String mMethod = METHOD_POST;
private String mUrl;
private LuaTable mParams;
private int mRetryTimes = DEFAULT_RETRY_TIMES;
private int mTimeout = DEFAULT_TIMEOUT;
//令牌
private Future<?> mFuture;
private static ExecutorService mExecutor;
private static synchronized ExecutorService getExecutor() {
if (mExecutor == null) {
mExecutor = Executors.newFixedThreadPool(5);
}
return mExecutor;
}
public UDHttp(Globals globals, LuaValue metatable, Varargs varargs) {
super(globals, metatable, varargs);
init();
}
/**
* 初始化mtop调用
*/
private void init() {
initVarargs();
}
/**
* 初始化数据
*/
private void initVarargs() {
final LuaValue param1 = getInitParam1();
final LuaFunction callback = LuaUtil.getFunction(initParams, 2);
final String method = LuaUtil.getString(param1, "method");
final LuaTable params = LuaUtil.getTable(param1, "params");
setMethod(method);
setParams(params);
setCallback(callback);
disableConnectionReuseIfNecessary();
}
/**
* 请求 url
*
* @param url
* @return
*/
public UDHttp setUrl(String url) {
this.mUrl = url;
return this;
}
public String getUrl() {
return this.mUrl;
}
/**
* 请求方法 post/get/put/delete等
*
* @param mMethod
* @return
*/
public UDHttp setMethod(String mMethod) {
this.mMethod = mMethod;
return this;
}
public String getMethod() {
return this.mMethod;
}
/**
* 请求参数,使用table构建
*
* @param mParams
* @return
*/
public UDHttp setParams(LuaTable mParams) {
this.mParams = mParams;
return this;
}
public LuaValue getParams() {
return this.mParams != null ? this.mParams : NIL;
}
/**
* 重试次数
*
* @param retryTimes
* @return
*/
public UDHttp setRetryTimes(int retryTimes) {
this.mRetryTimes = retryTimes;
return this;
}
public int getRetryTimes() {
return this.mRetryTimes;
}
/**
* 请求超时时长
*
* @param timeOut
* @return
*/
public UDHttp setTimeout(int timeOut) {
this.mTimeout = timeOut;
return this;
}
public int getTimeout() {
return this.mTimeout;
}
/**
* 清空net cookies
*/
public UDHttp clearNetCookies() {
CookieManager.clearNetCookies();
return this;
}
/**
* 回调
*
* @param luaFunction
*/
public UDHttp setCallback(final LuaFunction luaFunction) {
this.mCallback = luaFunction;
return this;
}
public LuaFunction getCallback() {
return this.mCallback;
}
/**
* 发送get请求
*
* @return
*/
public UDHttp get(String url, LuaTable params, LuaFunction callback) {
setMethod(METHOD_GET);
if (url != null) {
setUrl(url);
}
if (params != null) {
setParams(params);
}
if (callback != null) {
setCallback(callback);
}
return request();
}
public UDHttp post(String url, LuaTable params, LuaFunction callback) {
setMethod(METHOD_POST);
if (url != null) {
setUrl(url);
}
if (params != null) {
setParams(params);
}
if (callback != null) {
setCallback(callback);
}
return request();
}
/**
* 请求mtop
*/
public synchronized UDHttp request() {
if (mFuture == null) {
mFuture = getExecutor().submit(buildRunnable());
}
return this;
}
public synchronized UDHttp cancel() {
if (mFuture != null && !mFuture.isCancelled()) {
mFuture.cancel(true);
mFuture = null;
}
return this;
}
/**
* 调用回调,注意:需要在主线程执行该操作
*
* @param result
*/
private void callCallback(final LuaValue result) {
LuaViewUtil.runOnUiThread(getContext(), new Runnable() {
@Override
public void run() {
LuaUtil.callFunction(mCallback, result);
}
});
}
/**
* build a runnable to execute http request
* TODO retry times重试次数设置。
*
* @return
*/
private Runnable buildRunnable() {
return new Runnable() {
@Override
public void run() {
InputStream input = null;
OutputStream output = null;
HttpURLConnection connection = null;
final UDHttpResponse udHttpResponse = new UDHttpResponse(getGlobals(), getmetatable(), null);
try {
final URL url = new URL(mUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setInstanceFollowRedirects(true);//支持301重定向
HttpURLConnection.setFollowRedirects(true);//支持重定向
//method
connection.setRequestMethod(mMethod);
//timeout
connection.setConnectTimeout(mTimeout * 1000);
//请求的cookie
CookieManager.handleRequestCookies(connection, mUrl);
if (mParams != null) {
for (LuaValue key : mParams.keys()) {
final LuaValue value = mParams.get(key);
final String requestKey = key != null ? key.optjstring(null) : null;
final String requestValue = value != null ? value.optjstring(null) : null;
if (!TextUtils.isEmpty(requestKey) && !TextUtils.isEmpty(requestValue)) {
connection.setRequestProperty(requestKey, requestValue);
}
}
}
//连接服务器
connection.connect();
//301重定向&重定向cookie
connection = handle301Redirect(connection);
//code
udHttpResponse.setStatusCode(connection.getResponseCode());
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
LogUtil.e("[Server Returned HTTP] ", connection.getResponseCode(), connection.getResponseMessage());
//msg
udHttpResponse.setResponseMsg(connection.getResponseMessage());
return;
}
input = connection.getInputStream();
final byte[] fileData = IOUtil.toBytes(input, getContentCharset(connection));
//data
udHttpResponse.setData(fileData);
//header
udHttpResponse.setHeaders(connection.getHeaderFields());
//response cookie
// CookieManager.handleResponseCookies(connection, mUrl);
} catch (Exception e) {
LogUtil.e("[Http error] ", e);
e.printStackTrace();
udHttpResponse.setResponseMsg(e.toString());
} finally {
try {
if (output != null) {
output.flush();
output.close();
}
if (input != null) {
input.close();
}
} catch (IOException ignored) {
ignored.printStackTrace();
}
if (connection != null) {
connection.disconnect();
}
//调用回调
callCallback(udHttpResponse);
}
}
};
}
@NonNull
private HttpURLConnection handle301Redirect(HttpURLConnection connection) throws IOException {
if (connection != null) {
boolean redirect = false;
// normally, 3xx is redirect
int status = connection.getResponseCode();
if (status != HttpURLConnection.HTTP_OK) {
if (status == HttpURLConnection.HTTP_MOVED_TEMP
|| status == HttpURLConnection.HTTP_MOVED_PERM
|| status == HttpURLConnection.HTTP_SEE_OTHER)
redirect = true;
}
if (redirect) {
// get redirect url from "location" header field
String newUrl = connection.getHeaderField("Location");
// get the cookie if need, for login
String cookies = connection.getHeaderField(com.taobao.luaview.userdata.net.CookieManager.COOKIES_HEADER);
// open the new connnection again
connection = (HttpURLConnection) new URL(newUrl).openConnection();
connection.setRequestProperty(com.taobao.luaview.userdata.net.CookieManager.COOKIE, cookies);
//连接服务器
connection.connect();
//重复301重定向
connection = handle301Redirect(connection);
}
}
return connection;
}
private String getContentCharset(HttpURLConnection connection) {
if (connection != null) {
final String contentType = connection.getContentType();
if (contentType != null) {
final String[] values = contentType.split(";"); // values.length should be 2
String charset = null;
if (values != null) {
for (String value : values) {
value = value.trim();
if (value != null && value.toLowerCase().startsWith("charset=")) {
charset = value.substring("charset=".length());
}
}
}
return charset;
}
}
return null;
}
private void disableConnectionReuseIfNecessary() {
// Work around pre-Froyo bugs in HTTP connection reuse.
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
}
}