/** * */ package by.istin.android.xcore.source.impl.http; import android.content.Context; import android.net.ParseException; import android.net.Uri; import android.net.http.AndroidHttpClient; import android.os.Build; import android.text.TextUtils; import android.webkit.URLUtil; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Set; import by.istin.android.xcore.source.DataSourceRequest; import by.istin.android.xcore.source.IDataSource; import by.istin.android.xcore.source.impl.http.exception.IOStatusException; import by.istin.android.xcore.utils.AppUtils; import by.istin.android.xcore.utils.Log; import by.istin.android.xcore.utils.StringUtil; import by.istin.android.xcore.utils.UriUtils; /** * Class for load data from web. * * @author Uladzimir_Klyshevich * */ public class HttpAndroidDataSource implements IDataSource<InputStream> { public static final String SYSTEM_SERVICE_KEY = "xcore:httpdatasource"; private static final String ACCEPT_DEFAULT_VALUE = "*/*"; private static final String USER_AGENT_KEY = "User-Agent"; private static final String ACCEPT_KEY = "Accept"; private static final String USER_AGENT_END = ") AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"; private static final String USER_AGENT_BUILD = " Build/"; private static final String SHTRICH = "-"; private static final String USER_AGENT_DIVIDER = "; "; private static final String USER_AGENT_START = "Mozilla/5.0 (Linux; U; Android "; public static final int SO_TIMEOUT = 20000; /* Constant encoding for http client. */ public static final String UTF_8 = "UTF-8"; /* Constant Tag for logging. */ private static final String TAG = HttpAndroidDataSource.class.getSimpleName(); protected static final String sUserAgent; public static class DefaultHttpRequestBuilder implements IHttpRequestBuilder { public static final String TYPE = "_httptype"; protected static final String Q = "\\?"; private static final String SPACE = " "; private static final String PLUS = "+"; public static final String POST = "post"; public static enum Type { GET, PUT, POST, DELETE } public static String getUrl(String url, Type type) { if (url.indexOf("?") > 0) { return url + "&" + TYPE + "=" + type.name(); } else { return url + "?" + TYPE + "=" + type.name(); } } @Override public HttpRequestBase build(DataSourceRequest dataSourceRequest) throws IOException { String url = dataSourceRequest.getUri(); HttpRequestBase request = null; Uri uri = Uri.parse(url); String typeParam = uri.getQueryParameter(TYPE); Type type = Type.GET; if (!TextUtils.isEmpty(typeParam)) { type = Type.valueOf(typeParam.toUpperCase()); } switch (type) { case GET: request = creteGetRequest(dataSourceRequest, url, uri); break; case POST: request = createPostRequest(dataSourceRequest, url, uri); break; case PUT: request = createPutRequest(dataSourceRequest, url, uri); break; case DELETE: request = createDeleteRequest(dataSourceRequest, url, uri); break; default: break; } return request; } protected HttpRequestBase createDeleteRequest(DataSourceRequest dataSourceRequest, String url, Uri uri) { return new HttpDelete(url); } protected HttpRequestBase createPutRequest(DataSourceRequest dataSourceRequest, String url, Uri uri) { HttpPut putRequest = new HttpPut(url.split(Q)[0]); initEntity(uri, putRequest); return putRequest; } protected HttpRequestBase createPostRequest(DataSourceRequest dataSourceRequest, String url, Uri uri) { HttpPost postRequest = new HttpPost(url.split(Q)[0]); initEntity(uri, postRequest); return postRequest; } protected HttpRequestBase creteGetRequest(DataSourceRequest dataSourceRequest, String url, Uri uri) { return new HttpGet(url); } private void initEntity(Uri uri, HttpEntityEnclosingRequestBase postRequest) { Set<String> queryParameterNames = UriUtils.getQueryParameters(uri); List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(queryParameterNames.size()); for (String paramName : queryParameterNames) { if (paramName.equals(TYPE)) continue; String queryParameter = uri.getQueryParameter(paramName); if (Build.VERSION.SDK_INT < 16 && queryParameter != null) { queryParameter = queryParameter.replace(PLUS, SPACE); } nameValuePairs.add(new BasicNameValuePair(paramName, queryParameter)); } try { postRequest.setEntity(new UrlEncodedFormEntity(nameValuePairs, UTF_8)); } catch (UnsupportedEncodingException e) { // can be ignored } } } public static class DefaultResponseStatusHandler implements IResponseStatusHandler { @Override public void statusHandle(HttpAndroidDataSource dataSource, HttpUriRequest request, HttpResponse response) throws ParseException, IOException { int statusCode = response.getStatusLine().getStatusCode(); HttpEntity httpEntity = response.getEntity(); if (statusCode != HttpStatus.SC_OK) { String entityValue = EntityUtils.toString(httpEntity); Log.e(TAG, response.getStatusLine().getReasonPhrase() + " " + entityValue); throw new IOStatusException(response.getStatusLine().getReasonPhrase(), statusCode, entityValue); } } } static { sUserAgent = USER_AGENT_START + android.os.Build.VERSION.RELEASE + USER_AGENT_DIVIDER + Locale.getDefault().getLanguage() + SHTRICH + Locale.getDefault().getCountry() + USER_AGENT_DIVIDER + android.os.Build.DEVICE + USER_AGENT_BUILD + android.os.Build.ID + USER_AGENT_END; } private final IHttpRequestBuilder mRequestBuilder; private final IResponseStatusHandler mResponseStatusHandler; private final InputStreamHttpClientHelper mInputStreamHelper; public HttpAndroidDataSource() { this(new DefaultHttpRequestBuilder(), new DefaultResponseStatusHandler()); } /* Default constructor. */ public HttpAndroidDataSource(IHttpRequestBuilder requestBuilder, IResponseStatusHandler statusHandler) { mRequestBuilder = requestBuilder; mResponseStatusHandler = statusHandler; mInputStreamHelper = createInputStreamHttpClientHelper(); } protected InputStreamHttpClientHelper createInputStreamHttpClientHelper() { return new InputStreamHttpClientHelper(sUserAgent); } public IHttpRequestBuilder getRequestBuilder(){ return mRequestBuilder; } /** * Gets instance {@link HttpAndroidDataSource}. * * @return http client */ public static HttpAndroidDataSource get(Context ctx) { return (HttpAndroidDataSource) AppUtils.get(ctx, SYSTEM_SERVICE_KEY); } protected HttpRequestBase createRequest(DataSourceRequest request) throws IOException { return mRequestBuilder.build(request); } public IResponseStatusHandler getResponseStatusHandler() { return mResponseStatusHandler; } public InputStream getInputSteam(HttpUriRequest request) throws IllegalStateException, IOException { request.setHeader(ACCEPT_KEY, ACCEPT_DEFAULT_VALUE); request.setHeader(USER_AGENT_KEY, sUserAgent); AndroidHttpClient.modifyRequestToAcceptGzipResponse(request); Log.xd(this, request); HttpClient client = mInputStreamHelper.getClient(); HttpResponse response = client.execute(request); int statusCode = response.getStatusLine().getStatusCode(); boolean isRedirect = isRedirect(statusCode); if (isRedirect) { Header firstHeader = response.getFirstHeader("Location"); if (firstHeader != null) { String value = firstHeader.getValue(); if (!StringUtil.isEmpty(value) && !value.equals(request.getURI().toString())) { return createRedirectRequest(request, response, value); } } } if (mResponseStatusHandler != null) { mResponseStatusHandler.statusHandle(this, request, response); } HttpEntity httpEntity = response.getEntity(); InputStream ungzippedContent = AndroidHttpClient.getUngzippedContent(httpEntity); return mInputStreamHelper.getInputStream(ungzippedContent, client); } protected InputStream createRedirectRequest(HttpUriRequest request, HttpResponse response, String value) throws IOException { Log.xd(this, "redirect " + value); if (!URLUtil.isNetworkUrl(value)) { Log.xd(this, "redirect current request "); value = request.getURI().getScheme() + "://" + request.getURI().getHost() + value; Log.xd(this, "redirect current request " + value); } HttpGet redirectUri = new HttpGet(value); request.abort(); return getInputSteam(redirectUri); } protected boolean isRedirect(int statusCode) { return statusCode == HttpStatus.SC_MOVED_TEMPORARILY || statusCode == HttpStatus.SC_MOVED_PERMANENTLY; } @Override public InputStream getSource(DataSourceRequest dataSourceRequest) throws IOException { return getInputSteam(createRequest(dataSourceRequest)); } @Override public String getAppServiceKey() { return SYSTEM_SERVICE_KEY; } }