package com.litesuits.http.request; import android.net.Uri; import com.litesuits.http.annotation.*; import com.litesuits.http.data.Consts; import com.litesuits.http.data.NameValuePair; import com.litesuits.http.exception.ClientException; import com.litesuits.http.exception.HttpClientException; import com.litesuits.http.listener.GlobalHttpListener; import com.litesuits.http.listener.HttpListener; import com.litesuits.http.log.HttpLog; import com.litesuits.http.parser.DataParser; import com.litesuits.http.request.content.HttpBody; import com.litesuits.http.request.content.StringBody; import com.litesuits.http.request.content.UrlEncodedFormBody; import com.litesuits.http.request.content.multi.MultipartBody; import com.litesuits.http.request.param.*; import com.litesuits.http.request.query.JsonQueryBuilder; import com.litesuits.http.request.query.ModelQueryBuilder; import com.litesuits.http.utils.HexUtil; import com.litesuits.http.utils.HttpUtil; import com.litesuits.http.utils.MD5Util; import com.litesuits.http.utils.UriUtil; import java.io.File; import java.io.UnsupportedEncodingException; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.net.URLEncoder; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * Base request for {@link com.litesuits.http.LiteHttp} method * * @author MaTianyu * 2014-1-1下午9:51:59 */ public abstract class AbstractRequest<T> { private static final String TAG = AbstractRequest.class.getSimpleName(); private static final String ENCODE_PATTERN_URL = "^.+\\?(%[0-9a-fA-F]+|[=&A-Za-z0-9_#\\-\\.\\*])*$"; /** * you can give an id to a requestr */ private long id; /** * scheme and host for uri. * * such as https://abc.com. * * note: if {@link #uri} has be set completely and correctly(scheme + host + path), this will be ignored. */ private String baseUrl; /** * uri of http request. * * if you has set {@link #baseUrl}, this can be just set a path string for uri. * we will concat a full uri as: scheme + host + uri(path) * * with baseUrl, uri can be just set a path: "/path/api...". * * if {@link #baseUrl} is null, you must set a complete and correct uri for a request. */ private String uri; private String fullUri; /** * HTTP Method, such as GET, POST, PUT, DELETE, etc. * * Note: default method is GET: {@link HttpMethods#Get}, choice list is: * * {@link HttpMethods#Get} {@link HttpMethods#Post} {@link HttpMethods#Delete} * {@link HttpMethods#Head} {@link HttpMethods#Put} {@link HttpMethods#Trace} * {@link HttpMethods#Options} {@link HttpMethods#Patch} */ private HttpMethods method; /** * custom tag of request */ private Object tag; /** * charset of request, default charset is UTF-8 */ private String charSet; /** * max number of retry.. */ private int maxRetryTimes = -1; /** * max number of redirect.. */ private int maxRedirectTimes = -1; /** * connect timeout */ private int connectTimeout = -1; /** * socket timeout */ private int socketTimeout = -1; /** * callback of start, success, fialure, retry, redirect, loading, etc. */ private HttpListener<T> httpListener; /** * cancel this request */ private AtomicBoolean cancel = new AtomicBoolean(); /** * request cache mode * * default cache mode is NO-CACHE: * {@link CacheMode#NetOnly} * * other choice: * {@link CacheMode#NetFirst} * {@link CacheMode#CacheFirst} */ private CacheMode cacheMode; /** * key for cache data */ private String cacheKey; /** * dir for cache data */ private String cacheDir; /** * expire time of cache */ private long cacheExpireMillis = -1; /** * intelligently translate java object into mapping(k=v) parameters */ private HttpParamModel paramModel; /** * when parameter's value is complex, u can chose one buider, default mode * is build value into json string. * * Note : default model query builder is {@link JsonQueryBuilder} */ private ModelQueryBuilder queryBuilder; /** * body of post,put.. */ private HttpBody httpBody; /** * custom http data parser. * * such as: * {@link com.litesuits.http.parser.impl.BytesParser} * {@link com.litesuits.http.parser.impl.StringParser} * {@link com.litesuits.http.parser.impl.FileParser} * {@link com.litesuits.http.parser.impl.BitmapParser} * {@link com.litesuits.http.parser.impl.JsonParser} */ // protected DataParser<T> dataParser; /** * add custom header to request. */ private Map<String, String> headers; /** * key value parameters */ private Map<String, String> paramMap; /** * data parser */ protected DataParser<T> dataParser; /** * global http listener for request */ private GlobalHttpListener globalHttpListener; /** * parameters field */ private Map<String, Field> paramFieldMap = null; /*________________________ constructors ________________________*/ // public AbstractRequest() {} public AbstractRequest(String uri) { // init annotations //readParamFromAnnotations(this.getClass()); this.uri = uri; } public AbstractRequest(HttpParamModel paramModel) { // init annotations //readParamFromAnnotations(this.getClass()); setParamModel(paramModel); } public AbstractRequest(HttpParamModel paramModel, HttpListener<T> listener) { this(paramModel); setHttpListener(listener); } public AbstractRequest(String uri, HttpParamModel paramModel) { this(uri); setParamModel(paramModel); } /*________________________ abstract_method ________________________*/ /** * create the dataparser */ @SuppressWarnings("unchecked") public abstract DataParser<T> createDataParser(); /*________________________ getter_setter ________________________*/ /** * set a data parser */ @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setDataParser(DataParser<T> dataParser) { this.dataParser = dataParser; this.dataParser.setRequest(this); return (S) this; } /** * get dataparser */ @SuppressWarnings("unchecked") public <D extends DataParser<T>> D getDataParser() { if (dataParser == null) { setDataParser(createDataParser()); } return (D) dataParser; } public long getId() { return id; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setId(long id) { this.id = id; return (S) this; } public String getBaseUrl() { return baseUrl; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; return (S) this; } public String getUri() { return uri; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setUri(String uri) { this.uri = uri; return (S) this; } public HttpMethods getMethod() { return method; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setMethod(HttpMethods method) { this.method = method; return (S) this; } public Object getTag() { return tag; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setTag(Object tag) { this.tag = tag; return (S) this; } public String getCharSet() { return charSet; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setCharSet(String charSet) { this.charSet = charSet; return (S) this; } public int getMaxRetryTimes() { return maxRetryTimes; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setMaxRetryTimes(int maxRetryTimes) { this.maxRetryTimes = maxRetryTimes; return (S) this; } public int getMaxRedirectTimes() { return maxRedirectTimes; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setMaxRedirectTimes(int maxRedirectTimes) { this.maxRedirectTimes = maxRedirectTimes; return (S) this; } public int getConnectTimeout() { return connectTimeout; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; return (S) this; } public int getSocketTimeout() { return socketTimeout; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setSocketTimeout(int socketTimeout) { this.socketTimeout = socketTimeout; return (S) this; } public HttpListener<T> getHttpListener() { return httpListener; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setHttpListener(HttpListener<T> httpListener) { this.httpListener = httpListener; return (S) this; } public boolean isCancelled() { return cancel.get(); } @SuppressWarnings("unchecked") // public <S extends AbstractRequest<T>> S setCancel(boolean cancel) { // this.cancel.set(cancel); // return (S) this; // } public CacheMode getCacheMode() { return cacheMode; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setCacheMode(CacheMode cacheMode) { this.cacheMode = cacheMode; return (S) this; } public String getCacheKey() { if (cacheKey == null) { cacheKey = HexUtil.encodeHexStr(MD5Util.md5(getUri())); if (HttpLog.isPrint) { HttpLog.v(TAG, "generate cache key: " + cacheKey); } } return cacheKey; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setCacheKey(String cacheKey) { this.cacheKey = cacheKey; return (S) this; } public String getCacheDir() { return cacheDir; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setCacheDir(String cacheDir) { this.cacheDir = cacheDir; return (S) this; } public long getCacheExpireMillis() { return cacheExpireMillis; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setCacheExpireMillis(long cacheExpireMillis) { this.cacheExpireMillis = cacheExpireMillis; return (S) this; } public HttpParamModel getParamModel() { return paramModel; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setParamModel(HttpParamModel paramModel) { if (paramModel != null) { try { this.paramModel = paramModel; readParamFromAnnotations(paramModel); if (paramModel instanceof HttpRichParamModel) { HttpRichParamModel richModel = (HttpRichParamModel) paramModel; HttpBody hb = richModel.getHttpBody(); HttpListener<T> hl = richModel.getHttpListener(); LinkedHashMap<String, String> hs = richModel.getHeaders(); ModelQueryBuilder mb = richModel.getModelQueryBuilder(); if (hb != null) { setHttpBody(hb); } if (hl != null) { setHttpListener(hl); } if (hs != null) { setHeaders(hs); } if (mb != null) { setQueryBuilder(mb); } } } catch (Exception e) { e.printStackTrace(); } } return (S) this; } public ModelQueryBuilder getQueryBuilder() { return queryBuilder; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setQueryBuilder(ModelQueryBuilder queryBuilder) { this.queryBuilder = queryBuilder; return (S) this; } public HttpBody getHttpBody() { return httpBody; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setHttpBody(HttpBody httpBody) { if (httpBody != null) { httpBody.setRequest(this); } this.httpBody = httpBody; return (S) this; } public Map<String, String> getHeaders() { return headers; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setHeaders(Map<String, String> headers) { this.headers = headers; return (S) this; } public Map<String, String> getParamMap() { return paramMap; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setParamMap(Map<String, String> paramMap) { this.paramMap = paramMap; return (S) this; } public GlobalHttpListener getGlobalHttpListener() { return globalHttpListener; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setGlobalHttpListener(GlobalHttpListener globalHttpListener) { this.globalHttpListener = globalHttpListener; return (S) this; } //public boolean isFieldAttachToUrl() { // return isFieldAttachToUrl; //} // //public <S extends AbstractRequest<T>> S setFieldAttachToUrl(boolean fieldAttachToUrl) { // this.isFieldAttachToUrl = fieldAttachToUrl; // return (S) this; //} /*________________________ private_methods ________________________*/ /** * 融合hashmap和解析到的javamodel里的参数,即所有string 参数. */ public LinkedHashMap<String, String> getBasicParams() throws IllegalArgumentException, UnsupportedEncodingException, IllegalAccessException, InvocationTargetException { LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(); if (paramMap != null) { map.putAll(paramMap); } if (paramModel != null) { if (paramModel instanceof HttpRichParamModel && !((HttpRichParamModel) paramModel).isFieldsAttachToUrl()) { return map; } map.putAll(getQueryBuilder().buildPrimaryMap(paramModel)); } return map; } /*________________________ enhenced_methods ________________________*/ public File getCachedFile() { if (cacheDir == null) { throw new RuntimeException("lite-http cache dir for request is null !"); } return new File(cacheDir, getCacheKey()); } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setCacheMode(CacheMode cacheMode, String key) { this.cacheMode = cacheMode; this.cacheKey = key; return (S) this; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setCacheExpire(long expire, TimeUnit unit) { this.cacheExpireMillis = unit.toMillis(expire); return (S) this; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setCacheMode(CacheMode cacheMode, long expire, TimeUnit unit) { this.cacheMode = cacheMode; this.cacheExpireMillis = unit.toMillis(expire); return (S) this; } public boolean isCancelledOrInterrupted() { //System.out.println("req cancel: "+cancel.get()+" req interrupt: " + Thread.currentThread().isInterrupted()); return cancel.get() || Thread.currentThread().isInterrupted(); } public void cancel() { this.cancel.set(true); } public String createFullUri() throws HttpClientException { if (uri == null || !uri.startsWith(Consts.SCHEME_HTTP)) { if (baseUrl == null) { throw new HttpClientException(ClientException.UrlIsNull); } else if (!baseUrl.startsWith(Consts.SCHEME_HTTP)) { throw new HttpClientException(ClientException.IllegalScheme); } uri = uri == null ? baseUrl : baseUrl + uri; } try { StringBuilder sb = new StringBuilder(); boolean hasQes = uri.contains("?"); if (hasQes && !uri.matches(ENCODE_PATTERN_URL)) { Uri uri = Uri.parse(this.uri); Uri.Builder builder = uri.buildUpon(); builder.query(null); for (String key : UriUtil.getQueryParameterNames(uri)) { for (String value : UriUtil.getQueryParameters(uri, key)) { builder.appendQueryParameter(key, value); } } if (HttpLog.isPrint) { HttpLog.d(TAG, "param uri origin: " + uri); } uri = builder.build(); if (HttpLog.isPrint) { HttpLog.d(TAG, "param uri encode: " + uri); } sb.append(uri); } else { sb.append(uri); } if (paramMap == null && paramModel == null) { return sb.toString(); } LinkedHashMap<String, String> map = getBasicParams(); int size = map.size(); if (size > 0) { if (!hasQes) { sb.append("?"); } else if(uri.contains("=")){ sb.append("&"); } int i = 0; for (Entry<String, String> v : map.entrySet()) { sb.append(URLEncoder.encode(v.getKey(), charSet)).append("=") .append(URLEncoder.encode(v.getValue(), charSet)); if (++i != size) { sb.append("&"); } } } //if (Log.isPrint) Log.v(TAG, "lite request uri: " + sb.toString()); fullUri = sb.toString(); return fullUri; } catch (Exception e) { throw new HttpClientException(e); } } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S addUrlParam(String key, String value) { if (value != null) { if (paramMap == null) { paramMap = new LinkedHashMap<String, String>(); } paramMap.put(key, value); } return (S) this; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S addUrlParam(List<NameValuePair> list) { if (list != null) { if (paramMap == null) { paramMap = new LinkedHashMap<String, String>(); } for (NameValuePair pair : list) { paramMap.put(pair.getName(), pair.getValue()); } } return (S) this; } /** * if you setUri as "www.tb.cn" . * you must add prifix "http://" or "https://" yourself. */ @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S addUrlPrifix(String prifix) { setUri(prifix + uri); return (S) this; } /** * if your uri like this "http://tb.cn/i3.html" . * you can setUri("http://tb.cn/") then addUrlSuffix("i3.html"). */ @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S addUrlSuffix(String suffix) { setUri(uri + suffix); return (S) this; } /** * 设置消息体与请求方式 */ @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S setHttpBody(HttpBody httpBody, HttpMethods method) { setMethod(method); setHttpBody(httpBody); return (S) this; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S addHeader(List<NameValuePair> nps) { if (nps != null) { if (headers == null) { headers = new LinkedHashMap<String, String>(); } for (NameValuePair np : nps) { headers.put(np.getName(), np.getValue()); } } return (S) this; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S addHeader(String key, String value) { if (value != null) { if (headers == null) { headers = new LinkedHashMap<String, String>(); } headers.put(key, value); } return (S) this; } @SuppressWarnings("unchecked") public <S extends AbstractRequest<T>> S addHeader(Map<String, String> map) { if (map != null) { if (headers == null) { headers = new LinkedHashMap<String, String>(); } headers.putAll(map); } return (S) this; } /** * is cache mode * * @return true if in cache mode, else fasle. */ public boolean isCachedModel() { return cacheMode != null && cacheMode != CacheMode.NetOnly; } /** * @deprecated */ public boolean needCached() { return isCachedModel(); } /** * Override by sub-class, set id for request. */ //public long buildHttpID() { // return 0; //} /** * Override by sub-class, build tag for request. */ //public Object buildHttpTag() { // return null; //} /** * Override by sub-class, build uri for request. */ //public String buildHttpUri() { // return null; //} /** * Override by sub-class, build scheme host for request. */ //public String buildSchemeHost() { // return null; //} /** * Override by sub-class, build headers for request. */ //public LinkedHashMap<String, String> buildHeaders() {return null;} /** * Override by sub-class, build http body for POST/PUT... request. * * @return such as {@link StringBody}, {@link UrlEncodedFormBody}, {@link MultipartBody}... */ //public HttpBody buildHttpBody() {return null;} /** * Override by sub-class, whether http params be pinned to url */ //public void initHttpParams() { // Annotation annotations[] = this.getClass().getAnnotations(); // // 注解初始化 // readParamFromAnnotations(annotations); // // 注解初始化 // if (id <= 0) { // id = buildHttpID(); // } // if (tag == null) { // tag = buildHttpTag(); // } // if (uri == null) { // uri = buildHttpUri(); // } // if (baseUrl == null) { // baseUrl = buildSchemeHost(); // } // if (headers == null) { // headers = buildHeaders(); // } // if (httpBody == null) { // httpBody = buildHttpBody(); // } //} private void readParamFromAnnotations(HttpParamModel model) throws IllegalAccessException { Annotation as[] = model.getClass().getAnnotations(); if (as != null && as.length > 0) { for (Annotation a : as) { if (a instanceof HttpID) { setId(((HttpID) a).value()); } else if (a instanceof HttpTag) { setTag(handleAnnotation(model, ((HttpTag) a).value()));// may be replace{} } else if (a instanceof HttpBaseUrl) { baseUrl = handleAnnotation(model, ((HttpUri) a).value());// may be replace{} } else if (a instanceof HttpUri) { uri = handleAnnotation(model, ((HttpUri) a).value());// may be replace{} } else if (a instanceof HttpMethod) { method = ((HttpMethod) a).value(); } else if (a instanceof HttpCacheMode) { cacheMode = ((HttpCacheMode) a).value(); } else if (a instanceof HttpCacheExpire) { TimeUnit unit = ((HttpCacheExpire) a).unit(); long time = ((HttpCacheExpire) a).value(); cacheExpireMillis = unit.toMillis(time); } else if (a instanceof HttpCacheKey) { cacheKey = handleAnnotation(model, ((HttpCacheKey) a).value());// may be replace{} } else if (a instanceof HttpCharSet) { charSet = ((HttpCharSet) a).value(); } else if (a instanceof HttpMaxRedirect) { maxRetryTimes = ((HttpMaxRedirect) a).value(); } else if (a instanceof HttpMaxRetry) { maxRetryTimes = ((HttpMaxRetry) a).value(); } } } } private String handleAnnotation(HttpParamModel model, String value) throws IllegalAccessException { if (value.indexOf('{') >= 0) { if (paramFieldMap == null) { paramFieldMap = new HashMap<String, Field>(); List<Field> fields = HttpUtil.getAllParamModelFields(model.getClass()); if (fields != null) { HttpLog.i(TAG, "handleAnnotation fields: " + fields.size()); for (Field f : fields) { HttpReplace anno = f.getAnnotation(HttpReplace.class); if (anno != null) { paramFieldMap.put(anno.value(), f); } } } } if (!paramFieldMap.isEmpty()) { for (Entry<String, Field> entry : paramFieldMap.entrySet()) { entry.getValue().setAccessible(true); Object object = entry.getValue().get(model); if (object != null) { value = value.replace("{" + entry.getKey() + "}", object.toString()); } } } HttpLog.i(TAG, "handleAnnotation value: " + value); } return value; } /*________________________ string_methods ________________________*/ @Override public String toString() { return reqToString(); } public String reqToString() { StringBuilder sb = new StringBuilder(); sb.append("\n________________ request-start ________________") .append("\n full uri : ").append(fullUri) .append("\n id : ").append(id) .append("\n method : ").append(method) .append("\n tag : ").append(tag) .append("\n class : ").append(getClass().getSimpleName()) .append("\n charSet : ").append(charSet) .append("\n maxRetryTimes : ").append(maxRetryTimes) .append("\n maxRedirectTimes : ").append(maxRedirectTimes) .append("\n httpListener : ").append(httpListener) .append("\n cancelled : ").append(cancel.get()) .append("\n cacheMode : ").append(cacheMode) .append("\n cacheKey : ").append(cacheKey) .append("\n cacheExpireMillis: ").append(cacheExpireMillis) .append("\n model : ").append(paramModel) .append("\n queryBuilder : ").append(queryBuilder) .append("\n httpBody : ").append(httpBody) .append("\n dataParser : ").append(getDataParser()) .append("\n header "); if (headers == null) { sb.append(": null"); } else { for (Entry<String, String> en : headers.entrySet()) { sb.append("\n| ").append(String.format("%-20s", en.getKey())).append(" = ").append(en.getValue()); } } sb.append("\n paramMap "); if (paramMap == null) { sb.append(": null"); } else { for (Entry<String, String> en : paramMap.entrySet()) { sb.append("\n| ").append(String.format("%-20s", en.getKey())).append(" = ").append(en.getValue()); } } sb.append("\n________________ request-end ________________"); return sb.toString(); } }