Propagate updates of default header fields of the HttpDataSource.BaseFactory to HttpDataSource instances.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=149780233
This commit is contained in:
bachinger 2017-03-10 11:38:51 -08:00 committed by Oliver Woodman
parent 952bde700b
commit aede0f894d
9 changed files with 204 additions and 100 deletions

View File

@ -118,7 +118,8 @@ public final class CronetDataSourceTest {
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
mockClock));
mockClock,
null));
when(mockContentTypePredicate.evaluate(anyString())).thenReturn(true);
when(mockCronetEngine.newUrlRequestBuilder(
anyString(), any(UrlRequest.Callback.class), any(Executor.class)))

View File

@ -32,7 +32,6 @@ import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@ -98,7 +97,8 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
private final int connectTimeoutMs;
private final int readTimeoutMs;
private final boolean resetTimeoutOnRedirects;
private final Map<String, String> requestProperties;
private final RequestProperties defaultRequestProperties;
private final RequestProperties requestProperties;
private final ConditionVariable operation;
private final Clock clock;
@ -136,7 +136,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
public CronetDataSource(CronetEngine cronetEngine, Executor executor,
Predicate<String> contentTypePredicate, TransferListener<? super CronetDataSource> listener) {
this(cronetEngine, executor, contentTypePredicate, listener, DEFAULT_CONNECT_TIMEOUT_MILLIS,
DEFAULT_READ_TIMEOUT_MILLIS, false);
DEFAULT_READ_TIMEOUT_MILLIS, false, null);
}
/**
@ -149,17 +149,20 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
* @param connectTimeoutMs The connection timeout, in milliseconds.
* @param readTimeoutMs The read timeout, in milliseconds.
* @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs.
* @param defaultRequestProperties The default request properties to be used.
*/
public CronetDataSource(CronetEngine cronetEngine, Executor executor,
Predicate<String> contentTypePredicate, TransferListener<? super CronetDataSource> listener,
int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects) {
int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects,
RequestProperties defaultRequestProperties) {
this(cronetEngine, executor, contentTypePredicate, listener, connectTimeoutMs,
readTimeoutMs, resetTimeoutOnRedirects, new SystemClock());
readTimeoutMs, resetTimeoutOnRedirects, new SystemClock(), defaultRequestProperties);
}
/* package */ CronetDataSource(CronetEngine cronetEngine, Executor executor,
Predicate<String> contentTypePredicate, TransferListener<? super CronetDataSource> listener,
int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects, Clock clock) {
int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects, Clock clock,
RequestProperties defaultRequestProperties) {
this.cronetEngine = Assertions.checkNotNull(cronetEngine);
this.executor = Assertions.checkNotNull(executor);
this.contentTypePredicate = contentTypePredicate;
@ -168,7 +171,8 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
this.readTimeoutMs = readTimeoutMs;
this.resetTimeoutOnRedirects = resetTimeoutOnRedirects;
this.clock = Assertions.checkNotNull(clock);
requestProperties = new HashMap<>();
this.defaultRequestProperties = defaultRequestProperties;
requestProperties = new RequestProperties();
operation = new ConditionVariable();
}
@ -176,23 +180,17 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
@Override
public void setRequestProperty(String name, String value) {
synchronized (requestProperties) {
requestProperties.put(name, value);
}
requestProperties.set(name, value);
}
@Override
public void clearRequestProperty(String name) {
synchronized (requestProperties) {
requestProperties.remove(name);
}
requestProperties.remove(name);
}
@Override
public void clearAllRequestProperties() {
synchronized (requestProperties) {
requestProperties.clear();
}
requestProperties.clear();
}
@Override
@ -421,16 +419,24 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
UrlRequest.Builder requestBuilder = cronetEngine.newUrlRequestBuilder(dataSpec.uri.toString(),
this, executor);
// Set the headers.
synchronized (requestProperties) {
if (dataSpec.postBody != null && dataSpec.postBody.length != 0
&& !requestProperties.containsKey(CONTENT_TYPE)) {
throw new OpenException("POST request with non-empty body must set Content-Type", dataSpec,
Status.IDLE);
}
for (Entry<String, String> headerEntry : requestProperties.entrySet()) {
requestBuilder.addHeader(headerEntry.getKey(), headerEntry.getValue());
boolean isContentTypeHeaderSet = false;
if (defaultRequestProperties != null) {
for (Entry<String, String> headerEntry : defaultRequestProperties.getSnapshot().entrySet()) {
String key = headerEntry.getKey();
isContentTypeHeaderSet = isContentTypeHeaderSet || CONTENT_TYPE.equals(key);
requestBuilder.addHeader(key, headerEntry.getValue());
}
}
Map<String, String> requestPropertiesSnapshot = requestProperties.getSnapshot();
for (Entry<String, String> headerEntry : requestPropertiesSnapshot.entrySet()) {
String key = headerEntry.getKey();
isContentTypeHeaderSet = isContentTypeHeaderSet || CONTENT_TYPE.equals(key);
requestBuilder.addHeader(key, headerEntry.getValue());
}
if (dataSpec.postBody != null && dataSpec.postBody.length != 0 && !isContentTypeHeaderSet) {
throw new OpenException("POST request with non-empty body must set Content-Type", dataSpec,
Status.IDLE);
}
// Set the Range header.
if (currentDataSpec.position != 0 || currentDataSpec.length != C.LENGTH_UNSET) {
StringBuilder rangeValue = new StringBuilder();

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.ext.cronet;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource.Factory;
import com.google.android.exoplayer2.upstream.TransferListener;
@ -68,9 +69,10 @@ public final class CronetDataSourceFactory extends BaseFactory {
}
@Override
protected CronetDataSource createDataSourceInternal() {
protected CronetDataSource createDataSourceInternal(HttpDataSource.RequestProperties
defaultRequestProperties) {
return new CronetDataSource(cronetEngine, executor, contentTypePredicate, transferListener,
connectTimeoutMs, readTimeoutMs, resetTimeoutOnRedirects);
connectTimeoutMs, readTimeoutMs, resetTimeoutOnRedirects, null, defaultRequestProperties);
}
}

View File

@ -27,7 +27,6 @@ import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
@ -51,7 +50,8 @@ public class OkHttpDataSource implements HttpDataSource {
private final Predicate<String> contentTypePredicate;
private final TransferListener<? super OkHttpDataSource> listener;
private final CacheControl cacheControl;
private final HashMap<String, String> requestProperties;
private final RequestProperties defaultRequestProperties;
private final RequestProperties requestProperties;
private DataSpec dataSpec;
private Response response;
@ -87,7 +87,7 @@ public class OkHttpDataSource implements HttpDataSource {
*/
public OkHttpDataSource(Call.Factory callFactory, String userAgent,
Predicate<String> contentTypePredicate, TransferListener<? super OkHttpDataSource> listener) {
this(callFactory, userAgent, contentTypePredicate, listener, null);
this(callFactory, userAgent, contentTypePredicate, listener, null, null);
}
/**
@ -99,16 +99,19 @@ public class OkHttpDataSource implements HttpDataSource {
* {@link #open(DataSpec)}.
* @param listener An optional listener.
* @param cacheControl An optional {@link CacheControl} for setting the Cache-Control header.
* @param defaultRequestProperties The optional default {@link RequestProperties} to be sent to
* the server as HTTP headers on every request.
*/
public OkHttpDataSource(Call.Factory callFactory, String userAgent,
Predicate<String> contentTypePredicate, TransferListener<? super OkHttpDataSource> listener,
CacheControl cacheControl) {
CacheControl cacheControl, RequestProperties defaultRequestProperties) {
this.callFactory = Assertions.checkNotNull(callFactory);
this.userAgent = Assertions.checkNotEmpty(userAgent);
this.contentTypePredicate = contentTypePredicate;
this.listener = listener;
this.cacheControl = cacheControl;
this.requestProperties = new HashMap<>();
this.defaultRequestProperties = defaultRequestProperties;
this.requestProperties = new RequestProperties();
}
@Override
@ -125,24 +128,18 @@ public class OkHttpDataSource implements HttpDataSource {
public void setRequestProperty(String name, String value) {
Assertions.checkNotNull(name);
Assertions.checkNotNull(value);
synchronized (requestProperties) {
requestProperties.put(name, value);
}
requestProperties.set(name, value);
}
@Override
public void clearRequestProperty(String name) {
Assertions.checkNotNull(name);
synchronized (requestProperties) {
requestProperties.remove(name);
}
requestProperties.remove(name);
}
@Override
public void clearAllRequestProperties() {
synchronized (requestProperties) {
requestProperties.clear();
}
requestProperties.clear();
}
@Override
@ -268,11 +265,14 @@ public class OkHttpDataSource implements HttpDataSource {
if (cacheControl != null) {
builder.cacheControl(cacheControl);
}
synchronized (requestProperties) {
for (Map.Entry<String, String> property : requestProperties.entrySet()) {
builder.addHeader(property.getKey(), property.getValue());
if (defaultRequestProperties != null) {
for (Map.Entry<String, String> property : defaultRequestProperties.getSnapshot().entrySet()) {
builder.header(property.getKey(), property.getValue());
}
}
for (Map.Entry<String, String> property : requestProperties.getSnapshot().entrySet()) {
builder.header(property.getKey(), property.getValue());
}
if (!(position == 0 && length == C.LENGTH_UNSET)) {
String rangeRequest = "bytes=" + position + "-";
if (length != C.LENGTH_UNSET) {

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.ext.okhttp;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource.Factory;
import com.google.android.exoplayer2.upstream.TransferListener;
@ -59,8 +60,10 @@ public final class OkHttpDataSourceFactory extends BaseFactory {
}
@Override
protected OkHttpDataSource createDataSourceInternal() {
return new OkHttpDataSource(callFactory, userAgent, null, listener, cacheControl);
protected OkHttpDataSource createDataSourceInternal(
HttpDataSource.RequestProperties defaultRequestProperties) {
return new OkHttpDataSource(callFactory, userAgent, null, listener, cacheControl,
defaultRequestProperties);
}
}

View File

@ -81,7 +81,7 @@ public final class DefaultDataSource implements DataSource {
boolean allowCrossProtocolRedirects) {
this(context, listener,
new DefaultHttpDataSource(userAgent, null, listener, connectTimeoutMillis,
readTimeoutMillis, allowCrossProtocolRedirects));
readTimeoutMillis, allowCrossProtocolRedirects, null));
}
/**

View File

@ -32,7 +32,6 @@ import java.net.HttpURLConnection;
import java.net.NoRouteToHostException;
import java.net.ProtocolException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
@ -44,8 +43,8 @@ import java.util.regex.Pattern;
* <p>
* By default this implementation will not follow cross-protocol redirects (i.e. redirects from
* HTTP to HTTPS or vice versa). Cross-protocol redirects can be enabled by using the
* {@link #DefaultHttpDataSource(String, Predicate, TransferListener, int, int, boolean)}
* constructor and passing {@code true} as the final argument.
* {@link #DefaultHttpDataSource(String, Predicate, TransferListener, int, int, boolean,
* RequestProperties)} constructor and passing {@code true} as the second last argument.
*/
public class DefaultHttpDataSource implements HttpDataSource {
@ -70,7 +69,8 @@ public class DefaultHttpDataSource implements HttpDataSource {
private final int readTimeoutMillis;
private final String userAgent;
private final Predicate<String> contentTypePredicate;
private final HashMap<String, String> requestProperties;
private final RequestProperties defaultRequestProperties;
private final RequestProperties requestProperties;
private final TransferListener<? super DefaultHttpDataSource> listener;
private DataSpec dataSpec;
@ -121,7 +121,8 @@ public class DefaultHttpDataSource implements HttpDataSource {
public DefaultHttpDataSource(String userAgent, Predicate<String> contentTypePredicate,
TransferListener<? super DefaultHttpDataSource> listener, int connectTimeoutMillis,
int readTimeoutMillis) {
this(userAgent, contentTypePredicate, listener, connectTimeoutMillis, readTimeoutMillis, false);
this(userAgent, contentTypePredicate, listener, connectTimeoutMillis, readTimeoutMillis, false,
null);
}
/**
@ -137,17 +138,21 @@ public class DefaultHttpDataSource implements HttpDataSource {
* as an infinite timeout. Pass {@link #DEFAULT_READ_TIMEOUT_MILLIS} to use the default value.
* @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
* to HTTPS and vice versa) are enabled.
* @param defaultRequestProperties The default request properties to be sent to the server as
* HTTP headers or {@code null} if not required.
*/
public DefaultHttpDataSource(String userAgent, Predicate<String> contentTypePredicate,
TransferListener<? super DefaultHttpDataSource> listener, int connectTimeoutMillis,
int readTimeoutMillis, boolean allowCrossProtocolRedirects) {
int readTimeoutMillis, boolean allowCrossProtocolRedirects,
RequestProperties defaultRequestProperties) {
this.userAgent = Assertions.checkNotEmpty(userAgent);
this.contentTypePredicate = contentTypePredicate;
this.listener = listener;
this.requestProperties = new HashMap<>();
this.requestProperties = new RequestProperties();
this.connectTimeoutMillis = connectTimeoutMillis;
this.readTimeoutMillis = readTimeoutMillis;
this.allowCrossProtocolRedirects = allowCrossProtocolRedirects;
this.defaultRequestProperties = defaultRequestProperties;
}
@Override
@ -164,24 +169,18 @@ public class DefaultHttpDataSource implements HttpDataSource {
public void setRequestProperty(String name, String value) {
Assertions.checkNotNull(name);
Assertions.checkNotNull(value);
synchronized (requestProperties) {
requestProperties.put(name, value);
}
requestProperties.set(name, value);
}
@Override
public void clearRequestProperty(String name) {
Assertions.checkNotNull(name);
synchronized (requestProperties) {
requestProperties.remove(name);
}
requestProperties.remove(name);
}
@Override
public void clearAllRequestProperties() {
synchronized (requestProperties) {
requestProperties.clear();
}
requestProperties.clear();
}
@Override
@ -394,11 +393,14 @@ public class DefaultHttpDataSource implements HttpDataSource {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(connectTimeoutMillis);
connection.setReadTimeout(readTimeoutMillis);
synchronized (requestProperties) {
for (Map.Entry<String, String> property : requestProperties.entrySet()) {
if (defaultRequestProperties != null) {
for (Map.Entry<String, String> property : defaultRequestProperties.getSnapshot().entrySet()) {
connection.setRequestProperty(property.getKey(), property.getValue());
}
}
for (Map.Entry<String, String> property : requestProperties.getSnapshot().entrySet()) {
connection.setRequestProperty(property.getKey(), property.getValue());
}
if (!(position == 0 && length == C.LENGTH_UNSET)) {
String rangeRequest = "bytes=" + position + "-";
if (length != C.LENGTH_UNSET) {

View File

@ -76,9 +76,10 @@ public final class DefaultHttpDataSourceFactory extends BaseFactory {
}
@Override
protected DefaultHttpDataSource createDataSourceInternal() {
protected DefaultHttpDataSource createDataSourceInternal(
HttpDataSource.RequestProperties defaultRequestProperties) {
return new DefaultHttpDataSource(userAgent, null, listener, connectTimeoutMillis,
readTimeoutMillis, allowCrossProtocolRedirects);
readTimeoutMillis, allowCrossProtocolRedirects, getDefaultRequestProperties());
}
}

View File

@ -17,12 +17,12 @@ package com.google.android.exoplayer2.upstream;
import android.support.annotation.IntDef;
import android.text.TextUtils;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Predicate;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -41,84 +41,173 @@ public interface HttpDataSource extends DataSource {
HttpDataSource createDataSource();
/**
* Sets a default request header for {@link HttpDataSource} instances subsequently created by
* the factory. Previously created instances are not affected.
* Gets the default request properties used by all {@link HttpDataSource}s created by the
* factory. Changes to the properties will be reflected in any future requests made by
* {@link HttpDataSource}s created by the factory.
*
* @return The default request properties of the factory.
*/
RequestProperties getDefaultRequestProperties();
/**
* Sets a default request header for {@link HttpDataSource} instances created by the factory.
*
* @deprecated Use {@link #getDefaultRequestProperties} instead.
* @param name The name of the header field.
* @param value The value of the field.
*/
@Deprecated
void setDefaultRequestProperty(String name, String value);
/**
* Clears a default request header for {@link HttpDataSource} instances subsequently created by
* the factory. Previously created instances are not affected.
* Clears a default request header for {@link HttpDataSource} instances created by the factory.
*
* @deprecated Use {@link #getDefaultRequestProperties} instead.
* @param name The name of the header field.
*/
@Deprecated
void clearDefaultRequestProperty(String name);
/**
* Clears all default request header for all {@link HttpDataSource} instances subsequently
* created by the factory. Previously created instances are not affected.
* Clears all default request headers for all {@link HttpDataSource} instances created by the
* factory.
*
* @deprecated Use {@link #getDefaultRequestProperties} instead.
*/
@Deprecated
void clearAllDefaultRequestProperties();
}
/**
* Stores HTTP request properties (aka HTTP headers) and provides methods to modify the headers
* in a thread safe way to avoid the potential of creating snapshots of an inconsistent or
* unintended state.
*/
final class RequestProperties {
private final Map<String, String> requestProperties;
private Map<String, String> requestPropertiesSnapshot;
public RequestProperties() {
requestProperties = new HashMap<>();
}
/**
* Sets the specified property {@code value} for the specified {@code name}. If a property for
* this name previously existed, the old value is replaced by the specified value.
*
* @param name The name of the request property.
* @param value The value of the request property.
*/
public synchronized void set(String name, String value) {
requestPropertiesSnapshot = null;
requestProperties.put(name, value);
}
/**
* Sets the keys and values contained in the map. If a property previously existed, the old
* value is replaced by the specified value. If a property previously existed and is not in the
* map, the property is left unchanged.
*
* @param properties The request properties.
*/
public synchronized void set(Map<String, String> properties) {
requestPropertiesSnapshot = null;
requestProperties.putAll(properties);
}
/**
* Removes all properties previously existing and sets the keys and values of the map.
*
* @param properties The request properties.
*/
public synchronized void clearAndSet(Map<String, String> properties) {
requestPropertiesSnapshot = null;
requestProperties.clear();
requestProperties.putAll(properties);
}
/**
* Removes a request property by name.
*
* @param name The name of the request property to remove.
*/
public synchronized void remove(String name) {
requestPropertiesSnapshot = null;
requestProperties.remove(name);
}
/**
* Clears all request properties.
*/
public synchronized void clear() {
requestPropertiesSnapshot = null;
requestProperties.clear();
}
/**
* Gets a snapshot of the request properties.
*
* @return A snapshot of the request properties.
*/
public synchronized Map<String, String> getSnapshot() {
if (requestPropertiesSnapshot == null) {
requestPropertiesSnapshot = Collections.unmodifiableMap(new HashMap<>(requestProperties));
}
return requestPropertiesSnapshot;
}
}
/**
* Base implementation of {@link Factory} that sets default request properties.
*/
abstract class BaseFactory implements Factory {
private final HashMap<String, String> requestProperties;
private final RequestProperties defaultRequestProperties;
public BaseFactory() {
requestProperties = new HashMap<>();
defaultRequestProperties = new RequestProperties();
}
@Override
public final HttpDataSource createDataSource() {
HttpDataSource dataSource = createDataSourceInternal();
synchronized (requestProperties) {
for (Map.Entry<String, String> property : requestProperties.entrySet()) {
dataSource.setRequestProperty(property.getKey(), property.getValue());
}
}
return dataSource;
return createDataSourceInternal(defaultRequestProperties);
}
@Override
public RequestProperties getDefaultRequestProperties() {
return defaultRequestProperties;
}
@Deprecated
@Override
public final void setDefaultRequestProperty(String name, String value) {
Assertions.checkNotNull(name);
Assertions.checkNotNull(value);
synchronized (requestProperties) {
requestProperties.put(name, value);
}
defaultRequestProperties.set(name, value);
}
@Deprecated
@Override
public final void clearDefaultRequestProperty(String name) {
Assertions.checkNotNull(name);
synchronized (requestProperties) {
requestProperties.remove(name);
}
defaultRequestProperties.remove(name);
}
@Deprecated
@Override
public final void clearAllDefaultRequestProperties() {
synchronized (requestProperties) {
requestProperties.clear();
}
defaultRequestProperties.clear();
}
/**
* Called by {@link #createDataSource()} to create a {@link HttpDataSource} instance without
* default request properties set. Default request properties will be set by
* {@link #createDataSource()} before the instance is returned.
* Called by {@link #createDataSource()} to create a {@link HttpDataSource} instance.
*
* @return A {@link HttpDataSource} instance without default request properties set.
* @param defaultRequestProperties The default {@code RequestProperties} to be used by the
* {@link HttpDataSource} instance.
* @return A {@link HttpDataSource} instance.
*/
protected abstract HttpDataSource createDataSourceInternal();
protected abstract HttpDataSource createDataSourceInternal(RequestProperties
defaultRequestProperties);
}