package org.xutils.http.request;
import android.annotation.TargetApi;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import org.xutils.cache.DiskCacheEntity;
import org.xutils.cache.LruDiskCache;
import org.xutils.common.util.IOUtil;
import org.xutils.common.util.KeyValue;
import org.xutils.common.util.LogUtil;
import org.xutils.ex.HttpException;
import org.xutils.http.HttpMethod;
import org.xutils.http.RequestParams;
import org.xutils.http.body.ProgressBody;
import org.xutils.http.body.RequestBody;
import org.xutils.http.cookie.DbCookieStore;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TimeZone;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
/**
* Created by wyouflf on 15/7/23.
* Uri请求发送和数据接收
*/
public class HttpRequest extends UriRequest {
private String cacheKey = null;
private boolean isLoading = false;
private InputStream inputStream = null;
private HttpURLConnection connection = null;
private int responseCode = 0;
// cookie manager
private static final CookieManager COOKIE_MANAGER =
new CookieManager(DbCookieStore.INSTANCE, CookiePolicy.ACCEPT_ALL);
/*package*/ HttpRequest(RequestParams params, Type loadType) throws Throwable {
super(params, loadType);
}
// build query
@Override
protected String buildQueryUrl(RequestParams params) {
String uri = params.getUri();
StringBuilder queryBuilder = new StringBuilder(uri);
if (!uri.contains("?")) {
queryBuilder.append("?");
} else if (!uri.endsWith("?")) {
queryBuilder.append("&");
}
List<KeyValue> queryParams = params.getQueryStringParams();
if (queryParams != null) {
for (KeyValue kv : queryParams) {
String name = kv.key;
String value = kv.getValueStr();
if (!TextUtils.isEmpty(name) && value != null) {
queryBuilder.append(
Uri.encode(name, params.getCharset()))
.append("=")
.append(Uri.encode(value, params.getCharset()))
.append("&");
}
}
}
if (queryBuilder.charAt(queryBuilder.length() - 1) == '&') {
queryBuilder.deleteCharAt(queryBuilder.length() - 1);
}
if (queryBuilder.charAt(queryBuilder.length() - 1) == '?') {
queryBuilder.deleteCharAt(queryBuilder.length() - 1);
}
return queryBuilder.toString();
}
@Override
public String getRequestUri() {
String result = queryUrl;
if (connection != null) {
URL url = connection.getURL();
if (url != null) {
result = url.toString();
}
}
return result;
}
/**
* invoke via Loader
*
* @throws IOException
*/
@Override
@TargetApi(Build.VERSION_CODES.KITKAT)
public void sendRequest() throws Throwable {
isLoading = false;
responseCode = 0;
URL url = new URL(queryUrl);
{ // init connection
Proxy proxy = params.getProxy();
if (proxy != null) {
connection = (HttpURLConnection) url.openConnection(proxy);
} else {
connection = (HttpURLConnection) url.openConnection();
}
// try to fix bug: accidental EOFException before API 19
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
connection.setRequestProperty("Connection", "close");
}
connection.setReadTimeout(params.getConnectTimeout());
connection.setConnectTimeout(params.getConnectTimeout());
connection.setInstanceFollowRedirects(params.getRedirectHandler() == null);
if (connection instanceof HttpsURLConnection) {
SSLSocketFactory sslSocketFactory = params.getSslSocketFactory();
if (sslSocketFactory != null) {
((HttpsURLConnection) connection).setSSLSocketFactory(sslSocketFactory);
}
}
}
if (params.isUseCookie()) {// add cookies
try {
Map<String, List<String>> singleMap =
COOKIE_MANAGER.get(url.toURI(), new HashMap<String, List<String>>(0));
List<String> cookies = singleMap.get("Cookie");
if (cookies != null) {
connection.setRequestProperty("Cookie", TextUtils.join(";", cookies));
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
{// add headers
List<RequestParams.Header> headers = params.getHeaders();
if (headers != null) {
for (RequestParams.Header header : headers) {
String name = header.key;
String value = header.getValueStr();
if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(value)) {
if (header.setHeader) {
connection.setRequestProperty(name, value);
} else {
connection.addRequestProperty(name, value);
}
}
}
}
}
// intercept response
if (requestInterceptListener != null) {
requestInterceptListener.beforeRequest(this);
}
{ // write body
HttpMethod method = params.getMethod();
try {
connection.setRequestMethod(method.toString());
} catch (ProtocolException ex) {
try { // fix: HttpURLConnection not support PATCH method.
Field methodField = HttpURLConnection.class.getDeclaredField("method");
methodField.setAccessible(true);
methodField.set(connection, method.toString());
} catch (Throwable ignored) {
throw ex;
}
}
if (HttpMethod.permitsRequestBody(method)) {
RequestBody body = params.getRequestBody();
if (body != null) {
if (body instanceof ProgressBody) {
((ProgressBody) body).setProgressHandler(progressHandler);
}
String contentType = body.getContentType();
if (!TextUtils.isEmpty(contentType)) {
connection.setRequestProperty("Content-Type", contentType);
}
long contentLength = body.getContentLength();
if (contentLength < 0) {
connection.setChunkedStreamingMode(256 * 1024);
} else {
if (contentLength < Integer.MAX_VALUE) {
connection.setFixedLengthStreamingMode((int) contentLength);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
connection.setFixedLengthStreamingMode(contentLength);
} else {
connection.setChunkedStreamingMode(256 * 1024);
}
}
connection.setRequestProperty("Content-Length", String.valueOf(contentLength));
connection.setDoOutput(true);
body.writeTo(connection.getOutputStream());
}
}
}
if (params.isUseCookie()) { // save cookies
try {
Map<String, List<String>> headers = connection.getHeaderFields();
if (headers != null) {
COOKIE_MANAGER.put(url.toURI(), headers);
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
// check response code
responseCode = connection.getResponseCode();
// intercept response
if (requestInterceptListener != null) {
requestInterceptListener.afterRequest(this);
}
if (responseCode == 204 || responseCode == 205) { // empty content
throw new HttpException(responseCode, this.getResponseMessage());
} else if (responseCode >= 300) {
HttpException httpException = new HttpException(responseCode, this.getResponseMessage());
try {
httpException.setResult(IOUtil.readStr(this.getInputStream(), params.getCharset()));
} catch (Throwable ignored) {
}
LogUtil.e(httpException.toString() + ", url: " + queryUrl);
throw httpException;
}
isLoading = true;
}
@Override
public boolean isLoading() {
return isLoading;
}
@Override
public String getCacheKey() {
if (cacheKey == null) {
cacheKey = params.getCacheKey();
if (TextUtils.isEmpty(cacheKey)) {
cacheKey = params.toString();
}
}
return cacheKey;
}
@Override
public Object loadResult() throws Throwable {
isLoading = true;
return super.loadResult();
}
/**
* 尝试从缓存获取结果, 并为请求头加入缓存控制参数.
*
* @return
* @throws Throwable
*/
@Override
public Object loadResultFromCache() throws Throwable {
isLoading = true;
DiskCacheEntity cacheEntity = LruDiskCache.getDiskCache(params.getCacheDirName())
.setMaxSize(params.getCacheSize())
.get(this.getCacheKey());
if (cacheEntity != null) {
if (HttpMethod.permitsCache(params.getMethod())) {
Date lastModified = cacheEntity.getLastModify();
if (lastModified.getTime() > 0) {
params.setHeader("If-Modified-Since", toGMTString(lastModified));
}
String eTag = cacheEntity.getEtag();
if (!TextUtils.isEmpty(eTag)) {
params.setHeader("If-None-Match", eTag);
}
}
return loader.loadFromCache(cacheEntity);
} else {
return null;
}
}
@Override
public void clearCacheHeader() {
params.setHeader("If-Modified-Since", null);
params.setHeader("If-None-Match", null);
}
@Override
public InputStream getInputStream() throws IOException {
if (connection != null && inputStream == null) {
inputStream = connection.getResponseCode() >= 400 ?
connection.getErrorStream() : connection.getInputStream();
}
return inputStream;
}
@Override
public void close() throws IOException {
if (inputStream != null) {
IOUtil.closeQuietly(inputStream);
inputStream = null;
}
if (connection != null) {
connection.disconnect();
//connection = null;
}
}
@Override
public long getContentLength() {
long result = 0;
if (connection != null) {
try {
result = connection.getContentLength();
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
if (result < 1) {
try {
result = this.getInputStream().available();
} catch (Throwable ignored) {
}
}
} else {
try {
result = this.getInputStream().available();
} catch (Throwable ignored) {
}
}
return result;
}
@Override
public int getResponseCode() throws IOException {
if (connection != null) {
return responseCode;
} else {
if (this.getInputStream() != null) {
return 200;
} else {
return 404;
}
}
}
@Override
public String getResponseMessage() throws IOException {
if (connection != null) {
return URLDecoder.decode(connection.getResponseMessage(), params.getCharset());
} else {
return null;
}
}
@Override
public long getExpiration() {
if (connection == null) return -1L;
long expiration = -1L;
// from max-age
String cacheControl = connection.getHeaderField("Cache-Control");
if (!TextUtils.isEmpty(cacheControl)) {
StringTokenizer tok = new StringTokenizer(cacheControl, ",");
while (tok.hasMoreTokens()) {
String token = tok.nextToken().trim().toLowerCase();
if (token.startsWith("max-age")) {
int eqIdx = token.indexOf('=');
if (eqIdx > 0) {
try {
String value = token.substring(eqIdx + 1).trim();
long seconds = Long.parseLong(value);
if (seconds > 0L) {
expiration = System.currentTimeMillis() + seconds * 1000L;
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
break;
}
}
}
// from expires
if (expiration <= 0L) {
expiration = connection.getExpiration();
}
if (expiration <= 0L && params.getCacheMaxAge() > 0L) {
expiration = System.currentTimeMillis() + params.getCacheMaxAge();
}
if (expiration <= 0L) {
expiration = Long.MAX_VALUE;
}
return expiration;
}
@Override
public long getLastModified() {
return getHeaderFieldDate("Last-Modified", System.currentTimeMillis());
}
@Override
public String getETag() {
if (connection == null) return null;
return connection.getHeaderField("ETag");
}
@Override
public String getResponseHeader(String name) {
if (connection == null) return null;
return connection.getHeaderField(name);
}
@Override
public Map<String, List<String>> getResponseHeaders() {
if (connection == null) return null;
return connection.getHeaderFields();
}
@Override
public long getHeaderFieldDate(String name, long defaultValue) {
if (connection == null) return defaultValue;
return connection.getHeaderFieldDate(name, defaultValue);
}
private static String toGMTString(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat(
"EEE, dd MMM y HH:mm:ss 'GMT'", Locale.US);
TimeZone gmtZone = TimeZone.getTimeZone("GMT");
sdf.setTimeZone(gmtZone);
GregorianCalendar gc = new GregorianCalendar(gmtZone);
gc.setTimeInMillis(date.getTime());
return sdf.format(date);
}
}