/*
* Created by Angel Leon (@gubatron), Alden Torres (aldenml)
* Copyright (c) 2011, 2012, FrostWire(TM). All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.bt.download.android.core;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.params.ConnManagerPNames;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.FileEntity;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import android.util.Log;
import com.frostwire.util.UserAgentGenerator;
/**
* A Blocking HttpClient.
* Use fetch() to retrieve the byte[]
*
* @author gubatron
* @author aldenml
*
*/
public class HttpFetcher {
private static final String TAG = "FW.HttpFetcher";
private static final int HTTP_RETRY_COUNT = 4;
private static final String DEFAULT_USER_AGENT = UserAgentGenerator.getUserAgent();
private static final int DEFAULT_TIMEOUT = 5000;
private static HttpClient DEFAULT_HTTP_CLIENT;
private static HttpClient DEFAULT_HTTP_CLIENT_GZIP;
private final URI uri;
private final String userAgent;
private final int timeout;
private byte[] body = null;
static {
setupHttpClients();
}
public HttpFetcher(URI uri, String userAgent, int timeout) {
this.uri = uri;
this.userAgent = userAgent;
this.timeout = timeout;
}
public HttpFetcher(URI uri, String userAgent) {
this(uri, userAgent, DEFAULT_TIMEOUT);
}
public HttpFetcher(URI uri, int timeout) {
this(uri, DEFAULT_USER_AGENT, timeout);
}
public HttpFetcher(URI uri) {
this(uri, DEFAULT_USER_AGENT);
}
public HttpFetcher(String uri) {
this(convert(uri));
}
public HttpFetcher(String uri, int timeout) {
this(convert(uri), timeout);
}
public Object[] fetch(boolean gzip) throws IOException {
HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort());
HttpGet httpGet = new HttpGet(uri);
httpGet.addHeader("Connection", "close");
HttpParams params = httpGet.getParams();
HttpConnectionParams.setConnectionTimeout(params, timeout);
HttpConnectionParams.setSoTimeout(params, timeout);
HttpConnectionParams.setStaleCheckingEnabled(params, true);
HttpConnectionParams.setTcpNoDelay(params, true);
HttpClientParams.setRedirecting(params, true);
HttpProtocolParams.setUseExpectContinue(params, false);
HttpProtocolParams.setUserAgent(params, userAgent);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
HttpResponse response = (gzip ? DEFAULT_HTTP_CLIENT_GZIP : DEFAULT_HTTP_CLIENT).execute(httpHost, httpGet);
if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300) {
throw new IOException("bad status code, downloading file " + response.getStatusLine().getStatusCode());
}
Long date = Long.valueOf(0);
Header[] headers = response.getAllHeaders();
for (int i = 0; i < headers.length; i++) {
if (headers[i].getName().startsWith("Last-Modified")) {
try {
date = DateUtils.parseDate(headers[i].getValue()).getTime();
} catch (Exception e) {
}
break;
}
}
if (response.getEntity() != null) {
if (gzip) {
String str = EntityUtils.toString(response.getEntity());
baos.write(str.getBytes());
} else {
response.getEntity().writeTo(baos);
}
}
body = baos.toByteArray();
if (body == null || body.length == 0) {
throw new IOException("invalid response");
}
return new Object[] { body, date };
} finally {
try {
baos.close();
} catch (IOException e) {
}
}
}
public byte[] fetch() {
Object[] objArray = null;
try {
objArray = fetch(false);
} catch (Throwable e) {
Log.e(TAG, "Error performing http to " + uri + ", e: " + e.getMessage());
}
return objArray != null ? (byte[]) objArray[0] : null;
}
public byte[] fetchGzip() {
Object[] objArray = null;
try {
objArray = fetch(true);
} catch (Throwable e) {
Log.e(TAG, "Error performing http to " + uri + ", e: " + e.getMessage());
}
return objArray != null ? (byte[]) objArray[0] : null;
}
public void save(File file) throws IOException {
save(file, null);
}
public void save(File file, HttpFetcherListener listener) throws IOException {
HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort());
HttpGet httpGet = new HttpGet(uri);
httpGet.addHeader("Connection", "close");
HttpParams params = httpGet.getParams();
HttpConnectionParams.setConnectionTimeout(params, timeout);
HttpConnectionParams.setSoTimeout(params, timeout);
HttpConnectionParams.setStaleCheckingEnabled(params, true);
HttpConnectionParams.setTcpNoDelay(params, true);
HttpClientParams.setRedirecting(params, true);
HttpProtocolParams.setUseExpectContinue(params, false);
HttpProtocolParams.setUserAgent(params, userAgent);
FileOutputStream out = null;
int statusCode = -1;
Map<String, String> headers = null;
try {
out = new FileOutputStream(file);
HttpResponse response = DEFAULT_HTTP_CLIENT.execute(httpHost, httpGet);
statusCode = response.getStatusLine().getStatusCode();
headers = mapHeaders(response.headerIterator());
if (statusCode < 200 || statusCode >= 300) {
throw new IOException("bad status code, downloading file " + statusCode);
}
if (response.getEntity() != null) {
writeEntity(response.getEntity(), out, listener);
}
if (listener != null) {
listener.onSuccess(new byte[0]);
}
} catch (Throwable e) {
if (listener != null) {
listener.onError(e, statusCode, headers);
}
Log.e(TAG, "Error downloading from: " + uri + ", e: " + e.getMessage());
} finally {
IOUtils.closeQuietly(out);
}
}
public byte[] post(String postBody, String contentType) throws IOException {
HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort());
HttpPost httpPost = new HttpPost(uri);
StringEntity stringEntity = new StringEntity(postBody);
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Content-type", "application/json");
httpPost.setEntity(stringEntity);
HttpParams params = httpPost.getParams();
HttpConnectionParams.setConnectionTimeout(params, timeout);
HttpConnectionParams.setSoTimeout(params, timeout);
HttpConnectionParams.setStaleCheckingEnabled(params, true);
HttpConnectionParams.setTcpNoDelay(params, true);
HttpClientParams.setRedirecting(params, true);
HttpProtocolParams.setUseExpectContinue(params, false);
HttpProtocolParams.setUserAgent(params, DEFAULT_USER_AGENT);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
HttpResponse response = DEFAULT_HTTP_CLIENT.execute(httpHost, httpPost);
if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300)
throw new IOException("bad status code, upload file " + response.getStatusLine().getStatusCode());
if (response.getEntity() != null) {
response.getEntity().writeTo(baos);
}
body = baos.toByteArray();
if (body == null || body.length == 0) {
throw new IOException("invalid response");
}
return body;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Http error: " + e.getMessage());
} finally {
try {
baos.close();
} catch (IOException e) {
}
}
}
public void post(File file) throws IOException {
HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort());
HttpPost httpPost = new HttpPost(uri);
FileEntity fileEntity = new FileEntity(file, "binary/octet-stream");
fileEntity.setChunked(true);
httpPost.setEntity(fileEntity);
HttpParams params = httpPost.getParams();
HttpConnectionParams.setConnectionTimeout(params, timeout);
HttpConnectionParams.setSoTimeout(params, timeout);
HttpConnectionParams.setStaleCheckingEnabled(params, true);
HttpConnectionParams.setTcpNoDelay(params, true);
HttpClientParams.setRedirecting(params, true);
HttpProtocolParams.setUseExpectContinue(params, false);
HttpProtocolParams.setUserAgent(params, DEFAULT_USER_AGENT);
try {
HttpResponse response = DEFAULT_HTTP_CLIENT.execute(httpHost, httpPost);
if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300)
throw new IOException("bad status code, upload file " + response.getStatusLine().getStatusCode());
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Http error: " + e.getMessage());
} finally {
//
}
}
public void post(FileEntity fileEntity) throws IOException {
HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort());
HttpPost httpPost = new HttpPost(uri);
httpPost.setEntity(fileEntity);
HttpParams params = httpPost.getParams();
HttpConnectionParams.setConnectionTimeout(params, timeout);
HttpConnectionParams.setSoTimeout(params, timeout);
HttpConnectionParams.setStaleCheckingEnabled(params, true);
HttpConnectionParams.setTcpNoDelay(params, true);
HttpClientParams.setRedirecting(params, true);
HttpProtocolParams.setUseExpectContinue(params, false);
HttpProtocolParams.setUserAgent(params, DEFAULT_USER_AGENT);
try {
HttpResponse response = DEFAULT_HTTP_CLIENT.execute(httpHost, httpPost);
if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300)
throw new IOException("bad status code, upload file " + response.getStatusLine().getStatusCode());
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException("Http error: " + e.getMessage());
} finally {
//
}
}
private static void setupHttpClients() {
DEFAULT_HTTP_CLIENT = setupHttpClient(false);
DEFAULT_HTTP_CLIENT_GZIP = setupHttpClient(true);
}
private static HttpClient setupHttpClient(boolean gzip) {
SSLSocketFactory.getSocketFactory().setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
BasicHttpParams params = new BasicHttpParams();
params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(20));
params.setIntParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 200);
ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
DefaultHttpClient httpClient = new DefaultHttpClient(cm, new BasicHttpParams());
httpClient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(HTTP_RETRY_COUNT, true));
if (gzip) {
httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
if (!request.containsHeader("Accept-Encoding")) {
request.addHeader("Accept-Encoding", "gzip");
}
}
});
httpClient.addResponseInterceptor(new HttpResponseInterceptor() {
public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
HttpEntity entity = response.getEntity();
Header ceheader = entity.getContentEncoding();
if (ceheader != null) {
HeaderElement[] codecs = ceheader.getElements();
for (int i = 0; i < codecs.length; i++) {
if (codecs[i].getName().equalsIgnoreCase("gzip")) {
response.setEntity(new GzipDecompressingEntity(response.getEntity()));
return;
}
}
}
}
});
}
return httpClient;
}
private static void writeEntity(final HttpEntity entity, OutputStream out, HttpFetcherListener listener) throws IOException {
if (entity == null) {
throw new IllegalArgumentException("HTTP entity may not be null");
}
InputStream in = entity.getContent();
if (in == null) {
return;
}
int i = (int) entity.getContentLength();
if (i < 0) {
i = 4096;
}
try {
byte[] data = new byte[4096];
int n;
while ((n = in.read(data)) != -1) {
if (listener != null) {
listener.onData(data, n);
}
out.write(data, 0, n);
out.flush();
}
} finally {
in.close();
}
}
private static URI convert(String uri) {
try {
return new URI(uri);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
private static Map<String, String> mapHeaders(HeaderIterator it) {
Map<String, String> map = new HashMap<String, String>();
while (it.hasNext()) {
Header h = it.nextHeader();
map.put(h.getName(), h.getValue());
}
return map;
}
private static final class GzipDecompressingEntity extends HttpEntityWrapper {
public GzipDecompressingEntity(final HttpEntity entity) {
super(entity);
}
@Override
public InputStream getContent() throws IOException, IllegalStateException {
return new GZIPInputStream(wrappedEntity.getContent());
}
@Override
public long getContentLength() {
return -1;
}
}
}