tests for url redirections

This commit is contained in:
Alexey Danilov
2015-09-21 21:24:37 +03:00
parent 8263814aea
commit 15c5388f6c
8 changed files with 152 additions and 61 deletions

View File

@@ -12,7 +12,7 @@ repositories {
maven { url 'https://dl.bintray.com/alexeydanilov/maven' } maven { url 'https://dl.bintray.com/alexeydanilov/maven' }
} }
dependencies { dependencies {
compile 'com.danikula:videocache:2.0.9' compile 'com.danikula:videocache:2.1.0'
} }
``` ```

View File

@@ -26,7 +26,7 @@ publish {
userOrg = 'alexeydanilov' userOrg = 'alexeydanilov'
groupId = 'com.danikula' groupId = 'com.danikula'
artifactId = 'videocache' artifactId = 'videocache'
publishVersion = '2.0.9' publishVersion = '2.1.0'
description = 'Cache support for android VideoView' description = 'Cache support for android VideoView'
website = 'https://github.com/danikula/AndroidVideoCache' website = 'https://github.com/danikula/AndroidVideoCache'
} }

View File

@@ -2,6 +2,7 @@ package com.danikula.videocache;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -11,8 +12,11 @@ import java.net.URL;
import static com.danikula.videocache.ProxyCacheUtils.DEFAULT_BUFFER_SIZE; import static com.danikula.videocache.ProxyCacheUtils.DEFAULT_BUFFER_SIZE;
import static com.danikula.videocache.ProxyCacheUtils.LOG_TAG; import static com.danikula.videocache.ProxyCacheUtils.LOG_TAG;
import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
import static java.net.HttpURLConnection.HTTP_OK; import static java.net.HttpURLConnection.HTTP_OK;
import static java.net.HttpURLConnection.HTTP_PARTIAL; import static java.net.HttpURLConnection.HTTP_PARTIAL;
import static java.net.HttpURLConnection.HTTP_SEE_OTHER;
/** /**
* {@link Source} that uses http resource as source for {@link ProxyCache}. * {@link Source} that uses http resource as source for {@link ProxyCache}.
@@ -21,7 +25,8 @@ import static java.net.HttpURLConnection.HTTP_PARTIAL;
*/ */
public class HttpUrlSource implements Source { public class HttpUrlSource implements Source {
public String url; private static final int MAX_REDIRECTS = 5;
public final String url;
private HttpURLConnection connection; private HttpURLConnection connection;
private InputStream inputStream; private InputStream inputStream;
private volatile int available = Integer.MIN_VALUE; private volatile int available = Integer.MIN_VALUE;
@@ -47,33 +52,10 @@ public class HttpUrlSource implements Source {
@Override @Override
public void open(int offset) throws ProxyCacheException { public void open(int offset) throws ProxyCacheException {
try { try {
boolean isRedirected; connection = openConnection(offset, "GET", -1);
int redirectCount = 0;
int responseCode;
do {
Log.d(ProxyCacheUtils.LOG_TAG, "Open connection " + (offset > 0 ? " with offset " + offset : "") + " to " + url);
connection = (HttpURLConnection) new URL(url).openConnection();
if (offset > 0) {
connection.setRequestProperty("Range", "bytes=" + offset + "-");
}
responseCode = connection.getResponseCode();
if ((responseCode == HttpURLConnection.HTTP_MOVED_PERM
|| responseCode == HttpURLConnection.HTTP_MOVED_TEMP
|| responseCode == HttpURLConnection.HTTP_SEE_OTHER)) {
url = connection.getHeaderField("Location");
isRedirected = true;
redirectCount++;
} else {
isRedirected = false;
}
if (redirectCount > ProxyCacheUtils.MAX_REDIRECTS) {
throw new ProxyCacheException("Too many redirects");
}
} while (isRedirected);
mime = connection.getContentType(); mime = connection.getContentType();
inputStream = new BufferedInputStream(connection.getInputStream(), DEFAULT_BUFFER_SIZE); inputStream = new BufferedInputStream(connection.getInputStream(), DEFAULT_BUFFER_SIZE);
available = readSourceAvailableBytes(connection, offset, responseCode); available = readSourceAvailableBytes(connection, offset, connection.getResponseCode());
} catch (IOException e) { } catch (IOException e) {
throw new ProxyCacheException("Error opening connection for " + url + " with offset " + offset, e); throw new ProxyCacheException("Error opening connection for " + url + " with offset " + offset, e);
} }
@@ -111,28 +93,7 @@ public class HttpUrlSource implements Source {
HttpURLConnection urlConnection = null; HttpURLConnection urlConnection = null;
InputStream inputStream = null; InputStream inputStream = null;
try { try {
boolean isRedirected; urlConnection = openConnection(0, "HEAD", 10000);
int redirectCount = 0;
do {
urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setConnectTimeout(10000);
urlConnection.setReadTimeout(10000);
urlConnection.setRequestMethod("HEAD");
int responseCode = urlConnection.getResponseCode();
if ((responseCode == HttpURLConnection.HTTP_MOVED_PERM
|| responseCode == HttpURLConnection.HTTP_MOVED_TEMP
|| responseCode == HttpURLConnection.HTTP_SEE_OTHER)) {
url = urlConnection.getHeaderField("Location");
isRedirected = true;
redirectCount++;
} else {
isRedirected = false;
}
if (redirectCount > ProxyCacheUtils.MAX_REDIRECTS) {
throw new ProxyCacheException("Too many redirects");
}
} while (isRedirected);
available = urlConnection.getContentLength(); available = urlConnection.getContentLength();
mime = urlConnection.getContentType(); mime = urlConnection.getContentType();
inputStream = urlConnection.getInputStream(); inputStream = urlConnection.getInputStream();
@@ -147,6 +108,36 @@ public class HttpUrlSource implements Source {
} }
} }
private HttpURLConnection openConnection(int offset, String method, int timeout) throws IOException, ProxyCacheException {
HttpURLConnection connection;
boolean redirected;
int redirectCount = 0;
String url = this.url;
do {
Log.d(LOG_TAG, "Open connection " + (offset > 0 ? " with offset " + offset : "") + " to " + url);
connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod(method);
if (offset > 0) {
connection.setRequestProperty("Range", "bytes=" + offset + "-");
}
if (timeout > 0) {
connection.setConnectTimeout(timeout);
connection.setReadTimeout(timeout);
}
int code = connection.getResponseCode();
redirected = code == HTTP_MOVED_PERM || code == HTTP_MOVED_TEMP || code == HTTP_SEE_OTHER;
if (redirected) {
url = connection.getHeaderField("Location");
redirectCount++;
connection.disconnect();
}
if (redirectCount > MAX_REDIRECTS) {
throw new ProxyCacheException("Too many redirects: " + redirectCount);
}
} while (redirected);
return connection;
}
public synchronized String getMime() throws ProxyCacheException { public synchronized String getMime() throws ProxyCacheException {
if (TextUtils.isEmpty(mime)) { if (TextUtils.isEmpty(mime)) {
fetchContentInfo(); fetchContentInfo();
@@ -154,6 +145,10 @@ public class HttpUrlSource implements Source {
return mime; return mime;
} }
public String getUrl() {
return url;
}
@Override @Override
public String toString() { public String toString() {
return "HttpUrlSource{url='" + url + "}"; return "HttpUrlSource{url='" + url + "}";

View File

@@ -25,7 +25,6 @@ class ProxyCacheUtils {
static final String LOG_TAG = "ProxyCache"; static final String LOG_TAG = "ProxyCache";
static final int DEFAULT_BUFFER_SIZE = 8 * 1024; static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
static final int MAX_ARRAY_PREVIEW = 16; static final int MAX_ARRAY_PREVIEW = 16;
static final int MAX_REDIRECTS = 5;
static String getSupposablyMime(String url) { static String getSupposablyMime(String url) {
MimeTypeMap mimes = MimeTypeMap.getSingleton(); MimeTypeMap mimes = MimeTypeMap.getSingleton();

View File

@@ -17,7 +17,7 @@ apply plugin: 'com.neenbedankt.android-apt'
android { android {
compileSdkVersion 23 compileSdkVersion 23
buildToolsVersion '23.0.0' buildToolsVersion '23.0.1'
defaultConfig { defaultConfig {
applicationId "com.danikula.videocache.sample" applicationId "com.danikula.videocache.sample"
@@ -37,9 +37,9 @@ apt {
dependencies { dependencies {
// compile project(':library') // compile project(':library')
compile 'com.android.support:support-v4:23.0.0' compile 'com.android.support:support-v4:23.0.1'
compile 'org.androidannotations:androidannotations-api:3.3.2' compile 'org.androidannotations:androidannotations-api:3.3.2'
compile 'com.danikula:videocache:2.0.9' compile 'com.danikula:videocache:2.1.0'
compile 'com.viewpagerindicator:library:2.4.2-SNAPSHOT@aar' compile 'com.viewpagerindicator:library:2.4.2-SNAPSHOT@aar'
apt 'org.androidannotations:androidannotations:3.3.2' apt 'org.androidannotations:androidannotations:3.3.2'
} }

View File

@@ -21,6 +21,7 @@ import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_BI
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_NAME; import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_NAME;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_SIZE; import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_SIZE;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_URL; import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_URL;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_URL_ONE_REDIRECT;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL; import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL;
import static com.danikula.videocache.support.ProxyCacheTestUtils.getFileContent; import static com.danikula.videocache.support.ProxyCacheTestUtils.getFileContent;
import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile; import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile;
@@ -81,6 +82,20 @@ public class HttpProxyCacheServerTest {
assertThat(response.second.data).isEqualTo(loadAssetFile(ASSETS_DATA_BIG_NAME)); assertThat(response.second.data).isEqualTo(loadAssetFile(ASSETS_DATA_BIG_NAME));
} }
@Test
public void testProxyFullResponseWithRedirect() throws Exception {
Pair<File, Response> response = readProxyData(HTTP_DATA_BIG_URL_ONE_REDIRECT);
assertThat(response.second.code).isEqualTo(200);
assertThat(response.second.contentLength).isEqualTo(HTTP_DATA_BIG_SIZE);
assertThat(response.second.contentType).isEqualTo("image/jpeg");
assertThat(response.second.headers.containsKey("Accept-Ranges")).isTrue();
assertThat(response.second.headers.get("Accept-Ranges").get(0)).isEqualTo("bytes");
assertThat(response.second.headers.containsKey("Content-Range")).isFalse();
assertThat(response.second.data).isEqualTo(getFileContent(response.first));
assertThat(response.second.data).isEqualTo(loadAssetFile(ASSETS_DATA_BIG_NAME));
}
@Test @Test
public void testProxyPartialResponse() throws Exception { public void testProxyPartialResponse() throws Exception {
int offset = 42000; int offset = 42000;
@@ -99,6 +114,24 @@ public class HttpProxyCacheServerTest {
assertThat(getFileContent(response.first)).isEqualTo(loadAssetFile(ASSETS_DATA_BIG_NAME)); assertThat(getFileContent(response.first)).isEqualTo(loadAssetFile(ASSETS_DATA_BIG_NAME));
} }
@Test
public void testProxyPartialResponseWithRedirect() throws Exception {
int offset = 42000;
Pair<File, Response> response = readProxyData(HTTP_DATA_BIG_URL_ONE_REDIRECT, offset);
assertThat(response.second.code).isEqualTo(206);
assertThat(response.second.contentLength).isEqualTo(HTTP_DATA_BIG_SIZE - offset);
assertThat(response.second.contentType).isEqualTo("image/jpeg");
assertThat(response.second.headers.containsKey("Accept-Ranges")).isTrue();
assertThat(response.second.headers.get("Accept-Ranges").get(0)).isEqualTo("bytes");
assertThat(response.second.headers.containsKey("Content-Range")).isTrue();
String rangeHeader = String.format("bytes %d-%d/%d", offset, HTTP_DATA_BIG_SIZE, HTTP_DATA_BIG_SIZE);
assertThat(response.second.headers.get("Content-Range").get(0)).isEqualTo(rangeHeader);
byte[] expectedData = Arrays.copyOfRange(loadAssetFile(ASSETS_DATA_BIG_NAME), offset, HTTP_DATA_BIG_SIZE);
assertThat(response.second.data).isEqualTo(expectedData);
assertThat(getFileContent(response.first)).isEqualTo(loadAssetFile(ASSETS_DATA_BIG_NAME));
}
private Pair<File, Response> readProxyData(String url, int offset) throws IOException { private Pair<File, Response> readProxyData(String url, int offset) throws IOException {
File externalCacheDir = RuntimeEnvironment.application.getExternalCacheDir(); File externalCacheDir = RuntimeEnvironment.application.getExternalCacheDir();
FileNameGenerator fileNameGenerator = new Md5FileNameGenerator(externalCacheDir); FileNameGenerator fileNameGenerator = new Md5FileNameGenerator(externalCacheDir);

View File

@@ -1,21 +1,28 @@
package com.danikula.videocache; package com.danikula.videocache;
import com.danikula.videocache.test.BuildConfig; import com.danikula.videocache.test.BuildConfig;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_BIG_NAME; import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_BIG_NAME;
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_NAME; import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_NAME;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_SIZE; import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_SIZE;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_URL; import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_URL;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_SIZE;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL; import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL_3_REDIRECTS;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL_6_REDIRECTS;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL_ONE_REDIRECT;
import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile; import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile;
import static org.fest.assertions.api.Assertions.assertThat; import static org.fest.assertions.api.Assertions.assertThat;
import static org.fest.assertions.api.Assertions.fail;
/** /**
* @author Alexey Danilov (danikula@gmail.com). * @author Alexey Danilov (danikula@gmail.com).
@@ -59,15 +66,68 @@ public class HttpUrlSourceTest {
assertThat(source.available()).isEqualTo(loadAssetFile(ASSETS_DATA_NAME).length); assertThat(source.available()).isEqualTo(loadAssetFile(ASSETS_DATA_NAME).length);
} }
@Test
public void testFetchInfoWithRedirect() throws Exception {
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL_ONE_REDIRECT);
source.open(0);
int available = source.available();
String mime = source.getMime();
source.close();
assertThat(available).isEqualTo(HTTP_DATA_SIZE);
assertThat(mime).isEqualTo("image/jpeg");
}
@Test
public void testFetchDataWithRedirect() throws Exception {
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL_ONE_REDIRECT);
source.open(0);
byte[] readData = new byte[HTTP_DATA_SIZE];
source.read(readData);
source.close();
byte[] expectedData = Arrays.copyOfRange(loadAssetFile(ASSETS_DATA_NAME), 0, HTTP_DATA_SIZE);
assertThat(readData).isEqualTo(expectedData);
}
@Test
public void testFetchPartialDataWithRedirect() throws Exception {
int offset = 42;
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL_ONE_REDIRECT);
source.open(offset);
byte[] readData = new byte[HTTP_DATA_SIZE - offset];
source.read(readData);
source.close();
byte[] expectedData = Arrays.copyOfRange(loadAssetFile(ASSETS_DATA_NAME), offset, HTTP_DATA_SIZE);
assertThat(readData).isEqualTo(expectedData);
}
@Test
public void testFetchPartialDataWithMultiRedirects() throws Exception {
int offset = 42;
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL_3_REDIRECTS);
source.open(offset);
byte[] readData = new byte[HTTP_DATA_SIZE - offset];
source.read(readData);
source.close();
byte[] expectedData = Arrays.copyOfRange(loadAssetFile(ASSETS_DATA_NAME), offset, HTTP_DATA_SIZE);
assertThat(readData).isEqualTo(expectedData);
}
@Ignore("To test it fairly we should disable caching connection.setUseCaches(false), but it will decrease performance")
@Test(expected = ProxyCacheException.class)
public void testExceedingRedirects() throws Exception {
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL_6_REDIRECTS);
source.open(0);
fail("Too many redirects");
}
@Ignore("Seems Robolectric bug: MimeTypeMap.getFileExtensionFromUrl always returns null") @Ignore("Seems Robolectric bug: MimeTypeMap.getFileExtensionFromUrl always returns null")
@Test @Test
public void testMimeByUrl() throws Exception { public void testMimeByUrl() throws Exception {
assertThat(new HttpUrlSource("http://mysite.by/video.mp4").getMime()).isEqualTo("video/mp4"); assertThat(new HttpUrlSource("http://mysite.by/video.mp4").getMime()).isEqualTo("video/mp4");
assertThat(new HttpUrlSource(HTTP_DATA_URL).getMime()).isEqualTo("image/jpeg"); assertThat(new HttpUrlSource(HTTP_DATA_URL).getMime()).isEqualTo("image/jpeg");
} }
@Test
public void testHttpUrlSourceRedirect() throws Exception {
assertThat(new HttpUrlSource("http://goo.gl/K0gWQW").getMime()).isEqualTo("video/mp4");
}
} }

View File

@@ -21,7 +21,11 @@ import java.util.UUID;
public class ProxyCacheTestUtils { public class ProxyCacheTestUtils {
public static final String HTTP_DATA_URL = "https://dl.dropboxusercontent.com/u/15506779/persistent/proxycache/android.jpg"; public static final String HTTP_DATA_URL = "https://dl.dropboxusercontent.com/u/15506779/persistent/proxycache/android.jpg";
public static final String HTTP_DATA_URL_ONE_REDIRECT = "http://bit.ly/1V5PeY5";
public static final String HTTP_DATA_URL_3_REDIRECTS = "http://bit.ly/1KvVmgZ";
public static final String HTTP_DATA_URL_6_REDIRECTS = "http://ow.ly/SugRH";
public static final String HTTP_DATA_BIG_URL = "https://dl.dropboxusercontent.com/u/15506779/persistent/proxycache/phones.jpg"; public static final String HTTP_DATA_BIG_URL = "https://dl.dropboxusercontent.com/u/15506779/persistent/proxycache/phones.jpg";
public static final String HTTP_DATA_BIG_URL_ONE_REDIRECT = "http://bit.ly/1iJ69yA";
public static final String ASSETS_DATA_NAME = "android.jpg"; public static final String ASSETS_DATA_NAME = "android.jpg";
public static final String ASSETS_DATA_BIG_NAME = "phones.jpg"; public static final String ASSETS_DATA_BIG_NAME = "phones.jpg";
public static final int HTTP_DATA_SIZE = 4768; public static final int HTTP_DATA_SIZE = 4768;