mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion
|
||||||
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
|
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
|
||||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||||
|
implementation 'com.squareup.okhttp3:mockwebserver:' + mockWebServerVersion
|
||||||
implementation project(modulePrefix + 'library-core')
|
implementation project(modulePrefix + 'library-core')
|
||||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
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