package net.oschina.gitapp.api;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import javax.net.ssl.SSLHandshakeException;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.OptionsMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.TraceMethod;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.io.IOUtils;
import cz.msebera.android.httpclient.protocol.HTTP;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import net.oschina.gitapp.AppContext;
import net.oschina.gitapp.AppException;
import net.oschina.gitapp.bean.URLs;
/**
* gitlabApi网络请求类
* Gitlab HTTP Requestor
*
* Responsible for handling HTTP requests to the Gitlab API
*
* @author @timols
* 最后更新时间:2014-05-16
* 更新者:火蚁
*/
public class HTTPRequestor {
public static final byte GET_METHOD = 0x00;
public static final byte POST_METHOD = 0x01;
public static final byte PUT_METHOD = 0x02;
public static final byte PATCH_METHOD = 0x03;
public static final byte DELETE_METHOD = 0x04;
public static final byte HEAD_METHOD = 0x05;
public static final byte OPTIONS_METHOD = 0x06;
public static final byte TRACE_METHOD = 0x07;
public static final String UTF_8 = "UTF-8";
public static final String DESC = "descend";
public static final String ASC = "ascend";
public final static int TIMEOUT_CONNECTION = 20000;// 连接超时时间
public final static int TIMEOUT_SOCKET = 20000;// socket超时
private AppContext mContext;
private String url;
// 网络请求agent
private static String appUserAgent;
// 请求client
private HttpClient _httpClient;
// 请求方法类型
private byte _methodType;
// 请求方法
private HttpMethod _method;
//private String _method = "GET"; // 默认用GET方式请求
private Map<String, Object> _data = new HashMap<String, Object>();// 请求表单参数
private enum METHOD {
GET, PUT, POST, PATCH, DELETE, HEAD, OPTIONS, TRACE;
public static String prettyValues() {
METHOD[] methods = METHOD.values();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < methods.length; i++) {
METHOD method = methods[i];
builder.append(method.toString());
if (i != methods.length - 1) {
builder.append(", ");
}
}
return builder.toString();
}
}
/**
* Sets the HTTP Request method for the request.
*
* Has a fluent api for method chaining.
*
* @param method The HTTP method
* @return this
*/
public HTTPRequestor init(AppContext appContext, byte methodType, String url) {
_methodType = methodType;
_httpClient = getHttpClient();
this.mContext = appContext;
this.url = url;
String urser_agent = appContext != null ? getUserAgent(appContext) : "";
_method = getMethod(methodType, url, urser_agent);
return this;
}
/**
* 获得UserAgent
* @param appContext
* @return
*/
private static String getUserAgent(AppContext appContext) {
if(appUserAgent == null || appUserAgent == "") {
StringBuilder ua = new StringBuilder("Git@OSC.NET");
ua.append('/'+appContext.getPackageInfo().versionName+'_'+appContext.getPackageInfo().versionCode);//App版本
ua.append("/Android");//手机系统平台
ua.append("/"+android.os.Build.VERSION.RELEASE);//手机系统版本
ua.append("/"+android.os.Build.MODEL); //手机型号
ua.append("/"+appContext.getAppId());//客户端唯一标识
appUserAgent = ua.toString();
}
return appUserAgent;
}
/**
* 获得一个http连接
* @return
*/
private static HttpClient getHttpClient() {
HttpClient httpClient = new HttpClient();
// 设置 HttpClient 接收 Cookie,用与浏览器一样的策略
httpClient.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
// 设置 默认的超时重试处理策略
httpClient.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
// 设置 连接超时时间
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(TIMEOUT_CONNECTION);
// 设置 读数据超时时间
httpClient.getHttpConnectionManager().getParams().setSoTimeout(TIMEOUT_SOCKET);
// 设置 字符集
httpClient.getParams().setContentCharset(UTF_8);
return httpClient;
}
private static HttpMethod getMethod(byte methodType, String url, String userAgent) {
HttpMethod httpMethod = null;
switch (methodType) {
case GET_METHOD:
httpMethod = new GetMethod(url);
break;
case POST_METHOD:
httpMethod = new PostMethod(url);
break;
case PUT_METHOD:
httpMethod = new PutMethod(url);
break;
case PATCH_METHOD:
//httpMethod = new
break;
case DELETE_METHOD:
httpMethod = new DeleteMethod(url);
break;
case HEAD_METHOD:
httpMethod = new HeadMethod(url);
break;
case OPTIONS_METHOD:
httpMethod = new OptionsMethod(url);
break;
case TRACE_METHOD:
httpMethod = new TraceMethod(url);
break;
default:
break;
}
if (null == httpMethod) {
// 抛出一个不支持的请求方法
throw new IllegalArgumentException("Invalid HTTP Method: UnKonwn" + ". Must be one of " + METHOD.prettyValues());
}
// 设置 请求超时时间
httpMethod.getParams().setSoTimeout(TIMEOUT_SOCKET);
httpMethod.setRequestHeader("Host", URLs.HOST);
httpMethod.setRequestHeader("Accept-Encoding", "gzip,deflate,sdch");
httpMethod.setRequestHeader("Accept-Language", "zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4");
httpMethod.setRequestHeader("Connection","Keep-Alive");
httpMethod.setRequestHeader(HTTP.USER_AGENT, userAgent);
return httpMethod;
}
/**
* 获取网络图片
* @return
* @throws AppException
*/
public Bitmap getNetBitmap() throws AppException {
Bitmap bitmap = null;
InputStream inStream = getResponseBodyStream();
bitmap = BitmapFactory.decodeStream(inStream);
try {
inStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
/**
* 设置拼接请求的参数
*
* @param key
* @param value
* @return this
*/
public HTTPRequestor with(String key, Object value) {
if (value != null && key != null) {
_data.put(key, value);
}
return this;
}
public InputStream getResponseBodyStream() throws AppException {
// 设置请求参数
if (hasOutput()) {
submitData(_data, _method);
}
InputStream responseBodyStream = null;
try
{
int statusCode = _httpClient.executeMethod(_method);
if (statusCode != HttpStatus.SC_OK && !String.valueOf(statusCode).startsWith("2")) {
uploadErrorToServer(mContext, url, statusCode, getMethod(_methodType) + " " + _method.getResponseBodyAsString() + " " + getUserAgent(mContext) + " " + getJsonString(_data));
throw AppException.http(statusCode);
}
Header header = _method.getResponseHeader("Content-Encoding");
if (header != null && header.getValue().equalsIgnoreCase("gzip")) {
responseBodyStream = new GZIPInputStream(_method.getResponseBodyAsStream());
} else {
responseBodyStream = new ByteArrayInputStream(_method.getResponseBody());
}
} catch (HttpException e) {
e.printStackTrace();
// 发生致命的异常,可能是协议不对或者返回的内容有问题
throw AppException.http(e);
} catch (IOException e) {
// 发生网络异常
e.printStackTrace();
throw AppException.network(e);
} finally {
// 释放连接
releaseConnection();
}
return responseBodyStream;
}
public String getResponseBodyString() throws AppException {
// 设置请求参数
if (hasOutput()) {
submitData(_data, _method);
}
String responseBodyString = null;
GZIPInputStream gis = null;
try
{
int statusCode = _httpClient.executeMethod(_method);
if (statusCode != HttpStatus.SC_OK && !String.valueOf(statusCode).startsWith("2")) {
uploadErrorToServer(mContext, url, statusCode, getMethod(_methodType) + " " + _method.getResponseBodyAsString() + " " + getUserAgent(mContext) + " " + getJsonString(_data));
throw AppException.http(statusCode);
}
Header header = _method.getResponseHeader("Content-Encoding");
if (header != null && header.getValue().equalsIgnoreCase("gzip")) {
try {
gis = new GZIPInputStream(_method.getResponseBodyAsStream());
responseBodyString = IOUtils.toString(gis);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (gis != null) {
try {
gis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} else {
responseBodyString = _method.getResponseBodyAsString();
}
} catch (HttpException e) {
// 发生致命的异常,可能是协议不对或者返回的内容有问题
e.printStackTrace();
throw AppException.http(e);
} catch (IOException e) {
// 发生网络异常
e.printStackTrace();
throw AppException.network(e);
} finally {
// 释放连接
releaseConnection();
}
return responseBodyString;
}
private void releaseConnection() {
// 释放连接
if (_method != null) {
_method.releaseConnection();
}
if (_httpClient != null) {
_httpClient = null;
}
}
public <T> T to(T instance) throws AppException {
return to(null, instance);
}
public <T> T to(Class<T> type) throws AppException {
return to(type, null);
}
/**
* 获取单个对象
* @param type
* @param instance
* @return
* @throws AppException
*/
public <T> T to(Class<T> type, T instance) throws AppException {
InputStream inputStream = getResponseBodyStream();
try {
return parse(inputStream, type, instance);
} catch (IOException e) {
throw AppException.io(e);
}
}
/**
* 获取一个列表数据
* @param type
* @return
* @throws AppException
*/
public <T> List<T> getList(Class<T[]> type) throws AppException {
List<T> results = new ArrayList<T>();
InputStream inputStream = getResponseBodyStream();
if (inputStream == null) {
// 抛出一个网络异常
throw AppException.http(_method.getStatusLine().getStatusCode());
}
try {
T[] _next = parse(inputStream, type, null);
results.addAll(Arrays.asList(_next));
} catch (IOException e) {
throw AppException.io(e);
}
return results;
}
/**
* 表单参数处理
*/
private void submitData(Map<String, Object> data, HttpMethod method){
method.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
if (method instanceof PostMethod) {
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
if(data != null) {
for(String name : data.keySet()){
Object value = data.get(name);
NameValuePair nvp = new NameValuePair(name, String.valueOf(value));
nvps.add(nvp);
}
}
for (NameValuePair nameValuePair : nvps) {
((PostMethod)method).addParameter(nameValuePair);
}
}
if (_method instanceof PutMethod) {
Part parts[] = new Part[data.size()];
int i = 0;
for(String name : data.keySet()){
Object value = data.get(name);
parts[i++] = new StringPart(name, String.valueOf(value));
}
((PutMethod)_method).setRequestEntity(new MultipartRequestEntity(parts,_method.getParams()));
}
}
/**
* 判断是否需要设置表单参数
* @param methodType
* @return
*/
private boolean hasOutput() {
return _methodType == POST_METHOD || _methodType == PUT_METHOD;
}
/**
* 对获取到的网络数据进行处理
* @param connection
* @param type
* @param instance
* @return
* @throws IOException
*/
private <T> T parse(InputStream inputStream, Class<T> type, T instance) throws IOException {
InputStreamReader reader = null;
try {
reader = new InputStreamReader(inputStream);
String data = IOUtils.toString(reader);
if (type != null) {
return ApiClient.MAPPER.readValue(data, type);
} else if (instance != null) {
return ApiClient.MAPPER.readerForUpdating(instance).readValue(data);
} else {
return null;
}
} catch (SSLHandshakeException e) {
throw new SSLHandshakeException("You can disable certificate checking by setting ignoreCertificateErrors on GitlabHTTPRequestor");
} finally {
IOUtils.closeQuietly(reader);
}
}
/**
* 上传post请求错误到服务器日志上
* @param appContext
* @param errorUrl
* @param errorCode
* @param post_content
*/
private void uploadErrorToServer(final AppContext appContext, final String errorUrl, final int errorCode, final String post_content) {
new Thread(){
public void run() {
try {
HttpClient client = getHttpClient();
String url = URLs.URL_API_HOST + "app_logger";
HttpMethod post = getMethod(POST_METHOD, url, getUserAgent(appContext));
Map<String, Object> data = new HashMap<String, Object>();
data.put("app_type", "android");
data.put("url", errorUrl);
data.put("error", errorCode);
data.put("post_content", post_content);
submitData(data, post);
client.executeMethod(post);
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
private String getJsonString(Map<String, Object> data) {
String res = "";
if (data != null && data.size() > 0) {
StringBuilder ua = new StringBuilder("{");
int i = 0;
for(String name : data.keySet()){
String value = (String) data.get(name);
if (name.equalsIgnoreCase("password")) {
value = "*******";
}
i++;
if (i != data.size()) {
ua.append("\"" + name + "\"" + ":" + value + ",");
} else {
ua.append("\"" + name + "\"" + ":" + value);
}
}
ua.append("}");
res = ua.toString();
}
return res;
}
private String getMethod(byte method) {
String res = "";
switch (method) {
case GET_METHOD:
res = "GET";
break;
case POST_METHOD:
res = "POST";
break;
case PUT_METHOD:
res = "PUT";
break;
case PATCH_METHOD:
res = "PATCH";
break;
case DELETE_METHOD:
res = "DELETE";
break;
case HEAD_METHOD:
res = "HEAD";
break;
case OPTIONS_METHOD:
res = "OPTIONS";
break;
case TRACE_METHOD:
res = "TRACE";
break;
}
return res;
}
/**
* 处理错误异常
* @param e
* @param connection
* @throws Exception
*//*
private AppException handleAPIError(Exception e, InputStream errorInputStream) {
AppException exception = null;
if (e instanceof FileNotFoundException) {
return AppException.file(e); // pass through 404 Not Found to allow the caller to handle it intelligently
} else if (e instanceof UnknownHostException || e instanceof ConnectException) {
return AppException.network(e);
}
InputStream es = null;
try {
es = wrapStream(connection, errorInputStream);
//int code = connection.getResponseCode();
if (es != null) {
exception = AppException.io((IOException) new IOException(IOUtils.toString(es, "UTF-8")).initCause(e), code);
} else {
exception = AppException.run(e);
}
} catch (IOException e1) {
e1.printStackTrace();
} finally {
IOUtils.closeQuietly(es);
}
return exception;
}*/
}