mirror of
https://github.com/samsonjs/media.git
synced 2026-04-24 14:37:45 +00:00
Add a DefaultHttpDataSource contract test
Also add a MockWebServer Dispatcher that can be customised with different resources and behaviours for different paths. PiperOrigin-RevId: 348759662
This commit is contained in:
parent
e154cb1787
commit
f44e5bd292
4 changed files with 764 additions and 0 deletions
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* 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.google.android.exoplayer2.upstream;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.testutil.DataSourceContractTest;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import com.google.android.exoplayer2.testutil.WebServerDispatcher;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** {@link DataSource} contract tests for {@link DefaultHttpDataSource}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DefaultHttpDataSourceContractTest extends DataSourceContractTest {
|
||||
|
||||
private static int seed = 0;
|
||||
private static final WebServerDispatcher.Resource RANGE_SUPPORTED =
|
||||
new WebServerDispatcher.Resource.Builder()
|
||||
.setPath("/supports/range-requests")
|
||||
.setData(TestUtil.buildTestData(/* length= */ 20, seed++))
|
||||
.supportsRangeRequests(true)
|
||||
.build();
|
||||
|
||||
private static final WebServerDispatcher.Resource RANGE_SUPPORTED_LENGTH_UNKNOWN =
|
||||
new WebServerDispatcher.Resource.Builder()
|
||||
.setPath("/supports/range-requests-length-unknown")
|
||||
.setData(TestUtil.buildTestData(/* length= */ 20, seed++))
|
||||
.supportsRangeRequests(true)
|
||||
.resolvesToUnknownLength(true)
|
||||
.build();
|
||||
|
||||
private static final WebServerDispatcher.Resource RANGE_NOT_SUPPORTED =
|
||||
new WebServerDispatcher.Resource.Builder()
|
||||
.setPath("/doesnt/support/range-requests")
|
||||
.setData(TestUtil.buildTestData(/* length= */ 20, seed++))
|
||||
.supportsRangeRequests(false)
|
||||
.build();
|
||||
|
||||
private static final WebServerDispatcher.Resource RANGE_NOT_SUPPORTED_LENGTH_UNKNOWN =
|
||||
new WebServerDispatcher.Resource.Builder()
|
||||
.setPath("/doesnt/support/range-requests-length-unknown")
|
||||
.setData(TestUtil.buildTestData(/* length= */ 20, seed++))
|
||||
.supportsRangeRequests(false)
|
||||
.resolvesToUnknownLength(true)
|
||||
.build();
|
||||
|
||||
private final MockWebServer mockWebServer = new MockWebServer();
|
||||
|
||||
@Before
|
||||
public void startServer() throws Exception {
|
||||
mockWebServer.start();
|
||||
mockWebServer.setDispatcher(
|
||||
WebServerDispatcher.forResources(
|
||||
ImmutableList.of(
|
||||
RANGE_SUPPORTED,
|
||||
RANGE_SUPPORTED_LENGTH_UNKNOWN,
|
||||
RANGE_NOT_SUPPORTED,
|
||||
RANGE_NOT_SUPPORTED_LENGTH_UNKNOWN)));
|
||||
}
|
||||
|
||||
@After
|
||||
public void shutdownServer() throws Exception {
|
||||
mockWebServer.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataSource createDataSource() {
|
||||
return new DefaultHttpDataSource.Factory().createDataSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImmutableList<TestResource> getTestResources() {
|
||||
return ImmutableList.of(
|
||||
toTestResource("range supported", RANGE_SUPPORTED),
|
||||
toTestResource("range supported, length unknown", RANGE_SUPPORTED_LENGTH_UNKNOWN),
|
||||
toTestResource("range not supported", RANGE_NOT_SUPPORTED),
|
||||
toTestResource("range not supported, length unknown", RANGE_NOT_SUPPORTED_LENGTH_UNKNOWN));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Uri getNotFoundUri() {
|
||||
return Uri.parse(mockWebServer.url("/not/a/real/path").toString());
|
||||
}
|
||||
|
||||
private TestResource toTestResource(String name, WebServerDispatcher.Resource resource) {
|
||||
return new TestResource.Builder()
|
||||
.setName(name)
|
||||
.setUri(Uri.parse(mockWebServer.url(resource.getPath()).toString()))
|
||||
.setExpectedBytes(resource.getData())
|
||||
.setResolvesToUnknownLength(resource.resolvesToUnknownLength())
|
||||
.setEndOfInputExpected(!resource.resolvesToUnknownLength())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ dependencies {
|
|||
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion
|
||||
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
implementation 'com.squareup.okhttp3:mockwebserver:' + mockWebServerVersion
|
||||
implementation project(modulePrefix + 'library-core')
|
||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* 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.google.android.exoplayer2.testutil;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import okhttp3.mockwebserver.Dispatcher;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import okio.Buffer;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* A {@link Dispatcher} for {@link okhttp3.mockwebserver.MockWebServer} that allows per-path
|
||||
* customisation of the static data served.
|
||||
*/
|
||||
public class WebServerDispatcher extends Dispatcher {
|
||||
|
||||
/** A resource served by {@link WebServerDispatcher}. */
|
||||
public static class Resource {
|
||||
|
||||
/** Builder for {@link Resource}. */
|
||||
public static class Builder {
|
||||
private @MonotonicNonNull String path;
|
||||
private byte @MonotonicNonNull [] data;
|
||||
private boolean supportsRangeRequests;
|
||||
private boolean resolvesToUnknownLength;
|
||||
|
||||
/**
|
||||
* Sets the path this data should be served at. This is required.
|
||||
*
|
||||
* @return this builder, for convenience.
|
||||
*/
|
||||
public Builder setPath(String path) {
|
||||
this.path = path.startsWith("/") ? path : "/" + path;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data served by this resource. This is required.
|
||||
*
|
||||
* @return this builder, for convenience.
|
||||
*/
|
||||
public Builder setData(byte[] data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if RFC 7233 range requests should be supported for this resource. Defaults to false.
|
||||
*
|
||||
* @return this builder, for convenience.
|
||||
*/
|
||||
public Builder supportsRangeRequests(boolean supportsRangeRequests) {
|
||||
this.supportsRangeRequests = supportsRangeRequests;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the resource should resolve to an unknown length. Defaults to false.
|
||||
*
|
||||
* @return this builder, for convenience.
|
||||
*/
|
||||
public Builder resolvesToUnknownLength(boolean resolvesToUnknownLength) {
|
||||
this.resolvesToUnknownLength = resolvesToUnknownLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Builds the {@link Resource}. */
|
||||
public Resource build() {
|
||||
return new Resource(
|
||||
checkNotNull(path), checkNotNull(data), supportsRangeRequests, resolvesToUnknownLength);
|
||||
}
|
||||
}
|
||||
|
||||
private final String path;
|
||||
private final byte[] data;
|
||||
private final boolean supportsRangeRequests;
|
||||
private final boolean resolvesToUnknownLength;
|
||||
|
||||
private Resource(
|
||||
String path, byte[] data, boolean supportsRangeRequests, boolean resolvesToUnknownLength) {
|
||||
this.path = path;
|
||||
this.data = data;
|
||||
this.supportsRangeRequests = supportsRangeRequests;
|
||||
this.resolvesToUnknownLength = resolvesToUnknownLength;
|
||||
}
|
||||
|
||||
/** Returns the path this resource is available at. */
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/** Returns the data served by this resource. */
|
||||
public byte[] getData() {
|
||||
return data.clone();
|
||||
}
|
||||
|
||||
/** Returns true if RFC 7233 range requests should be supported for this resource. */
|
||||
public boolean supportsRangeRequests() {
|
||||
return supportsRangeRequests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the server shouldn't include the resource length in header responses.
|
||||
*
|
||||
* <p>Responses to unbound requests won't include a Content-Length header, and Content-Range
|
||||
* headers won't include the total resource length.
|
||||
*/
|
||||
public boolean resolvesToUnknownLength() {
|
||||
return resolvesToUnknownLength;
|
||||
}
|
||||
}
|
||||
|
||||
private final ImmutableMap<String, Resource> resourcesByPath;
|
||||
|
||||
/**
|
||||
* Constructs a dispatcher that handles requests based the provided {@link Resource} instances.
|
||||
*/
|
||||
public static WebServerDispatcher forResources(Iterable<Resource> resources) {
|
||||
return new WebServerDispatcher(Maps.uniqueIndex(resources, Resource::getPath));
|
||||
}
|
||||
|
||||
private WebServerDispatcher(ImmutableMap<String, Resource> resourcesByPath) {
|
||||
this.resourcesByPath = resourcesByPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockResponse dispatch(RecordedRequest request) {
|
||||
MockResponse response = new MockResponse();
|
||||
if (!resourcesByPath.containsKey(request.getPath())) {
|
||||
return response.setResponseCode(404);
|
||||
}
|
||||
Resource resource = checkNotNull(resourcesByPath.get(request.getPath()));
|
||||
byte[] resourceData = resource.getData();
|
||||
if (resource.supportsRangeRequests()) {
|
||||
response.setHeader("Accept-ranges", "bytes");
|
||||
}
|
||||
String rangeHeader = request.getHeader("Range");
|
||||
if (!resource.supportsRangeRequests() || rangeHeader == null) {
|
||||
response.setBody(new Buffer().write(resourceData));
|
||||
if (resource.resolvesToUnknownLength()) {
|
||||
response.setHeader("Content-Length", "");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Pair<@NullableType Integer, @NullableType Integer> range = parseRangeHeader(rangeHeader);
|
||||
|
||||
if (range == null || (range.first != null && range.first >= resourceData.length)) {
|
||||
return response
|
||||
.setResponseCode(416)
|
||||
.setHeader("Content-Range", "bytes */" + resourceData.length);
|
||||
}
|
||||
|
||||
if (range.first == null || range.second == null) {
|
||||
int start;
|
||||
if (range.first == null) {
|
||||
// We're handling a suffix range
|
||||
if (resource.resolvesToUnknownLength()) {
|
||||
// Can't return the suffix of an unknown-length resource.
|
||||
return response
|
||||
.setResponseCode(416)
|
||||
.setHeader("Content-Range", "bytes */" + resourceData.length);
|
||||
}
|
||||
start = max(0, resourceData.length - checkNotNull(range.second));
|
||||
} else {
|
||||
// We're handling an unbounded range
|
||||
start = checkNotNull(range.first);
|
||||
}
|
||||
response
|
||||
.setResponseCode(206)
|
||||
.setHeader(
|
||||
"Content-Range",
|
||||
"bytes "
|
||||
+ start
|
||||
+ "-"
|
||||
+ (resourceData.length - 1)
|
||||
+ "/"
|
||||
+ (resource.resolvesToUnknownLength() ? "*" : resourceData.length))
|
||||
.setBody(new Buffer().write(resourceData, start, resourceData.length - start));
|
||||
if (resource.resolvesToUnknownLength()) {
|
||||
response.setHeader("Content-Length", "");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// range.first and range.second are both non-null, so the range is bounded.
|
||||
|
||||
if (range.second < range.first) {
|
||||
return response
|
||||
.setResponseCode(416)
|
||||
.setHeader("Content-Range", "bytes */" + resourceData.length);
|
||||
}
|
||||
|
||||
int end = min(range.second + 1, resourceData.length);
|
||||
return response
|
||||
.setResponseCode(206)
|
||||
.setHeader(
|
||||
"Content-Range",
|
||||
"bytes "
|
||||
+ range.first
|
||||
+ "-"
|
||||
+ (end - 1)
|
||||
+ "/"
|
||||
+ (resource.resolvesToUnknownLength() ? "*" : resourceData.length))
|
||||
.setBody(new Buffer().write(resourceData, range.first, end - range.first));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an RFC 7233 Range header to its component parts. Returns null if the Range is invalid.
|
||||
*/
|
||||
@Nullable
|
||||
private static Pair<@NullableType Integer, @NullableType Integer> parseRangeHeader(
|
||||
String rangeHeader) {
|
||||
Pattern rangePattern = Pattern.compile("bytes=(\\d*)-(\\d*)");
|
||||
Matcher rangeMatcher = rangePattern.matcher(rangeHeader);
|
||||
if (!rangeMatcher.matches() || rangeHeader.contains(",")) {
|
||||
// This implementation only supports byte ranges and doesn't support multiple ranges.
|
||||
return null;
|
||||
}
|
||||
String first = checkNotNull(rangeMatcher.group(1));
|
||||
String second = checkNotNull(rangeMatcher.group(2));
|
||||
|
||||
Pair<@NullableType Integer, @NullableType Integer> result =
|
||||
Pair.create(
|
||||
first.isEmpty() ? null : Integer.parseInt(first),
|
||||
second.isEmpty() ? null : Integer.parseInt(second));
|
||||
if (result.first != null && result.second != null && result.second < result.first) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,395 @@
|
|||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* 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.google.android.exoplayer2.testutil;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.Arrays;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Tests for {@link WebServerDispatcher}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class WebServerDispatcherTest {
|
||||
|
||||
private static int seed;
|
||||
// Note: Leading slash is deliberately skipped to test that Resource#setPath() will add it if
|
||||
// it's missing.
|
||||
private static final String RANGE_SUPPORTED_PATH = "range/requests-supported";
|
||||
private static final byte[] RANGE_SUPPORTED_DATA =
|
||||
TestUtil.buildTestData(/* length= */ 20, seed++);
|
||||
private static final String RANGE_SUPPORTED_LENGTH_UNKNOWN_PATH =
|
||||
"range/requests-supported-length-unknown";
|
||||
private static final byte[] RANGE_SUPPORTED_LENGTH_UNKNOWN_DATA =
|
||||
TestUtil.buildTestData(/* length= */ 20, seed++);
|
||||
private static final String RANGE_UNSUPPORTED_PATH = "/range/requests/not-supported";
|
||||
private static final byte[] RANGE_UNSUPPORTED_DATA =
|
||||
TestUtil.buildTestData(/* length= */ 20, seed++);
|
||||
private static final String RANGE_UNSUPPORTED_LENGTH_UNKNOWN_PATH =
|
||||
"/range/requests/not-supported-length-unknown";
|
||||
private static final byte[] RANGE_UNSUPPORTED_LENGTH_UNKNOWN_DATA =
|
||||
TestUtil.buildTestData(/* length= */ 20, seed++);
|
||||
|
||||
private MockWebServer mockWebServer;
|
||||
|
||||
@Before
|
||||
public void setupServer() {
|
||||
mockWebServer = new MockWebServer();
|
||||
mockWebServer.setDispatcher(
|
||||
WebServerDispatcher.forResources(
|
||||
ImmutableList.of(
|
||||
new WebServerDispatcher.Resource.Builder()
|
||||
.setPath(RANGE_SUPPORTED_PATH)
|
||||
.setData(RANGE_SUPPORTED_DATA)
|
||||
.supportsRangeRequests(true)
|
||||
.build(),
|
||||
new WebServerDispatcher.Resource.Builder()
|
||||
.setPath(RANGE_SUPPORTED_LENGTH_UNKNOWN_PATH)
|
||||
.setData(RANGE_SUPPORTED_LENGTH_UNKNOWN_DATA)
|
||||
.supportsRangeRequests(true)
|
||||
.resolvesToUnknownLength(true)
|
||||
.build(),
|
||||
new WebServerDispatcher.Resource.Builder()
|
||||
.setPath(RANGE_UNSUPPORTED_PATH)
|
||||
.setData(RANGE_UNSUPPORTED_DATA)
|
||||
.supportsRangeRequests(false)
|
||||
.build(),
|
||||
new WebServerDispatcher.Resource.Builder()
|
||||
.setPath(RANGE_UNSUPPORTED_LENGTH_UNKNOWN_PATH)
|
||||
.setData(RANGE_UNSUPPORTED_LENGTH_UNKNOWN_DATA)
|
||||
.supportsRangeRequests(false)
|
||||
.resolvesToUnknownLength(true)
|
||||
.build())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_handlesConventionalRequest() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request = new Request.Builder().url(mockWebServer.url(RANGE_SUPPORTED_PATH)).build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(200);
|
||||
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(response.header("Content-Length")).isEqualTo("20");
|
||||
assertThat(response.header("Content-Range")).isNull();
|
||||
assertThat(response.body().bytes()).isEqualTo(RANGE_SUPPORTED_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_boundedRange() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_PATH))
|
||||
.addHeader("Range", "bytes=5-10")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(206);
|
||||
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(response.header("Content-Length")).isEqualTo("6");
|
||||
assertThat(response.header("Content-Range")).isEqualTo("bytes 5-10/20");
|
||||
assertThat(response.body().bytes())
|
||||
.isEqualTo(Arrays.copyOfRange(RANGE_SUPPORTED_DATA, 5, 11));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_startOnly() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_PATH))
|
||||
.addHeader("Range", "bytes=5-")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(206);
|
||||
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(response.header("Content-Length")).isEqualTo("15");
|
||||
assertThat(response.header("Content-Range")).isEqualTo("bytes 5-19/20");
|
||||
assertThat(response.body().bytes())
|
||||
.isEqualTo(Arrays.copyOfRange(RANGE_SUPPORTED_DATA, 5, 20));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_suffixBytes() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_PATH))
|
||||
.addHeader("Range", "bytes=-5")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(206);
|
||||
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(response.header("Content-Length")).isEqualTo("5");
|
||||
assertThat(response.header("Content-Range")).isEqualTo("bytes 15-19/20");
|
||||
assertThat(response.body().bytes())
|
||||
.isEqualTo(Arrays.copyOfRange(RANGE_SUPPORTED_DATA, 15, 20));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_truncatesBoundedRangeToLength() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_PATH))
|
||||
.addHeader("Range", "bytes=5-25")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(206);
|
||||
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(response.header("Content-Length")).isEqualTo("15");
|
||||
assertThat(response.header("Content-Range")).isEqualTo("bytes 5-19/20");
|
||||
assertThat(response.body().bytes())
|
||||
.isEqualTo(Arrays.copyOfRange(RANGE_SUPPORTED_DATA, 5, 20));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_truncatesSuffixToLength() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_PATH))
|
||||
.addHeader("Range", "bytes=-25")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(206);
|
||||
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(response.header("Content-Length")).isEqualTo("20");
|
||||
assertThat(response.header("Content-Range")).isEqualTo("bytes 0-19/20");
|
||||
assertThat(response.body().bytes()).isEqualTo(RANGE_SUPPORTED_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_rejectsStartAtResourceEnd() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_PATH))
|
||||
.addHeader("Range", "bytes=20-25")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(416);
|
||||
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(response.header("Content-Range")).isEqualTo("bytes */20");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_rejectsStartAfterResourceEnd() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_PATH))
|
||||
.addHeader("Range", "bytes=25-30")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(416);
|
||||
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(response.header("Content-Range")).isEqualTo("bytes */20");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_lengthUnknown_handlesConventionalRequest() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder().url(mockWebServer.url(RANGE_SUPPORTED_LENGTH_UNKNOWN_PATH)).build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(200);
|
||||
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(response.header("Content-Length")).isEmpty();
|
||||
assertThat(response.header("Content-Range")).isNull();
|
||||
assertThat(response.body().contentLength()).isEqualTo(-1);
|
||||
|
||||
// Calling ResponseBody#bytes() times out because Content-Length isn't set, so instead we
|
||||
// read exactly the number of bytes we expect.
|
||||
byte[] actualBytes = new byte[20];
|
||||
response.body().byteStream().read(actualBytes);
|
||||
assertThat(actualBytes).isEqualTo(RANGE_SUPPORTED_LENGTH_UNKNOWN_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_lengthUnknown_boundedRange() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_LENGTH_UNKNOWN_PATH))
|
||||
.addHeader("Range", "bytes=5-10")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(206);
|
||||
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(response.header("Content-Length")).isEqualTo("6");
|
||||
assertThat(response.header("Content-Range")).isEqualTo("bytes 5-10/*");
|
||||
assertThat(response.body().bytes())
|
||||
.isEqualTo(Arrays.copyOfRange(RANGE_SUPPORTED_LENGTH_UNKNOWN_DATA, 5, 11));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_lengthUnknown_startOnly() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_LENGTH_UNKNOWN_PATH))
|
||||
.addHeader("Range", "bytes=5-")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(206);
|
||||
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(response.header("Content-Length")).isEmpty();
|
||||
assertThat(response.header("Content-Range")).isEqualTo("bytes 5-19/*");
|
||||
assertThat(response.body().contentLength()).isEqualTo(-1);
|
||||
|
||||
// Calling ResponseBody#bytes() times out because Content-Length isn't set, so instead we
|
||||
// read exactly the number of bytes we expect.
|
||||
byte[] actualBytes = new byte[15];
|
||||
response.body().byteStream().read(actualBytes);
|
||||
assertThat(actualBytes)
|
||||
.isEqualTo(Arrays.copyOfRange(RANGE_SUPPORTED_LENGTH_UNKNOWN_DATA, 5, 20));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_lengthUnknown_truncatesBoundedRangeToLength()
|
||||
throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_LENGTH_UNKNOWN_PATH))
|
||||
.addHeader("Range", "bytes=5-25")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(206);
|
||||
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(response.header("Content-Length")).isEqualTo("15");
|
||||
assertThat(response.header("Content-Range")).isEqualTo("bytes 5-19/*");
|
||||
assertThat(response.body().bytes())
|
||||
.isEqualTo(Arrays.copyOfRange(RANGE_SUPPORTED_LENGTH_UNKNOWN_DATA, 5, 20));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_lengthUnknown_rejectsSuffixRange() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_LENGTH_UNKNOWN_PATH))
|
||||
.addHeader("Range", "bytes=-5")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(416);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_rejectsRangeWithEndBeforeStart() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_PATH))
|
||||
.addHeader("Range", "bytes=15-10")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(416);
|
||||
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(response.header("Content-Range")).isEqualTo("bytes */20");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_rejectsNonByteRange() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_PATH))
|
||||
.addHeader("Range", "seconds=5-10")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(416);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_rejectsMultipartRange() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_PATH))
|
||||
.addHeader("Range", "bytes=2-6,10-12")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(416);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsSupported_rejectsMalformedRange() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_SUPPORTED_PATH))
|
||||
.addHeader("Range", "bytes=foo")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(416);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsUnsupported_conventionalRequestWorksAsExpected() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request = new Request.Builder().url(mockWebServer.url(RANGE_UNSUPPORTED_PATH)).build();
|
||||
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(200);
|
||||
assertThat(response.header("Accept-Ranges")).isNull();
|
||||
assertThat(response.header("Content-Length")).isEqualTo("20");
|
||||
assertThat(response.header("Content-Range")).isNull();
|
||||
assertThat(response.body().bytes()).isEqualTo(RANGE_UNSUPPORTED_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeRequestsUnsupported_rangeRequestHeadersIgnored() throws Exception {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request =
|
||||
new Request.Builder()
|
||||
.url(mockWebServer.url(RANGE_UNSUPPORTED_PATH))
|
||||
.addHeader("Range", "bytes=5-10")
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(200);
|
||||
assertThat(response.header("Accept-Ranges")).isNull();
|
||||
assertThat(response.header("Content-Length")).isEqualTo("20");
|
||||
assertThat(response.header("Content-Range")).isNull();
|
||||
assertThat(response.body().bytes()).isEqualTo(RANGE_UNSUPPORTED_DATA);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue