/*
* Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.lidroid.xutils.http;
import android.os.SystemClock;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.callback.*;
import com.lidroid.xutils.task.PriorityAsyncTask;
import com.lidroid.xutils.util.OtherUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolException;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.RedirectHandler;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.protocol.HttpContext;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.UnknownHostException;
public class HttpHandler<T> extends PriorityAsyncTask<Object, Object, Void> implements RequestCallBackHandler {
private final AbstractHttpClient client;
private final HttpContext context;
private HttpRedirectHandler httpRedirectHandler;
public void setHttpRedirectHandler(HttpRedirectHandler httpRedirectHandler) {
if (httpRedirectHandler != null) {
this.httpRedirectHandler = httpRedirectHandler;
}
}
private String requestUrl;
private String requestMethod;
private HttpRequestBase request;
private boolean isUploading = true;
private RequestCallBack<T> callback;
private int retriedCount = 0;
private String fileSavePath = null;
private boolean isDownloadingFile = false;
private boolean autoResume = false; // Whether the downloading could continue from the point of interruption.
private boolean autoRename = false; // Whether rename the file by response header info when the download completely.
private String charset; // The default charset of response header info.
public HttpHandler(AbstractHttpClient client, HttpContext context, String charset, RequestCallBack<T> callback) {
this.client = client;
this.context = context;
this.callback = callback;
this.charset = charset;
this.client.setRedirectHandler(notUseApacheRedirectHandler);
}
private State state = State.WAITING;
public State getState() {
return state;
}
private long expiry = HttpCache.getDefaultExpiryTime();
public void setExpiry(long expiry) {
this.expiry = expiry;
}
public void setRequestCallBack(RequestCallBack<T> callback) {
this.callback = callback;
}
public RequestCallBack<T> getRequestCallBack() {
return this.callback;
}
// 执行请求
@SuppressWarnings("unchecked")
private ResponseInfo<T> sendRequest(HttpRequestBase request) throws HttpException {
HttpRequestRetryHandler retryHandler = client.getHttpRequestRetryHandler();
while (true) {
if (autoResume && isDownloadingFile) {
File downloadFile = new File(fileSavePath);
long fileLen = 0;
if (downloadFile.isFile() && downloadFile.exists()) {
fileLen = downloadFile.length();
}
if (fileLen > 0) {
request.setHeader("RANGE", "bytes=" + fileLen + "-"); //这里是断点下载的关键
}
}
boolean retry = true;
IOException exception = null;
try {
requestMethod = request.getMethod();
//主要是判断请求是否存在缓存, 存在直接从缓存中读取,直接返回。
if (HttpUtils.sHttpCache.isEnabled(requestMethod)) {
String result = HttpUtils.sHttpCache.get(requestUrl);
if (result != null) {
return new ResponseInfo<T>(null, (T) result, true);
}
}
ResponseInfo<T> responseInfo = null;
if (!isCancelled()) {
HttpResponse response = client.execute(request, context); //获取网络返回的数据
responseInfo = handleResponse(response); //将数据包装
}
return responseInfo;
} catch (UnknownHostException e) {
exception = e;
retry = retryHandler.retryRequest(exception, ++retriedCount, context); //重试
} catch (IOException e) {
exception = e;
retry = retryHandler.retryRequest(exception, ++retriedCount, context);
} catch (NullPointerException e) {
exception = new IOException(e.getMessage());
exception.initCause(e);
retry = retryHandler.retryRequest(exception, ++retriedCount, context);
} catch (HttpException e) {
throw e;
} catch (Throwable e) {
exception = new IOException(e.getMessage());
exception.initCause(e);
retry = retryHandler.retryRequest(exception, ++retriedCount, context);
}
if (!retry) {
throw new HttpException(exception);
}
}
}
@Override
protected Void doInBackground(Object... params) {
if (this.state == State.CANCELLED || params == null || params.length == 0) return null;
//主要是判断是否是下载文件
if (params.length > 3) {
fileSavePath = String.valueOf(params[1]);
isDownloadingFile = fileSavePath != null;
autoResume = (Boolean) params[2];
autoRename = (Boolean) params[3];
}
try {
if (this.state == State.CANCELLED) return null;
// init request & requestUrl
request = (HttpRequestBase) params[0];
requestUrl = request.getURI().toString();
if (callback != null) {
callback.setRequestUrl(requestUrl);
}
this.publishProgress(UPDATE_START); //回调, 开始
lastUpdateTime = SystemClock.uptimeMillis();
ResponseInfo<T> responseInfo = sendRequest(request);
if (responseInfo != null) {
this.publishProgress(UPDATE_SUCCESS, responseInfo); //回调, 成功
return null;
}
} catch (HttpException e) {
this.publishProgress(UPDATE_FAILURE, e, e.getMessage()); //回调, 失败
}
return null;
}
private final static int UPDATE_START = 1;
private final static int UPDATE_LOADING = 2;
private final static int UPDATE_FAILURE = 3;
private final static int UPDATE_SUCCESS = 4;
/* (non-Javadoc)
* @see com.lidroid.xutils.task.PriorityAsyncTask#onProgressUpdate(Progress[])
*回调
*/
@Override
@SuppressWarnings("unchecked")
protected void onProgressUpdate(Object... values) {
if (this.state == State.CANCELLED || values == null || values.length == 0 || callback == null) return;
switch ((Integer) values[0]) {
case UPDATE_START:
this.state = State.STARTED;
callback.onStart();
break;
case UPDATE_LOADING:
if (values.length != 3) return;
this.state = State.LOADING;
callback.onLoading(
Long.valueOf(String.valueOf(values[1])),
Long.valueOf(String.valueOf(values[2])),
isUploading);
break;
case UPDATE_FAILURE:
if (values.length != 3) return;
this.state = State.FAILURE;
callback.onFailure((HttpException) values[1], (String) values[2]);
break;
case UPDATE_SUCCESS:
if (values.length != 2) return;
this.state = State.SUCCESS;
callback.onSuccess((ResponseInfo<T>) values[1]);
break;
default:
break;
}
}
@SuppressWarnings("unchecked")
private ResponseInfo<T> handleResponse(HttpResponse response) throws HttpException, IOException {
if (response == null) {
throw new HttpException("response is null");
}
if (isCancelled()) return null;
StatusLine status = response.getStatusLine();
int statusCode = status.getStatusCode();
if (statusCode < 300) {
Object result = null;
HttpEntity entity = response.getEntity();
if (entity != null) {
isUploading = false;
if (isDownloadingFile) {
autoResume = autoResume && OtherUtils.isSupportRange(response);
String responseFileName = autoRename ? OtherUtils.getFileNameFromHttpResponse(response) : null;
FileDownloadHandler downloadHandler = new FileDownloadHandler();
result = downloadHandler.handleEntity(entity, this, fileSavePath, autoResume, responseFileName);
} else {
StringDownloadHandler downloadHandler = new StringDownloadHandler();
result = downloadHandler.handleEntity(entity, this, charset);
if (HttpUtils.sHttpCache.isEnabled(requestMethod)) {
HttpUtils.sHttpCache.put(requestUrl, (String) result, expiry); //只有是字符串才会缓存,文件不缓存, 否则OOM
}
}
}
return new ResponseInfo<T>(response, (T) result, false);
} else if (statusCode == 301 || statusCode == 302) {
if (httpRedirectHandler == null) {
httpRedirectHandler = new DefaultHttpRedirectHandler();
}
HttpRequestBase request = httpRedirectHandler.getDirectRequest(response);
if (request != null) {
return this.sendRequest(request);
}
} else if (statusCode == 416) {
throw new HttpException(statusCode, "maybe the file has downloaded completely");
} else {
throw new HttpException(statusCode, status.getReasonPhrase());
}
return null;
}
/**
* cancel request task.
*/
@Override
public void cancel() {
this.state = State.CANCELLED;
if (request != null && !request.isAborted()) {
try {
request.abort();
} catch (Throwable e) {
}
}
if (!this.isCancelled()) {
try {
this.cancel(true);
} catch (Throwable e) {
}
}
if (callback != null) {
callback.onCancelled();
}
}
private long lastUpdateTime;
@Override
public boolean updateProgress(long total, long current, boolean forceUpdateUI) {
if (callback != null && this.state != State.CANCELLED) {
if (forceUpdateUI) {
this.publishProgress(UPDATE_LOADING, total, current);
} else {
long currTime = SystemClock.uptimeMillis();
if (currTime - lastUpdateTime >= callback.getRate()) {
lastUpdateTime = currTime;
this.publishProgress(UPDATE_LOADING, total, current);
}
}
}
return this.state != State.CANCELLED;
}
public enum State {
WAITING(0), STARTED(1), LOADING(2), FAILURE(3), CANCELLED(4), SUCCESS(5);
private int value = 0;
State(int value) {
this.value = value;
}
public static State valueOf(int value) {
switch (value) {
case 0:
return WAITING;
case 1:
return STARTED;
case 2:
return LOADING;
case 3:
return FAILURE;
case 4:
return CANCELLED;
case 5:
return SUCCESS;
default:
return FAILURE;
}
}
public int value() {
return this.value;
}
}
private static final NotUseApacheRedirectHandler notUseApacheRedirectHandler = new NotUseApacheRedirectHandler();
private static final class NotUseApacheRedirectHandler implements RedirectHandler {
@Override
public boolean isRedirectRequested(HttpResponse httpResponse, HttpContext httpContext) {
return false;
}
@Override
public URI getLocationURI(HttpResponse httpResponse, HttpContext httpContext) throws ProtocolException {
return null;
}
}
}