// Copyright 2015 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.net; import android.content.Context; import android.net.http.HttpResponseCache; import android.util.Log; import androidx.annotation.VisibleForTesting; import java.io.IOException; import java.lang.reflect.Method; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandlerFactory; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; import javax.net.ssl.HttpsURLConnection; /** * An engine to process {@link UrlRequest}s, which uses the best HTTP stack available on the current * platform. An instance of this class can be created using {@link Builder}. */ public abstract class CronetEngine { private static final String TAG = CronetEngine.class.getSimpleName(); /** The value of the active request count is unknown */ public static final int ACTIVE_REQUEST_COUNT_UNKNOWN = -1; /** The value of a connection metric is unknown. */ public static final int CONNECTION_METRIC_UNKNOWN = -1; /** * The estimate of the effective connection type is unknown. * * @see #getEffectiveConnectionType */ public static final int EFFECTIVE_CONNECTION_TYPE_UNKNOWN = 0; /** * The device is offline. * * @see #getEffectiveConnectionType */ public static final int EFFECTIVE_CONNECTION_TYPE_OFFLINE = 1; /** * The estimate of the effective connection type is slow 2G. * * @see #getEffectiveConnectionType */ public static final int EFFECTIVE_CONNECTION_TYPE_SLOW_2G = 2; /** * The estimate of the effective connection type is 2G. * * @see #getEffectiveConnectionType */ public static final int EFFECTIVE_CONNECTION_TYPE_2G = 3; /** * The estimate of the effective connection type is 3G. * * @see #getEffectiveConnectionType */ public static final int EFFECTIVE_CONNECTION_TYPE_3G = 4; /** * The estimate of the effective connection type is 4G. * * @see #getEffectiveConnectionType */ public static final int EFFECTIVE_CONNECTION_TYPE_4G = 5; /** The value to be used to undo any previous network binding. */ public static final long UNBIND_NETWORK_HANDLE = -1; /** * A builder for {@link CronetEngine}s, which allows runtime configuration of {@code * CronetEngine}. Configuration options are set on the builder and then {@link #build} is called * to create the {@code CronetEngine}. */ // NOTE(kapishnikov): In order to avoid breaking the existing API clients, all future methods // added to this class and other API classes must have default implementation. public static class Builder { private static final String TAG = "CronetEngine.Builder"; /** * A class which provides a method for loading the cronet native library. Apps needing to * implement custom library loading logic can inherit from this class and pass an instance * to * {@link CronetEngine.Builder#setLibraryLoader}. For example, this might be required to * work around {@code UnsatisfiedLinkError}s caused by flaky installation on certain older * devices. */ public abstract static class LibraryLoader { /** * Loads the native library. * * @param libName name of the library to load */ public abstract void loadLibrary(String libName); } /** Reference to the actual builder implementation. {@hide exclude from JavaDoc}. */ protected final ICronetEngineBuilder mBuilderDelegate; /** * Constructs a {@link Builder} object that facilitates creating a {@link CronetEngine}. The * default configuration enables HTTP/2 and QUIC, but disables the HTTP cache. * * @param context Android {@link Context}, which is used by {@link Builder} to retrieve the * application context. A reference to only the application context will be kept, so as to * avoid extending the lifetime of {@code context} unnecessarily. */ public Builder(Context context) { this(createBuilderDelegate(context)); } /** * Constructs {@link Builder} with a given delegate that provides the actual implementation * of the {@code Builder} methods. This constructor is used only by the internal * implementation. * * @param builderDelegate delegate that provides the actual implementation. *
{@hide} */ public Builder(ICronetEngineBuilder builderDelegate) { if (builderDelegate instanceof ExperimentalOptionsTranslatingCronetEngineBuilder) { // Already wrapped at the top level, no need to do it again mBuilderDelegate = builderDelegate; } else { mBuilderDelegate = new ExperimentalOptionsTranslatingCronetEngineBuilder(builderDelegate); } } /** * Constructs a User-Agent string including application name and version, system build * version, model and id, and Cronet version. * * @return User-Agent string. */ public String getDefaultUserAgent() { return mBuilderDelegate.getDefaultUserAgent(); } /** * Overrides the User-Agent header for all requests. An explicitly set User-Agent header * (set using {@link UrlRequest.Builder#addHeader}) will override a value set using this * function. * * @param userAgent the User-Agent string to use for all requests. * @return the builder to facilitate chaining. */ public Builder setUserAgent(String userAgent) { mBuilderDelegate.setUserAgent(userAgent); return this; } /** * Sets directory for HTTP Cache and Cookie Storage. The directory must exist. * *
NOTE: Do not use the same storage directory with more than one {@code * CronetEngine} at a time. Access to the storage directory does not support concurrent * access by multiple {@code CronetEngine}s. * * @param value path to existing directory. * @return the builder to facilitate chaining. */ public Builder setStoragePath(String value) { mBuilderDelegate.setStoragePath(value); return this; } /** * Sets a {@link LibraryLoader} to be used to load the native library. If not set, the * library will be loaded using {@link System#loadLibrary}. * * @param loader {@code LibraryLoader} to be used to load the native library. * @return the builder to facilitate chaining. */ public Builder setLibraryLoader(LibraryLoader loader) { mBuilderDelegate.setLibraryLoader(loader); return this; } /** * Sets whether QUIC protocol is enabled. * Defaults to enabled. If QUIC is enabled, then QUIC User Agent Id containing application * name and Cronet version is sent to the server. * * @param value {@code true} to enable QUIC, {@code false} to disable. * @return the builder to facilitate chaining. */ public Builder enableQuic(boolean value) { mBuilderDelegate.enableQuic(value); return this; } /** * Sets whether HTTP/2 protocol is * enabled. Defaults to enabled. * * @param value {@code true} to enable HTTP/2, {@code false} to disable. * @return the builder to facilitate chaining. */ public Builder enableHttp2(boolean value) { mBuilderDelegate.enableHttp2(value); return this; } /** * @deprecated SDCH is deprecated in Cronet M63. This method is a no-op. {@hide exclude from * JavaDoc}. */ @Deprecated public Builder enableSdch(boolean value) { return this; } /** * Sets whether Brotli compression is * enabled. If enabled, Brotli will be advertised in Accept-Encoding request headers. * Defaults to disabled. * * @param value {@code true} to enable Brotli, {@code false} to disable. * @return the builder to facilitate chaining. */ public Builder enableBrotli(boolean value) { mBuilderDelegate.enableBrotli(value); return this; } /** * Setting to disable HTTP cache. Some data may still be temporarily stored in memory. * Passed to * {@link #enableHttpCache}. */ public static final int HTTP_CACHE_DISABLED = 0; /** * Setting to enable in-memory HTTP cache, including HTTP data. Passed to {@link * #enableHttpCache}. */ public static final int HTTP_CACHE_IN_MEMORY = 1; /** * Setting to enable on-disk cache, excluding HTTP data. {@link #setStoragePath} must be * called prior to passing this constant to {@link #enableHttpCache}. */ public static final int HTTP_CACHE_DISK_NO_HTTP = 2; /** * Setting to enable on-disk cache, including HTTP data. {@link #setStoragePath} must be * called prior to passing this constant to {@link #enableHttpCache}. */ public static final int HTTP_CACHE_DISK = 3; /** * Enables or disables caching of HTTP data and other information like QUIC server * information. * * @param cacheMode control location and type of cached data. Must be one of {@link * #HTTP_CACHE_DISABLED HTTP_CACHE_*}. * @param maxSize maximum size in bytes used to cache data (advisory and maybe exceeded at * times). * @return the builder to facilitate chaining. */ public Builder enableHttpCache(int cacheMode, long maxSize) { mBuilderDelegate.enableHttpCache(cacheMode, maxSize); return this; } /** * Adds hint that {@code host} supports QUIC. Note that {@link #enableHttpCache * enableHttpCache} * ({@link #HTTP_CACHE_DISK}) is needed to take advantage of 0-RTT connection establishment * between sessions. * * @param host hostname of the server that supports QUIC. * @param port host of the server that supports QUIC. * @param alternatePort alternate port to use for QUIC. * @return the builder to facilitate chaining. */ public Builder addQuicHint(String host, int port, int alternatePort) { mBuilderDelegate.addQuicHint(host, port, alternatePort); return this; } /** * Pins a set of public keys for a given host. By pinning a set of public keys, {@code * pinsSha256}, communication with {@code hostName} is required to authenticate with a * certificate with a public key from the set of pinned ones. An app can pin the public key * of the root certificate, any of the intermediate certificates or the end-entry * certificate. Authentication will fail and secure communication will not be established if * none of the public keys is present in the host's certificate chain, even if the host * attempts to authenticate with a certificate allowed by the device's trusted store of * certificates. * *
Calling this method multiple times with the same host name overrides the previously * set pins for the host. * *
More information about the public key pinning can be found in RFC 7469.
*
* @param hostName name of the host to which the public keys should be pinned. A host that
* consists only of digits and the dot character is treated as invalid.
* @param pinsSha256 a set of pins. Each pin is the SHA-256 cryptographic hash of the
* DER-encoded ASN.1 representation of the Subject Public Key Info (SPKI) of the host's
* X.509 certificate. Use {@link java.security.cert.Certificate#getPublicKey()
* Certificate.getPublicKey()} and {@link java.security.Key#getEncoded() Key.getEncoded()}
* to obtain DER-encoded ASN.1 representation of the SPKI. Although, the method does not
* mandate the presence of the backup pin that can be used if the control of the primary
* private key has been lost, it is highly recommended to supply one.
* @param includeSubdomains indicates whether the pinning policy should be applied to
* subdomains
* of {@code hostName}.
* @param expirationDate specifies the expiration date for the pins.
* @return the builder to facilitate chaining.
* @throws NullPointerException if any of the input parameters are {@code null}.
* @throws IllegalArgumentException if the given host name is invalid or {@code pinsSha256}
* contains a byte array that does not represent a valid SHA-256 hash.
*/
public Builder addPublicKeyPins(
String hostName,
Set Only relevant if {@link #enableQuic(boolean)} is enabled.
*
* @return the builder to facilitate chaining.
*/
@QuicOptions.Experimental
public Builder setQuicOptions(QuicOptions quicOptions) {
mBuilderDelegate.setQuicOptions(quicOptions);
return this;
}
/** @see #setQuicOptions(QuicOptions) */
@QuicOptions.Experimental
public Builder setQuicOptions(QuicOptions.Builder quicOptionsBuilder) {
return setQuicOptions(quicOptionsBuilder.build());
}
/**
* Configures the behavior of hostname lookup. For more details, see documentation
* of {@link DnsOptions} and the individual methods of {@link DnsOptions.Builder}.
*
* Only relevant if {@link #enableQuic(boolean)} is enabled.
*
* @return the builder to facilitate chaining.
*/
@DnsOptions.Experimental
public Builder setDnsOptions(DnsOptions dnsOptions) {
mBuilderDelegate.setDnsOptions(dnsOptions);
return this;
}
/** @see #setDnsOptions(DnsOptions) */
@DnsOptions.Experimental
public Builder setDnsOptions(DnsOptions.Builder dnsOptions) {
return setDnsOptions(dnsOptions.build());
}
/**
* Configures the behavior of connection migration. For more details, see documentation
* of {@link ConnectionMigrationOptions} and the individual methods of {@link
* ConnectionMigrationOptions.Builder}.
*
* Only relevant if {@link #enableQuic(boolean)} is enabled.
*
* @return the builder to facilitate chaining.
*/
@ConnectionMigrationOptions.Experimental
public Builder setConnectionMigrationOptions(
ConnectionMigrationOptions connectionMigrationOptions) {
mBuilderDelegate.setConnectionMigrationOptions(connectionMigrationOptions);
return this;
}
/** @see #setConnectionMigrationOptions(ConnectionMigrationOptions) */
@ConnectionMigrationOptions.Experimental
public Builder setConnectionMigrationOptions(
ConnectionMigrationOptions.Builder connectionMigrationOptionsBuilder) {
return setConnectionMigrationOptions(connectionMigrationOptionsBuilder.build());
}
/**
* Build a {@link CronetEngine} using this builder's configuration.
*
* @return constructed {@link CronetEngine}.
*/
public CronetEngine build() {
int implLevel = getImplementationApiLevel();
if (implLevel != -1 && implLevel < getMaximumApiLevel()) {
Log.w(
TAG,
"The implementation version is lower than the API version. Calls to "
+ "methods added in API "
+ (implLevel + 1)
+ " and newer will "
+ "likely have no effect.");
}
return mBuilderDelegate.build();
}
/**
* Creates an implementation of {@link ICronetEngineBuilder} that can be used to delegate
* the builder calls to. The method uses {@link CronetProvider} to obtain the list of
* available providers.
*
* @param context Android Context to use.
* @return the created {@code ICronetEngineBuilder}.
*/
private static ICronetEngineBuilder createBuilderDelegate(Context context) {
List Cannot be called on network thread - the thread Cronet calls into Executor on (which is
* different from the thread the Executor invokes callbacks on). May block until all the {@code
* CronetEngine}'s resources have been cleaned up.
*/
public abstract void shutdown();
/**
* Starts NetLog logging to a file. The NetLog will contain events emitted by all live
* CronetEngines. The NetLog is useful for debugging. The file can be viewed using a Chrome
* browser navigated to chrome://net-internals/#import
*
* @param fileName the complete file path. It must not be empty. If the file exists, it is
* truncated before starting. If actively logging, this method is ignored.
* @param logAll {@code true} to include basic events, user cookies, credentials and all
* transferred bytes in the log. This option presents a privacy risk, since it exposes the
* user's credentials, and should only be used with the user's consent and in situations where
* the log won't be public. {@code false} to just include basic events.
*/
public abstract void startNetLogToFile(String fileName, boolean logAll);
/**
* Stops NetLog logging and flushes file to disk. If a logging session is not in progress, this
* call is ignored.
*/
public abstract void stopNetLog();
/**
* Returns differences in metrics collected by Cronet since the last call to this method.
*
* Cronet collects these metrics globally. This means deltas returned by {@code
* getGlobalMetricsDeltas()} will include measurements of requests processed by other {@link
* CronetEngine} instances. Since this function returns differences in metrics collected since
* the last call, and these metrics are collected globally, a call to any {@code CronetEngine}
* instance's {@code getGlobalMetricsDeltas()} method will affect the deltas returned by any
* other
* {@code CronetEngine} instance's {@code getGlobalMetricsDeltas()}.
*
* Cronet starts collecting these metrics after the first call to {@code
* getGlobalMetricsDeltras()}, so the first call returns no useful data as no metrics have yet
* been collected.
*
* @return differences in metrics collected by Cronet, since the last call to {@code
* getGlobalMetricsDeltas()}, serialized as a protobuf
* .
*/
public abstract byte[] getGlobalMetricsDeltas();
/**
* Establishes a new connection to the resource specified by the {@link URL} {@code url}.
*
* Note: Cronet's {@link java.net.HttpURLConnection} implementation is subject to
* certain limitations, see {@link #createURLStreamHandlerFactory} for details.
*
* @param url URL of resource to connect to.
* @return an {@link java.net.HttpURLConnection} instance implemented by this CronetEngine.
* @throws IOException if an error occurs while opening the connection.
*/
public abstract URLConnection openConnection(URL url) throws IOException;
/**
* Creates a {@link URLStreamHandlerFactory} to handle HTTP and HTTPS traffic. An instance of
* this class can be installed via {@link URL#setURLStreamHandlerFactory} thus using this
* CronetEngine by default for all requests created via {@link URL#openConnection}.
*
* Cronet does not use certain HTTP features provided via the system:
*
* While Cronet supports and encourages requests using the HTTPS protocol, Cronet does not
* provide support for the {@link HttpsURLConnection} API. This lack of support also includes
* not using certain HTTPS features provided via the system:
*
*
* A request becomes "active" in UrlRequest.start(), assuming that method
* does not throw an exception. It becomes inactive when all callbacks have
* returned and no additional callbacks can be triggered in the future. In
* practice, that means the request is inactive once
* onSucceeded/onCanceled/onFailed has returned and all request finished
* listeners have returned.
*
* Cronet
* requests's lifecycle for more information.
*/
public int getActiveRequestCount() {
return ACTIVE_REQUEST_COUNT_UNKNOWN;
}
/**
* Registers a listener that gets called after the end of each request with the request info.
*
* The listener is called on an {@link java.util.concurrent.Executor} provided by the
* listener.
*
* @param listener the listener for finished requests.
*/
public void addRequestFinishedListener(RequestFinishedInfo.Listener listener) {}
/**
* Removes a finished request listener.
*
* @param listener the listener to remove.
*/
public void removeRequestFinishedListener(RequestFinishedInfo.Listener listener) {}
/**
* Returns the HTTP RTT estimate (in milliseconds) computed by the network quality estimator.
* Set to {@link #CONNECTION_METRIC_UNKNOWN} if the value is unavailable. This must be called
* after
* {@link Builder#enableNetworkQualityEstimator}, and will throw an exception otherwise.
*
* @return Estimate of the HTTP RTT in milliseconds.
*/
public int getHttpRttMs() {
return CONNECTION_METRIC_UNKNOWN;
}
/**
* Returns the transport RTT estimate (in milliseconds) computed by the network quality
* estimator. Set to {@link #CONNECTION_METRIC_UNKNOWN} if the value is unavailable. This must
* be called after {@link Builder#enableNetworkQualityEstimator}, and will throw an exception
* otherwise.
*
* @return Estimate of the transport RTT in milliseconds.
*/
public int getTransportRttMs() {
return CONNECTION_METRIC_UNKNOWN;
}
/**
* Returns the downstream throughput estimate (in kilobits per second) computed by the network
* quality estimator. Set to {@link #CONNECTION_METRIC_UNKNOWN} if the value is unavailable.
* This must be called after {@link Builder#enableNetworkQualityEstimator}, and will throw an
* exception otherwise.
*
* @return Estimate of the downstream throughput in kilobits per second.
*/
public int getDownstreamThroughputKbps() {
return CONNECTION_METRIC_UNKNOWN;
}
/**
* Starts NetLog logging to a specified directory with a bounded size. The NetLog will contain
* events emitted by all live CronetEngines. The NetLog is useful for debugging. Once logging
* has stopped {@link #stopNetLog}, the data will be written to netlog.json in {@code dirPath}.
* If logging is interrupted, you can stitch the files found in .inprogress subdirectory
* manually using:
* https://chromium.googlesource.com/chromium/src/+/main/net/tools/stitch_net_log_files.py. The
* log can be viewed using a Chrome browser navigated to chrome://net-internals/#import.
*
* @param dirPath the directory where the netlog.json file will be created. dirPath must already
* exist. NetLog files must not exist in the directory. If actively logging, this method is
* ignored.
* @param logAll {@code true} to include basic events, user cookies, credentials and all
* transferred bytes in the log. This option presents a privacy risk, since it exposes the
* user's credentials, and should only be used with the user's consent and in situations where
* the log won't be public. {@code false} to just include basic events.
* @param maxSize the maximum total disk space in bytes that should be used by NetLog. Actual
* disk
* space usage may exceed this limit slightly.
*/
public void startNetLogToDisk(String dirPath, boolean logAll, int maxSize) {}
/**
* Binds the engine to the specified network handle. All requests created through this engine
* will use the network associated to this handle. If this network disconnects all requests will
* fail, the exact error will depend on the stage of request processing when the network
* disconnects. Network handles can be obtained through {@code Network#getNetworkHandle}. Only
* available starting from Android Marshmallow.
*
* @param networkHandle the network handle to bind the engine to. Specify {@link
* #UNBIND_NETWORK_HANDLE} to unbind.
*/
public void bindToNetwork(long networkHandle) {}
/**
* Returns an estimate of the effective connection type computed by the network quality
* estimator. Call {@link Builder#enableNetworkQualityEstimator} to begin computing this value.
*
* @return the estimated connection type. The returned value is one of {@link
* #EFFECTIVE_CONNECTION_TYPE_UNKNOWN EFFECTIVE_CONNECTION_TYPE_* }.
*/
public int getEffectiveConnectionType() {
return EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
}
/**
* Configures the network quality estimator for testing. This must be called before round trip
* time and throughput listeners are added, and after the network quality estimator has been
* enabled.
*
* @param useLocalHostRequests include requests to localhost in estimates.
* @param useSmallerResponses include small responses in throughput estimates.
* @param disableOfflineCheck when set to true, disables the device offline checks when
* computing
* the effective connection type or when writing the prefs.
*/
public void configureNetworkQualityEstimatorForTesting(
boolean useLocalHostRequests,
boolean useSmallerResponses,
boolean disableOfflineCheck) {}
/**
* Registers a listener that gets called whenever the network quality estimator witnesses a
* sample round trip time. This must be called after {@link
* Builder#enableNetworkQualityEstimator}, and with throw an exception otherwise. Round trip
* times may be recorded at various layers of the network stack, including TCP, QUIC, and at the
* URL request layer. The listener is called on the
* {@link java.util.concurrent.Executor} that is passed to {@link
* Builder#enableNetworkQualityEstimator}.
*
* @param listener the listener of round trip times.
*/
public void addRttListener(NetworkQualityRttListener listener) {}
/**
* Removes a listener of round trip times if previously registered with {@link #addRttListener}.
* This should be called after a {@link NetworkQualityRttListener} is added in order to stop
* receiving observations.
*
* @param listener the listener of round trip times.
*/
public void removeRttListener(NetworkQualityRttListener listener) {}
/**
* Registers a listener that gets called whenever the network quality estimator witnesses a
* sample throughput measurement. This must be called after {@link
* Builder#enableNetworkQualityEstimator}. Throughput observations are computed by measuring
* bytes read over the active network interface at times when at least one URL response is being
* received. The listener is called on the {@link java.util.concurrent.Executor} that is passed
* to {@link Builder#enableNetworkQualityEstimator}.
*
* @param listener the listener of throughput.
*/
public void addThroughputListener(NetworkQualityThroughputListener listener) {}
/**
* Removes a listener of throughput. This should be called after a {@link
* NetworkQualityThroughputListener} is added with {@link #addThroughputListener} in order to
* stop receiving observations.
*
* @param listener the listener of throughput.
*/
public void removeThroughputListener(NetworkQualityThroughputListener listener) {}
}
*
*
*
*
*
* @return an {@link URLStreamHandlerFactory} instance implemented by this CronetEngine.
*/
public abstract URLStreamHandlerFactory createURLStreamHandlerFactory();
/**
* Creates a builder for {@link UrlRequest}. All callbacks for generated {@link UrlRequest}
* objects will be invoked on {@code executor}'s threads. {@code executor} must not run tasks on
* the thread calling {@link Executor#execute} to prevent blocking networking operations and
* causing exceptions during shutdown.
*
* @param url URL for the generated requests.
* @param callback callback object that gets invoked on different events.
* @param executor {@link Executor} on which all callbacks will be invoked.
*/
public abstract UrlRequest.Builder newUrlRequestBuilder(
String url, UrlRequest.Callback callback, Executor executor);
/**
* Creates a builder for {@link BidirectionalStream} objects. All callbacks for generated {@code
* BidirectionalStream} objects will be invoked on {@code executor}. {@code executor} must not
* run tasks on the current thread, otherwise the networking operations may block and exceptions
* may be thrown at shutdown time.
*
* @param url URL for the generated streams.
* @param callback the {@link BidirectionalStream.Callback} object that gets invoked upon
* different events occurring.
* @param executor the {@link Executor} on which {@code callback} methods will be invoked.
* @return the created builder.
*
* {@hide}
*/
public BidirectionalStream.Builder newBidirectionalStreamBuilder(
String url, BidirectionalStream.Callback callback, Executor executor) {
throw new UnsupportedOperationException("Not implemented.");
}
/**
* Returns the number of active requests.
*