package cn.mutils.core.net; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.cookie.Cookie; import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.CoreConnectionPNames; import org.apache.http.params.HttpParams; import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; import java.io.File; import java.lang.reflect.Type; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import cn.mutils.core.IClearable; import cn.mutils.core.annotation.net.Head; import cn.mutils.core.beans.BeanField; import cn.mutils.core.codec.FlagUtil; import cn.mutils.core.err.AbortedException; import cn.mutils.core.err.HttpStatusException; import cn.mutils.core.event.IListener; import cn.mutils.core.json.JsonUtil; import cn.mutils.core.reflect.ReflectUtil; import cn.mutils.core.text.StringUtil; import cn.mutils.core.time.MillisFormat; /** * Template of request and response for net API at Java application level */ @SuppressWarnings({"serial", "unchecked", "deprecation", "unused", "UnusedAssignment", "ConstantConditions"}) public class NetClient<REQUEST, RESPONSE> { /** * Listener for {@link NetClient} */ public static abstract class NetClientListener<REQUEST, RESPONSE> implements IListener { /** * Get request entity class */ public abstract Class<?> requestRawType(); /** * Get generic type info of request */ public Type requestGenericType() { return null; } /** * Get response entity class */ public abstract Class<?> responseRawType(); /** * Get generic type info of response */ public Type responseGenericType() { return null; } /** * Convert some properties who are come form UI to REQUEST */ public REQUEST convertToRequest() { return null; } /** * Convert RESPONSE to object who are defined by UI */ public Object convertFromResponse(RESPONSE response) { return null; } /** * Sign post JSON request content */ public Object signPostJson(REQUEST request) throws Exception { return request; } /** * Give cookie to request */ public abstract String requestCookie(URL url); /** * Receive cookie from response */ public abstract void responseCookie(URL url, String cookie); /** * Verify error code of RESPONSE */ public void errorCodeVerify(RESPONSE response) throws Exception { debugging(EVENT_ERROR_CODE, "errorCodeVerify"); } /** * Debugging */ public abstract void debugging(String event, String message); } public static final String REQUEST_METHOD_GET = "GET"; public static final String REQUEST_METHOD_POST = "POST"; public static final String EVENT_URL = "event.url"; public static final String EVENT_REQUEST_METHOD = "event.request.method"; public static final String EVENT_PARAMS = "event.params"; public static final String EVENT_ERROR_CODE = "event.error.code"; public static final String EVENT_EXCEPTION = "event.exception"; public static final String EVENT_RESPONSE = "event.response"; public static final String EVENT_REQUEST_COOKIE = "event.request.cookie"; public static final String EVENT_RESPONSE_COOKIE = "event.response.cookie"; public static final String EVENT_ELASE_TIME = "event.elapse.time"; public static final String EVENT_REQUEST_HEADERS = "event.request.headers"; /** * Flag of request convertible<br> * Whether request is convertible */ public static final int FLAG_REQUEST_CONVERTIBLE = FlagUtil.FLAG_01; /** * Flag of response convertible<br> * Whether response is convertible */ public static final int FLAG_RESPONSE_CONVERTIBLE = FlagUtil.FLAG_02; /** * Flag of split array parameters<br> * Whether to request by split array parameters: ids=1,2,3&name=wavinsun */ public static final int FLAG_SPLIT_ARRAY_PARAMS = FlagUtil.FLAG_03; /** * Flag of rest URL<br> * Whether to request by restful URL: http://www.xxx.cn/detail/{id}/{name}/ */ public static final int FLAG_REST_URL = FlagUtil.FLAG_04; /** * Flag of post parameters<br> * Whether to request by post parameter:id=1&name=wavinsun */ public static final int FLAG_POST_PARAMS = FlagUtil.FLAG_05; /** * Flag of post JSON<br> * Whether to request by post JSON: {"id":1,name:"wavinsun"} */ public static final int FLAG_POST_JSON = FlagUtil.FLAG_06; /** * Flag of post JSON signed<br> * Whether to request by signed JSON:<br> * {"key":"key","sign_type":"MD5","sign":"sign_result","data":"data"} */ public static final int FLAG_POST_JSON_SIGNED = FlagUtil.FLAG_07; /** * Flag of cookie with request<br> * Whether to use cookie cached to make request */ public static final int FLAG_COOKIE_WITH_REQUEST = FlagUtil.FLAG_08; /** * Flag of cookie with response<br> * Whether to cache cookie responded */ public static final int FLAG_COOKIE_WITH_RESPONSE = FlagUtil.FLAG_09; protected REQUEST mRequest; protected RESPONSE mResponse; protected int mFlags = FlagUtil.FLAGS_FALSE; /** * Response object converted by {@link NetClientListener#convertFromResponse(Object)} */ protected Object mResponseConverted; protected String mUrl; protected String mRequestMethod = REQUEST_METHOD_GET; /** * Value of HTTP head Refer */ protected String mRefer; /** * Cookie identity */ protected String mCookieCacheId = "JSESSIONID"; /** * Regular expression for HTTP 500 * <p> * Apache Tomcat 7.0.47 HTTP Status 500 */ protected String mHttp500HtmlRegex = "(.*?)<pre>(.*?)</pre>(.*?)"; /** * Server stack trace index of regular expression for HTTP 500 * <p> * 2 is server stack trace index of Apache Tomcat 7.0.47 HTTP Status 500 */ protected int mHttp500HtmlRegexGroup = 2; protected long mResponseTime; protected HttpUriRequest mHttpRequest; protected boolean mAborted; protected NetClientListener<REQUEST, RESPONSE> mListener; public NetClient() { setSplitArrayParams(true); } public NetClientListener<REQUEST, RESPONSE> getListener() { return mListener; } public void setListener(NetClientListener<REQUEST, RESPONSE> listener) { mListener = listener; } public long getResponseTime() { return mResponseTime; } public void setResponseTime(long responseTime) { mResponseTime = responseTime; } public boolean isSplitArrayParams() { return FlagUtil.hasFlags(mFlags, FLAG_SPLIT_ARRAY_PARAMS); } public void setSplitArrayParams(boolean value) { mFlags = FlagUtil.setFlags(mFlags, FLAG_SPLIT_ARRAY_PARAMS, value); } public boolean isRestUrl() { return FlagUtil.hasFlags(mFlags, FLAG_REST_URL); } public void setRestUrl(boolean value) { mFlags = FlagUtil.setFlags(mFlags, FLAG_REST_URL, value); } public boolean isPostParams() { return FlagUtil.hasFlags(mFlags, FLAG_POST_PARAMS); } public void setPostParams(boolean value) { mFlags = FlagUtil.setFlags(mFlags, FLAG_POST_PARAMS, value); } public boolean isPostJson() { return FlagUtil.hasFlags(mFlags, FLAG_POST_JSON); } public void setPostJson(boolean value) { mFlags = FlagUtil.setFlags(mFlags, FLAG_POST_JSON, value); } public boolean isPostJsonSigned() { return FlagUtil.hasFlags(mFlags, FLAG_POST_JSON_SIGNED); } public void setPostJsonSigned(boolean value) { mFlags = FlagUtil.setFlags(mFlags, FLAG_POST_JSON_SIGNED, value); } public boolean isCookieWithRequest() { return FlagUtil.hasFlags(mFlags, FLAG_COOKIE_WITH_REQUEST); } public void setCookieWithRequest(boolean value) { mFlags = FlagUtil.setFlags(mFlags, FLAG_COOKIE_WITH_REQUEST, value); } public boolean isCookieWithResponse() { return FlagUtil.hasFlags(mFlags, FLAG_COOKIE_WITH_RESPONSE); } public void setCookieWithResponse(boolean value) { mFlags = FlagUtil.setFlags(mFlags, FLAG_COOKIE_WITH_RESPONSE, value); } public boolean isRequestConvertible() { return FlagUtil.hasFlags(mFlags, FLAG_REQUEST_CONVERTIBLE); } public void setRequestConvertible(boolean value) { mFlags = FlagUtil.setFlags(mFlags, FLAG_REQUEST_CONVERTIBLE, value); } public boolean isResponseConvertible() { return FlagUtil.hasFlags(mFlags, FLAG_RESPONSE_CONVERTIBLE); } public void setResponseConvertible(boolean value) { mFlags = FlagUtil.setFlags(mFlags, FLAG_RESPONSE_CONVERTIBLE, value); } public Object getResponseConverted() { return mResponseConverted; } public void setResponseConverted(Object responseConverted) { mResponseConverted = responseConverted; } public void setCookieCachedId(String cookieCachedId) { mCookieCacheId = cookieCachedId; } public String getUrl() { return this.mUrl; } public void setUrl(String url) { this.mUrl = url; } public String getRequestMethod() { return this.mRequestMethod; } public void setRequestMethod(String requestMethod) { this.mRequestMethod = requestMethod; } public REQUEST getRequest() { return this.mRequest; } public void setRequest(REQUEST request) { this.mRequest = request; } public RESPONSE getResponse() { return this.mResponse; } public void setResponse(RESPONSE response) { this.mResponse = response; } public boolean isAborted() { return mAborted; } public synchronized void abort() { if (!mAborted) { mAborted = true; if (mHttpRequest != null && !mHttpRequest.isAborted()) { try { mHttpRequest.abort(); } catch (Exception e) { // UnsupportedOperationException } } } } /** * Execute client * * @return RESPONSE or exception */ public Object execute() { Type resJsonGenericType = null; long time = System.currentTimeMillis(); DefaultHttpClient client = new DefaultHttpClient(); client.setRedirectHandler(new NetRedirectHandler()); HttpParams clientParams = client.getParams(); clientParams.setParameter(CoreConnectionPNames.SO_TIMEOUT, 60000); clientParams.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000); try { if (mAborted) { throw new AbortedException(); } if (isRequestConvertible() && mListener != null) { mRequest = mListener.convertToRequest(); } String params = null, spec = isRestUrl() ? convertToRestUrl(mRequest, mUrl) : mUrl; URL url = new URL(spec); if (REQUEST_METHOD_POST.equals(mRequestMethod)) { synchronized (this) { mHttpRequest = new HttpPost(url.toURI()); } params = isPostParams() ? convertToParameters(mRequest, isSplitArrayParams()) : null; if (isPostJson()) { if (isPostJsonSigned() && mListener != null) { params = JsonUtil.toString(mListener.signPostJson(mRequest)); } else { params = JsonUtil.toString(mRequest); } } HttpEntity entity = convertToEntity(params != null ? params : mRequest); if (isPostJson() && entity != null) { ((StringEntity) entity).setContentType("application/json"); } ((HttpPost) mHttpRequest).setEntity(entity); } else { params = convertToParameters(mRequest, isSplitArrayParams()); synchronized (this) { mHttpRequest = new HttpGet(spec + (spec.contains("?") ? "&" : "?") + params); } } String headers = convertToHeaders(mRequest, mHttpRequest); if (mListener != null) { mListener.debugging(EVENT_URL, spec); mListener.debugging(EVENT_REQUEST_METHOD, mRequestMethod); if (params != null && !params.isEmpty()) { mListener.debugging(EVENT_PARAMS, params); } if (headers != null && !headers.isEmpty()) { mListener.debugging(EVENT_REQUEST_HEADERS, headers); } } if (isCookieWithRequest()) { String cookie = mListener != null ? mListener.requestCookie(url) : (mCookieCacheId + "=Cookie"); mHttpRequest.setHeader("Cookie", cookie); if (mListener != null) { mListener.debugging(EVENT_REQUEST_COOKIE, cookie); } } if (mRefer != null) { mHttpRequest.setHeader("Refer", mRefer); } mHttpRequest.setHeader("Cache-Control", "no-cache"); HttpParams requestParams = mHttpRequest.getParams(); requestParams.setParameter(HTTP.CONTENT_ENCODING, "UTF-8"); requestParams.setParameter(HTTP.CHARSET_PARAM, "UTF-8"); requestParams.setParameter(HTTP.DEFAULT_PROTOCOL_CHARSET, "UTF-8"); HttpResponse httpResponse = client.execute(mHttpRequest); int statusCode = httpResponse.getStatusLine().getStatusCode(); if (statusCode != 200 && statusCode != 500) { throw new HttpStatusException(statusCode); } String response = EntityUtils.toString(httpResponse.getEntity(), "UTF-8"); if (mAborted) { throw new AbortedException(); } mResponseTime = System.currentTimeMillis(); if (statusCode == 500) { throw new HttpStatusException(statusCode, getStackTrace(response, mHttp500HtmlRegex, mHttp500HtmlRegexGroup)); } if (isCookieWithResponse()) { List<Cookie> cookies = client.getCookieStore().getCookies(); if (cookies != null) { for (Cookie c : cookies) { String cookieName = c.getName(); if (cookieName.contains(mCookieCacheId)) { String cookie = cookieName + "=" + c.getValue(); if (mListener != null) { mListener.responseCookie(url, cookie); mListener.debugging(EVENT_RESPONSE_COOKIE, cookie); } break; } } } } if (mListener != null) { mListener.debugging(EVENT_RESPONSE, response); mListener.debugging(EVENT_ELASE_TIME, MillisFormat.formatAll(mResponseTime - time)); } Class<RESPONSE> resJsonClass = (Class<RESPONSE>) (mListener != null ? mListener.responseRawType() : ReflectUtil.getParamRawType(getClass(), 1)); resJsonGenericType = mListener != null ? mListener.responseGenericType() : null; RESPONSE resJson = JsonUtil.fromString(response, resJsonClass, resJsonGenericType); if (mListener != null) { mListener.errorCodeVerify(resJson); } if (mAborted) { throw new AbortedException(); } mResponse = resJson; if (isResponseConvertible() && mListener != null) { mResponseConverted = mListener.convertFromResponse(mResponse); } return resJson; } catch (Exception e) { if (mListener != null) { mListener.debugging(EVENT_EXCEPTION, StringUtil.printStackTrace(e)); } return e; } finally { synchronized (this) { mAborted = false;// Reuse mHttpRequest = null; } client.getConnectionManager().shutdown(); if (resJsonGenericType != null && (resJsonGenericType instanceof IClearable)) { ((IClearable) resJsonGenericType).clear(); } } } /** * Convert object whose properties have {@link Head} to headers * * @param object Object * @return log message */ public static String convertToHeaders(Object object, HttpUriRequest httpRequest) { StringBuilder sb = new StringBuilder(); if (object == null) { return sb.toString(); } if (object instanceof Map || object instanceof List) { return sb.toString(); } else { for (BeanField field : BeanField.getFields(object.getClass())) { try { Object value = field.get(object); if (value == null) { continue; } if (field.getAnnotation(Head.class) == null) { continue; } String name = field.getName(); String hValue = JsonUtil.toString(value); httpRequest.setHeader(name, hValue); if (sb.length() != 0) { sb.append("\n"); } sb.append(name); sb.append(":"); sb.append(hValue); } catch (Exception e) { // IllegalAccessException } } } return sb.toString(); } /** * Convert restful URL expression to exactly URL */ public static String convertToRestUrl(Object object, String url) { if (url == null) { return null; } if (object == null) { return null; } if (object instanceof Map) { for (Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) { Object key = entry.getKey(); Object value = entry.getValue(); if (key == null || value == null) { continue; } try { String restKey = "{" + key.toString() + "}"; if (!url.contains(restKey)) { continue; } String restValue = URLEncoder.encode(JsonUtil.toString(value), "UTF-8"); url = url.replace(restKey, restValue); } catch (Exception e) { // UnsupportedEncodingException } } } else { for (BeanField field : BeanField.getFields(object.getClass())) { try { Object value = field.get(object); if (value == null) { continue; } String restKey = "{" + field.getName() + "}"; if (!url.contains(restKey)) { continue; } String restValue = URLEncoder.encode(JsonUtil.toString(value), "UTF-8"); url = url.replace(restKey, restValue); } catch (Exception e) { // UnsupportedEncodingException } } } return url; } /** * Convert object to URL parameters like "key=value" */ public static String convertToParameters(Object object, boolean splitArrayParams) { if (object == null) { return ""; } if (object instanceof String) { return (String) object; } boolean firstParam = true; StringBuilder sb = new StringBuilder(); if (object instanceof Map) { for (Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) { Object key = entry.getKey(); Object value = entry.getValue(); if (key == null || value == null) { continue; } if (firstParam) { firstParam = false; } else { sb.append("&"); } sb.append(key.toString()); sb.append("="); try { if ((value instanceof List) && splitArrayParams) { sb.append(convertToSplitArrayParam((List<?>) value)); } else { sb.append(URLEncoder.encode(JsonUtil.toString(value), "UTF-8")); } } catch (Exception e) { // UnsupportedEncodingException } } } else { for (BeanField field : BeanField.getFields(object.getClass())) { try { Object value = field.get(object); if (value == null) { continue; } if (firstParam) { firstParam = false; } else { sb.append("&"); } sb.append(field.getName()); sb.append("="); if ((value instanceof List) && splitArrayParams) { sb.append(convertToSplitArrayParam((List<?>) value)); } else { sb.append(URLEncoder.encode(JsonUtil.toString(value), "UTF-8")); } } catch (Exception e) { // UnsupportedEncodingException } } } return sb.toString(); } /** * Convert array to URL parameter */ public static String convertToSplitArrayParam(List<?> value) { StringBuilder sb = new StringBuilder(); for (int i = 0, size = value.size(); i < size; i++) { try { if (i != 0) { sb.append(","); } sb.append(URLEncoder.encode(JsonUtil.toString(value.get(i)), "UTF-8")); } catch (Exception e) { // UnsupportedEncodingException } } return sb.toString(); } /** * Convert object to HttpEntity */ public static HttpEntity convertToEntity(Object object) { if (object == null) { return null; } if (object instanceof String) { try { return new StringEntity((String) object, "UTF-8"); } catch (Exception e) { return null; } } else { MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); if (object instanceof Map) { for (Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) { Object key = entry.getKey(); Object value = entry.getValue(); if (key == null || value == null) { continue; } if (value instanceof File) { entity.addPart(key.toString(), new FileBody((File) value)); } else { try { entity.addPart(key.toString(), new StringBody(JsonUtil.toString(value), Charset.forName("UTF-8"))); } catch (Exception e) { // UnsupportedCharsetException } } } } else { for (BeanField field : BeanField.getFields(object.getClass())) { try { Object value = field.get(object); if (value == null) { continue; } if (value instanceof File) { entity.addPart(field.getName(), new FileBody((File) value)); } else { entity.addPart(field.getName(), new StringBody(JsonUtil.toString(value), Charset.forName("UTF-8"))); } } catch (Exception e) { // IllegalAccessException } } } return entity; } } /** * Parse response of HTTP 500 to get server stack trace */ public static String getStackTrace(String html500, String regex, int regexGroup) { try { if (html500 == null) { return null; } Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); Matcher m = p.matcher(html500); if (m.find()) { String stackTrace = m.group(regexGroup); if (!stackTrace.isEmpty()) { return stackTrace; } } return StringUtil.htmlText(html500); } catch (Exception e) { return StringUtil.htmlText(html500); } } public static StringBuilder getLog(String event, String message) { StringBuilder sb = new StringBuilder(); sb.append("["); sb.append(event); sb.append("]"); message = message != null ? message : "null"; if (message.contains("\n")) { sb.append("\n"); } else { sb.append(" "); } sb.append(message); return sb; } }