Add components

This commit is contained in:
klzgrad 2022-05-02 08:33:40 +08:00
parent c647d3d363
commit 7743b2c008
481 changed files with 89733 additions and 0 deletions

View File

@ -0,0 +1,271 @@
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/buildflag_header.gni")
import("//build/toolchain/toolchain.gni")
import("//build/util/lastchange.gni")
import("//build/util/process_version.gni")
import("//build/util/version.gni")
import("//components/cronet/native/include/headers.gni")
import("//components/grpc_support/include/headers.gni")
import("//testing/test.gni")
declare_args() {
# If set to true, this will remove histogram manager to reduce binary size.
disable_histogram_support = is_mac || is_win
}
# Disable histogram support is not allowed on Android.
assert(!disable_histogram_support || !is_android)
buildflag_header("cronet_buildflags") {
header = "cronet_buildflags.h"
flags = [ "DISABLE_HISTOGRAM_SUPPORT=$disable_histogram_support" ]
}
process_version("cronet_version_header") {
template_file = "//components/cronet/version.h.in"
sources = [ "//chrome/VERSION" ]
output = "$target_gen_dir/version.h"
extra_args = [
"-e",
"VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH)",
]
}
# Cronet common implementation.
source_set("cronet_common") {
sources = [
"cronet_global_state.h",
"cronet_prefs_manager.cc",
"cronet_prefs_manager.h",
"cronet_upload_data_stream.cc",
"cronet_upload_data_stream.h",
"cronet_url_request.cc",
"cronet_url_request.h",
"cronet_url_request_context.cc",
"cronet_url_request_context.h",
"host_cache_persistence_manager.cc",
"host_cache_persistence_manager.h",
"stale_host_resolver.cc",
"stale_host_resolver.h",
"url_request_context_config.cc",
"url_request_context_config.h",
]
deps = [
":cronet_buildflags",
":cronet_version_header",
"//base",
"//components/prefs:prefs",
"//net",
"//third_party/metrics_proto",
]
if (!disable_histogram_support) {
public_deps = [ "//components/metrics:library_support" ]
}
}
source_set("metrics_util") {
sources = [
"metrics_util.cc",
"metrics_util.h",
]
deps = [ "//base" ]
}
# Unit tests for Cronet common implementation.
source_set("cronet_common_unittests") {
testonly = true
deps = [
":cronet_common",
"//components/prefs:test_support",
"//net:test_support",
]
sources = [
"host_cache_persistence_manager_unittest.cc",
"stale_host_resolver_unittest.cc",
"url_request_context_config_unittest.cc",
]
}
# For platforms on which the native Cronet library is used, build the library,
# a cronet_tests binary that exercises it, and a unit-tests binary.
# Android and iOS have their own platform-specific rules to build Cronet.
if (is_android) {
group("cronet_package") {
testonly = true
deps = [ "//components/cronet/android:cronet_package_android" ]
}
} else if (is_ios) {
group("cronet_package") {
deps = [ "//components/cronet/ios:cronet_package_ios" ]
}
} else {
config("shared_library_public_config") {
if (is_mac && !is_component_build) {
# Executable targets that depend on the shared libraries below need to have
# the rpath setup in non-component build configurations.
ldflags = [ "-Wl,-rpath,@executable_path/" ]
}
}
_cronet_shared_lib_name = "cronet.$chrome_version_full"
_cronet_shared_lib_file_name =
"$shlib_prefix$_cronet_shared_lib_name$shlib_extension"
shared_library("cronet") {
output_name = _cronet_shared_lib_name
deps = [
"//base",
"//components/cronet:cronet_common",
"//components/cronet/native:cronet_native_impl",
"//net",
]
sources = [ "cronet_global_state_stubs.cc" ]
if (is_mac && !is_component_build) {
ldflags = [
"-install_name",
"@executable_path/$_cronet_shared_lib_file_name",
]
public_configs = [ ":shared_library_public_config" ]
}
}
test("cronet_tests") {
deps = [
":cronet_common",
"//base",
"//base/test:test_support",
"//components/cronet/native:cronet_native_impl",
"//components/cronet/native/test:cronet_native_tests",
"//net",
]
sources = [
"cronet_global_state_stubs.cc",
"run_all_unittests.cc",
]
defines = [ "CRONET_TESTS_IMPLEMENTATION" ]
if ((is_linux || is_chromeos) && !is_component_build) {
public_configs = [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
}
if (is_fuchsia) {
use_cfv2 = false
additional_manifest_fragments =
[ "//build/config/fuchsia/test/network_capabilities.test-cmx" ]
}
}
test("cronet_unittests") {
deps = [
":cronet_common",
":cronet_common_unittests",
"//base",
"//base/test:test_support",
"//components/cronet/native:cronet_native_unittests",
"//net",
]
sources = [
"cronet_global_state_stubs.cc",
"run_all_unittests.cc",
]
}
_package_dir = "$root_out_dir/cronet"
# Generate LICENSE file by recursively joining all dependent licenses.
action("generate_license") {
_license_path = "$_package_dir/LICENSE"
script = "//tools/licenses.py"
inputs = [ lastchange_file ]
outputs = [ _license_path ]
args = [
"license_file",
rebase_path(_license_path, root_build_dir),
"--gn-target",
"//components/cronet:cronet",
"--gn-out-dir",
".",
]
}
# Copy boiler-plate files into the package.
copy("cronet_package_copy") {
sources = [
"${root_out_dir}${shlib_subdir}/${_cronet_shared_lib_file_name}",
"//AUTHORS",
"//chrome/VERSION",
]
deps = [ ":cronet" ]
outputs = [ "$_package_dir/{{source_file_part}}" ]
}
# Copy headers.
copy("cronet_package_headers") {
sources = cronet_native_public_headers + grpc_public_headers
outputs = [ "$_package_dir/include/{{source_file_part}}" ]
}
group("cronet_package") {
deps = [
":cronet_package_copy",
":cronet_package_headers",
":generate_license",
]
}
executable("cronet_native_perf_test") {
testonly = true
sources = [
"native/perftest/main.cc",
"native/perftest/perf_test.cc",
]
deps = [
"//base",
"//components/cronet",
"//components/cronet/native:cronet_native_headers",
"//components/cronet/native/test:cronet_native_tests",
"//components/cronet/native/test:cronet_native_testutil",
"//net:test_support",
]
}
executable("cronet_sample") {
testonly = true
sources = [
"native/sample/main.cc",
"native/sample/sample_executor.cc",
"native/sample/sample_executor.h",
"native/sample/sample_url_request_callback.cc",
"native/sample/sample_url_request_callback.h",
]
deps = [
"//components/cronet",
"//components/cronet/native:cronet_native_headers",
]
if ((is_linux || is_chromeos) && !is_component_build) {
public_configs = [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
}
}
test("cronet_sample_test") {
sources = [ "native/sample/test/sample_test.cc" ]
deps = [
":cronet_sample",
"//testing/gtest:gtest",
]
}
}

View File

@ -0,0 +1,8 @@
include_rules = [
"+components/grpc_support",
"+components/metrics",
"+components/prefs",
"+net",
"+third_party/metrics_proto",
"+third_party/zlib",
]

View File

@ -0,0 +1,5 @@
monorail {
component: "Internals>Network>Library"
}
team_email: "net-dev@chromium.org"

View File

@ -0,0 +1,5 @@
cleborgne@google.com
danstahr@google.com
sporeba@google.com
torne@chromium.org
file://net/OWNERS

View File

@ -0,0 +1,106 @@
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Top-level presubmit script for src/components/cronet.
See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
for more details about the presubmit API built into depot_tools.
"""
import os
USE_PYTHON3 = True
def _PyLintChecks(input_api, output_api):
pylint_checks = input_api.canned_checks.GetPylint(input_api, output_api,
extra_paths_list=_GetPathsToPrepend(input_api), pylintrc='pylintrc')
return input_api.RunTests(pylint_checks)
def _GetPathsToPrepend(input_api):
current_dir = input_api.PresubmitLocalPath()
chromium_src_dir = input_api.os_path.join(current_dir, '..', '..')
return [
input_api.os_path.join(chromium_src_dir, 'components'),
input_api.os_path.join(chromium_src_dir, 'tools', 'perf'),
input_api.os_path.join(chromium_src_dir, 'build', 'android'),
input_api.os_path.join(chromium_src_dir, 'build', 'android', 'gyp'),
input_api.os_path.join(chromium_src_dir,
'mojo', 'public', 'tools', 'bindings', 'pylib'),
input_api.os_path.join(chromium_src_dir, 'net', 'tools', 'net_docs'),
input_api.os_path.join(chromium_src_dir, 'tools'),
input_api.os_path.join(chromium_src_dir, 'third_party'),
input_api.os_path.join(chromium_src_dir,
'third_party', 'catapult', 'telemetry'),
input_api.os_path.join(chromium_src_dir,
'third_party', 'catapult', 'devil'),
input_api.os_path.join(chromium_src_dir,
'third_party', 'catapult', 'common', 'py_utils'),
]
def _PackageChecks(input_api, output_api):
"""Verify API classes are in org.chromium.net package, and implementation
classes are not in org.chromium.net package."""
api_file_pattern = input_api.re.compile(
r'^components/cronet/android/api/.*\.(java|template)$')
impl_file_pattern = input_api.re.compile(
r'^components/cronet/android/java/.*\.(java|template)$')
api_package_pattern = input_api.re.compile(r'^package (?!org.chromium.net;)')
impl_package_pattern = input_api.re.compile(r'^package org.chromium.net;')
source_filter = lambda path: input_api.FilterSourceFile(path,
files_to_check=[r'^components/cronet/android/.*\.(java|template)$'])
problems = []
for f in input_api.AffectedSourceFiles(source_filter):
local_path = f.LocalPath()
for line_number, line in f.ChangedContents():
if (api_file_pattern.search(local_path)):
if (api_package_pattern.search(line)):
problems.append(
'%s:%d\n %s' % (local_path, line_number, line.strip()))
elif (impl_file_pattern.search(local_path)):
if (impl_package_pattern.search(line)):
problems.append(
'%s:%d\n %s' % (local_path, line_number, line.strip()))
if problems:
return [output_api.PresubmitError(
'API classes must be in org.chromium.net package, and implementation\n'
'classes must not be in org.chromium.net package.',
problems)]
else:
return []
def _RunToolsUnittests(input_api, output_api):
return input_api.canned_checks.RunUnitTestsInDirectory(
input_api, output_api,
'.',
[ r'^tools_unittest\.py$'],
run_on_python3=True)
def _ChangeAffectsCronetTools(change):
""" Returns |true| if the change may affect Cronet tools. """
for path in change.LocalPaths():
if path.startswith(os.path.join('components', 'cronet', 'tools')):
return True
return False
def CheckChangeOnUpload(input_api, output_api):
results = []
results.extend(_PyLintChecks(input_api, output_api))
results.extend(_PackageChecks(input_api, output_api))
if _ChangeAffectsCronetTools(input_api.change):
results.extend(_RunToolsUnittests(input_api, output_api))
return results
def CheckChangeOnCommit(input_api, output_api):
return _RunToolsUnittests(input_api, output_api)

View File

@ -0,0 +1,176 @@
# Quick Start Guide to Using Cronet
Cronet is the networking stack of Chromium put into a library for use on
mobile. This is the same networking stack that is used in the Chrome browser
by over a billion people. It offers an easy-to-use, high performance,
standards-compliant, and secure way to perform HTTP requests. Cronet has support
for both Android and iOS. On Android, Cronet offers its own Java asynchronous
API as well as support for the [java.net.HttpURLConnection] API.
This document gives a brief introduction to using these two Java APIs.
For instructions on checking out and building Cronet see
[Cronet build instructions](build_instructions.md).
Testing information is available on the [native
API](native/test_instructions.md) and [Android
API](android/test_instructions.md) pages.
### Basics
First you will need to extend `UrlRequest.Callback` to handle
events during the lifetime of a request. For example:
class MyCallback extends UrlRequest.Callback {
@Override
public void onRedirectReceived(UrlRequest request,
UrlResponseInfo responseInfo, String newLocationUrl) {
if (followRedirect) {
// Let's tell Cronet to follow the redirect!
request.followRedirect();
} else {
// Not worth following the redirect? Abandon the request.
request.cancel();
}
}
@Override
public void onResponseStarted(UrlRequest request,
UrlResponseInfo responseInfo) {
// Now we have response headers!
int httpStatusCode = responseInfo.getHttpStatusCode();
if (httpStatusCode == 200) {
// Success! Let's tell Cronet to read the response body.
request.read(myBuffer);
} else if (httpStatusCode == 503) {
// Do something. Note that 4XX and 5XX are not considered
// errors from Cronet's perspective since the response is
// successfully read.
}
mResponseHeaders = responseInfo.getAllHeaders();
}
@Override
public void onReadCompleted(UrlRequest request,
UrlResponseInfo responseInfo, ByteBuffer byteBuffer) {
// Response body is available.
doSomethingWithResponseData(byteBuffer);
// Let's tell Cronet to continue reading the response body or
// inform us that the response is complete!
request.read(mBuffer);
}
@Override
public void onSucceeded(UrlRequest request,
UrlResponseInfo responseInfo) {
// Request has completed successfully!
}
@Override
public void onFailed(UrlRequest request,
UrlResponseInfo responseInfo, CronetException error) {
// Request has failed. responseInfo might be null.
Log.e("MyCallback", "Request failed. " + error.getMessage());
// Maybe handle error here. Typical errors include hostname
// not resolved, connection to server refused, etc.
}
}
Make a request like this:
CronetEngine.Builder engineBuilder = new CronetEngine.Builder(getContext());
CronetEngine engine = engineBuilder.build();
Executor executor = Executors.newSingleThreadExecutor();
MyCallback callback = new MyCallback();
UrlRequest.Builder requestBuilder = engine.newUrlRequestBuilder(
"https://www.example.com", callback, executor);
UrlRequest request = requestBuilder.build();
request.start();
In the above example, `MyCallback` extends `UrlRequest.Callback`. The request
is started asynchronously. When the response is ready (fully or partially), and
in the event of failures or redirects, `callback`'s methods will be invoked on
`executor`'s thread to inform the client of the request state and/or response
information.
### Downloading Data
When Cronet fetches response headers from the server or gets them from the
cache, `UrlRequest.Callback.onResponseStarted` will be invoked. To read the
response body, the client should call `UrlRequest.read` and supply a
[ByteBuffer] for Cronet to fill. Once a portion or all of
the response body is read, `UrlRequest.Callback.onReadCompleted` will be invoked.
The client may then read and consume the data within `byteBuffer`.
Once the client is ready to consume more data, the client should call
`UrlRequest.read` again. The process continues until
`UrlRequest.Callback.onSucceeded` or `UrlRequest.Callback.onFailed` is invoked,
which signals the completion of the request.
### Uploading Data
MyUploadDataProvider myUploadDataProvider = new MyUploadDataProvider();
requestBuilder.setHttpMethod("POST");
requestBuilder.setUploadDataProvider(myUploadDataProvider, executor);
In the above example, `MyUploadDataProvider` extends `UploadDataProvider`.
When Cronet is ready to send the request body,
`myUploadDataProvider.read(UploadDataSink uploadDataSink,
ByteBuffer byteBuffer)` will be invoked. The client will need to write the
request body into `byteBuffer`. Once the client is done writing into
`byteBuffer`, the client can let Cronet know by calling
`uploadDataSink.onReadSucceeded`. If the request body doesn't fit into
`byteBuffer`, the client can continue writing when `UploadDataProvider.read` is
invoked again. For more details, please see the API reference.
### <a id=configuring-cronet></a> Configuring Cronet
Various configuration options are available via the `CronetEngine.Builder`
object.
Enabling HTTP/2 and QUIC:
- For Example:
engineBuilder.enableHttp2(true).enableQuic(true);
Controlling the cache:
- Use a 100KiB in-memory cache:
engineBuilder.enableHttpCache(
CronetEngine.Builder.HttpCache.IN_MEMORY, 100 * 1024);
- or use a 1MiB disk cache:
engineBuilder.setStoragePath(storagePathString);
engineBuilder.enableHttpCache(CronetEngine.Builder.HttpCache.DISK,
1024 * 1024);
### Debugging
To get more information about how Cronet is processing network
requests, you can start and stop **NetLog** logging by calling
`CronetEngine.startNetLogToFile` and `CronetEngine.stopNetLog`.
Bear in mind that logs may contain sensitive data. You may analyze the
generated log by navigating to [chrome://net-internals#import] using a
Chrome browser.
# Using the java.net.HttpURLConnection API
Cronet offers an implementation of the [java.net.HttpURLConnection] API to make
it easier for apps which rely on this API to use Cronet.
To open individual connections using Cronet's implementation, do the following:
HttpURLConnection connection =
(HttpURLConnection)engine.openConnection(url);
To use Cronet's implementation instead of the system's default implementation
for all connections established using `URL.openConnection()` do the following:
URL.setURLStreamHandlerFactory(engine.createURLStreamHandlerFactory());
Cronet's
HttpURLConnection implementation has some limitations as compared to the system
implementation, including not utilizing the default system HTTP cache (Please
see {@link org.chromium.net.CronetEngine#createURLStreamHandlerFactory} for
more information).
You can configure Cronet and control caching through the
`CronetEngine.Builder` instance, `engineBuilder`
(See [Configuring Cronet](#configuring-cronet) section), before you build the
`CronetEngine` and then call `CronetEngine.createURLStreamHandlerFactory()`.
[ByteBuffer]: https://developer.android.com/reference/java/nio/ByteBuffer.html
[chrome://net-internals#import]: chrome://net-internals#import
[java.net.HttpURLConnection]: https://developer.android.com/reference/java/net/HttpURLConnection.html

View File

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
include_rules = [
"+components/metrics",
"+crypto",
"+jni",
]

View File

@ -0,0 +1 @@
per-file lint-*.xml=*

View File

@ -0,0 +1,13 @@
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Linker script that exports only symbols required for JNI and Cronet Native
# API to work.
{
global:
JNI_OnLoad;
Cronet_*;
local:
*;
};

View File

@ -0,0 +1,401 @@
DO NOT EDIT THIS FILE, USE update_api.py TO UPDATE IT
public class org.chromium.net.ApiVersion {
public static java.lang.String getCronetVersionWithLastChange();
public static int getMaximumAvailableApiLevel();
public static int getApiLevel();
public static java.lang.String getCronetVersion();
public static java.lang.String getLastChange();
}
public abstract class org.chromium.net.BidirectionalStream$Builder {
public static final int STREAM_PRIORITY_IDLE;
public static final int STREAM_PRIORITY_LOWEST;
public static final int STREAM_PRIORITY_LOW;
public static final int STREAM_PRIORITY_MEDIUM;
public static final int STREAM_PRIORITY_HIGHEST;
public org.chromium.net.BidirectionalStream$Builder();
public abstract org.chromium.net.BidirectionalStream$Builder setHttpMethod(java.lang.String);
public abstract org.chromium.net.BidirectionalStream$Builder addHeader(java.lang.String, java.lang.String);
public abstract org.chromium.net.BidirectionalStream$Builder setPriority(int);
public abstract org.chromium.net.BidirectionalStream$Builder delayRequestHeadersUntilFirstFlush(boolean);
public abstract org.chromium.net.BidirectionalStream build();
}
public abstract class org.chromium.net.BidirectionalStream$Callback {
public org.chromium.net.BidirectionalStream$Callback();
public abstract void onStreamReady(org.chromium.net.BidirectionalStream);
public abstract void onResponseHeadersReceived(org.chromium.net.BidirectionalStream, org.chromium.net.UrlResponseInfo);
public abstract void onReadCompleted(org.chromium.net.BidirectionalStream, org.chromium.net.UrlResponseInfo, java.nio.ByteBuffer, boolean);
public abstract void onWriteCompleted(org.chromium.net.BidirectionalStream, org.chromium.net.UrlResponseInfo, java.nio.ByteBuffer, boolean);
public void onResponseTrailersReceived(org.chromium.net.BidirectionalStream, org.chromium.net.UrlResponseInfo, org.chromium.net.UrlResponseInfo$HeaderBlock);
public abstract void onSucceeded(org.chromium.net.BidirectionalStream, org.chromium.net.UrlResponseInfo);
public abstract void onFailed(org.chromium.net.BidirectionalStream, org.chromium.net.UrlResponseInfo, org.chromium.net.CronetException);
public void onCanceled(org.chromium.net.BidirectionalStream, org.chromium.net.UrlResponseInfo);
}
public abstract class org.chromium.net.BidirectionalStream {
public org.chromium.net.BidirectionalStream();
public abstract void start();
public abstract void read(java.nio.ByteBuffer);
public abstract void write(java.nio.ByteBuffer, boolean);
public abstract void flush();
public abstract void cancel();
public abstract boolean isDone();
}
public abstract class org.chromium.net.CallbackException extends org.chromium.net.CronetException {
protected org.chromium.net.CallbackException(java.lang.String, java.lang.Throwable);
}
public abstract class org.chromium.net.CronetEngine$Builder$LibraryLoader {
public org.chromium.net.CronetEngine$Builder$LibraryLoader();
public abstract void loadLibrary(java.lang.String);
}
public class org.chromium.net.CronetEngine$Builder {
protected final org.chromium.net.ICronetEngineBuilder mBuilderDelegate;
public static final int HTTP_CACHE_DISABLED;
public static final int HTTP_CACHE_IN_MEMORY;
public static final int HTTP_CACHE_DISK_NO_HTTP;
public static final int HTTP_CACHE_DISK;
public org.chromium.net.CronetEngine$Builder(android.content.Context);
public org.chromium.net.CronetEngine$Builder(org.chromium.net.ICronetEngineBuilder);
public java.lang.String getDefaultUserAgent();
public org.chromium.net.CronetEngine$Builder setUserAgent(java.lang.String);
public org.chromium.net.CronetEngine$Builder setStoragePath(java.lang.String);
public org.chromium.net.CronetEngine$Builder setLibraryLoader(org.chromium.net.CronetEngine$Builder$LibraryLoader);
public org.chromium.net.CronetEngine$Builder enableQuic(boolean);
public org.chromium.net.CronetEngine$Builder enableHttp2(boolean);
public org.chromium.net.CronetEngine$Builder enableSdch(boolean);
public org.chromium.net.CronetEngine$Builder enableBrotli(boolean);
public org.chromium.net.CronetEngine$Builder enableHttpCache(int, long);
public org.chromium.net.CronetEngine$Builder addQuicHint(java.lang.String, int, int);
public org.chromium.net.CronetEngine$Builder addPublicKeyPins(java.lang.String, java.util.Set<byte[]>, boolean, java.util.Date);
public org.chromium.net.CronetEngine$Builder enablePublicKeyPinningBypassForLocalTrustAnchors(boolean);
public org.chromium.net.CronetEngine build();
}
public abstract class org.chromium.net.CronetEngine {
public org.chromium.net.CronetEngine();
public abstract java.lang.String getVersionString();
public abstract void shutdown();
public abstract void startNetLogToFile(java.lang.String, boolean);
public abstract void stopNetLog();
public abstract byte[] getGlobalMetricsDeltas();
public abstract java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException;
public abstract java.net.URLStreamHandlerFactory createURLStreamHandlerFactory();
public abstract org.chromium.net.UrlRequest$Builder newUrlRequestBuilder(java.lang.String, org.chromium.net.UrlRequest$Callback, java.util.concurrent.Executor);
}
public abstract class org.chromium.net.CronetException extends java.io.IOException {
protected org.chromium.net.CronetException(java.lang.String, java.lang.Throwable);
}
public abstract class org.chromium.net.CronetProvider {
public static final java.lang.String PROVIDER_NAME_APP_PACKAGED;
public static final java.lang.String PROVIDER_NAME_FALLBACK;
protected final android.content.Context mContext;
protected org.chromium.net.CronetProvider(android.content.Context);
public abstract org.chromium.net.CronetEngine$Builder createBuilder();
public abstract java.lang.String getName();
public abstract java.lang.String getVersion();
public abstract boolean isEnabled();
public java.lang.String toString();
public static java.util.List<org.chromium.net.CronetProvider> getAllProviders(android.content.Context);
}
public abstract class org.chromium.net.ExperimentalBidirectionalStream$Builder extends org.chromium.net.BidirectionalStream$Builder {
public org.chromium.net.ExperimentalBidirectionalStream$Builder();
public org.chromium.net.ExperimentalBidirectionalStream$Builder addRequestAnnotation(java.lang.Object);
public org.chromium.net.ExperimentalBidirectionalStream$Builder setTrafficStatsTag(int);
public org.chromium.net.ExperimentalBidirectionalStream$Builder setTrafficStatsUid(int);
public abstract org.chromium.net.ExperimentalBidirectionalStream$Builder setHttpMethod(java.lang.String);
public abstract org.chromium.net.ExperimentalBidirectionalStream$Builder addHeader(java.lang.String, java.lang.String);
public abstract org.chromium.net.ExperimentalBidirectionalStream$Builder setPriority(int);
public abstract org.chromium.net.ExperimentalBidirectionalStream$Builder delayRequestHeadersUntilFirstFlush(boolean);
public abstract org.chromium.net.ExperimentalBidirectionalStream build();
public org.chromium.net.BidirectionalStream build();
public org.chromium.net.BidirectionalStream$Builder delayRequestHeadersUntilFirstFlush(boolean);
public org.chromium.net.BidirectionalStream$Builder setPriority(int);
public org.chromium.net.BidirectionalStream$Builder addHeader(java.lang.String, java.lang.String);
public org.chromium.net.BidirectionalStream$Builder setHttpMethod(java.lang.String);
}
public abstract class org.chromium.net.ExperimentalBidirectionalStream extends org.chromium.net.BidirectionalStream {
public org.chromium.net.ExperimentalBidirectionalStream();
}
public class org.chromium.net.ExperimentalCronetEngine$Builder extends org.chromium.net.CronetEngine$Builder {
public org.chromium.net.ExperimentalCronetEngine$Builder(android.content.Context);
public org.chromium.net.ExperimentalCronetEngine$Builder(org.chromium.net.ICronetEngineBuilder);
public org.chromium.net.ExperimentalCronetEngine$Builder enableNetworkQualityEstimator(boolean);
public org.chromium.net.ExperimentalCronetEngine$Builder setExperimentalOptions(java.lang.String);
public org.chromium.net.ExperimentalCronetEngine$Builder setThreadPriority(int);
public org.chromium.net.ICronetEngineBuilder getBuilderDelegate();
public org.chromium.net.ExperimentalCronetEngine$Builder setUserAgent(java.lang.String);
public org.chromium.net.ExperimentalCronetEngine$Builder setStoragePath(java.lang.String);
public org.chromium.net.ExperimentalCronetEngine$Builder setLibraryLoader(org.chromium.net.CronetEngine$Builder$LibraryLoader);
public org.chromium.net.ExperimentalCronetEngine$Builder enableQuic(boolean);
public org.chromium.net.ExperimentalCronetEngine$Builder enableHttp2(boolean);
public org.chromium.net.ExperimentalCronetEngine$Builder enableSdch(boolean);
public org.chromium.net.ExperimentalCronetEngine$Builder enableHttpCache(int, long);
public org.chromium.net.ExperimentalCronetEngine$Builder addQuicHint(java.lang.String, int, int);
public org.chromium.net.ExperimentalCronetEngine$Builder addPublicKeyPins(java.lang.String, java.util.Set<byte[]>, boolean, java.util.Date);
public org.chromium.net.ExperimentalCronetEngine$Builder enablePublicKeyPinningBypassForLocalTrustAnchors(boolean);
public org.chromium.net.ExperimentalCronetEngine build();
public org.chromium.net.CronetEngine build();
public org.chromium.net.CronetEngine$Builder enablePublicKeyPinningBypassForLocalTrustAnchors(boolean);
public org.chromium.net.CronetEngine$Builder addPublicKeyPins(java.lang.String, java.util.Set, boolean, java.util.Date);
public org.chromium.net.CronetEngine$Builder addQuicHint(java.lang.String, int, int);
public org.chromium.net.CronetEngine$Builder enableHttpCache(int, long);
public org.chromium.net.CronetEngine$Builder enableSdch(boolean);
public org.chromium.net.CronetEngine$Builder enableHttp2(boolean);
public org.chromium.net.CronetEngine$Builder enableQuic(boolean);
public org.chromium.net.CronetEngine$Builder setLibraryLoader(org.chromium.net.CronetEngine$Builder$LibraryLoader);
public org.chromium.net.CronetEngine$Builder setStoragePath(java.lang.String);
public org.chromium.net.CronetEngine$Builder setUserAgent(java.lang.String);
}
public abstract class org.chromium.net.ExperimentalCronetEngine extends org.chromium.net.CronetEngine {
public static final int CONNECTION_METRIC_UNKNOWN;
public static final int EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
public static final int EFFECTIVE_CONNECTION_TYPE_OFFLINE;
public static final int EFFECTIVE_CONNECTION_TYPE_SLOW_2G;
public static final int EFFECTIVE_CONNECTION_TYPE_2G;
public static final int EFFECTIVE_CONNECTION_TYPE_3G;
public static final int EFFECTIVE_CONNECTION_TYPE_4G;
public org.chromium.net.ExperimentalCronetEngine();
public abstract org.chromium.net.ExperimentalBidirectionalStream$Builder newBidirectionalStreamBuilder(java.lang.String, org.chromium.net.BidirectionalStream$Callback, java.util.concurrent.Executor);
public abstract org.chromium.net.ExperimentalUrlRequest$Builder newUrlRequestBuilder(java.lang.String, org.chromium.net.UrlRequest$Callback, java.util.concurrent.Executor);
public void startNetLogToDisk(java.lang.String, boolean, int);
public int getEffectiveConnectionType();
public void configureNetworkQualityEstimatorForTesting(boolean, boolean, boolean);
public void addRttListener(org.chromium.net.NetworkQualityRttListener);
public void removeRttListener(org.chromium.net.NetworkQualityRttListener);
public void addThroughputListener(org.chromium.net.NetworkQualityThroughputListener);
public void removeThroughputListener(org.chromium.net.NetworkQualityThroughputListener);
public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException;
public void addRequestFinishedListener(org.chromium.net.RequestFinishedInfo$Listener);
public void removeRequestFinishedListener(org.chromium.net.RequestFinishedInfo$Listener);
public int getHttpRttMs();
public int getTransportRttMs();
public int getDownstreamThroughputKbps();
public org.chromium.net.UrlRequest$Builder newUrlRequestBuilder(java.lang.String, org.chromium.net.UrlRequest$Callback, java.util.concurrent.Executor);
}
public abstract class org.chromium.net.ExperimentalUrlRequest$Builder extends org.chromium.net.UrlRequest$Builder {
public static final int DEFAULT_IDEMPOTENCY;
public static final int IDEMPOTENT;
public static final int NOT_IDEMPOTENT;
public org.chromium.net.ExperimentalUrlRequest$Builder();
public org.chromium.net.ExperimentalUrlRequest$Builder disableConnectionMigration();
public org.chromium.net.ExperimentalUrlRequest$Builder addRequestAnnotation(java.lang.Object);
public org.chromium.net.ExperimentalUrlRequest$Builder setTrafficStatsTag(int);
public org.chromium.net.ExperimentalUrlRequest$Builder setTrafficStatsUid(int);
public org.chromium.net.ExperimentalUrlRequest$Builder setRequestFinishedListener(org.chromium.net.RequestFinishedInfo$Listener);
public org.chromium.net.ExperimentalUrlRequest$Builder setIdempotency(int);
public abstract org.chromium.net.ExperimentalUrlRequest$Builder setHttpMethod(java.lang.String);
public abstract org.chromium.net.ExperimentalUrlRequest$Builder addHeader(java.lang.String, java.lang.String);
public abstract org.chromium.net.ExperimentalUrlRequest$Builder disableCache();
public abstract org.chromium.net.ExperimentalUrlRequest$Builder setPriority(int);
public abstract org.chromium.net.ExperimentalUrlRequest$Builder setUploadDataProvider(org.chromium.net.UploadDataProvider, java.util.concurrent.Executor);
public abstract org.chromium.net.ExperimentalUrlRequest$Builder allowDirectExecutor();
public abstract org.chromium.net.ExperimentalUrlRequest build();
public org.chromium.net.UrlRequest build();
public org.chromium.net.UrlRequest$Builder allowDirectExecutor();
public org.chromium.net.UrlRequest$Builder setUploadDataProvider(org.chromium.net.UploadDataProvider, java.util.concurrent.Executor);
public org.chromium.net.UrlRequest$Builder setPriority(int);
public org.chromium.net.UrlRequest$Builder disableCache();
public org.chromium.net.UrlRequest$Builder addHeader(java.lang.String, java.lang.String);
public org.chromium.net.UrlRequest$Builder setHttpMethod(java.lang.String);
}
public abstract class org.chromium.net.ExperimentalUrlRequest extends org.chromium.net.UrlRequest {
public org.chromium.net.ExperimentalUrlRequest();
}
public abstract class org.chromium.net.ICronetEngineBuilder {
public org.chromium.net.ICronetEngineBuilder();
public abstract org.chromium.net.ICronetEngineBuilder addPublicKeyPins(java.lang.String, java.util.Set<byte[]>, boolean, java.util.Date);
public abstract org.chromium.net.ICronetEngineBuilder addQuicHint(java.lang.String, int, int);
public abstract org.chromium.net.ICronetEngineBuilder enableHttp2(boolean);
public abstract org.chromium.net.ICronetEngineBuilder enableHttpCache(int, long);
public abstract org.chromium.net.ICronetEngineBuilder enablePublicKeyPinningBypassForLocalTrustAnchors(boolean);
public abstract org.chromium.net.ICronetEngineBuilder enableQuic(boolean);
public abstract org.chromium.net.ICronetEngineBuilder enableSdch(boolean);
public org.chromium.net.ICronetEngineBuilder enableBrotli(boolean);
public abstract org.chromium.net.ICronetEngineBuilder setExperimentalOptions(java.lang.String);
public abstract org.chromium.net.ICronetEngineBuilder setLibraryLoader(org.chromium.net.CronetEngine$Builder$LibraryLoader);
public abstract org.chromium.net.ICronetEngineBuilder setStoragePath(java.lang.String);
public abstract org.chromium.net.ICronetEngineBuilder setUserAgent(java.lang.String);
public abstract java.lang.String getDefaultUserAgent();
public abstract org.chromium.net.ExperimentalCronetEngine build();
public org.chromium.net.ICronetEngineBuilder enableNetworkQualityEstimator(boolean);
public org.chromium.net.ICronetEngineBuilder setThreadPriority(int);
}
public final class org.chromium.net.InlineExecutionProhibitedException extends java.util.concurrent.RejectedExecutionException {
public org.chromium.net.InlineExecutionProhibitedException();
}
public abstract class org.chromium.net.NetworkException extends org.chromium.net.CronetException {
public static final int ERROR_HOSTNAME_NOT_RESOLVED;
public static final int ERROR_INTERNET_DISCONNECTED;
public static final int ERROR_NETWORK_CHANGED;
public static final int ERROR_TIMED_OUT;
public static final int ERROR_CONNECTION_CLOSED;
public static final int ERROR_CONNECTION_TIMED_OUT;
public static final int ERROR_CONNECTION_REFUSED;
public static final int ERROR_CONNECTION_RESET;
public static final int ERROR_ADDRESS_UNREACHABLE;
public static final int ERROR_QUIC_PROTOCOL_FAILED;
public static final int ERROR_OTHER;
protected org.chromium.net.NetworkException(java.lang.String, java.lang.Throwable);
public abstract int getErrorCode();
public abstract int getCronetInternalErrorCode();
public abstract boolean immediatelyRetryable();
}
public abstract class org.chromium.net.NetworkQualityRttListener {
public org.chromium.net.NetworkQualityRttListener(java.util.concurrent.Executor);
public java.util.concurrent.Executor getExecutor();
public abstract void onRttObservation(int, long, int);
}
public abstract class org.chromium.net.NetworkQualityThroughputListener {
public org.chromium.net.NetworkQualityThroughputListener(java.util.concurrent.Executor);
public java.util.concurrent.Executor getExecutor();
public abstract void onThroughputObservation(int, long, int);
}
public abstract class org.chromium.net.QuicException extends org.chromium.net.NetworkException {
protected org.chromium.net.QuicException(java.lang.String, java.lang.Throwable);
public abstract int getQuicDetailedErrorCode();
}
public abstract class org.chromium.net.RequestFinishedInfo$Listener {
public org.chromium.net.RequestFinishedInfo$Listener(java.util.concurrent.Executor);
public abstract void onRequestFinished(org.chromium.net.RequestFinishedInfo);
public java.util.concurrent.Executor getExecutor();
}
public abstract class org.chromium.net.RequestFinishedInfo$Metrics {
public org.chromium.net.RequestFinishedInfo$Metrics();
public abstract java.util.Date getRequestStart();
public abstract java.util.Date getDnsStart();
public abstract java.util.Date getDnsEnd();
public abstract java.util.Date getConnectStart();
public abstract java.util.Date getConnectEnd();
public abstract java.util.Date getSslStart();
public abstract java.util.Date getSslEnd();
public abstract java.util.Date getSendingStart();
public abstract java.util.Date getSendingEnd();
public abstract java.util.Date getPushStart();
public abstract java.util.Date getPushEnd();
public abstract java.util.Date getResponseStart();
public abstract java.util.Date getRequestEnd();
public abstract boolean getSocketReused();
public abstract java.lang.Long getTtfbMs();
public abstract java.lang.Long getTotalTimeMs();
public abstract java.lang.Long getSentByteCount();
public abstract java.lang.Long getReceivedByteCount();
}
public abstract class org.chromium.net.RequestFinishedInfo {
public static final int SUCCEEDED;
public static final int FAILED;
public static final int CANCELED;
public org.chromium.net.RequestFinishedInfo();
public abstract java.lang.String getUrl();
public abstract java.util.Collection<java.lang.Object> getAnnotations();
public abstract org.chromium.net.RequestFinishedInfo$Metrics getMetrics();
public abstract int getFinishedReason();
public abstract org.chromium.net.UrlResponseInfo getResponseInfo();
public abstract org.chromium.net.CronetException getException();
}
public abstract class org.chromium.net.UploadDataProvider implements java.io.Closeable {
public org.chromium.net.UploadDataProvider();
public abstract long getLength() throws java.io.IOException;
public abstract void read(org.chromium.net.UploadDataSink, java.nio.ByteBuffer) throws java.io.IOException;
public abstract void rewind(org.chromium.net.UploadDataSink) throws java.io.IOException;
public void close() throws java.io.IOException;
}
final class org.chromium.net.UploadDataProviders$ByteBufferUploadProvider extends org.chromium.net.UploadDataProvider {
public long getLength();
public void read(org.chromium.net.UploadDataSink, java.nio.ByteBuffer);
public void rewind(org.chromium.net.UploadDataSink);
}
interface org.chromium.net.UploadDataProviders$FileChannelProvider {
public abstract java.nio.channels.FileChannel getChannel() throws java.io.IOException;
}
final class org.chromium.net.UploadDataProviders$FileUploadProvider extends org.chromium.net.UploadDataProvider {
public long getLength() throws java.io.IOException;
public void read(org.chromium.net.UploadDataSink, java.nio.ByteBuffer) throws java.io.IOException;
public void rewind(org.chromium.net.UploadDataSink) throws java.io.IOException;
public void close() throws java.io.IOException;
}
public final class org.chromium.net.UploadDataProviders {
public static org.chromium.net.UploadDataProvider create(java.io.File);
public static org.chromium.net.UploadDataProvider create(android.os.ParcelFileDescriptor);
public static org.chromium.net.UploadDataProvider create(java.nio.ByteBuffer);
public static org.chromium.net.UploadDataProvider create(byte[], int, int);
public static org.chromium.net.UploadDataProvider create(byte[]);
}
public abstract class org.chromium.net.UploadDataSink {
public org.chromium.net.UploadDataSink();
public abstract void onReadSucceeded(boolean);
public abstract void onReadError(java.lang.Exception);
public abstract void onRewindSucceeded();
public abstract void onRewindError(java.lang.Exception);
}
public abstract class org.chromium.net.UrlRequest$Builder {
public static final int REQUEST_PRIORITY_IDLE;
public static final int REQUEST_PRIORITY_LOWEST;
public static final int REQUEST_PRIORITY_LOW;
public static final int REQUEST_PRIORITY_MEDIUM;
public static final int REQUEST_PRIORITY_HIGHEST;
public org.chromium.net.UrlRequest$Builder();
public abstract org.chromium.net.UrlRequest$Builder setHttpMethod(java.lang.String);
public abstract org.chromium.net.UrlRequest$Builder addHeader(java.lang.String, java.lang.String);
public abstract org.chromium.net.UrlRequest$Builder disableCache();
public abstract org.chromium.net.UrlRequest$Builder setPriority(int);
public abstract org.chromium.net.UrlRequest$Builder setUploadDataProvider(org.chromium.net.UploadDataProvider, java.util.concurrent.Executor);
public abstract org.chromium.net.UrlRequest$Builder allowDirectExecutor();
public abstract org.chromium.net.UrlRequest build();
}
public abstract class org.chromium.net.UrlRequest$Callback {
public org.chromium.net.UrlRequest$Callback();
public abstract void onRedirectReceived(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, java.lang.String) throws java.lang.Exception;
public abstract void onResponseStarted(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo) throws java.lang.Exception;
public abstract void onReadCompleted(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, java.nio.ByteBuffer) throws java.lang.Exception;
public abstract void onSucceeded(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo);
public abstract void onFailed(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, org.chromium.net.CronetException);
public void onCanceled(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo);
}
public class org.chromium.net.UrlRequest$Status {
public static final int INVALID;
public static final int IDLE;
public static final int WAITING_FOR_STALLED_SOCKET_POOL;
public static final int WAITING_FOR_AVAILABLE_SOCKET;
public static final int WAITING_FOR_DELEGATE;
public static final int WAITING_FOR_CACHE;
public static final int DOWNLOADING_PAC_FILE;
public static final int RESOLVING_PROXY_FOR_URL;
public static final int RESOLVING_HOST_IN_PAC_FILE;
public static final int ESTABLISHING_PROXY_TUNNEL;
public static final int RESOLVING_HOST;
public static final int CONNECTING;
public static final int SSL_HANDSHAKE;
public static final int SENDING_REQUEST;
public static final int WAITING_FOR_RESPONSE;
public static final int READING_RESPONSE;
}
public abstract class org.chromium.net.UrlRequest$StatusListener {
public org.chromium.net.UrlRequest$StatusListener();
public abstract void onStatus(int);
}
public abstract class org.chromium.net.UrlRequest {
public org.chromium.net.UrlRequest();
public abstract void start();
public abstract void followRedirect();
public abstract void read(java.nio.ByteBuffer);
public abstract void cancel();
public abstract boolean isDone();
public abstract void getStatus(org.chromium.net.UrlRequest$StatusListener);
}
public abstract class org.chromium.net.UrlResponseInfo$HeaderBlock {
public org.chromium.net.UrlResponseInfo$HeaderBlock();
public abstract java.util.List<java.util.Map$Entry<java.lang.String, java.lang.String>> getAsList();
public abstract java.util.Map<java.lang.String, java.util.List<java.lang.String>> getAsMap();
}
public abstract class org.chromium.net.UrlResponseInfo {
public org.chromium.net.UrlResponseInfo();
public abstract java.lang.String getUrl();
public abstract java.util.List<java.lang.String> getUrlChain();
public abstract int getHttpStatusCode();
public abstract java.lang.String getHttpStatusText();
public abstract java.util.List<java.util.Map$Entry<java.lang.String, java.lang.String>> getAllHeadersAsList();
public abstract java.util.Map<java.lang.String, java.util.List<java.lang.String>> getAllHeaders();
public abstract boolean wasCached();
public abstract java.lang.String getNegotiatedProtocol();
public abstract java.lang.String getProxyServer();
public abstract long getReceivedByteCount();
}
Stamp: 1c548f381e3715d983e967e830d675d8

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@string/CronetProviderClassName" />

View File

@ -0,0 +1,63 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net;
/**
* Version based on chrome/VERSION.
* {@hide as it's only used internally}
*/
public class ApiVersion {
private static final String CRONET_VERSION = "@MAJOR@.@MINOR@.@BUILD@.@PATCH@";
private static final int API_LEVEL = @API_LEVEL@;
/**
* The minimum API level of implementations that are compatible with this API.
* The last API level which broke backwards API compatibility. In other words, the
* Cronet API that this class is part of won't work with Cronet implementations that implement
* API levels less than this value. That is if
* ImplVersion.getApiLevel() < ApiVersion.getApiLevel(), then the Cronet implementation
* providing ImplVersion cannot be used with the Cronet API providing ApiVersion; if they are
* used together various unexpected Errors, like AbstractMethodError, may result.
*/
private static final int MIN_COMPATIBLE_API_LEVEL = 3;
private static final String LAST_CHANGE = "@LASTCHANGE@";
/**
* Private constructor. All members of this class should be static.
*/
private ApiVersion() {}
public static String getCronetVersionWithLastChange() {
return CRONET_VERSION + "@" + LAST_CHANGE.substring(0, 8);
}
/**
* Returns API level of the API linked into the application. This is the maximum API
* level the application can use, even if the application is run with a newer implementation.
*/
public static int getMaximumAvailableApiLevel() {
return API_LEVEL;
}
/**
* The minimum API level of implementations that are compatible with this API.
* Returns the last API level which broke backwards API compatibility. In other words, the
* Cronet API that this class is part of won't work with Cronet implementations that implement
* API levels less than this value. That is if
* ImplVersion.getApiLevel() < ApiVersion.getApiLevel(), then the Cronet implementation
* providing ImplVersion cannot be used with the Cronet API providing ApiVersion; if they are
* used together various unexpected Errors, like AbstractMethodError, may result.
*/
public static int getApiLevel() {
return MIN_COMPATIBLE_API_LEVEL;
}
public static String getCronetVersion() {
return CRONET_VERSION;
}
public static String getLastChange() {
return LAST_CHANGE;
}
}

View File

@ -0,0 +1,303 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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.annotation.SuppressLint;
import java.nio.ByteBuffer;
import java.util.concurrent.Executor;
/**
* Class for bidirectional sending and receiving of data over HTTP/2 or QUIC connections.
* Created by {@link Builder}.
*
* Note: There are ordering restrictions on methods of {@link BidirectionalStream};
* please see individual methods for description of restrictions.
*
* {@hide experimental}
*/
public abstract class BidirectionalStream {
/**
* Builder for {@link BidirectionalStream}s. Allows configuring stream before constructing
* it via {@link Builder#build}. Created by
* {@link ExperimentalCronetEngine#newBidirectionalStreamBuilder}.
*/
public abstract static class Builder {
/**
* Sets the HTTP method for the request. Returns builder to facilitate chaining.
*
* @param method the method to use for request. Default is 'POST'
* @return the builder to facilitate chaining
*/
public abstract Builder setHttpMethod(String method);
/**
* Adds a request header. Returns builder to facilitate chaining.
*
* @param header the header name
* @param value the header value
* @return the builder to facilitate chaining
*/
public abstract Builder addHeader(String header, String value);
/**
* Lowest stream priority. Passed to {@link #setPriority}.
*/
public static final int STREAM_PRIORITY_IDLE = 0;
/**
* Very low stream priority. Passed to {@link #setPriority}.
*/
public static final int STREAM_PRIORITY_LOWEST = 1;
/**
* Low stream priority. Passed to {@link #setPriority}.
*/
public static final int STREAM_PRIORITY_LOW = 2;
/**
* Medium stream priority. Passed to {@link #setPriority}. This is the
* default priority given to the stream.
*/
public static final int STREAM_PRIORITY_MEDIUM = 3;
/**
* Highest stream priority. Passed to {@link #setPriority}.
*/
public static final int STREAM_PRIORITY_HIGHEST = 4;
/**
* Sets priority of the stream which should be one of the
* {@link #STREAM_PRIORITY_IDLE STREAM_PRIORITY_*} values.
* The stream is given {@link #STREAM_PRIORITY_MEDIUM} priority if
* this method is not called.
*
* @param priority priority of the stream which should be one of the
* {@link #STREAM_PRIORITY_IDLE STREAM_PRIORITY_*} values.
* @return the builder to facilitate chaining.
*/
public abstract Builder setPriority(int priority);
/**
* Delays sending request headers until {@link BidirectionalStream#flush()}
* is called. This flag is currently only respected when QUIC is negotiated.
* When true, QUIC will send request header frame along with data frame(s)
* as a single packet when possible.
*
* @param delayRequestHeadersUntilFirstFlush if true, sending request headers will
* be delayed until flush() is called.
* @return the builder to facilitate chaining.
*/
public abstract Builder delayRequestHeadersUntilFirstFlush(
boolean delayRequestHeadersUntilFirstFlush);
/**
* Creates a {@link BidirectionalStream} using configuration from this
* {@link Builder}. The returned {@code BidirectionalStream} can then be started
* by calling {@link BidirectionalStream#start}.
*
* @return constructed {@link BidirectionalStream} using configuration from
* this {@link Builder}
*/
@SuppressLint("WrongConstant") // TODO(jbudorick): Remove this after rolling to the N SDK.
public abstract BidirectionalStream build();
}
/**
* Callback class used to receive callbacks from a {@link BidirectionalStream}.
*/
public abstract static class Callback {
/**
* Invoked when the stream is ready for reading and writing.
* Consumer may call {@link BidirectionalStream#read read()} to start reading data.
* Consumer may call {@link BidirectionalStream#write write()} to start writing data.
*
* @param stream the stream that is ready.
*/
public abstract void onStreamReady(BidirectionalStream stream);
/**
* Invoked when initial response headers are received. Headers are available from
* {@code info.}{@link UrlResponseInfo#getAllHeaders getAllHeaders()}.
* Consumer may call {@link BidirectionalStream#read read()} to start reading.
* Consumer may call {@link BidirectionalStream#write write()} to start writing or close the
* stream.
*
* @param stream the stream on which response headers were received.
* @param info the response information.
*/
public abstract void onResponseHeadersReceived(
BidirectionalStream stream, UrlResponseInfo info);
/**
* Invoked when data is read into the buffer passed to {@link BidirectionalStream#read
* read()}. Only part of the buffer may be populated. To continue reading, call {@link
* BidirectionalStream#read read()}. It may be invoked after {@code
* onResponseTrailersReceived()}, if there was pending read data before trailers were
* received.
*
* @param stream the stream on which the read completed
* @param info the response information
* @param buffer the buffer that was passed to {@link BidirectionalStream#read read()},
* now containing the received data. The buffer's limit is not changed.
* The buffer's position is set to the end of the received data. If position is not
* updated, it means the remote side has signaled that it will send no more data.
* @param endOfStream if true, this is the last read data, remote will not send more data,
* and the read side is closed.
*
*/
public abstract void onReadCompleted(BidirectionalStream stream, UrlResponseInfo info,
ByteBuffer buffer, boolean endOfStream);
/**
* Invoked when the entire ByteBuffer passed to {@link BidirectionalStream#write write()}
* is sent. The buffer's position is updated to be the same as the buffer's limit.
* The buffer's limit is not changed. To continue writing, call
* {@link BidirectionalStream#write write()}.
*
* @param stream the stream on which the write completed
* @param info the response information
* @param buffer the buffer that was passed to {@link BidirectionalStream#write write()}.
* The buffer's position is set to the buffer's limit. The buffer's limit
* is not changed.
* @param endOfStream the endOfStream flag that was passed to the corresponding
* {@link BidirectionalStream#write write()}. If true, the write side is closed.
*/
public abstract void onWriteCompleted(BidirectionalStream stream, UrlResponseInfo info,
ByteBuffer buffer, boolean endOfStream);
/**
* Invoked when trailers are received before closing the stream. Only invoked
* when server sends trailers, which it may not. May be invoked while there is read data
* remaining in local buffer.
*
* Default implementation takes no action.
*
* @param stream the stream on which response trailers were received
* @param info the response information
* @param trailers the trailers received
*/
public void onResponseTrailersReceived(BidirectionalStream stream, UrlResponseInfo info,
UrlResponseInfo.HeaderBlock trailers) {}
/**
* Invoked when there is no data to be read or written and the stream is closed successfully
* remotely and locally. Once invoked, no further {@link BidirectionalStream.Callback}
* methods will be invoked.
*
* @param stream the stream which is closed successfully
* @param info the response information
*/
public abstract void onSucceeded(BidirectionalStream stream, UrlResponseInfo info);
/**
* Invoked if the stream failed for any reason after {@link BidirectionalStream#start}.
* <a href="https://tools.ietf.org/html/rfc7540#section-7">HTTP/2 error codes</a> are
* mapped to {@link UrlRequestException#getCronetInternalErrorCode} codes. Once invoked,
* no further {@link BidirectionalStream.Callback} methods will be invoked.
*
* @param stream the stream which has failed
* @param info the response information. May be {@code null} if no response was
* received.
* @param error information about the failure
*/
public abstract void onFailed(
BidirectionalStream stream, UrlResponseInfo info, CronetException error);
/**
* Invoked if the stream was canceled via {@link BidirectionalStream#cancel}. Once
* invoked, no further {@link BidirectionalStream.Callback} methods will be invoked.
* Default implementation takes no action.
*
* @param stream the stream that was canceled
* @param info the response information. May be {@code null} if no response was
* received.
*/
public void onCanceled(BidirectionalStream stream, UrlResponseInfo info) {}
}
/**
* Starts the stream, all callbacks go to the {@code callback} argument passed to {@link
* BidirectionalStream.Builder}'s constructor. Should only be called once.
*/
public abstract void start();
/**
* Reads data from the stream into the provided buffer.
* Can only be called at most once in response to each invocation of the
* {@link Callback#onStreamReady onStreamReady()}/
* {@link Callback#onResponseHeadersReceived onResponseHeadersReceived()} and {@link
* Callback#onReadCompleted onReadCompleted()} methods of the {@link
* Callback}. Each call will result in an invocation of one of the
* {@link Callback Callback}'s {@link Callback#onReadCompleted onReadCompleted()}
* method if data is read, or its {@link Callback#onFailed onFailed()} method if
* there's an error.
*
* An attempt to read data into {@code buffer} starting at {@code
* buffer.position()} is begun. At most {@code buffer.remaining()} bytes are
* read. {@code buffer.position()} is updated upon invocation of {@link
* Callback#onReadCompleted onReadCompleted()} to indicate how much data was read.
*
* @param buffer the {@link ByteBuffer} to read data into. Must be a
* direct ByteBuffer. The embedder must not read or modify buffer's
* position, limit, or data between its position and limit until
* {@link Callback#onReadCompleted onReadCompleted()}, {@link Callback#onCanceled
* onCanceled()}, or {@link Callback#onFailed onFailed()} are invoked.
*/
public abstract void read(ByteBuffer buffer);
/**
* Attempts to write data from the provided buffer into the stream.
* If auto flush is disabled, data will be sent only after {@link #flush flush()} is called.
* Each call will result in an invocation of one of the
* {@link Callback Callback}'s {@link Callback#onWriteCompleted onWriteCompleted()}
* method if data is sent, or its {@link Callback#onFailed onFailed()} method if
* there's an error.
*
* An attempt to write data from {@code buffer} starting at {@code buffer.position()}
* is begun. {@code buffer.remaining()} bytes will be written.
* {@link Callback#onWriteCompleted onWriteCompleted()} will be invoked only when the
* full ByteBuffer is written.
*
* @param buffer the {@link ByteBuffer} to write data from. Must be a
* direct ByteBuffer. The embedder must not read or modify buffer's
* position, limit, or data between its position and limit until
* {@link Callback#onWriteCompleted onWriteCompleted()}, {@link Callback#onCanceled
* onCanceled()}, or {@link Callback#onFailed onFailed()} are invoked. Can be empty
* when {@code endOfStream} is {@code true}.
* @param endOfStream if {@code true}, then {@code buffer} is the last buffer to be written,
* and once written, stream is closed from the client side, resulting in half-closed
* stream or a fully closed stream if the remote side has already closed.
*/
public abstract void write(ByteBuffer buffer, boolean endOfStream);
/**
* Flushes pending writes. This method should not be invoked before {@link
* Callback#onStreamReady onStreamReady()}. For previously delayed {@link
* #write write()}s, a corresponding {@link Callback#onWriteCompleted onWriteCompleted()}
* will be invoked when the buffer is sent.
*/
public abstract void flush();
/**
* Cancels the stream. Can be called at any time after {@link #start}.
* {@link Callback#onCanceled onCanceled()} will be invoked when cancelation
* is complete and no further callback methods will be invoked. If the
* stream has completed or has not started, calling {@code cancel()} has no
* effect and {@code onCanceled()} will not be invoked. If the
* {@link Executor} passed in during {@code BidirectionalStream} construction runs
* tasks on a single thread, and {@code cancel()} is called on that thread,
* no listener methods (besides {@code onCanceled()}) will be invoked after
* {@code cancel()} is called. Otherwise, at most one callback method may be
* invoked after {@code cancel()} has completed.
*/
public abstract void cancel();
/**
* Returns {@code true} if the stream was successfully started and is now
* done (succeeded, canceled, or failed).
*
* @return {@code true} if the stream was successfully started and is now
* done (completed, canceled, or failed), otherwise returns {@code false}
* to indicate stream is not yet started or is in progress.
*/
public abstract boolean isDone();
}

View File

@ -0,0 +1,24 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net;
/**
* Exception passed to {@link UrlRequest.Callback#onFailed UrlRequest.Callback.onFailed()} when
* {@link UrlRequest.Callback} or {@link UploadDataProvider} method throws an exception. In this
* case {@link java.io.IOException#getCause getCause()} can be used to find the thrown
* exception.
*/
public abstract class CallbackException extends CronetException {
/**
* Constructs an exception that wraps {@code cause} thrown by a {@link UrlRequest.Callback}.
*
* @param message explanation of failure.
* @param cause exception thrown by {@link UrlRequest.Callback} that's being wrapped. It is
* saved for later retrieval by the {@link java.io.IOException#getCause getCause()}.
*/
protected CallbackException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,545 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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.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();
/**
* 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 {
/**
* 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) {
mBuilderDelegate = 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.
* <p>
* <b>NOTE:</b> 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 <a href="https://www.chromium.org/quic">QUIC</a> 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 <a href="https://tools.ietf.org/html/rfc7540">HTTP/2</a>
* 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 <a href="https://tools.ietf.org/html/rfc7932">Brotli</a> 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;
}
/**
* <p>
* 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.
* </p>
* <p>
* Calling this method multiple times with the same host name overrides the previously
* set pins for the host.
* </p>
* <p>
* More information about the public key pinning can be found in
* <a href="https://tools.ietf.org/html/rfc7469">RFC 7469</a>.
* </p>
*
* @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<byte[]> pinsSha256,
boolean includeSubdomains, Date expirationDate) {
mBuilderDelegate.addPublicKeyPins(
hostName, pinsSha256, includeSubdomains, expirationDate);
return this;
}
/**
* Enables or disables public key pinning bypass for local trust anchors. Disabling the
* bypass for local trust anchors is highly discouraged since it may prohibit the app
* from communicating with the pinned hosts. E.g., a user may want to send all traffic
* through an SSL enabled proxy by changing the device proxy settings and adding the
* proxy certificate to the list of local trust anchor. Disabling the bypass will most
* likly prevent the app from sending any traffic to the pinned hosts. For more
* information see 'How does key pinning interact with local proxies and filters?' at
* https://www.chromium.org/Home/chromium-security/security-faq
*
* @param value {@code true} to enable the bypass, {@code false} to disable.
* @return the builder to facilitate chaining.
*/
public Builder enablePublicKeyPinningBypassForLocalTrustAnchors(boolean value) {
mBuilderDelegate.enablePublicKeyPinningBypassForLocalTrustAnchors(value);
return this;
}
/**
* Build a {@link CronetEngine} using this builder's configuration.
* @return constructed {@link CronetEngine}.
*/
public CronetEngine build() {
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<CronetProvider> providers =
new ArrayList<>(CronetProvider.getAllProviders(context));
CronetProvider provider = getEnabledCronetProviders(context, providers).get(0);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG,
String.format("Using '%s' provider for creating CronetEngine.Builder.",
provider));
}
return provider.createBuilder().mBuilderDelegate;
}
/**
* Returns the list of available and enabled {@link CronetProvider}. The returned list
* is sorted based on the provider versions and types.
*
* @param context Android Context to use.
* @param providers the list of enabled and disabled providers to filter out and sort.
* @return the sorted list of enabled providers. The list contains at least one provider.
* @throws RuntimeException is the list of providers is empty or all of the providers
* are disabled.
*/
@VisibleForTesting
static List<CronetProvider> getEnabledCronetProviders(
Context context, List<CronetProvider> providers) {
// Check that there is at least one available provider.
if (providers.size() == 0) {
throw new RuntimeException("Unable to find any Cronet provider."
+ " Have you included all necessary jars?");
}
// Exclude disabled providers from the list.
for (Iterator<CronetProvider> i = providers.iterator(); i.hasNext();) {
CronetProvider provider = i.next();
if (!provider.isEnabled()) {
i.remove();
}
}
// Check that there is at least one enabled provider.
if (providers.size() == 0) {
throw new RuntimeException("All available Cronet providers are disabled."
+ " A provider should be enabled before it can be used.");
}
// Sort providers based on version and type.
Collections.sort(providers, new Comparator<CronetProvider>() {
@Override
public int compare(CronetProvider p1, CronetProvider p2) {
// The fallback provider should always be at the end of the list.
if (CronetProvider.PROVIDER_NAME_FALLBACK.equals(p1.getName())) {
return 1;
}
if (CronetProvider.PROVIDER_NAME_FALLBACK.equals(p2.getName())) {
return -1;
}
// A provider with higher version should go first.
return -compareVersions(p1.getVersion(), p2.getVersion());
}
});
return providers;
}
/**
* Compares two strings that contain versions. The string should only contain
* dot-separated segments that contain an arbitrary number of digits digits [0-9].
*
* @param s1 the first string.
* @param s2 the second string.
* @return -1 if s1<s2, +1 if s1>s2 and 0 if s1=s2. If two versions are equal, the
* version with the higher number of segments is considered to be higher.
*
* @throws IllegalArgumentException if any of the strings contains an illegal
* version number.
*/
@VisibleForTesting
static int compareVersions(String s1, String s2) {
if (s1 == null || s2 == null) {
throw new IllegalArgumentException("The input values cannot be null");
}
String[] s1segments = s1.split("\\.");
String[] s2segments = s2.split("\\.");
for (int i = 0; i < s1segments.length && i < s2segments.length; i++) {
try {
int s1segment = Integer.parseInt(s1segments[i]);
int s2segment = Integer.parseInt(s2segments[i]);
if (s1segment != s2segment) {
return Integer.signum(s1segment - s2segment);
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Unable to convert version segments into"
+ " integers: " + s1segments[i] + " & " + s2segments[i],
e);
}
}
return Integer.signum(s1segments.length - s2segments.length);
}
}
/**
* @return a human-readable version string of the engine.
*/
public abstract String getVersionString();
/**
* Shuts down the {@link CronetEngine} if there are no active requests,
* otherwise throws an exception.
*
* 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.
* <p>
* 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()}.
* <p>
* 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
* <a href=https://developers.google.com/protocol-buffers>protobuf
* </a>.
*/
public abstract byte[] getGlobalMetricsDeltas();
/**
* Establishes a new connection to the resource specified by the {@link URL} {@code url}.
* <p>
* <b>Note:</b> 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}.
* <p>
* Cronet does not use certain HTTP features provided via the system:
* <ul>
* <li>the HTTP cache installed via
* {@link HttpResponseCache#install(java.io.File, long) HttpResponseCache.install()}</li>
* <li>the HTTP authentication method installed via
* {@link java.net.Authenticator#setDefault}</li>
* <li>the HTTP cookie storage installed via {@link java.net.CookieHandler#setDefault}</li>
* </ul>
* <p>
* 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:
* <ul>
* <li>the HTTPS hostname verifier installed via {@link
* HttpsURLConnection#setDefaultHostnameVerifier(javax.net.ssl.HostnameVerifier)
* HttpsURLConnection.setDefaultHostnameVerifier()}</li>
* <li>the HTTPS socket factory installed via {@link
* HttpsURLConnection#setDefaultSSLSocketFactory(javax.net.ssl.SSLSocketFactory)
* HttpsURLConnection.setDefaultSSLSocketFactory()}</li>
* </ul>
*
* @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);
}

View File

@ -0,0 +1,24 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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 java.io.IOException;
/**
* Base exception passed to {@link UrlRequest.Callback#onFailed UrlRequest.Callback.onFailed()}.
*/
public abstract class CronetException extends IOException {
/**
* Constructs an exception that is caused by {@code cause}.
*
* @param message explanation of failure.
* @param cause the cause (which is saved for later retrieval by the {@link
* java.io.IOException#getCause getCause()} method). A null value is permitted, and
* indicates that the cause is nonexistent or unknown.
*/
protected CronetException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,246 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// 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.util.Log;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Provides a factory method to create {@link CronetEngine.Builder} instances.
* A {@code CronetEngine.Builder} instance can be used to create a specific {@link CronetEngine}
* implementation. To get the list of available {@link CronetProvider}s call
* {@link #getAllProviders(Context)}.
* <p/>
* <b>NOTE:</b> This class is for advanced users that want to select a particular
* Cronet implementation. Most users should simply use {@code new} {@link
* CronetEngine.Builder#CronetEngine.Builder(android.content.Context)}.
*
* {@hide}
*/
public abstract class CronetProvider {
/**
* String returned by {@link CronetProvider#getName} for {@link CronetProvider}
* that provides native Cronet implementation packaged inside an application.
* This implementation offers significantly higher performance relative to the
* fallback Cronet implementations (see {@link #PROVIDER_NAME_FALLBACK}).
*/
public static final String PROVIDER_NAME_APP_PACKAGED = "App-Packaged-Cronet-Provider";
/**
* String returned by {@link CronetProvider#getName} for {@link CronetProvider}
* that provides Cronet implementation based on the system's
* {@link java.net.HttpURLConnection} implementation. This implementation
* offers significantly degraded performance relative to native Cronet
* implementations (see {@link #PROVIDER_NAME_APP_PACKAGED}).
*/
public static final String PROVIDER_NAME_FALLBACK = "Fallback-Cronet-Provider";
/**
* The name of an optional key in the app string resource file that contains the class name of
* an alternative {@code CronetProvider} implementation.
*/
private static final String RES_KEY_CRONET_IMPL_CLASS = "CronetProviderClassName";
private static final String TAG = CronetProvider.class.getSimpleName();
protected final Context mContext;
protected CronetProvider(Context context) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null");
}
mContext = context;
}
/**
* Creates and returns an instance of {@link CronetEngine.Builder}.
* <p/>
* <b>NOTE:</b> This class is for advanced users that want to select a particular
* Cronet implementation. Most users should simply use {@code new} {@link
* CronetEngine.Builder#CronetEngine.Builder(android.content.Context)}.
*
* @return {@code CronetEngine.Builder}.
* @throws IllegalStateException if the provider is not enabled (see {@link #isEnabled}.
*/
public abstract CronetEngine.Builder createBuilder();
/**
* Returns the provider name. The well-know provider names include:
* <ul>
* <li>{@link #PROVIDER_NAME_APP_PACKAGED}</li>
* <li>{@link #PROVIDER_NAME_FALLBACK}</li>
* </ul>
*
* @return provider name.
*/
public abstract String getName();
/**
* Returns the provider version. The version can be used to select the newest
* available provider if multiple providers are available.
*
* @return provider version.
*/
public abstract String getVersion();
/**
* Returns whether the provider is enabled and can be used to instantiate the Cronet engine.
* A provider being out-of-date (older than the API) and needing updating is one potential
* reason it could be disabled. Please read the provider documentation for
* enablement procedure.
*
* @return {@code true} if the provider is enabled.
*/
public abstract boolean isEnabled();
@Override
public String toString() {
return "["
+ "class=" + getClass().getName() + ", "
+ "name=" + getName() + ", "
+ "version=" + getVersion() + ", "
+ "enabled=" + isEnabled() + "]";
}
/**
* Name of the Java {@link CronetProvider} class.
*/
private static final String JAVA_CRONET_PROVIDER_CLASS =
"org.chromium.net.impl.JavaCronetProvider";
/**
* Name of the native {@link CronetProvider} class.
*/
private static final String NATIVE_CRONET_PROVIDER_CLASS =
"org.chromium.net.impl.NativeCronetProvider";
/**
* {@link CronetProvider} class that is packaged with Google Play Services.
*/
private static final String PLAY_SERVICES_CRONET_PROVIDER_CLASS =
"com.google.android.gms.net.PlayServicesCronetProvider";
/**
* {@link CronetProvider} a deprecated class that may be packaged with
* some old versions of Google Play Services.
*/
private static final String GMS_CORE_CRONET_PROVIDER_CLASS =
"com.google.android.gms.net.GmsCoreCronetProvider";
/**
* Returns an unmodifiable list of all available {@link CronetProvider}s.
* The providers are returned in no particular order. Some of the returned
* providers may be in a disabled state and should be enabled by the invoker.
* See {@link CronetProvider#isEnabled()}.
*
* @return the list of available providers.
*/
public static List<CronetProvider> getAllProviders(Context context) {
// Use LinkedHashSet to preserve the order and eliminate duplicate providers.
Set<CronetProvider> providers = new LinkedHashSet<>();
addCronetProviderFromResourceFile(context, providers);
addCronetProviderImplByClassName(
context, PLAY_SERVICES_CRONET_PROVIDER_CLASS, providers, false);
addCronetProviderImplByClassName(context, GMS_CORE_CRONET_PROVIDER_CLASS, providers, false);
addCronetProviderImplByClassName(context, NATIVE_CRONET_PROVIDER_CLASS, providers, false);
addCronetProviderImplByClassName(context, JAVA_CRONET_PROVIDER_CLASS, providers, false);
return Collections.unmodifiableList(new ArrayList<>(providers));
}
/**
* Attempts to add a new provider referenced by the class name to a set.
*
* @param className the class name of the provider that should be instantiated.
* @param providers the set of providers to add the new provider to.
* @return {@code true} if the provider was added to the set; {@code false}
* if the provider couldn't be instantiated.
*/
private static boolean addCronetProviderImplByClassName(
Context context, String className, Set<CronetProvider> providers, boolean logError) {
ClassLoader loader = context.getClassLoader();
try {
Class<? extends CronetProvider> providerClass =
loader.loadClass(className).asSubclass(CronetProvider.class);
Constructor<? extends CronetProvider> ctor =
providerClass.getConstructor(Context.class);
providers.add(ctor.newInstance(context));
return true;
} catch (InstantiationException e) {
logReflectiveOperationException(className, logError, e);
} catch (InvocationTargetException e) {
logReflectiveOperationException(className, logError, e);
} catch (NoSuchMethodException e) {
logReflectiveOperationException(className, logError, e);
} catch (IllegalAccessException e) {
logReflectiveOperationException(className, logError, e);
} catch (ClassNotFoundException e) {
logReflectiveOperationException(className, logError, e);
}
return false;
}
/**
* De-duplicates exception handling logic in {@link #addCronetProviderImplByClassName}.
* It should be removed when support of API Levels lower than 19 is deprecated.
*/
private static void logReflectiveOperationException(
String className, boolean logError, Exception e) {
if (logError) {
Log.e(TAG, "Unable to load provider class: " + className, e);
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG,
"Tried to load " + className + " provider class but it wasn't"
+ " included in the app classpath");
}
}
}
/**
* Attempts to add a provider specified in the app resource file to a set.
*
* @param providers the set of providers to add the new provider to.
* @return {@code true} if the provider was added to the set; {@code false}
* if the app resources do not include the string with
* {@link #RES_KEY_CRONET_IMPL_CLASS} key.
* @throws RuntimeException if the provider cannot be found or instantiated.
*/
private static boolean addCronetProviderFromResourceFile(
Context context, Set<CronetProvider> providers) {
int resId = context.getResources().getIdentifier(
RES_KEY_CRONET_IMPL_CLASS, "string", context.getPackageName());
// Resource not found
if (resId == 0) {
// The resource wasn't included in the app; therefore, there is nothing to add.
return false;
}
String className = context.getResources().getString(resId);
// If the resource specifies a well known provider, don't load it because
// there will be an attempt to load it anyways.
if (className == null || className.equals(PLAY_SERVICES_CRONET_PROVIDER_CLASS)
|| className.equals(GMS_CORE_CRONET_PROVIDER_CLASS)
|| className.equals(JAVA_CRONET_PROVIDER_CLASS)
|| className.equals(NATIVE_CRONET_PROVIDER_CLASS)) {
return false;
}
if (!addCronetProviderImplByClassName(context, className, providers, true)) {
Log.e(TAG,
"Unable to instantiate Cronet implementation class " + className
+ " that is listed as in the app string resource file under "
+ RES_KEY_CRONET_IMPL_CLASS + " key");
}
return true;
}
}

View File

@ -0,0 +1,101 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net;
/**
* {@link BidirectionalStream} that exposes experimental features. To obtain an
* instance of this class, cast a {@code BidirectionalStream} to this type. Every
* instance of {@code BidirectionalStream} can be cast to an instance of this class,
* as they are backed by the same implementation and hence perform identically.
* Instances of this class are not meant for general use, but instead only
* to access experimental features. Experimental features may be deprecated in the
* future. Use at your own risk.
*
* {@hide prototype}
*/
public abstract class ExperimentalBidirectionalStream extends BidirectionalStream {
/**
* {@link BidirectionalStream#Builder} that exposes experimental features. To obtain an
* instance of this class, cast a {@code BidirectionalStream.Builder} to this type. Every
* instance of {@code BidirectionalStream.Builder} can be cast to an instance of this class,
* as they are backed by the same implementation and hence perform identically.
* Instances of this class are not meant for general use, but instead only
* to access experimental features. Experimental features may be deprecated in the
* future. Use at your own risk.
*/
public abstract static class Builder extends BidirectionalStream.Builder {
/**
* Associates the annotation object with this request. May add more than one.
* Passed through to a {@link RequestFinishedInfo.Listener},
* see {@link RequestFinishedInfo#getAnnotations}.
*
* @param annotation an object to pass on to the {@link RequestFinishedInfo.Listener} with a
* {@link RequestFinishedInfo}.
* @return the builder to facilitate chaining.
*/
public Builder addRequestAnnotation(Object annotation) {
return this;
}
/**
* Sets {@link android.net.TrafficStats} tag to use when accounting socket traffic caused by
* this request. See {@link android.net.TrafficStats} for more information. If no tag is
* set (e.g. this method isn't called), then Android accounts for the socket traffic caused
* by this request as if the tag value were set to 0.
* <p>
* <b>NOTE:</b>Setting a tag disallows sharing of sockets with requests
* with other tags, which may adversely effect performance by prohibiting
* connection sharing. In other words use of multiplexed sockets (e.g. HTTP/2
* and QUIC) will only be allowed if all requests have the same socket tag.
*
* @param tag the tag value used to when accounting for socket traffic caused by this
* request. Tags between 0xFFFFFF00 and 0xFFFFFFFF are reserved and used
* internally by system services like {@link android.app.DownloadManager} when
* performing traffic on behalf of an application.
* @return the builder to facilitate chaining.
*/
public Builder setTrafficStatsTag(int tag) {
return this;
}
/**
* Sets specific UID to use when accounting socket traffic caused by this request. See
* {@link android.net.TrafficStats} for more information. Designed for use when performing
* an operation on behalf of another application. Caller must hold
* {@link android.Manifest.permission#MODIFY_NETWORK_ACCOUNTING} permission. By default
* traffic is attributed to UID of caller.
* <p>
* <b>NOTE:</b>Setting a UID disallows sharing of sockets with requests
* with other UIDs, which may adversely effect performance by prohibiting
* connection sharing. In other words use of multiplexed sockets (e.g. HTTP/2
* and QUIC) will only be allowed if all requests have the same UID set.
*
* @param uid the UID to attribute socket traffic caused by this request.
* @return the builder to facilitate chaining.
*/
public Builder setTrafficStatsUid(int uid) {
return this;
}
// To support method chaining, override superclass methods to return an
// instance of this class instead of the parent.
@Override
public abstract Builder setHttpMethod(String method);
@Override
public abstract Builder addHeader(String header, String value);
@Override
public abstract Builder setPriority(int priority);
@Override
public abstract Builder delayRequestHeadersUntilFirstFlush(
boolean delayRequestHeadersUntilFirstFlush);
@Override
public abstract ExperimentalBidirectionalStream build();
}
}

View File

@ -0,0 +1,413 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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 androidx.annotation.VisibleForTesting;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.Executor;
/**
* {@link CronetEngine} that exposes experimental features. To obtain an
* instance of this class, cast a {@code CronetEngine} to this type. Every
* instance of {@code CronetEngine} can be cast to an instance of this class,
* as they are backed by the same implementation and hence perform identically.
* Instances of this class are not meant for general use, but instead only
* to access experimental features. Experimental features may be deprecated in the
* future. Use at your own risk.
*
* {@hide since this class exposes experimental features that should be hidden.}
*/
public abstract class ExperimentalCronetEngine extends CronetEngine {
/**
* 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;
/**
* A version of {@link CronetEngine.Builder} that exposes experimental
* features. Instances of this class are not meant for general use, but
* instead only to access experimental features. Experimental features
* may be deprecated in the future. Use at your own risk.
*/
public static class Builder extends CronetEngine.Builder {
/**
* Constructs a {@link Builder} object that facilitates creating a
* {@link CronetEngine}. The default configuration enables HTTP/2 and
* disables QUIC, SDCH and 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) {
super(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) {
super(builderDelegate);
}
/**
* Enables the network quality estimator, which collects and reports
* measurements of round trip time (RTT) and downstream throughput at
* various layers of the network stack. After enabling the estimator,
* listeners of RTT and throughput can be added with
* {@link #addRttListener} and {@link #addThroughputListener} and
* removed with {@link #removeRttListener} and
* {@link #removeThroughputListener}. The estimator uses memory and CPU
* only when enabled.
* @param value {@code true} to enable network quality estimator,
* {@code false} to disable.
* @return the builder to facilitate chaining.
*/
public Builder enableNetworkQualityEstimator(boolean value) {
mBuilderDelegate.enableNetworkQualityEstimator(value);
return this;
}
/**
* Sets experimental options to be used in Cronet.
*
* @param options JSON formatted experimental options.
* @return the builder to facilitate chaining.
*/
public Builder setExperimentalOptions(String options) {
mBuilderDelegate.setExperimentalOptions(options);
return this;
}
/**
* Sets the thread priority of Cronet's internal thread.
*
* @param priority the thread priority of Cronet's internal thread.
* A Linux priority level, from -20 for highest scheduling
* priority to 19 for lowest scheduling priority. For more
* information on values, see
* {@link android.os.Process#setThreadPriority(int, int)} and
* {@link android.os.Process#THREAD_PRIORITY_DEFAULT
* THREAD_PRIORITY_*} values.
* @return the builder to facilitate chaining.
*/
public Builder setThreadPriority(int priority) {
mBuilderDelegate.setThreadPriority(priority);
return this;
}
/**
* Returns delegate, only for testing.
* @hide
*/
@VisibleForTesting
public ICronetEngineBuilder getBuilderDelegate() {
return mBuilderDelegate;
}
// To support method chaining, override superclass methods to return an
// instance of this class instead of the parent.
@Override
public Builder setUserAgent(String userAgent) {
super.setUserAgent(userAgent);
return this;
}
@Override
public Builder setStoragePath(String value) {
super.setStoragePath(value);
return this;
}
@Override
public Builder setLibraryLoader(LibraryLoader loader) {
super.setLibraryLoader(loader);
return this;
}
@Override
public Builder enableQuic(boolean value) {
super.enableQuic(value);
return this;
}
@Override
public Builder enableHttp2(boolean value) {
super.enableHttp2(value);
return this;
}
@Override
public Builder enableSdch(boolean value) {
return this;
}
@Override
public Builder enableHttpCache(int cacheMode, long maxSize) {
super.enableHttpCache(cacheMode, maxSize);
return this;
}
@Override
public Builder addQuicHint(String host, int port, int alternatePort) {
super.addQuicHint(host, port, alternatePort);
return this;
}
@Override
public Builder addPublicKeyPins(String hostName, Set<byte[]> pinsSha256,
boolean includeSubdomains, Date expirationDate) {
super.addPublicKeyPins(hostName, pinsSha256, includeSubdomains, expirationDate);
return this;
}
@Override
public Builder enablePublicKeyPinningBypassForLocalTrustAnchors(boolean value) {
super.enablePublicKeyPinningBypassForLocalTrustAnchors(value);
return this;
}
@Override
public ExperimentalCronetEngine build() {
return mBuilderDelegate.build();
}
}
/**
* 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.
*/
public abstract ExperimentalBidirectionalStream.Builder newBidirectionalStreamBuilder(
String url, BidirectionalStream.Callback callback, Executor executor);
@Override
public abstract ExperimentalUrlRequest.Builder newUrlRequestBuilder(
String url, UrlRequest.Callback callback, Executor executor);
/**
* 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) {}
/**
* 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) {}
/**
* Establishes a new connection to the resource specified by the {@link URL} {@code url}
* using the given proxy.
* <p>
* <b>Note:</b> 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.
* @param proxy proxy to use when establishing connection.
* @return an {@link java.net.HttpURLConnection} instance implemented by this CronetEngine.
* @throws IOException if an error occurs while opening the connection.
*/
// TODO(pauljensen): Expose once implemented, http://crbug.com/418111
public URLConnection openConnection(URL url, Proxy proxy) throws IOException {
return url.openConnection(proxy);
}
/**
* Registers a listener that gets called after the end of each request with the request info.
*
* <p>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;
}
}

View File

@ -0,0 +1,162 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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 java.util.concurrent.Executor;
/**
* {@link UrlRequest} that exposes experimental features. To obtain an
* instance of this class, cast a {@code UrlRequest} to this type. Every
* instance of {@code UrlRequest} can be cast to an instance of this class,
* as they are backed by the same implementation and hence perform identically.
* Instances of this class are not meant for general use, but instead only
* to access experimental features. Experimental features may be deprecated in the
* future. Use at your own risk.
*
* {@hide since this class exposes experimental features that should be hidden}.
*/
public abstract class ExperimentalUrlRequest extends UrlRequest {
/**
* {@link UrlRequest#Builder} that exposes experimental features. To obtain an
* instance of this class, cast a {@code UrlRequest.Builder} to this type. Every
* instance of {@code UrlRequest.Builder} can be cast to an instance of this class,
* as they are backed by the same implementation and hence perform identically.
* Instances of this class are not meant for general use, but instead only
* to access experimental features. Experimental features may be deprecated in the
* future. Use at your own risk.
*/
public abstract static class Builder extends UrlRequest.Builder {
/**
* Disables connection migration for the request if enabled for
* the session.
* @return the builder to facilitate chaining.
*/
public Builder disableConnectionMigration() {
return this;
}
/**
* Associates the annotation object with this request. May add more than one.
* Passed through to a {@link RequestFinishedInfo.Listener},
* see {@link RequestFinishedInfo#getAnnotations}.
*
* @param annotation an object to pass on to the {@link RequestFinishedInfo.Listener} with a
* {@link RequestFinishedInfo}.
* @return the builder to facilitate chaining.
*/
public Builder addRequestAnnotation(Object annotation) {
return this;
}
/**
* Sets {@link android.net.TrafficStats} tag to use when accounting socket traffic caused by
* this request. See {@link android.net.TrafficStats} for more information. If no tag is
* set (e.g. this method isn't called), then Android accounts for the socket traffic caused
* by this request as if the tag value were set to 0.
* <p>
* <b>NOTE:</b>Setting a tag disallows sharing of sockets with requests
* with other tags, which may adversely effect performance by prohibiting
* connection sharing. In other words use of multiplexed sockets (e.g. HTTP/2
* and QUIC) will only be allowed if all requests have the same socket tag.
*
* @param tag the tag value used to when accounting for socket traffic caused by this
* request. Tags between 0xFFFFFF00 and 0xFFFFFFFF are reserved and used
* internally by system services like {@link android.app.DownloadManager} when
* performing traffic on behalf of an application.
* @return the builder to facilitate chaining.
*/
public Builder setTrafficStatsTag(int tag) {
return this;
}
/**
* Sets specific UID to use when accounting socket traffic caused by this request. See
* {@link android.net.TrafficStats} for more information. Designed for use when performing
* an operation on behalf of another application. Caller must hold
* {@link android.Manifest.permission#MODIFY_NETWORK_ACCOUNTING} permission. By default
* traffic is attributed to UID of caller.
* <p>
* <b>NOTE:</b>Setting a UID disallows sharing of sockets with requests
* with other UIDs, which may adversely effect performance by prohibiting
* connection sharing. In other words use of multiplexed sockets (e.g. HTTP/2
* and QUIC) will only be allowed if all requests have the same UID set.
*
* @param uid the UID to attribute socket traffic caused by this request.
* @return the builder to facilitate chaining.
*/
public Builder setTrafficStatsUid(int uid) {
return this;
}
/**
* Sets a listener that gets invoked after {@link Callback#onCanceled onCanceled()},
* {@link Callback#onFailed onFailed()} or {@link Callback#onSucceeded onSucceeded()}
* return.
*
* <p>The listener is invoked with the request finished info on an
* {@link java.util.concurrent.Executor} provided by
* {@link RequestFinishedInfo.Listener#getExecutor getExecutor()}.
*
* @param listener the listener for finished requests.
* @return the builder to facilitate chaining.
*/
public Builder setRequestFinishedListener(RequestFinishedInfo.Listener listener) {
return this;
}
/**
* Default request idempotency, only enable 0-RTT for safe HTTP methods. Passed to {@link
* #setIdempotency}.
*/
public static final int DEFAULT_IDEMPOTENCY = 0;
/**
* Request is idempotent. Passed to {@link #setIdempotency}.
*/
public static final int IDEMPOTENT = 1;
/**
* Request is not idempotent. Passed to {@link #setIdempotency}.
*/
public static final int NOT_IDEMPOTENT = 2;
/**
* Sets idempotency of the request which should be one of the {@link #DEFAULT_IDEMPOTENCY
* IDEMPOTENT NOT_IDEMPOTENT} values. The default idempotency indicates that 0-RTT is only
* enabled for safe HTTP methods (GET, HEAD, OPTIONS, and TRACE).
*
* @param idempotency idempotency of the request which should be one of the {@link
* #DEFAULT_IDEMPOTENCY IDEMPOTENT NOT_IDEMPOTENT} values.
* @return the builder to facilitate chaining.
*/
public Builder setIdempotency(int idempotency) {
return this;
}
// To support method chaining, override superclass methods to return an
// instance of this class instead of the parent.
@Override
public abstract Builder setHttpMethod(String method);
@Override
public abstract Builder addHeader(String header, String value);
@Override
public abstract Builder disableCache();
@Override
public abstract Builder setPriority(int priority);
@Override
public abstract Builder setUploadDataProvider(
UploadDataProvider uploadDataProvider, Executor executor);
@Override
public abstract Builder allowDirectExecutor();
@Override
public abstract ExperimentalUrlRequest build();
}
}

View File

@ -0,0 +1,54 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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 java.util.Date;
import java.util.Set;
/**
* Defines methods that the actual implementation of {@link CronetEngine.Builder} has to implement.
* {@code CronetEngine.Builder} uses this interface to delegate the calls.
* For the documentation of individual methods, please see the identically named methods in
* {@link org.chromium.net.CronetEngine.Builder} and
* {@link org.chromium.net.ExperimentalCronetEngine.Builder}.
*
* {@hide internal class}
*/
public abstract class ICronetEngineBuilder {
// Public API methods.
public abstract ICronetEngineBuilder addPublicKeyPins(String hostName, Set<byte[]> pinsSha256,
boolean includeSubdomains, Date expirationDate);
public abstract ICronetEngineBuilder addQuicHint(String host, int port, int alternatePort);
public abstract ICronetEngineBuilder enableHttp2(boolean value);
public abstract ICronetEngineBuilder enableHttpCache(int cacheMode, long maxSize);
public abstract ICronetEngineBuilder enablePublicKeyPinningBypassForLocalTrustAnchors(
boolean value);
public abstract ICronetEngineBuilder enableQuic(boolean value);
public abstract ICronetEngineBuilder enableSdch(boolean value);
public ICronetEngineBuilder enableBrotli(boolean value) {
// Do nothing for older implementations.
return this;
}
public abstract ICronetEngineBuilder setExperimentalOptions(String options);
public abstract ICronetEngineBuilder setLibraryLoader(
CronetEngine.Builder.LibraryLoader loader);
public abstract ICronetEngineBuilder setStoragePath(String value);
public abstract ICronetEngineBuilder setUserAgent(String userAgent);
public abstract String getDefaultUserAgent();
public abstract ExperimentalCronetEngine build();
// Experimental API methods.
//
// Note: all experimental API methods should have default implementation. This will allow
// removing the experimental methods from the implementation layer without breaking
// the client.
public ICronetEngineBuilder enableNetworkQualityEstimator(boolean value) {
return this;
}
public ICronetEngineBuilder setThreadPriority(int priority) {
return this;
}
}

View File

@ -0,0 +1,18 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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 java.util.concurrent.RejectedExecutionException;
/**
* Thrown when an executor runs a submitted runnable inline in {@link
* java.util.concurrent.Executor#execute(Runnable)} and {@link
* UrlRequest.Builder#allowDirectExecutor} was not called.
*/
public final class InlineExecutionProhibitedException extends RejectedExecutionException {
public InlineExecutionProhibitedException() {
super("Inline execution is prohibited for this request");
}
}

View File

@ -0,0 +1,111 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net;
/**
* Exception passed to {@link UrlRequest.Callback#onFailed UrlRequest.Callback.onFailed()} when
* Cronet fails to process a network request. In this case {@link #getErrorCode} and
* {@link #getCronetInternalErrorCode} can be used to get more information about the specific
* type of failure. If {@link #getErrorCode} returns {@link #ERROR_QUIC_PROTOCOL_FAILED},
* this exception can be cast to a {@link QuicException} which can provide further details.
*/
public abstract class NetworkException extends CronetException {
/**
* Error code indicating the host being sent the request could not be resolved to an IP address.
*/
public static final int ERROR_HOSTNAME_NOT_RESOLVED = 1;
/**
* Error code indicating the device was not connected to any network.
*/
public static final int ERROR_INTERNET_DISCONNECTED = 2;
/**
* Error code indicating that as the request was processed the network configuration changed.
* When {@link #getErrorCode} returns this code, this exception may be cast to
* {@link QuicException} for more information if <a href="https://www.chromium.org/quic">
* QUIC</a> protocol is used.
*/
public static final int ERROR_NETWORK_CHANGED = 3;
/**
* Error code indicating a timeout expired. Timeouts expiring while attempting to connect will
* be reported as the more specific {@link #ERROR_CONNECTION_TIMED_OUT}.
*/
public static final int ERROR_TIMED_OUT = 4;
/**
* Error code indicating the connection was closed unexpectedly.
*/
public static final int ERROR_CONNECTION_CLOSED = 5;
/**
* Error code indicating the connection attempt timed out.
*/
public static final int ERROR_CONNECTION_TIMED_OUT = 6;
/**
* Error code indicating the connection attempt was refused.
*/
public static final int ERROR_CONNECTION_REFUSED = 7;
/**
* Error code indicating the connection was unexpectedly reset.
*/
public static final int ERROR_CONNECTION_RESET = 8;
/**
* Error code indicating the IP address being contacted is unreachable, meaning there is no
* route to the specified host or network.
*/
public static final int ERROR_ADDRESS_UNREACHABLE = 9;
/**
* Error code indicating an error related to the <a href="https://www.chromium.org/quic">
* QUIC</a> protocol. When {@link #getErrorCode} returns this code, this exception can be cast
* to {@link QuicException} for more information.
*/
public static final int ERROR_QUIC_PROTOCOL_FAILED = 10;
/**
* Error code indicating another type of error was encountered.
* {@link #getCronetInternalErrorCode} can be consulted to get a more specific cause.
*/
public static final int ERROR_OTHER = 11;
/**
* Constructs an exception that is caused by a network error.
*
* @param message explanation of failure.
* @param cause the cause (which is saved for later retrieval by the {@link
* java.io.IOException#getCause getCause()} method). A null value is permitted, and
* indicates that the cause is nonexistent or unknown.
*/
protected NetworkException(String message, Throwable cause) {
super(message, cause);
}
/**
* Returns error code, one of {@link #ERROR_HOSTNAME_NOT_RESOLVED ERROR_*}.
*
* @return error code, one of {@link #ERROR_HOSTNAME_NOT_RESOLVED ERROR_*}.
*/
public abstract int getErrorCode();
/**
* Returns a Cronet internal error code. This may provide more specific error
* diagnosis than {@link #getErrorCode}, but the constant values are not exposed to Java and
* may change over time. See
* <a href=https://chromium.googlesource.com/chromium/src/+/main/net/base/net_error_list.h>
* here</a> for the lastest list of values.
*
* @return Cronet internal error code.
*/
public abstract int getCronetInternalErrorCode();
/**
* Returns {@code true} if retrying this request right away might succeed, {@code false}
* otherwise. For example returns {@code true} when {@link #getErrorCode} returns
* {@link #ERROR_NETWORK_CHANGED} because trying the request might succeed using the new
* network configuration, but {@code false} when {@code getErrorCode()} returns
* {@link #ERROR_INTERNET_DISCONNECTED} because retrying the request right away will
* encounter the same failure (instead retrying should be delayed until device regains
* network connectivity).
*
* @return {@code true} if retrying this request right away might succeed, {@code false}
* otherwise.
*/
public abstract boolean immediatelyRetryable();
}

View File

@ -0,0 +1,44 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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 java.util.concurrent.Executor;
/**
* Watches observations of various round trip times (RTTs) at various layers of
* the network stack. These include RTT estimates by QUIC and TCP, as well as
* the time between when a URL request is sent and when the first byte of the
* response is received.
* {@hide} as it's a prototype.
*/
public abstract class NetworkQualityRttListener {
/**
* The executor on which this listener will be notified. Set as a final
* field, so it can be safely accessed across threads.
*/
private final Executor mExecutor;
/**
* @param executor The executor on which the observations are reported.
*/
public NetworkQualityRttListener(Executor executor) {
if (executor == null) {
throw new IllegalStateException("Executor must not be null");
}
mExecutor = executor;
}
public Executor getExecutor() {
return mExecutor;
}
/**
* Reports a new round trip time observation.
* @param rttMs the round trip time in milliseconds.
* @param whenMs milliseconds since the Epoch (January 1st 1970, 00:00:00.000).
* @param source the observation source from {@link NetworkQualityObservationSource}.
*/
public abstract void onRttObservation(int rttMs, long whenMs, int source);
}

View File

@ -0,0 +1,42 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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 java.util.concurrent.Executor;
/**
* Listener that is notified of throughput observations from the network quality
* estimator.
* {@hide} as it's a prototype.
*/
public abstract class NetworkQualityThroughputListener {
/**
* The executor on which this listener will be notified. Set as a final
* field, so it can be safely accessed across threads.
*/
private final Executor mExecutor;
/**
* @param executor The executor on which the observations are reported.
*/
public NetworkQualityThroughputListener(Executor executor) {
if (executor == null) {
throw new IllegalStateException("Executor must not be null");
}
mExecutor = executor;
}
public Executor getExecutor() {
return mExecutor;
}
/**
* Reports a new throughput observation.
* @param throughputKbps the downstream throughput in kilobits per second.
* @param whenMs milliseconds since the Epoch (January 1st 1970, 00:00:00.000).
* @param source the observation source from {@link NetworkQualityObservationSource}.
*/
public abstract void onThroughputObservation(int throughputKbps, long whenMs, int source);
}

View File

@ -0,0 +1,35 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net;
/**
* Subclass of {@link NetworkException} which contains a detailed
* <a href="https://www.chromium.org/quic">QUIC</a> error code from <a
* href="https://cs.chromium.org/search/?q=symbol:%5CbQuicErrorCode%5Cb">
* QuicErrorCode</a>. An instance of {@code QuicException} is passed to {@code onFailed} callbacks
* when the error code is {@link NetworkException#ERROR_QUIC_PROTOCOL_FAILED
* NetworkException.ERROR_QUIC_PROTOCOL_FAILED}.
*/
public abstract class QuicException extends NetworkException {
/**
* Constructs an exception that is caused by a QUIC protocol error.
*
* @param message explanation of failure.
* @param cause the cause (which is saved for later retrieval by the {@link
* java.io.IOException#getCause getCause()} method). A null value is permitted, and
* indicates that the cause is nonexistent or unknown.
*/
protected QuicException(String message, Throwable cause) {
super(message, cause);
}
/**
* Returns the <a href="https://www.chromium.org/quic">QUIC</a> error code, which is a value
* from <a
* href="https://cs.chromium.org/search/?q=symbol:%5CbQuicErrorCode%5Cb">
* QuicErrorCode</a>.
*/
public abstract int getQuicDetailedErrorCode();
}

View File

@ -0,0 +1,320 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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 androidx.annotation.Nullable;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.Executor;
/**
* Information about a finished request. Passed to {@link RequestFinishedInfo.Listener}.
*
* To associate the data with the original request, use
* {@link ExperimentalUrlRequest.Builder#addRequestAnnotation} or
* {@link ExperimentalBidirectionalStream.Builder#addRequestAnnotation} to add a unique identifier
* when creating the request, and call {@link #getAnnotations} when the {@link RequestFinishedInfo}
* is received to retrieve the identifier.
*
* {@hide} as it's a prototype.
*/
public abstract class RequestFinishedInfo {
/**
* Listens for finished requests for the purpose of collecting metrics.
*
* {@hide} as it's a prototype.
*/
public abstract static class Listener {
private final Executor mExecutor;
public Listener(Executor executor) {
if (executor == null) {
throw new IllegalStateException("Executor must not be null");
}
mExecutor = executor;
}
/**
* Invoked with request info. Will be called in a task submitted to the
* {@link java.util.concurrent.Executor} returned by {@link #getExecutor}.
* @param requestInfo {@link RequestFinishedInfo} for finished request.
*/
public abstract void onRequestFinished(RequestFinishedInfo requestInfo);
/**
* Returns this listener's executor. Can be called on any thread.
* @return this listener's {@link java.util.concurrent.Executor}
*/
public Executor getExecutor() {
return mExecutor;
}
}
/**
* Metrics collected for a single request. Most of these metrics are timestamps for events
* during the lifetime of the request, which can be used to build a detailed timeline for
* investigating performance.
*
* Events happen in this order:
* <ol>
* <li>{@link #getRequestStart request start}</li>
* <li>{@link #getDnsStart DNS start}</li>
* <li>{@link #getDnsEnd DNS end}</li>
* <li>{@link #getConnectStart connect start}</li>
* <li>{@link #getSslStart SSL start}</li>
* <li>{@link #getSslEnd SSL end}</li>
* <li>{@link #getConnectEnd connect end}</li>
* <li>{@link #getSendingStart sending start}</li>
* <li>{@link #getSendingEnd sending end}</li>
* <li>{@link #getResponseStart response start}</li>
* <li>{@link #getRequestEnd request end}</li>
* </ol>
*
* Start times are reported as the time when a request started blocking on event, not when the
* event actually occurred, with the exception of push start and end. If a metric is not
* meaningful or not available, including cases when a request finished before reaching that
* stage, start and end times will be {@code null}. If no time was spent blocking on an event,
* start and end will be the same time.
*
* If the system clock is adjusted during the request, some of the {@link java.util.Date} values
* might not match it. Timestamps are recorded using a clock that is guaranteed not to run
* backwards. All timestamps are correct relative to the system clock at the time of request
* start, and taking the difference between two timestamps will give the correct difference
* between the events. In order to preserve this property, timestamps for events other than
* request start are not guaranteed to match the system clock at the times they represent.
*
* Most timing metrics are taken from
* <a
* href="https://cs.chromium.org/chromium/src/net/base/load_timing_info.h">LoadTimingInfo</a>,
* which holds the information for <a href="http://w3c.github.io/navigation-timing/"></a> and
* <a href="https://www.w3.org/TR/resource-timing/"></a>.
*
* {@hide} as it's a prototype.
*/
public abstract static class Metrics {
/**
* Returns time when the request started.
* @return {@link java.util.Date} representing when the native request actually started.
* This timestamp will match the system clock at the time it represents.
*/
@Nullable
public abstract Date getRequestStart();
/**
* Returns time when DNS lookup started. This and {@link #getDnsEnd} will return non-null
* values regardless of whether the result came from a DNS server or the local cache.
* @return {@link java.util.Date} representing when DNS lookup started. {@code null} if the
* socket was reused (see {@link #getSocketReused}).
*/
@Nullable
public abstract Date getDnsStart();
/**
* Returns time when DNS lookup finished. This and {@link #getDnsStart} will return non-null
* values regardless of whether the result came from a DNS server or the local cache.
* @return {@link java.util.Date} representing when DNS lookup finished. {@code null} if the
* socket was reused (see {@link #getSocketReused}).
*/
@Nullable
public abstract Date getDnsEnd();
/**
* Returns time when connection establishment started.
* @return {@link java.util.Date} representing when connection establishment started,
* typically when DNS resolution finishes. {@code null} if the socket was reused (see
* {@link #getSocketReused}).
*/
@Nullable
public abstract Date getConnectStart();
/**
* Returns time when connection establishment finished.
* @return {@link java.util.Date} representing when connection establishment finished,
* after TCP connection is established and, if using HTTPS, SSL handshake is completed.
* For QUIC 0-RTT, this represents the time of handshake confirmation and might happen
* later than {@link #getSendingStart}.
* {@code null} if the socket was reused (see {@link #getSocketReused}).
*/
@Nullable
public abstract Date getConnectEnd();
/**
* Returns time when SSL handshake started. For QUIC, this will be the same time as
* {@link #getConnectStart}.
* @return {@link java.util.Date} representing when SSL handshake started. {@code null} if
* SSL is not used or if the socket was reused (see {@link #getSocketReused}).
*/
@Nullable
public abstract Date getSslStart();
/**
* Returns time when SSL handshake finished. For QUIC, this will be the same time as
* {@link #getConnectEnd}.
* @return {@link java.util.Date} representing when SSL handshake finished. {@code null} if
* SSL is not used or if the socket was reused (see {@link #getSocketReused}).
*/
@Nullable
public abstract Date getSslEnd();
/**
* Returns time when sending the request started.
* @return {@link java.util.Date} representing when sending HTTP request headers started.
*/
@Nullable
public abstract Date getSendingStart();
/**
* Returns time when sending the request finished.
* @return {@link java.util.Date} representing when sending HTTP request body finished.
* (Sending request body happens after sending request headers.)
*/
@Nullable
public abstract Date getSendingEnd();
/**
* Returns time when first byte of HTTP/2 server push was received.
* @return {@link java.util.Date} representing when the first byte of an HTTP/2 server push
* was received. {@code null} if server push is not used.
*/
@Nullable
public abstract Date getPushStart();
/**
* Returns time when last byte of HTTP/2 server push was received.
* @return {@link java.util.Date} representing when the last byte of an HTTP/2 server push
* was received. {@code null} if server push is not used.
*/
@Nullable
public abstract Date getPushEnd();
/**
* Returns time when the end of the response headers was received.
* @return {@link java.util.Date} representing when the end of the response headers was
* received.
*/
@Nullable
public abstract Date getResponseStart();
/**
* Returns time when the request finished.
* @return {@link java.util.Date} representing when the request finished.
*/
@Nullable
public abstract Date getRequestEnd();
/**
* Returns whether the socket was reused from a previous request. In HTTP/2 or QUIC, if
* streams are multiplexed in a single connection, returns {@code true} for all streams
* after the first.
* @return whether this request reused a socket from a previous request. When {@code true},
* DNS, connection, and SSL times will be {@code null}.
*/
public abstract boolean getSocketReused();
/**
* Returns milliseconds between request initiation and first byte of response headers,
* or {@code null} if not collected.
* TODO(mgersh): Remove once new API works http://crbug.com/629194
* {@hide}
*/
@Nullable
public abstract Long getTtfbMs();
/**
* Returns milliseconds between request initiation and finish,
* including a failure or cancellation, or {@code null} if not collected.
* TODO(mgersh): Remove once new API works http://crbug.com/629194
* {@hide}
*/
@Nullable
public abstract Long getTotalTimeMs();
/**
* Returns total bytes sent over the network transport layer, or {@code null} if not
* collected.
*/
@Nullable
public abstract Long getSentByteCount();
/**
* Returns total bytes received over the network transport layer, or {@code null} if not
* collected. Number of bytes does not include any previous redirects.
*/
@Nullable
public abstract Long getReceivedByteCount();
}
/**
* Reason value indicating that the request succeeded. Returned from {@link #getFinishedReason}.
*/
public static final int SUCCEEDED = 0;
/**
* Reason value indicating that the request failed or returned an error. Returned from
* {@link #getFinishedReason}.
*/
public static final int FAILED = 1;
/**
* Reason value indicating that the request was canceled. Returned from
* {@link #getFinishedReason}.
*/
public static final int CANCELED = 2;
/**
* Returns the request's original URL.
*
* @return the request's original URL
*/
public abstract String getUrl();
/**
* Returns the objects that the caller has supplied when initiating the request, using
* {@link ExperimentalUrlRequest.Builder#addRequestAnnotation} or
* {@link ExperimentalBidirectionalStream.Builder#addRequestAnnotation}.
* Annotations can be used to associate a {@link RequestFinishedInfo} with the original request
* or type of request.
*
* @return annotations supplied when creating the request
*/
public abstract Collection<Object> getAnnotations();
// TODO(klm): Collect and return a chain of Metrics objects for redirect responses.
// TODO(mgersh): Update this javadoc when new metrics are fully implemented
/**
* Returns metrics collected for this request.
*
* <p>The reported times and bytes account for all redirects, i.e.
* the TTFB is from the start of the original request to the ultimate response headers,
* the TTLB is from the start of the original request to the end of the ultimate response,
* the received byte count is for all redirects and the ultimate response combined.
* These cumulative metric definitions are debatable, but are chosen to make sense
* for user-facing latency analysis.
*
* @return metrics collected for this request.
*/
public abstract Metrics getMetrics();
/**
* Returns the reason why the request finished.
* @return one of {@link #SUCCEEDED}, {@link #FAILED}, or {@link #CANCELED}
*/
public abstract int getFinishedReason();
/**
* Returns a {@link UrlResponseInfo} for the request, if its response had started.
* @return {@link UrlResponseInfo} for the request, if its response had started.
*/
@Nullable
public abstract UrlResponseInfo getResponseInfo();
/**
* If the request failed, returns the same {@link CronetException} provided to
* {@link UrlRequest.Callback#onFailed}.
*
* @return the request's {@link CronetException}, if the request failed
*/
@Nullable
public abstract CronetException getException();
}

View File

@ -0,0 +1,89 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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 java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Abstract class allowing the embedder to provide an upload body to
* {@link UrlRequest}. It supports both non-chunked (size known in advanced) and
* chunked (size not known in advance) uploads. Be aware that not all servers
* support chunked uploads.
*
* <p>An upload is either always chunked, across multiple uploads if the data
* ends up being sent more than once, or never chunked.
*/
public abstract class UploadDataProvider implements Closeable {
/**
* If this is a non-chunked upload, returns the length of the upload. Must
* always return -1 if this is a chunked upload.
*
* @return the length of the upload for non-chunked uploads, -1 otherwise.
* @throws IOException if any IOException occurred during the process.
*/
public abstract long getLength() throws IOException;
/**
* Reads upload data into {@code byteBuffer}. Upon completion, the buffer's
* position is updated to the end of the bytes that were read. The buffer's
* limit is not changed. Each call of this method must be followed be a
* single call, either synchronous or asynchronous, to
* {@code uploadDataSink}: {@link UploadDataSink#onReadSucceeded} on success
* or {@link UploadDataSink#onReadError} on failure. Neither read nor rewind
* will be called until one of those methods or the other is called. Even if
* the associated {@link UrlRequest} is canceled, one or the other must
* still be called before resources can be safely freed. Throwing an
* exception will also result in resources being freed and the request being
* errored out.
*
* @param uploadDataSink The object to notify when the read has completed,
* successfully or otherwise.
* @param byteBuffer The buffer to copy the read bytes into. Do not change
* byteBuffer's limit.
* @throws IOException if any IOException occurred during the process.
* {@link UrlRequest.Callback#onFailed} will be called with the
* thrown exception set as the cause of the
* {@link CallbackException}.
*/
public abstract void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer)
throws IOException;
/**
* Rewinds upload data. Each call must be followed be a single
* call, either synchronous or asynchronous, to {@code uploadDataSink}:
* {@link UploadDataSink#onRewindSucceeded} on success or
* {@link UploadDataSink#onRewindError} on failure. Neither read nor rewind
* will be called until one of those methods or the other is called.
* Even if the associated {@link UrlRequest} is canceled, one or the other
* must still be called before resources can be safely freed. Throwing an
* exception will also result in resources being freed and the request being
* errored out.
*
* <p>If rewinding is not supported, this should call
* {@link UploadDataSink#onRewindError}. Note that rewinding is required to
* follow redirects that preserve the upload body, and for retrying when the
* server times out stale sockets.
*
* @param uploadDataSink The object to notify when the rewind operation has
* completed, successfully or otherwise.
* @throws IOException if any IOException occurred during the process.
* {@link UrlRequest.Callback#onFailed} will be called with the
* thrown exception set as the cause of the
* {@link CallbackException}.
*/
public abstract void rewind(UploadDataSink uploadDataSink) throws IOException;
/**
* Called when this UploadDataProvider is no longer needed by a request, so that resources
* (like a file) can be explicitly released.
*
* @throws IOException if any IOException occurred during the process. This will cause the
* request to fail if it is not yet complete; otherwise it will be logged.
*/
@Override
public void close() throws IOException {}
}

View File

@ -0,0 +1,186 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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.os.ParcelFileDescriptor;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* Provides implementations of {@link UploadDataProvider} for common use cases.
*/
public final class UploadDataProviders {
/**
* Uploads an entire file.
*
* @param file The file to upload
* @return A new UploadDataProvider for the given file
*/
public static UploadDataProvider create(final File file) {
return new FileUploadProvider(new FileChannelProvider() {
@Override
public FileChannel getChannel() throws IOException {
return new FileInputStream(file).getChannel();
}
});
}
/**
* Uploads an entire file, closing the descriptor when it is no longer needed.
*
* @param fd The file descriptor to upload
* @throws IllegalArgumentException if {@code fd} is not a file.
* @return A new UploadDataProvider for the given file descriptor
*/
public static UploadDataProvider create(final ParcelFileDescriptor fd) {
return new FileUploadProvider(new FileChannelProvider() {
@Override
public FileChannel getChannel() throws IOException {
if (fd.getStatSize() != -1) {
return new ParcelFileDescriptor.AutoCloseInputStream(fd).getChannel();
} else {
fd.close();
throw new IllegalArgumentException("Not a file: " + fd);
}
}
});
}
/**
* Uploads a ByteBuffer, from the current {@code buffer.position()} to {@code buffer.limit()}
* @param buffer The data to upload
* @return A new UploadDataProvider for the given buffer
*/
public static UploadDataProvider create(ByteBuffer buffer) {
return new ByteBufferUploadProvider(buffer.slice());
}
/**
* Uploads {@code length} bytes from {@code data}, starting from {@code offset}
* @param data Array containing data to upload
* @param offset Offset within data to start with
* @param length Number of bytes to upload
* @return A new UploadDataProvider for the given data
*/
public static UploadDataProvider create(byte[] data, int offset, int length) {
return new ByteBufferUploadProvider(ByteBuffer.wrap(data, offset, length).slice());
}
/**
* Uploads the contents of {@code data}
* @param data Array containing data to upload
* @return A new UploadDataProvider for the given data
*/
public static UploadDataProvider create(byte[] data) {
return create(data, 0, data.length);
}
private interface FileChannelProvider { FileChannel getChannel() throws IOException; }
private static final class FileUploadProvider extends UploadDataProvider {
private volatile FileChannel mChannel;
private final FileChannelProvider mProvider;
/** Guards initalization of {@code mChannel} */
private final Object mLock = new Object();
private FileUploadProvider(FileChannelProvider provider) {
this.mProvider = provider;
}
@Override
public long getLength() throws IOException {
return getChannel().size();
}
@Override
public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) throws IOException {
if (!byteBuffer.hasRemaining()) {
throw new IllegalStateException("Cronet passed a buffer with no bytes remaining");
}
FileChannel channel = getChannel();
int bytesRead = 0;
while (bytesRead == 0) {
int read = channel.read(byteBuffer);
if (read == -1) {
break;
} else {
bytesRead += read;
}
}
uploadDataSink.onReadSucceeded(false);
}
@Override
public void rewind(UploadDataSink uploadDataSink) throws IOException {
getChannel().position(0);
uploadDataSink.onRewindSucceeded();
}
/**
* Lazily initializes the channel so that a blocking operation isn't performed on
* a non-executor thread.
*/
private FileChannel getChannel() throws IOException {
if (mChannel == null) {
synchronized (mLock) {
if (mChannel == null) {
mChannel = mProvider.getChannel();
}
}
}
return mChannel;
}
@Override
public void close() throws IOException {
FileChannel channel = mChannel;
if (channel != null) {
channel.close();
}
}
}
private static final class ByteBufferUploadProvider extends UploadDataProvider {
private final ByteBuffer mUploadBuffer;
private ByteBufferUploadProvider(ByteBuffer uploadBuffer) {
this.mUploadBuffer = uploadBuffer;
}
@Override
public long getLength() {
return mUploadBuffer.limit();
}
@Override
public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) {
if (!byteBuffer.hasRemaining()) {
throw new IllegalStateException("Cronet passed a buffer with no bytes remaining");
}
if (byteBuffer.remaining() >= mUploadBuffer.remaining()) {
byteBuffer.put(mUploadBuffer);
} else {
int oldLimit = mUploadBuffer.limit();
mUploadBuffer.limit(mUploadBuffer.position() + byteBuffer.remaining());
byteBuffer.put(mUploadBuffer);
mUploadBuffer.limit(oldLimit);
}
uploadDataSink.onReadSucceeded(false);
}
@Override
public void rewind(UploadDataSink uploadDataSink) {
mUploadBuffer.position(0);
uploadDataSink.onRewindSucceeded();
}
}
// Prevent instantiation
private UploadDataProviders() {}
}

View File

@ -0,0 +1,36 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net;
/**
* Defines callbacks methods for {@link UploadDataProvider}. All methods
* may be called synchronously or asynchronously, on any thread.
*/
public abstract class UploadDataSink {
/**
* Called by {@link UploadDataProvider} when a read succeeds.
* @param finalChunk For chunked uploads, {@code true} if this is the final
* read. It must be {@code false} for non-chunked uploads.
*/
public abstract void onReadSucceeded(boolean finalChunk);
/**
* Called by {@link UploadDataProvider} when a read fails.
* @param exception Exception passed on to the embedder.
*/
public abstract void onReadError(Exception exception);
/**
* Called by {@link UploadDataProvider} when a rewind succeeds.
*/
public abstract void onRewindSucceeded();
/**
* Called by {@link UploadDataProvider} when a rewind fails, or if rewinding
* uploads is not supported.
* @param exception Exception passed on to the embedder.
*/
public abstract void onRewindError(Exception exception);
}

View File

@ -0,0 +1,422 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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 java.nio.ByteBuffer;
import java.util.concurrent.Executor;
/**
* Controls an HTTP request (GET, PUT, POST etc).
* Created by {@link UrlRequest.Builder}, which can be obtained by calling
* {@link CronetEngine#newUrlRequestBuilder}.
* Note: All methods must be called on the {@link Executor} passed to
* {@link CronetEngine#newUrlRequestBuilder}.
*/
public abstract class UrlRequest {
/**
* Builder for {@link UrlRequest}s. Allows configuring requests before constructing them
* with {@link Builder#build}. The builder can be created by calling
* {@link CronetEngine#newUrlRequestBuilder}.
*/
public abstract static class Builder {
/**
* Sets the HTTP method verb to use for this request.
*
* <p>The default when this method is not called is "GET" if the request has
* no body or "POST" if it does.
*
* @param method "GET", "HEAD", "DELETE", "POST" or "PUT".
* @return the builder to facilitate chaining.
*/
public abstract Builder setHttpMethod(String method);
/**
* Adds a request header.
*
* @param header header name.
* @param value header value.
* @return the builder to facilitate chaining.
*/
public abstract Builder addHeader(String header, String value);
/**
* Disables cache for the request. If context is not set up to use cache,
* this call has no effect.
* @return the builder to facilitate chaining.
*/
public abstract Builder disableCache();
/**
* Lowest request priority. Passed to {@link #setPriority}.
*/
public static final int REQUEST_PRIORITY_IDLE = 0;
/**
* Very low request priority. Passed to {@link #setPriority}.
*/
public static final int REQUEST_PRIORITY_LOWEST = 1;
/**
* Low request priority. Passed to {@link #setPriority}.
*/
public static final int REQUEST_PRIORITY_LOW = 2;
/**
* Medium request priority. Passed to {@link #setPriority}. This is the
* default priority given to the request.
*/
public static final int REQUEST_PRIORITY_MEDIUM = 3;
/**
* Highest request priority. Passed to {@link #setPriority}.
*/
public static final int REQUEST_PRIORITY_HIGHEST = 4;
/**
* Sets priority of the request which should be one of the
* {@link #REQUEST_PRIORITY_IDLE REQUEST_PRIORITY_*} values.
* The request is given {@link #REQUEST_PRIORITY_MEDIUM} priority if
* this method is not called.
*
* @param priority priority of the request which should be one of the
* {@link #REQUEST_PRIORITY_IDLE REQUEST_PRIORITY_*} values.
* @return the builder to facilitate chaining.
*/
public abstract Builder setPriority(int priority);
/**
* Sets upload data provider. Switches method to "POST" if not
* explicitly set. Starting the request will throw an exception if a
* Content-Type header is not set.
*
* @param uploadDataProvider responsible for providing the upload data.
* @param executor All {@code uploadDataProvider} methods will be invoked
* using this {@code Executor}. May optionally be the same
* {@code Executor} the request itself is using.
* @return the builder to facilitate chaining.
*/
public abstract Builder setUploadDataProvider(
UploadDataProvider uploadDataProvider, Executor executor);
/**
* Marks that the executors this request will use to notify callbacks (for
* {@code UploadDataProvider}s and {@code UrlRequest.Callback}s) is intentionally performing
* inline execution, like Guava's directExecutor or
* {@link java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy}.
*
* <p><b>Warning:</b> This option makes it easy to accidentally block the network thread.
* It should not be used if your callbacks perform disk I/O, acquire locks, or call into
* other code you don't carefully control and audit.
*/
public abstract Builder allowDirectExecutor();
/**
* Creates a {@link UrlRequest} using configuration within this
* {@link Builder}. The returned {@code UrlRequest} can then be started
* by calling {@link UrlRequest#start}.
*
* @return constructed {@link UrlRequest} using configuration within
* this {@link Builder}.
*/
public abstract UrlRequest build();
}
/**
* Users of Cronet extend this class to receive callbacks indicating the
* progress of a {@link UrlRequest} being processed. An instance of this class
* is passed in to {@link UrlRequest.Builder}'s constructor when
* constructing the {@code UrlRequest}.
* <p>
* Note: All methods will be invoked on the thread of the
* {@link java.util.concurrent.Executor} used during construction of the
* {@code UrlRequest}.
*/
public abstract static class Callback {
/**
* Invoked whenever a redirect is encountered. This will only be invoked
* between the call to {@link UrlRequest#start} and
* {@link Callback#onResponseStarted onResponseStarted()}.
* The body of the redirect response, if it has one, will be ignored.
*
* The redirect will not be followed until the URLRequest's
* {@link UrlRequest#followRedirect} method is called, either
* synchronously or asynchronously.
*
* @param request Request being redirected.
* @param info Response information.
* @param newLocationUrl Location where request is redirected.
* @throws Exception if an error occurs while processing a redirect. {@link #onFailed}
* will be called with the thrown exception set as the cause of the
* {@link CallbackException}.
*/
public abstract void onRedirectReceived(
UrlRequest request, UrlResponseInfo info, String newLocationUrl) throws Exception;
/**
* Invoked when the final set of headers, after all redirects, is received.
* Will only be invoked once for each request.
*
* With the exception of {@link Callback#onCanceled onCanceled()},
* no other {@link Callback} method will be invoked for the request,
* including {@link Callback#onSucceeded onSucceeded()} and {@link
* Callback#onFailed onFailed()}, until {@link UrlRequest#read
* UrlRequest.read()} is called to attempt to start reading the response
* body.
*
* @param request Request that started to get response.
* @param info Response information.
* @throws Exception if an error occurs while processing response start. {@link #onFailed}
* will be called with the thrown exception set as the cause of the
* {@link CallbackException}.
*/
public abstract void onResponseStarted(UrlRequest request, UrlResponseInfo info)
throws Exception;
/**
* Invoked whenever part of the response body has been read. Only part of
* the buffer may be populated, even if the entire response body has not yet
* been consumed.
*
* With the exception of {@link Callback#onCanceled onCanceled()},
* no other {@link Callback} method will be invoked for the request,
* including {@link Callback#onSucceeded onSucceeded()} and {@link
* Callback#onFailed onFailed()}, until {@link
* UrlRequest#read UrlRequest.read()} is called to attempt to continue
* reading the response body.
*
* @param request Request that received data.
* @param info Response information.
* @param byteBuffer The buffer that was passed in to
* {@link UrlRequest#read UrlRequest.read()}, now containing the
* received data. The buffer's position is updated to the end of
* the received data. The buffer's limit is not changed.
* @throws Exception if an error occurs while processing a read completion.
* {@link #onFailed} will be called with the thrown exception set as the cause of
* the {@link CallbackException}.
*/
public abstract void onReadCompleted(
UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) throws Exception;
/**
* Invoked when request is completed successfully. Once invoked, no other
* {@link Callback} methods will be invoked.
*
* @param request Request that succeeded.
* @param info Response information.
*/
public abstract void onSucceeded(UrlRequest request, UrlResponseInfo info);
/**
* Invoked if request failed for any reason after {@link UrlRequest#start}.
* Once invoked, no other {@link Callback} methods will be invoked.
* {@code error} provides information about the failure.
*
* @param request Request that failed.
* @param info Response information. May be {@code null} if no response was
* received.
* @param error information about error.
*/
public abstract void onFailed(
UrlRequest request, UrlResponseInfo info, CronetException error);
/**
* Invoked if request was canceled via {@link UrlRequest#cancel}. Once
* invoked, no other {@link Callback} methods will be invoked.
* Default implementation takes no action.
*
* @param request Request that was canceled.
* @param info Response information. May be {@code null} if no response was
* received.
*/
public void onCanceled(UrlRequest request, UrlResponseInfo info) {}
}
/**
* Request status values returned by {@link #getStatus}.
*/
public static class Status {
/**
* This state indicates that the request is completed, canceled, or is not
* started.
*/
public static final int INVALID = -1;
/**
* This state corresponds to a resource load that has either not yet begun
* or is idle waiting for the consumer to do something to move things along
* (e.g. when the consumer of a {@link UrlRequest} has not called
* {@link UrlRequest#read read()} yet).
*/
public static final int IDLE = 0;
/**
* When a socket pool group is below the maximum number of sockets allowed
* per group, but a new socket cannot be created due to the per-pool socket
* limit, this state is returned by all requests for the group waiting on an
* idle connection, except those that may be serviced by a pending new
* connection.
*/
public static final int WAITING_FOR_STALLED_SOCKET_POOL = 1;
/**
* When a socket pool group has reached the maximum number of sockets
* allowed per group, this state is returned for all requests that don't
* have a socket, except those that correspond to a pending new connection.
*/
public static final int WAITING_FOR_AVAILABLE_SOCKET = 2;
/**
* This state indicates that the URLRequest delegate has chosen to block
* this request before it was sent over the network.
*/
public static final int WAITING_FOR_DELEGATE = 3;
/**
* This state corresponds to a resource load that is blocked waiting for
* access to a resource in the cache. If multiple requests are made for the
* same resource, the first request will be responsible for writing (or
* updating) the cache entry and the second request will be deferred until
* the first completes. This may be done to optimize for cache reuse.
*/
public static final int WAITING_FOR_CACHE = 4;
/**
* This state corresponds to a resource being blocked waiting for the
* PAC script to be downloaded.
*/
public static final int DOWNLOADING_PAC_FILE = 5;
/**
* This state corresponds to a resource load that is blocked waiting for a
* proxy autoconfig script to return a proxy server to use.
*/
public static final int RESOLVING_PROXY_FOR_URL = 6;
/**
* This state corresponds to a resource load that is blocked waiting for a
* proxy autoconfig script to return a proxy server to use, but that proxy
* script is busy resolving the IP address of a host.
*/
public static final int RESOLVING_HOST_IN_PAC_FILE = 7;
/**
* This state indicates that we're in the process of establishing a tunnel
* through the proxy server.
*/
public static final int ESTABLISHING_PROXY_TUNNEL = 8;
/**
* This state corresponds to a resource load that is blocked waiting for a
* host name to be resolved. This could either indicate resolution of the
* origin server corresponding to the resource or to the host name of a
* proxy server used to fetch the resource.
*/
public static final int RESOLVING_HOST = 9;
/**
* This state corresponds to a resource load that is blocked waiting for a
* TCP connection (or other network connection) to be established. HTTP
* requests that reuse a keep-alive connection skip this state.
*/
public static final int CONNECTING = 10;
/**
* This state corresponds to a resource load that is blocked waiting for the
* SSL handshake to complete.
*/
public static final int SSL_HANDSHAKE = 11;
/**
* This state corresponds to a resource load that is blocked waiting to
* completely upload a request to a server. In the case of a HTTP POST
* request, this state includes the period of time during which the message
* body is being uploaded.
*/
public static final int SENDING_REQUEST = 12;
/**
* This state corresponds to a resource load that is blocked waiting for the
* response to a network request. In the case of a HTTP transaction, this
* corresponds to the period after the request is sent and before all of the
* response headers have been received.
*/
public static final int WAITING_FOR_RESPONSE = 13;
/**
* This state corresponds to a resource load that is blocked waiting for a
* read to complete. In the case of a HTTP transaction, this corresponds to
* the period after the response headers have been received and before all
* of the response body has been downloaded. (NOTE: This state only applies
* for an {@link UrlRequest} while there is an outstanding
* {@link UrlRequest#read read()} operation.)
*/
public static final int READING_RESPONSE = 14;
private Status() {}
}
/**
* Listener class used with {@link #getStatus} to receive the status of a
* {@link UrlRequest}.
*/
public abstract static class StatusListener {
/**
* Invoked on {@link UrlRequest}'s {@link Executor}'s thread when request
* status is obtained.
* @param status integer representing the status of the request. It is
* one of the values defined in {@link Status}.
*/
public abstract void onStatus(int status);
}
/**
* Starts the request, all callbacks go to {@link Callback}. May only be called
* once. May not be called if {@link #cancel} has been called.
*/
public abstract void start();
/**
* Follows a pending redirect. Must only be called at most once for each
* invocation of {@link Callback#onRedirectReceived
* onRedirectReceived()}.
*/
public abstract void followRedirect();
/**
* Attempts to read part of the response body into the provided buffer.
* Must only be called at most once in response to each invocation of the
* {@link Callback#onResponseStarted onResponseStarted()} and {@link
* Callback#onReadCompleted onReadCompleted()} methods of the {@link
* Callback}. Each call will result in an asynchronous call to
* either the {@link Callback Callback's}
* {@link Callback#onReadCompleted onReadCompleted()} method if data
* is read, its {@link Callback#onSucceeded onSucceeded()} method if
* there's no more data to read, or its {@link Callback#onFailed
* onFailed()} method if there's an error.
*
* @param buffer {@link ByteBuffer} to write response body to. Must be a
* direct ByteBuffer. The embedder must not read or modify buffer's
* position, limit, or data between its position and limit until the
* request calls back into the {@link Callback}.
*/
public abstract void read(ByteBuffer buffer);
/**
* Cancels the request. Can be called at any time.
* {@link Callback#onCanceled onCanceled()} will be invoked when cancellation
* is complete and no further callback methods will be invoked. If the
* request has completed or has not started, calling {@code cancel()} has no
* effect and {@code onCanceled()} will not be invoked. If the
* {@link Executor} passed in during {@code UrlRequest} construction runs
* tasks on a single thread, and {@code cancel()} is called on that thread,
* no callback methods (besides {@code onCanceled()}) will be invoked after
* {@code cancel()} is called. Otherwise, at most one callback method may be
* invoked after {@code cancel()} has completed.
*/
public abstract void cancel();
/**
* Returns {@code true} if the request was successfully started and is now
* finished (completed, canceled, or failed).
* @return {@code true} if the request was successfully started and is now
* finished (completed, canceled, or failed).
*/
public abstract boolean isDone();
/**
* Queries the status of the request.
* @param listener a {@link StatusListener} that will be invoked with
* the request's current status. {@code listener} will be invoked
* back on the {@link Executor} passed in when the request was
* created.
*/
public abstract void getStatus(final StatusListener listener);
// Note: There are deliberately no accessors for the results of the request
// here. Having none removes any ambiguity over when they are populated,
// particularly in the redirect case.
}

View File

@ -0,0 +1,121 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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 java.util.List;
import java.util.Map;
/**
* Basic information about a response. Included in {@link UrlRequest.Callback} callbacks.
* Each {@link UrlRequest.Callback#onRedirectReceived onRedirectReceived()}
* callback gets a different copy of {@code UrlResponseInfo} describing a particular redirect
* response.
*/
public abstract class UrlResponseInfo {
/**
* Unmodifiable container of response headers or trailers.
* {@hide}.
*/
public abstract static class HeaderBlock {
/**
* Returns an unmodifiable list of the response header field and value pairs.
* The headers are in the same order they are received over the wire.
*
* @return an unmodifiable list of response header field and value pairs
*/
public abstract List<Map.Entry<String, String>> getAsList();
/**
* Returns an unmodifiable map from response-header field names to lists of values.
* Each list of values for a single header field is in the same order they
* were received over the wire.
*
* @return an unmodifiable map from response-header field names to lists of values
*/
public abstract Map<String, List<String>> getAsMap();
}
/**
* Returns the URL the response is for. This is the URL after following
* redirects, so it may not be the originally requested URL.
* @return the URL the response is for.
*/
public abstract String getUrl();
/**
* Returns the URL chain. The first entry is the originally requested URL;
* the following entries are redirects followed.
* @return the URL chain.
*/
public abstract List<String> getUrlChain();
/**
* Returns the HTTP status code. When a resource is retrieved from the cache,
* whether it was revalidated or not, the original status code is returned.
* @return the HTTP status code.
*/
public abstract int getHttpStatusCode();
/**
* Returns the HTTP status text of the status line. For example, if the
* request received a "HTTP/1.1 200 OK" response, this method returns "OK".
* @return the HTTP status text of the status line.
*/
public abstract String getHttpStatusText();
/**
* Returns an unmodifiable list of response header field and value pairs.
* The headers are in the same order they are received over the wire.
* @return an unmodifiable list of response header field and value pairs.
*/
public abstract List<Map.Entry<String, String>> getAllHeadersAsList();
/**
* Returns an unmodifiable map of the response-header fields and values.
* Each list of values for a single header field is in the same order they
* were received over the wire.
* @return an unmodifiable map of the response-header fields and values.
*/
public abstract Map<String, List<String>> getAllHeaders();
/**
* Returns {@code true} if the response came from the cache, including
* requests that were revalidated over the network before being retrieved
* from the cache.
* @return {@code true} if the response came from the cache, {@code false}
* otherwise.
*/
public abstract boolean wasCached();
/**
* Returns the protocol (for example 'quic/1+spdy/3') negotiated with the server.
* Returns an empty string if no protocol was negotiated, the protocol is
* not known, or when using plain HTTP or HTTPS.
* @return the protocol negotiated with the server.
*/
// TODO(mef): Figure out what this returns in the cached case, both with
// and without a revalidation request.
public abstract String getNegotiatedProtocol();
/**
* Returns the proxy server that was used for the request.
* @return the proxy server that was used for the request.
*/
public abstract String getProxyServer();
/**
* Returns a minimum count of bytes received from the network to process this
* request. This count may ignore certain overheads (for example IP and TCP/UDP framing,
* SSL handshake and framing, proxy handling). This count is taken prior to decompression
* (for example GZIP) and includes headers and data from all redirects.
*
* This value may change (even for one {@link UrlResponseInfo} instance) as the request
* progresses until completion, when {@link UrlRequest.Callback#onSucceeded onSucceeded()},
* {@link UrlRequest.Callback#onFailed onFailed()}, or
* {@link UrlRequest.Callback#onCanceled onCanceled()} is called.
* @return a minimum count of bytes received from the network to process this request.
*/
public abstract long getReceivedByteCount();
}

View File

@ -0,0 +1 @@
14

View File

@ -0,0 +1,514 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cronet_bidirectional_stream_adapter.h"
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/strings/abseil_string_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "components/cronet/android/cronet_jni_headers/CronetBidirectionalStream_jni.h"
#include "components/cronet/android/cronet_url_request_context_adapter.h"
#include "components/cronet/android/io_buffer_with_byte_buffer.h"
#include "components/cronet/android/url_request_error.h"
#include "components/cronet/metrics_util.h"
#include "net/base/http_user_agent_settings.h"
#include "net/base/net_errors.h"
#include "net/base/request_priority.h"
#include "net/http/bidirectional_stream_request_info.h"
#include "net/http/http_network_session.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/http_transaction_factory.h"
#include "net/http/http_util.h"
#include "net/ssl/ssl_info.h"
#include "net/third_party/quiche/src/quic/core/quic_packets.h"
#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
#include "net/url_request/url_request_context.h"
#include "url/gurl.h"
using base::android::ConvertUTF8ToJavaString;
using base::android::ConvertJavaStringToUTF8;
using base::android::JavaRef;
using base::android::ScopedJavaLocalRef;
namespace cronet {
namespace {
// As |GetArrayLength| makes no guarantees about the returned value (e.g., it
// may be -1 if |array| is not a valid Java array), provide a safe wrapper
// that always returns a valid, non-negative size.
template <typename JavaArrayType>
size_t SafeGetArrayLength(JNIEnv* env, JavaArrayType jarray) {
DCHECK(jarray);
jsize length = env->GetArrayLength(jarray);
DCHECK_GE(length, 0) << "Invalid array length: " << length;
return static_cast<size_t>(std::max(0, length));
}
} // namespace
PendingWriteData::PendingWriteData(
JNIEnv* env,
const JavaRef<jobjectArray>& jwrite_buffer_list,
const JavaRef<jintArray>& jwrite_buffer_pos_list,
const JavaRef<jintArray>& jwrite_buffer_limit_list,
jboolean jwrite_end_of_stream) {
this->jwrite_buffer_list.Reset(jwrite_buffer_list);
this->jwrite_buffer_pos_list.Reset(jwrite_buffer_pos_list);
this->jwrite_buffer_limit_list.Reset(jwrite_buffer_limit_list);
this->jwrite_end_of_stream = jwrite_end_of_stream;
}
PendingWriteData::~PendingWriteData() {
// Reset global references.
jwrite_buffer_list.Reset();
jwrite_buffer_pos_list.Reset();
jwrite_buffer_limit_list.Reset();
}
static jlong JNI_CronetBidirectionalStream_CreateBidirectionalStream(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jbidi_stream,
jlong jurl_request_context_adapter,
jboolean jsend_request_headers_automatically,
jboolean jenable_metrics,
jboolean jtraffic_stats_tag_set,
jint jtraffic_stats_tag,
jboolean jtraffic_stats_uid_set,
jint jtraffic_stats_uid) {
CronetURLRequestContextAdapter* context_adapter =
reinterpret_cast<CronetURLRequestContextAdapter*>(
jurl_request_context_adapter);
DCHECK(context_adapter);
CronetBidirectionalStreamAdapter* adapter =
new CronetBidirectionalStreamAdapter(
context_adapter, env, jbidi_stream,
jsend_request_headers_automatically, jenable_metrics,
jtraffic_stats_tag_set, jtraffic_stats_tag, jtraffic_stats_uid_set,
jtraffic_stats_uid);
return reinterpret_cast<jlong>(adapter);
}
CronetBidirectionalStreamAdapter::CronetBidirectionalStreamAdapter(
CronetURLRequestContextAdapter* context,
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jbidi_stream,
bool send_request_headers_automatically,
bool enable_metrics,
bool traffic_stats_tag_set,
int32_t traffic_stats_tag,
bool traffic_stats_uid_set,
int32_t traffic_stats_uid)
: context_(context),
owner_(env, jbidi_stream),
send_request_headers_automatically_(send_request_headers_automatically),
enable_metrics_(enable_metrics),
traffic_stats_tag_set_(traffic_stats_tag_set),
traffic_stats_tag_(traffic_stats_tag),
traffic_stats_uid_set_(traffic_stats_uid_set),
traffic_stats_uid_(traffic_stats_uid),
stream_failed_(false) {}
CronetBidirectionalStreamAdapter::~CronetBidirectionalStreamAdapter() {
DCHECK(context_->IsOnNetworkThread());
}
void CronetBidirectionalStreamAdapter::SendRequestHeaders(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller) {
context_->PostTaskToNetworkThread(
FROM_HERE,
base::BindOnce(
&CronetBidirectionalStreamAdapter::SendRequestHeadersOnNetworkThread,
base::Unretained(this)));
}
jint CronetBidirectionalStreamAdapter::Start(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jstring>& jurl,
jint jpriority,
const base::android::JavaParamRef<jstring>& jmethod,
const base::android::JavaParamRef<jobjectArray>& jheaders,
jboolean jend_of_stream) {
// Prepare request info here to be able to return the error.
std::unique_ptr<net::BidirectionalStreamRequestInfo> request_info(
new net::BidirectionalStreamRequestInfo());
request_info->url = GURL(ConvertJavaStringToUTF8(env, jurl));
request_info->priority = static_cast<net::RequestPriority>(jpriority);
// Http method is a token, just as header name.
request_info->method = ConvertJavaStringToUTF8(env, jmethod);
if (!net::HttpUtil::IsValidHeaderName(request_info->method))
return -1;
std::vector<std::string> headers;
base::android::AppendJavaStringArrayToStringVector(env, jheaders, &headers);
for (size_t i = 0; i < headers.size(); i += 2) {
std::string name(headers[i]);
std::string value(headers[i + 1]);
if (!net::HttpUtil::IsValidHeaderName(name) ||
!net::HttpUtil::IsValidHeaderValue(value)) {
return i + 1;
}
request_info->extra_headers.SetHeader(name, value);
}
request_info->end_stream_on_headers = jend_of_stream;
if (traffic_stats_tag_set_ || traffic_stats_uid_set_) {
request_info->socket_tag = net::SocketTag(
traffic_stats_uid_set_ ? traffic_stats_uid_ : net::SocketTag::UNSET_UID,
traffic_stats_tag_set_ ? traffic_stats_tag_
: net::SocketTag::UNSET_TAG);
}
context_->PostTaskToNetworkThread(
FROM_HERE,
base::BindOnce(&CronetBidirectionalStreamAdapter::StartOnNetworkThread,
base::Unretained(this), std::move(request_info)));
return 0;
}
jboolean CronetBidirectionalStreamAdapter::ReadData(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jobject>& jbyte_buffer,
jint jposition,
jint jlimit) {
DCHECK_LT(jposition, jlimit);
void* data = env->GetDirectBufferAddress(jbyte_buffer);
if (!data)
return JNI_FALSE;
scoped_refptr<IOBufferWithByteBuffer> read_buffer(
new IOBufferWithByteBuffer(env, jbyte_buffer, data, jposition, jlimit));
int remaining_capacity = jlimit - jposition;
context_->PostTaskToNetworkThread(
FROM_HERE,
base::BindOnce(&CronetBidirectionalStreamAdapter::ReadDataOnNetworkThread,
base::Unretained(this), read_buffer, remaining_capacity));
return JNI_TRUE;
}
jboolean CronetBidirectionalStreamAdapter::WritevData(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jobjectArray>& jbyte_buffers,
const base::android::JavaParamRef<jintArray>& jbyte_buffers_pos,
const base::android::JavaParamRef<jintArray>& jbyte_buffers_limit,
jboolean jend_of_stream) {
size_t buffers_array_size = SafeGetArrayLength(env, jbyte_buffers.obj());
size_t pos_array_size = SafeGetArrayLength(env, jbyte_buffers.obj());
size_t limit_array_size = SafeGetArrayLength(env, jbyte_buffers.obj());
if (buffers_array_size != pos_array_size ||
buffers_array_size != limit_array_size) {
DLOG(ERROR) << "Illegal arguments.";
return JNI_FALSE;
}
std::unique_ptr<PendingWriteData> pending_write_data;
pending_write_data.reset(
new PendingWriteData(env, jbyte_buffers, jbyte_buffers_pos,
jbyte_buffers_limit, jend_of_stream));
for (size_t i = 0; i < buffers_array_size; ++i) {
ScopedJavaLocalRef<jobject> jbuffer(
env, env->GetObjectArrayElement(
pending_write_data->jwrite_buffer_list.obj(), i));
void* data = env->GetDirectBufferAddress(jbuffer.obj());
if (!data)
return JNI_FALSE;
jint pos;
env->GetIntArrayRegion(pending_write_data->jwrite_buffer_pos_list.obj(), i,
1, &pos);
jint limit;
env->GetIntArrayRegion(pending_write_data->jwrite_buffer_limit_list.obj(),
i, 1, &limit);
DCHECK_LE(pos, limit);
scoped_refptr<net::WrappedIOBuffer> write_buffer =
base::MakeRefCounted<net::WrappedIOBuffer>(static_cast<char*>(data) +
pos);
pending_write_data->write_buffer_list.push_back(write_buffer);
pending_write_data->write_buffer_len_list.push_back(limit - pos);
}
context_->PostTaskToNetworkThread(
FROM_HERE,
base::BindOnce(
&CronetBidirectionalStreamAdapter::WritevDataOnNetworkThread,
base::Unretained(this), std::move(pending_write_data)));
return JNI_TRUE;
}
void CronetBidirectionalStreamAdapter::Destroy(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
jboolean jsend_on_canceled) {
// Destroy could be called from any thread, including network thread (if
// posting task to executor throws an exception), but is posted, so |this|
// is valid until calling task is complete. Destroy() is always called from
// within a synchronized java block that guarantees no future posts to the
// network thread with the adapter pointer.
context_->PostTaskToNetworkThread(
FROM_HERE,
base::BindOnce(&CronetBidirectionalStreamAdapter::DestroyOnNetworkThread,
base::Unretained(this), jsend_on_canceled));
}
void CronetBidirectionalStreamAdapter::OnStreamReady(
bool request_headers_sent) {
DCHECK(context_->IsOnNetworkThread());
JNIEnv* env = base::android::AttachCurrentThread();
cronet::Java_CronetBidirectionalStream_onStreamReady(
env, owner_, request_headers_sent ? JNI_TRUE : JNI_FALSE);
}
void CronetBidirectionalStreamAdapter::OnHeadersReceived(
const spdy::Http2HeaderBlock& response_headers) {
DCHECK(context_->IsOnNetworkThread());
JNIEnv* env = base::android::AttachCurrentThread();
// Get http status code from response headers.
jint http_status_code = 0;
const auto http_status_header = response_headers.find(":status");
if (http_status_header != response_headers.end())
base::StringToInt(base::StringViewToStringPiece(http_status_header->second),
&http_status_code);
std::string protocol;
switch (bidi_stream_->GetProtocol()) {
case net::kProtoHTTP2:
protocol = "h2";
break;
case net::kProtoQUIC:
protocol = "quic/1+spdy/3";
break;
default:
break;
}
cronet::Java_CronetBidirectionalStream_onResponseHeadersReceived(
env, owner_, http_status_code, ConvertUTF8ToJavaString(env, protocol),
GetHeadersArray(env, response_headers),
bidi_stream_->GetTotalReceivedBytes());
}
void CronetBidirectionalStreamAdapter::OnDataRead(int bytes_read) {
DCHECK(context_->IsOnNetworkThread());
JNIEnv* env = base::android::AttachCurrentThread();
cronet::Java_CronetBidirectionalStream_onReadCompleted(
env, owner_, read_buffer_->byte_buffer(), bytes_read,
read_buffer_->initial_position(), read_buffer_->initial_limit(),
bidi_stream_->GetTotalReceivedBytes());
// Free the read buffer. This lets the Java ByteBuffer be freed, if the
// embedder releases it, too.
read_buffer_ = nullptr;
}
void CronetBidirectionalStreamAdapter::OnDataSent() {
DCHECK(context_->IsOnNetworkThread());
DCHECK(pending_write_data_);
JNIEnv* env = base::android::AttachCurrentThread();
// Call into Java.
cronet::Java_CronetBidirectionalStream_onWritevCompleted(
env, owner_, pending_write_data_->jwrite_buffer_list,
pending_write_data_->jwrite_buffer_pos_list,
pending_write_data_->jwrite_buffer_limit_list,
pending_write_data_->jwrite_end_of_stream);
// Free the java objects. This lets the Java ByteBuffers be freed, if the
// embedder releases it, too.
pending_write_data_.reset();
}
void CronetBidirectionalStreamAdapter::OnTrailersReceived(
const spdy::Http2HeaderBlock& response_trailers) {
DCHECK(context_->IsOnNetworkThread());
JNIEnv* env = base::android::AttachCurrentThread();
cronet::Java_CronetBidirectionalStream_onResponseTrailersReceived(
env, owner_, GetHeadersArray(env, response_trailers));
}
void CronetBidirectionalStreamAdapter::OnFailed(int error) {
DCHECK(context_->IsOnNetworkThread());
stream_failed_ = true;
JNIEnv* env = base::android::AttachCurrentThread();
net::NetErrorDetails net_error_details;
bidi_stream_->PopulateNetErrorDetails(&net_error_details);
cronet::Java_CronetBidirectionalStream_onError(
env, owner_, NetErrorToUrlRequestError(error), error,
net_error_details.quic_connection_error,
ConvertUTF8ToJavaString(env, net::ErrorToString(error)),
bidi_stream_->GetTotalReceivedBytes());
}
void CronetBidirectionalStreamAdapter::StartOnNetworkThread(
std::unique_ptr<net::BidirectionalStreamRequestInfo> request_info) {
DCHECK(context_->IsOnNetworkThread());
DCHECK(!bidi_stream_);
request_info->detect_broken_connection =
context_->cronet_url_request_context()
->bidi_stream_detect_broken_connection();
request_info->heartbeat_interval =
context_->cronet_url_request_context()->heartbeat_interval();
request_info->extra_headers.SetHeaderIfMissing(
net::HttpRequestHeaders::kUserAgent, context_->GetURLRequestContext()
->http_user_agent_settings()
->GetUserAgent());
bidi_stream_.reset(new net::BidirectionalStream(
std::move(request_info), context_->GetURLRequestContext()
->http_transaction_factory()
->GetSession(),
send_request_headers_automatically_, this));
}
void CronetBidirectionalStreamAdapter::SendRequestHeadersOnNetworkThread() {
DCHECK(context_->IsOnNetworkThread());
DCHECK(!send_request_headers_automatically_);
if (stream_failed_) {
// If stream failed between the time when SendRequestHeaders is invoked and
// SendRequestHeadersOnNetworkThread is executed, do not call into
// |bidi_stream_| since the underlying stream might have been destroyed.
// Do not invoke Java callback either, since onError is posted when
// |stream_failed_| is set to true.
return;
}
bidi_stream_->SendRequestHeaders();
}
void CronetBidirectionalStreamAdapter::ReadDataOnNetworkThread(
scoped_refptr<IOBufferWithByteBuffer> read_buffer,
int buffer_size) {
DCHECK(context_->IsOnNetworkThread());
DCHECK(read_buffer);
DCHECK(!read_buffer_);
read_buffer_ = read_buffer;
int bytes_read = bidi_stream_->ReadData(read_buffer_.get(), buffer_size);
// If IO is pending, wait for the BidirectionalStream to call OnDataRead.
if (bytes_read == net::ERR_IO_PENDING)
return;
if (bytes_read < 0) {
OnFailed(bytes_read);
return;
}
OnDataRead(bytes_read);
}
void CronetBidirectionalStreamAdapter::WritevDataOnNetworkThread(
std::unique_ptr<PendingWriteData> pending_write_data) {
DCHECK(context_->IsOnNetworkThread());
DCHECK(pending_write_data);
DCHECK(!pending_write_data_);
if (stream_failed_) {
// If stream failed between the time when WritevData is invoked and
// WritevDataOnNetworkThread is executed, do not call into |bidi_stream_|
// since the underlying stream might have been destroyed. Do not invoke
// Java callback either, since onError is posted when |stream_failed_| is
// set to true.
return;
}
pending_write_data_ = std::move(pending_write_data);
bool end_of_stream = pending_write_data_->jwrite_end_of_stream == JNI_TRUE;
bidi_stream_->SendvData(pending_write_data_->write_buffer_list,
pending_write_data_->write_buffer_len_list,
end_of_stream);
}
void CronetBidirectionalStreamAdapter::DestroyOnNetworkThread(
bool send_on_canceled) {
DCHECK(context_->IsOnNetworkThread());
if (send_on_canceled) {
JNIEnv* env = base::android::AttachCurrentThread();
cronet::Java_CronetBidirectionalStream_onCanceled(env, owner_);
}
MaybeReportMetrics();
delete this;
}
base::android::ScopedJavaLocalRef<jobjectArray>
CronetBidirectionalStreamAdapter::GetHeadersArray(
JNIEnv* env,
const spdy::Http2HeaderBlock& header_block) {
DCHECK(context_->IsOnNetworkThread());
std::vector<std::string> headers;
for (const auto& header : header_block) {
auto value = std::string(header.second);
size_t start = 0;
size_t end = 0;
// The do loop will split headers by '\0' so that applications can skip it.
do {
end = value.find('\0', start);
std::string split_value;
if (end != value.npos) {
split_value = value.substr(start, end - start);
} else {
split_value = value.substr(start);
}
headers.push_back(std::string(header.first));
headers.push_back(split_value);
start = end + 1;
} while (end != value.npos);
}
return base::android::ToJavaArrayOfStrings(env, headers);
}
void CronetBidirectionalStreamAdapter::MaybeReportMetrics() {
if (!enable_metrics_)
return;
if (!bidi_stream_)
return;
net::LoadTimingInfo load_timing_info;
bidi_stream_->GetLoadTimingInfo(&load_timing_info);
JNIEnv* env = base::android::AttachCurrentThread();
base::Time start_time = load_timing_info.request_start_time;
base::TimeTicks start_ticks = load_timing_info.request_start;
cronet::Java_CronetBidirectionalStream_onMetricsCollected(
env, owner_,
metrics_util::ConvertTime(start_ticks, start_ticks, start_time),
metrics_util::ConvertTime(load_timing_info.connect_timing.dns_start,
start_ticks, start_time),
metrics_util::ConvertTime(load_timing_info.connect_timing.dns_end,
start_ticks, start_time),
metrics_util::ConvertTime(load_timing_info.connect_timing.connect_start,
start_ticks, start_time),
metrics_util::ConvertTime(load_timing_info.connect_timing.connect_end,
start_ticks, start_time),
metrics_util::ConvertTime(load_timing_info.connect_timing.ssl_start,
start_ticks, start_time),
metrics_util::ConvertTime(load_timing_info.connect_timing.ssl_end,
start_ticks, start_time),
metrics_util::ConvertTime(load_timing_info.send_start, start_ticks,
start_time),
metrics_util::ConvertTime(load_timing_info.send_end, start_ticks,
start_time),
metrics_util::ConvertTime(load_timing_info.push_start, start_ticks,
start_time),
metrics_util::ConvertTime(load_timing_info.push_end, start_ticks,
start_time),
metrics_util::ConvertTime(load_timing_info.receive_headers_end,
start_ticks, start_time),
metrics_util::ConvertTime(base::TimeTicks::Now(), start_ticks,
start_time),
load_timing_info.socket_reused, bidi_stream_->GetTotalSentBytes(),
bidi_stream_->GetTotalReceivedBytes());
}
} // namespace cronet

View File

@ -0,0 +1,197 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_CRONET_ANDROID_CRONET_BIDIRECTIONAL_STREAM_ADAPTER_H_
#define COMPONENTS_CRONET_ANDROID_CRONET_BIDIRECTIONAL_STREAM_ADAPTER_H_
#include <jni.h>
#include <memory>
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "net/http/bidirectional_stream.h"
#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
namespace net {
struct BidirectionalStreamRequestInfo;
} // namespace net
namespace cronet {
class CronetURLRequestContextAdapter;
class IOBufferWithByteBuffer;
// Convenient wrapper to hold Java references and data to represent the pending
// data to be written.
struct PendingWriteData {
PendingWriteData(
JNIEnv* env,
const base::android::JavaRef<jobjectArray>& jwrite_buffer_list,
const base::android::JavaRef<jintArray>& jwrite_buffer_pos_list,
const base::android::JavaRef<jintArray>& jwrite_buffer_limit_list,
jboolean jwrite_end_of_stream);
PendingWriteData(const PendingWriteData&) = delete;
PendingWriteData& operator=(const PendingWriteData&) = delete;
~PendingWriteData();
// Arguments passed in from Java. Retain a global ref so they won't get GC-ed
// until the corresponding onWriteCompleted is invoked.
base::android::ScopedJavaGlobalRef<jobjectArray> jwrite_buffer_list;
base::android::ScopedJavaGlobalRef<jintArray> jwrite_buffer_pos_list;
base::android::ScopedJavaGlobalRef<jintArray> jwrite_buffer_limit_list;
// A copy of the end of stream flag passed in from Java.
jboolean jwrite_end_of_stream;
// Every IOBuffer in |write_buffer_list| points to the memory owned by the
// corresponding Java ByteBuffer in |jwrite_buffer_list|.
std::vector<scoped_refptr<net::IOBuffer>> write_buffer_list;
// A list of the length of each IOBuffer in |write_buffer_list|.
std::vector<int> write_buffer_len_list;
};
// An adapter from Java BidirectionalStream object to net::BidirectionalStream.
// Created and configured from a Java thread. Start, ReadData, WritevData and
// Destroy can be called on any thread (including network thread), and post
// calls to corresponding {Start|ReadData|WritevData|Destroy}OnNetworkThread to
// the network thread. The object is always deleted on network thread. All
// callbacks into the Java BidirectionalStream are done on the network thread.
// Java BidirectionalStream is expected to initiate the next step like ReadData
// or Destroy. Public methods can be called on any thread.
class CronetBidirectionalStreamAdapter
: public net::BidirectionalStream::Delegate {
public:
CronetBidirectionalStreamAdapter(
CronetURLRequestContextAdapter* context,
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jbidi_stream,
bool jsend_request_headers_automatically,
bool enable_metrics,
bool traffic_stats_tag_set,
int32_t traffic_stats_tag,
bool traffic_stats_uid_set,
int32_t traffic_stats_uid);
CronetBidirectionalStreamAdapter(const CronetBidirectionalStreamAdapter&) =
delete;
CronetBidirectionalStreamAdapter& operator=(
const CronetBidirectionalStreamAdapter&) = delete;
~CronetBidirectionalStreamAdapter() override;
// Validates method and headers, initializes and starts the request. If
// |jend_of_stream| is true, then stream is half-closed after sending header
// frame and no data is expected to be written.
// Returns 0 if request is valid and started successfully,
// Returns -1 if |jmethod| is not valid HTTP method name.
// Returns position of invalid header value in |jheaders| if header name is
// not valid.
jint Start(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jstring>& jurl,
jint jpriority,
const base::android::JavaParamRef<jstring>& jmethod,
const base::android::JavaParamRef<jobjectArray>& jheaders,
jboolean jend_of_stream);
// Sends request headers to server.
// When |send_request_headers_automatically_| is
// false and OnStreamReady() is invoked with request_headers_sent = false,
// headers will be combined with next WriteData/WritevData unless this
// method is called first, in which case headers will be sent separately
// without delay.
// (This method cannot be called when |send_request_headers_automatically_| is
// true nor when OnStreamReady() is invoked with request_headers_sent = true,
// since headers have been sent by the stream when stream is negotiated
// successfully.)
void SendRequestHeaders(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller);
// Reads more data into |jbyte_buffer| starting at |jposition| and not
// exceeding |jlimit|. Arguments are preserved to ensure that |jbyte_buffer|
// is not modified by the application during read.
jboolean ReadData(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jobject>& jbyte_buffer,
jint jposition,
jint jlimit);
// Writes more data from |jbyte_buffers|. For the i_th buffer in
// |jbyte_buffers|, bytes to write start from i_th position in |jpositions|
// and end at i_th limit in |jlimits|.
// Arguments are preserved to ensure that |jbyte_buffer|
// is not modified by the application during write. The |jend_of_stream| is
// passed to remote to indicate end of stream.
jboolean WritevData(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jobjectArray>& jbyte_buffers,
const base::android::JavaParamRef<jintArray>& jpositions,
const base::android::JavaParamRef<jintArray>& jlimits,
jboolean jend_of_stream);
// Releases all resources for the request and deletes the object itself.
// |jsend_on_canceled| indicates if Java onCanceled callback should be
// issued to indicate that no more callbacks will be issued.
void Destroy(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
jboolean jsend_on_canceled);
private:
// net::BidirectionalStream::Delegate implementations:
void OnStreamReady(bool request_headers_sent) override;
void OnHeadersReceived(
const spdy::Http2HeaderBlock& response_headers) override;
void OnDataRead(int bytes_read) override;
void OnDataSent() override;
void OnTrailersReceived(const spdy::Http2HeaderBlock& trailers) override;
void OnFailed(int error) override;
void StartOnNetworkThread(
std::unique_ptr<net::BidirectionalStreamRequestInfo> request_info);
void SendRequestHeadersOnNetworkThread();
void ReadDataOnNetworkThread(
scoped_refptr<IOBufferWithByteBuffer> read_buffer,
int buffer_size);
void WritevDataOnNetworkThread(
std::unique_ptr<PendingWriteData> pending_write_data);
void DestroyOnNetworkThread(bool send_on_canceled);
// Gets headers as a Java array.
base::android::ScopedJavaLocalRef<jobjectArray> GetHeadersArray(
JNIEnv* env,
const spdy::Http2HeaderBlock& header_block);
// Helper method to report metrics to the Java layer.
void MaybeReportMetrics();
const raw_ptr<CronetURLRequestContextAdapter> context_;
// Java object that owns this CronetBidirectionalStreamAdapter.
base::android::ScopedJavaGlobalRef<jobject> owner_;
const bool send_request_headers_automatically_;
// Whether metrics collection is enabled when |this| is created.
const bool enable_metrics_;
// Whether |traffic_stats_tag_| should be applied.
const bool traffic_stats_tag_set_;
// TrafficStats tag to apply to URLRequest.
const int32_t traffic_stats_tag_;
// Whether |traffic_stats_uid_| should be applied.
const bool traffic_stats_uid_set_;
// UID to be applied to URLRequest.
const int32_t traffic_stats_uid_;
scoped_refptr<IOBufferWithByteBuffer> read_buffer_;
std::unique_ptr<PendingWriteData> pending_write_data_;
std::unique_ptr<net::BidirectionalStream> bidi_stream_;
// Whether BidirectionalStream::Delegate::OnFailed callback is invoked.
bool stream_failed_;
};
} // namespace cronet
#endif // COMPONENTS_CRONET_ANDROID_CRONET_BIDIRECTIONAL_STREAM_ADAPTER_H_

View File

@ -0,0 +1 @@
# Proguard config for apps that depend on cronet_impl_common_java.jar.

View File

@ -0,0 +1,6 @@
# Proguard config for apps that depend on cronet_impl_fake_java.jar.
# This constructor is called using the reflection from Cronet API (cronet_api.jar).
-keep class org.chromium.net.test.FakeCronetProvider {
public <init>(android.content.Context);
}

View File

@ -0,0 +1,32 @@
# Proguard config for apps that depend on cronet_impl_native_java.jar.
# This constructor is called using the reflection from Cronet API (cronet_api.jar).
-keep class org.chromium.net.impl.NativeCronetProvider {
public <init>(android.content.Context);
}
# Suppress unnecessary warnings.
-dontnote org.chromium.net.ProxyChangeListener$ProxyReceiver
-dontnote org.chromium.net.AndroidKeyStore
# Needs 'void setTextAppearance(int)' (API level 23).
-dontwarn org.chromium.base.ApiCompatibilityUtils
# Needs 'boolean onSearchRequested(android.view.SearchEvent)' (API level 23).
-dontwarn org.chromium.base.WindowCallbackWrapper
# Generated for chrome apk and not included into cronet.
-dontwarn org.chromium.base.multidex.ChromiumMultiDexInstaller
-dontwarn org.chromium.base.library_loader.LibraryLoader
-dontwarn org.chromium.base.SysUtils
-dontwarn org.chromium.build.NativeLibraries
# Objects of this type are passed around by native code, but the class
# is never used directly by native code. Since the class is not loaded, it does
# not need to be preserved as an entry point.
-dontnote org.chromium.net.UrlRequest$ResponseHeadersMap
# https://android.googlesource.com/platform/sdk/+/marshmallow-mr1-release/files/proguard-android.txt#54
-dontwarn android.support.**
# This class should be explicitly kept to avoid failure if
# class/merging/horizontal proguard optimization is enabled.
-keep class org.chromium.base.CollectionUtil

View File

@ -0,0 +1,6 @@
# Proguard config for apps that depend on cronet_impl_platform_java.jar.
# This constructor is called using the reflection from Cronet API (cronet_api.jar).
-keep class org.chromium.net.impl.JavaCronetProvider {
public <init>(android.content.Context);
}

View File

@ -0,0 +1,31 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/cronet/android/cronet_integrated_mode_state.h"
#include "base/atomicops.h"
namespace cronet {
namespace {
base::subtle::AtomicWord g_integrated_mode_network_task_runner = 0;
} // namespace
void SetIntegratedModeNetworkTaskRunner(
base::SingleThreadTaskRunner* network_task_runner) {
CHECK_EQ(base::subtle::Release_CompareAndSwap(
&g_integrated_mode_network_task_runner, 0,
reinterpret_cast<base::subtle::AtomicWord>(network_task_runner)),
0);
}
base::SingleThreadTaskRunner* GetIntegratedModeNetworkTaskRunner() {
base::subtle::AtomicWord task_runner =
base::subtle::Acquire_Load(&g_integrated_mode_network_task_runner);
CHECK(task_runner);
return reinterpret_cast<base::SingleThreadTaskRunner*>(task_runner);
}
} // namespace cronet

View File

@ -0,0 +1,29 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_CRONET_ANDROID_CRONET_INTEGRATED_MODE_STATE_H_
#define COMPONENTS_CRONET_ANDROID_CRONET_INTEGRATED_MODE_STATE_H_
#include "base/task/thread_pool/thread_pool_instance.h"
namespace cronet {
/**
* Set a shared network task runner into Cronet in integrated mode. All the
* Cronet network tasks would be running in this task runner. This method should
* be invoked in native side before creating Cronet instance.
*/
void SetIntegratedModeNetworkTaskRunner(
base::SingleThreadTaskRunner* network_task_runner);
/**
* Get the task runner for Cronet integrated mode. It would be invoked in the
* initialization of CronetURLRequestContext. This method must be invoked after
* SetIntegratedModeNetworkTaskRunner.
*/
base::SingleThreadTaskRunner* GetIntegratedModeNetworkTaskRunner();
} // namespace cronet
#endif // COMPONENTS_CRONET_ANDROID_CRONET_INTEGRATED_MODE_STATE_H_

View File

@ -0,0 +1,15 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/cronet/android/cronet_library_loader.h"
// This is called by the VM when the shared library is first loaded.
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) {
return cronet::CronetOnLoad(vm, reserved);
}
extern "C" void JNI_OnUnLoad(JavaVM* vm, void* reserved) {
cronet::CronetOnUnLoad(vm, reserved);
}

View File

@ -0,0 +1,221 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <jni.h>
#include <memory>
#include <string>
#include <utility>
#include "base/android/base_jni_onload.h"
#include "base/android/build_info.h"
#include "base/android/jni_android.h"
#include "base/android/jni_registrar.h"
#include "base/android/jni_string.h"
#include "base/android/jni_utils.h"
#include "base/android/library_loader/library_loader_hooks.h"
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/message_loop/message_pump_type.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "build/build_config.h"
#include "components/cronet/android/buildflags.h"
#include "components/cronet/android/cronet_jni_headers/CronetLibraryLoader_jni.h"
#include "components/cronet/cronet_global_state.h"
#include "components/cronet/version.h"
#include "net/android/network_change_notifier_factory_android.h"
#include "net/base/network_change_notifier.h"
#include "net/proxy_resolution/configured_proxy_resolution_service.h"
#include "net/proxy_resolution/proxy_config_service_android.h"
#include "third_party/zlib/zlib.h"
#include "url/buildflags.h"
#if !BUILDFLAG(USE_PLATFORM_ICU_ALTERNATIVES)
#include "base/i18n/icu_util.h" // nogncheck
#endif
#if !BUILDFLAG(INTEGRATED_MODE)
#include "components/cronet/android/cronet_jni_registration.h"
#include "components/cronet/android/cronet_library_loader.h"
#endif
using base::android::JavaParamRef;
using base::android::ScopedJavaLocalRef;
namespace cronet {
namespace {
// SingleThreadTaskExecutor on the init thread, which is where objects that
// receive Java notifications generally live.
base::SingleThreadTaskExecutor* g_init_task_executor = nullptr;
#if !BUILDFLAG(INTEGRATED_MODE)
std::unique_ptr<net::NetworkChangeNotifier> g_network_change_notifier;
#endif
base::WaitableEvent g_init_thread_init_done(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
void NativeInit() {
// In integrated mode, ICU and FeatureList has been initialized by the host.
#if !BUILDFLAG(INTEGRATED_MODE)
#if !BUILDFLAG(USE_PLATFORM_ICU_ALTERNATIVES)
base::i18n::InitializeICU();
#endif
base::FeatureList::InitializeInstance(std::string(), std::string());
#endif
if (!base::ThreadPoolInstance::Get())
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("Cronet");
}
} // namespace
bool OnInitThread() {
DCHECK(g_init_task_executor);
return g_init_task_executor->task_runner()->RunsTasksInCurrentSequence();
}
// In integrated mode, Cronet native library is built and loaded together with
// the native library of the host app.
#if !BUILDFLAG(INTEGRATED_MODE)
// Checks the available version of JNI. Also, caches Java reflection artifacts.
jint CronetOnLoad(JavaVM* vm, void* reserved) {
base::android::InitVM(vm);
JNIEnv* env = base::android::AttachCurrentThread();
if (!RegisterMainDexNatives(env) || !RegisterNonMainDexNatives(env)) {
return -1;
}
if (!base::android::OnJNIOnLoadInit())
return -1;
NativeInit();
return JNI_VERSION_1_6;
}
void CronetOnUnLoad(JavaVM* jvm, void* reserved) {
if (base::ThreadPoolInstance::Get())
base::ThreadPoolInstance::Get()->Shutdown();
base::android::LibraryLoaderExitHook();
}
#endif
void JNI_CronetLibraryLoader_CronetInitOnInitThread(JNIEnv* env) {
// Initialize SingleThreadTaskExecutor for init thread.
DCHECK(!base::CurrentThread::IsSet());
DCHECK(!g_init_task_executor);
g_init_task_executor =
new base::SingleThreadTaskExecutor(base::MessagePumpType::JAVA);
// In integrated mode, NetworkChangeNotifier has been initialized by the host.
#if BUILDFLAG(INTEGRATED_MODE)
CHECK(!net::NetworkChangeNotifier::CreateIfNeeded());
#else
DCHECK(!g_network_change_notifier);
if (!net::NetworkChangeNotifier::GetFactory()) {
net::NetworkChangeNotifier::SetFactory(
new net::NetworkChangeNotifierFactoryAndroid());
}
g_network_change_notifier = net::NetworkChangeNotifier::CreateIfNeeded();
DCHECK(g_network_change_notifier);
#endif
g_init_thread_init_done.Signal();
}
ScopedJavaLocalRef<jstring> JNI_CronetLibraryLoader_GetCronetVersion(
JNIEnv* env) {
#if defined(ARCH_CPU_ARM64)
// Attempt to avoid crashes on some ARM64 Marshmallow devices by
// prompting zlib ARM feature detection early on. https://crbug.com/853725
if (base::android::BuildInfo::GetInstance()->sdk_int() ==
base::android::SDK_VERSION_MARSHMALLOW) {
crc32(0, Z_NULL, 0);
}
#endif
return base::android::ConvertUTF8ToJavaString(env, CRONET_VERSION);
}
void PostTaskToInitThread(const base::Location& posted_from,
base::OnceClosure task) {
g_init_thread_init_done.Wait();
g_init_task_executor->task_runner()->PostTask(posted_from, std::move(task));
}
void EnsureInitialized() {
if (g_init_task_executor) {
// Ensure that init is done on the init thread.
g_init_thread_init_done.Wait();
return;
}
// The initialization can only be done once, so static |s_run_once| variable
// is used to do it in the constructor.
static class RunOnce {
public:
RunOnce() {
NativeInit();
JNIEnv* env = base::android::AttachCurrentThread();
// Ensure initialized from Java side to properly create Init thread.
cronet::Java_CronetLibraryLoader_ensureInitializedFromNative(env);
}
} s_run_once;
}
std::unique_ptr<net::ProxyConfigService> CreateProxyConfigService(
const scoped_refptr<base::SequencedTaskRunner>& io_task_runner) {
std::unique_ptr<net::ProxyConfigService> service =
net::ConfiguredProxyResolutionService::CreateSystemProxyConfigService(
io_task_runner);
// If a PAC URL is present, ignore it and use the address and port of
// Android system's local HTTP proxy server. See: crbug.com/432539.
// TODO(csharrison) Architect the wrapper better so we don't need to cast for
// android ProxyConfigServices.
net::ProxyConfigServiceAndroid* android_proxy_config_service =
static_cast<net::ProxyConfigServiceAndroid*>(service.get());
android_proxy_config_service->set_exclude_pac_url(true);
return service;
}
// Creates a proxy resolution service appropriate for this platform.
std::unique_ptr<net::ProxyResolutionService> CreateProxyResolutionService(
std::unique_ptr<net::ProxyConfigService> proxy_config_service,
net::NetLog* net_log) {
// Android provides a local HTTP proxy server that handles proxying when a PAC
// URL is present. Create a proxy service without a resolver and rely on this
// local HTTP proxy. See: crbug.com/432539.
return net::ConfiguredProxyResolutionService::CreateWithoutProxyResolver(
std::move(proxy_config_service), net_log);
}
// Creates default User-Agent request value, combining optional
// |partial_user_agent| with system-dependent values.
std::string CreateDefaultUserAgent(const std::string& partial_user_agent) {
// Cronet global state must be initialized to include application info
// into default user agent
cronet::EnsureInitialized();
JNIEnv* env = base::android::AttachCurrentThread();
std::string user_agent = base::android::ConvertJavaStringToUTF8(
cronet::Java_CronetLibraryLoader_getDefaultUserAgent(env));
if (!partial_user_agent.empty())
user_agent.insert(user_agent.size() - 1, "; " + partial_user_agent);
return user_agent;
}
void SetNetworkThreadPriorityOnNetworkThread(double priority) {
int priority_int = priority;
DCHECK_LE(priority_int, 19);
DCHECK_GE(priority_int, -20);
if (priority_int >= -20 && priority_int <= 19) {
JNIEnv* env = base::android::AttachCurrentThread();
cronet::Java_CronetLibraryLoader_setNetworkThreadPriorityOnNetworkThread(
env, priority_int);
}
}
} // namespace cronet

View File

@ -0,0 +1,17 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_CRONET_ANDROID_CRONET_LIBRARY_LOADER_H_
#define COMPONENTS_CRONET_ANDROID_CRONET_LIBRARY_LOADER_H_
#include <jni.h>
namespace cronet {
jint CronetOnLoad(JavaVM* vm, void* reserved);
void CronetOnUnLoad(JavaVM* jvm, void* reserved);
} // namespace cronet
#endif // COMPONENTS_CRONET_ANDROID_CRONET_LIBRARY_LOADER_H_

View File

@ -0,0 +1,147 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/cronet/android/cronet_upload_data_stream_adapter.h"
#include <memory>
#include <string>
#include <utility>
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/bind.h"
#include "base/check_op.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/cronet/android/cronet_jni_headers/CronetUploadDataStream_jni.h"
#include "components/cronet/android/cronet_url_request_adapter.h"
#include "components/cronet/android/io_buffer_with_byte_buffer.h"
using base::android::JavaParamRef;
namespace cronet {
CronetUploadDataStreamAdapter::CronetUploadDataStreamAdapter(
JNIEnv* env,
jobject jupload_data_stream) {
jupload_data_stream_.Reset(env, jupload_data_stream);
}
CronetUploadDataStreamAdapter::~CronetUploadDataStreamAdapter() {
}
void CronetUploadDataStreamAdapter::InitializeOnNetworkThread(
base::WeakPtr<CronetUploadDataStream> upload_data_stream) {
DCHECK(!upload_data_stream_);
DCHECK(!network_task_runner_.get());
upload_data_stream_ = upload_data_stream;
network_task_runner_ = base::ThreadTaskRunnerHandle::Get();
DCHECK(network_task_runner_);
}
void CronetUploadDataStreamAdapter::Read(scoped_refptr<net::IOBuffer> buffer,
int buf_len) {
DCHECK(upload_data_stream_);
DCHECK(network_task_runner_);
DCHECK(network_task_runner_->BelongsToCurrentThread());
DCHECK_GT(buf_len, 0);
JNIEnv* env = base::android::AttachCurrentThread();
// Allow buffer reuse if |buffer| and |buf_len| are exactly the same as the
// ones used last time.
if (!(buffer_ && buffer_->io_buffer()->data() == buffer->data() &&
buffer_->io_buffer_len() == buf_len)) {
buffer_ = std::make_unique<ByteBufferWithIOBuffer>(env, std::move(buffer),
buf_len);
}
Java_CronetUploadDataStream_readData(env, jupload_data_stream_,
buffer_->byte_buffer());
}
void CronetUploadDataStreamAdapter::Rewind() {
DCHECK(upload_data_stream_);
DCHECK(network_task_runner_->BelongsToCurrentThread());
JNIEnv* env = base::android::AttachCurrentThread();
Java_CronetUploadDataStream_rewind(env, jupload_data_stream_);
}
void CronetUploadDataStreamAdapter::OnUploadDataStreamDestroyed() {
// If CronetUploadDataStream::InitInternal was never called,
// |upload_data_stream_| and |network_task_runner_| will be NULL.
DCHECK(!network_task_runner_ ||
network_task_runner_->BelongsToCurrentThread());
JNIEnv* env = base::android::AttachCurrentThread();
Java_CronetUploadDataStream_onUploadDataStreamDestroyed(env,
jupload_data_stream_);
// |this| is invalid here since the Java call above effectively destroys it.
}
void CronetUploadDataStreamAdapter::OnReadSucceeded(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller,
int bytes_read,
bool final_chunk) {
DCHECK(bytes_read > 0 || (final_chunk && bytes_read == 0));
network_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CronetUploadDataStream::OnReadSuccess,
upload_data_stream_, bytes_read, final_chunk));
}
void CronetUploadDataStreamAdapter::OnRewindSucceeded(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller) {
network_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CronetUploadDataStream::OnRewindSuccess,
upload_data_stream_));
}
void CronetUploadDataStreamAdapter::Destroy(JNIEnv* env) {
delete this;
}
static jlong JNI_CronetUploadDataStream_AttachUploadDataToRequest(
JNIEnv* env,
const JavaParamRef<jobject>& jupload_data_stream,
jlong jcronet_url_request_adapter,
jlong jlength) {
CronetURLRequestAdapter* request_adapter =
reinterpret_cast<CronetURLRequestAdapter*>(jcronet_url_request_adapter);
DCHECK(request_adapter != nullptr);
CronetUploadDataStreamAdapter* adapter =
new CronetUploadDataStreamAdapter(env, jupload_data_stream);
std::unique_ptr<CronetUploadDataStream> upload_data_stream(
new CronetUploadDataStream(adapter, jlength));
request_adapter->SetUpload(std::move(upload_data_stream));
return reinterpret_cast<jlong>(adapter);
}
static jlong JNI_CronetUploadDataStream_CreateAdapterForTesting(
JNIEnv* env,
const JavaParamRef<jobject>& jupload_data_stream) {
CronetUploadDataStreamAdapter* adapter =
new CronetUploadDataStreamAdapter(env, jupload_data_stream);
return reinterpret_cast<jlong>(adapter);
}
static jlong JNI_CronetUploadDataStream_CreateUploadDataStreamForTesting(
JNIEnv* env,
const JavaParamRef<jobject>& jupload_data_stream,
jlong jlength,
jlong jadapter) {
CronetUploadDataStreamAdapter* adapter =
reinterpret_cast<CronetUploadDataStreamAdapter*>(jadapter);
CronetUploadDataStream* upload_data_stream =
new CronetUploadDataStream(adapter, jlength);
return reinterpret_cast<jlong>(upload_data_stream);
}
} // namespace cronet

View File

@ -0,0 +1,79 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_CRONET_ANDROID_CRONET_UPLOAD_DATA_STREAM_ADAPTER_H_
#define COMPONENTS_CRONET_ANDROID_CRONET_UPLOAD_DATA_STREAM_ADAPTER_H_
#include <jni.h>
#include "base/android/scoped_java_ref.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "components/cronet/cronet_upload_data_stream.h"
#include "net/base/io_buffer.h"
namespace base {
class SingleThreadTaskRunner;
} // namespace base
namespace cronet {
class ByteBufferWithIOBuffer;
// The Adapter holds onto a reference to the IOBuffer that is currently being
// written to in Java, so may not be deleted until any read operation in Java
// has completed.
//
// The Adapter is owned by the Java CronetUploadDataStream, and also owns a
// reference to it. The Adapter is only destroyed after the net::URLRequest
// destroys the C++ CronetUploadDataStream and the Java CronetUploadDataStream
// has no read operation pending, at which point it also releases its reference
// to the Java CronetUploadDataStream.
//
// Failures don't go back through the Adapter, but directly to the Java request
// object, since normally reads aren't allowed to fail during an upload.
class CronetUploadDataStreamAdapter : public CronetUploadDataStream::Delegate {
public:
CronetUploadDataStreamAdapter(JNIEnv* env, jobject jupload_data_stream);
CronetUploadDataStreamAdapter(const CronetUploadDataStreamAdapter&) = delete;
CronetUploadDataStreamAdapter& operator=(
const CronetUploadDataStreamAdapter&) = delete;
~CronetUploadDataStreamAdapter() override;
// CronetUploadDataStream::Delegate implementation. Called on network thread.
void InitializeOnNetworkThread(
base::WeakPtr<CronetUploadDataStream> upload_data_stream) override;
void Read(scoped_refptr<net::IOBuffer> buffer, int buf_len) override;
void Rewind() override;
void OnUploadDataStreamDestroyed() override;
// Callbacks from Java, called on some Java thread.
void OnReadSucceeded(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
int bytes_read,
bool final_chunk);
void OnRewindSucceeded(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
// Destroys |this|. Can be called from any thread, but needs to be protected
// by the adapter lock.
void Destroy(JNIEnv* env);
private:
// Initialized on construction, effectively constant.
base::android::ScopedJavaGlobalRef<jobject> jupload_data_stream_;
// These are initialized in InitializeOnNetworkThread, so are safe to access
// during Java callbacks, which all happen after initialization.
scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
base::WeakPtr<CronetUploadDataStream> upload_data_stream_;
// Keeps the net::IOBuffer and Java ByteBuffer alive until the next Read().
std::unique_ptr<ByteBufferWithIOBuffer> buffer_;
};
} // namespace cronet
#endif // COMPONENTS_CRONET_ANDROID_CRONET_UPLOAD_DATA_STREAM_ADAPTER_H_

View File

@ -0,0 +1,326 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/cronet/android/cronet_url_request_adapter.h"
#include <limits>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "components/cronet/android/cronet_jni_headers/CronetUrlRequest_jni.h"
#include "components/cronet/android/cronet_url_request_context_adapter.h"
#include "components/cronet/android/io_buffer_with_byte_buffer.h"
#include "components/cronet/android/url_request_error.h"
#include "components/cronet/metrics_util.h"
#include "net/base/idempotency.h"
#include "net/base/load_flags.h"
#include "net/base/load_states.h"
#include "net/base/net_errors.h"
#include "net/base/proxy_server.h"
#include "net/base/request_priority.h"
#include "net/base/upload_data_stream.h"
#include "net/cert/cert_status_flags.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "net/ssl/ssl_info.h"
#include "net/third_party/quiche/src/quic/core/quic_packets.h"
#include "net/url_request/redirect_info.h"
#include "net/url_request/url_request_context.h"
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaParamRef;
namespace {
base::android::ScopedJavaLocalRef<jobjectArray> ConvertResponseHeadersToJava(
JNIEnv* env,
const net::HttpResponseHeaders* headers) {
std::vector<std::string> response_headers;
// Returns an empty array if |headers| is nullptr.
if (headers != nullptr) {
size_t iter = 0;
std::string header_name;
std::string header_value;
while (headers->EnumerateHeaderLines(&iter, &header_name, &header_value)) {
response_headers.push_back(header_name);
response_headers.push_back(header_value);
}
}
return base::android::ToJavaArrayOfStrings(env, response_headers);
}
} // namespace
namespace cronet {
static jlong JNI_CronetUrlRequest_CreateRequestAdapter(
JNIEnv* env,
const JavaParamRef<jobject>& jurl_request,
jlong jurl_request_context_adapter,
const JavaParamRef<jstring>& jurl_string,
jint jpriority,
jboolean jdisable_cache,
jboolean jdisable_connection_migration,
jboolean jenable_metrics,
jboolean jtraffic_stats_tag_set,
jint jtraffic_stats_tag,
jboolean jtraffic_stats_uid_set,
jint jtraffic_stats_uid,
jint jidempotency) {
CronetURLRequestContextAdapter* context_adapter =
reinterpret_cast<CronetURLRequestContextAdapter*>(
jurl_request_context_adapter);
DCHECK(context_adapter);
GURL url(base::android::ConvertJavaStringToUTF8(env, jurl_string));
VLOG(1) << "New chromium network request_adapter: "
<< url.possibly_invalid_spec();
CronetURLRequestAdapter* adapter = new CronetURLRequestAdapter(
context_adapter, env, jurl_request, url,
static_cast<net::RequestPriority>(jpriority), jdisable_cache,
jdisable_connection_migration, jenable_metrics, jtraffic_stats_tag_set,
jtraffic_stats_tag, jtraffic_stats_uid_set, jtraffic_stats_uid,
static_cast<net::Idempotency>(jidempotency));
return reinterpret_cast<jlong>(adapter);
}
CronetURLRequestAdapter::CronetURLRequestAdapter(
CronetURLRequestContextAdapter* context,
JNIEnv* env,
jobject jurl_request,
const GURL& url,
net::RequestPriority priority,
jboolean jdisable_cache,
jboolean jdisable_connection_migration,
jboolean jenable_metrics,
jboolean jtraffic_stats_tag_set,
jint jtraffic_stats_tag,
jboolean jtraffic_stats_uid_set,
jint jtraffic_stats_uid,
net::Idempotency idempotency)
: request_(
new CronetURLRequest(context->cronet_url_request_context(),
std::unique_ptr<CronetURLRequestAdapter>(this),
url,
priority,
jdisable_cache == JNI_TRUE,
jdisable_connection_migration == JNI_TRUE,
jenable_metrics == JNI_TRUE,
jtraffic_stats_tag_set == JNI_TRUE,
jtraffic_stats_tag,
jtraffic_stats_uid_set == JNI_TRUE,
jtraffic_stats_uid,
idempotency)) {
owner_.Reset(env, jurl_request);
}
CronetURLRequestAdapter::~CronetURLRequestAdapter() {
}
jboolean CronetURLRequestAdapter::SetHttpMethod(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller,
const JavaParamRef<jstring>& jmethod) {
std::string method(base::android::ConvertJavaStringToUTF8(env, jmethod));
return request_->SetHttpMethod(method) ? JNI_TRUE : JNI_FALSE;
}
jboolean CronetURLRequestAdapter::AddRequestHeader(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller,
const JavaParamRef<jstring>& jname,
const JavaParamRef<jstring>& jvalue) {
std::string name(base::android::ConvertJavaStringToUTF8(env, jname));
std::string value(base::android::ConvertJavaStringToUTF8(env, jvalue));
return request_->AddRequestHeader(name, value) ? JNI_TRUE : JNI_FALSE;
}
void CronetURLRequestAdapter::SetUpload(
std::unique_ptr<net::UploadDataStream> upload) {
request_->SetUpload(std::move(upload));
}
void CronetURLRequestAdapter::Start(JNIEnv* env,
const JavaParamRef<jobject>& jcaller) {
request_->Start();
}
void CronetURLRequestAdapter::GetStatus(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller,
const JavaParamRef<jobject>& jstatus_listener) {
base::android::ScopedJavaGlobalRef<jobject> status_listener_ref;
status_listener_ref.Reset(env, jstatus_listener);
request_->GetStatus(base::BindOnce(&CronetURLRequestAdapter::OnStatus,
base::Unretained(this),
status_listener_ref));
}
void CronetURLRequestAdapter::FollowDeferredRedirect(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller) {
request_->FollowDeferredRedirect();
}
jboolean CronetURLRequestAdapter::ReadData(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller,
const JavaParamRef<jobject>& jbyte_buffer,
jint jposition,
jint jlimit) {
DCHECK_LT(jposition, jlimit);
void* data = env->GetDirectBufferAddress(jbyte_buffer);
if (!data)
return JNI_FALSE;
IOBufferWithByteBuffer* read_buffer =
new IOBufferWithByteBuffer(env, jbyte_buffer, data, jposition, jlimit);
int remaining_capacity = jlimit - jposition;
request_->ReadData(read_buffer, remaining_capacity);
return JNI_TRUE;
}
void CronetURLRequestAdapter::Destroy(JNIEnv* env,
const JavaParamRef<jobject>& jcaller,
jboolean jsend_on_canceled) {
// Destroy could be called from any thread, including network thread (if
// posting task to executor throws an exception), but is posted, so |this|
// is valid until calling task is complete. Destroy() is always called from
// within a synchronized java block that guarantees no future posts to the
// network thread with the adapter pointer.
request_->Destroy(jsend_on_canceled == JNI_TRUE);
}
void CronetURLRequestAdapter::OnReceivedRedirect(
const std::string& new_location,
int http_status_code,
const std::string& http_status_text,
const net::HttpResponseHeaders* headers,
bool was_cached,
const std::string& negotiated_protocol,
const std::string& proxy_server,
int64_t received_byte_count) {
JNIEnv* env = base::android::AttachCurrentThread();
cronet::Java_CronetUrlRequest_onRedirectReceived(
env, owner_, ConvertUTF8ToJavaString(env, new_location), http_status_code,
ConvertUTF8ToJavaString(env, http_status_text),
ConvertResponseHeadersToJava(env, headers),
was_cached ? JNI_TRUE : JNI_FALSE,
ConvertUTF8ToJavaString(env, negotiated_protocol),
ConvertUTF8ToJavaString(env, proxy_server), received_byte_count);
}
void CronetURLRequestAdapter::OnResponseStarted(
int http_status_code,
const std::string& http_status_text,
const net::HttpResponseHeaders* headers,
bool was_cached,
const std::string& negotiated_protocol,
const std::string& proxy_server,
int64_t received_byte_count) {
JNIEnv* env = base::android::AttachCurrentThread();
cronet::Java_CronetUrlRequest_onResponseStarted(
env, owner_, http_status_code,
ConvertUTF8ToJavaString(env, http_status_text),
ConvertResponseHeadersToJava(env, headers),
was_cached ? JNI_TRUE : JNI_FALSE,
ConvertUTF8ToJavaString(env, negotiated_protocol),
ConvertUTF8ToJavaString(env, proxy_server), received_byte_count);
}
void CronetURLRequestAdapter::OnReadCompleted(
scoped_refptr<net::IOBuffer> buffer,
int bytes_read,
int64_t received_byte_count) {
IOBufferWithByteBuffer* read_buffer =
reinterpret_cast<IOBufferWithByteBuffer*>(buffer.get());
JNIEnv* env = base::android::AttachCurrentThread();
cronet::Java_CronetUrlRequest_onReadCompleted(
env, owner_, read_buffer->byte_buffer(), bytes_read,
read_buffer->initial_position(), read_buffer->initial_limit(),
received_byte_count);
}
void CronetURLRequestAdapter::OnSucceeded(int64_t received_byte_count) {
JNIEnv* env = base::android::AttachCurrentThread();
cronet::Java_CronetUrlRequest_onSucceeded(env, owner_, received_byte_count);
}
void CronetURLRequestAdapter::OnError(int net_error,
int quic_error,
const std::string& error_string,
int64_t received_byte_count) {
JNIEnv* env = base::android::AttachCurrentThread();
cronet::Java_CronetUrlRequest_onError(
env, owner_, NetErrorToUrlRequestError(net_error), net_error, quic_error,
ConvertUTF8ToJavaString(env, error_string), received_byte_count);
}
void CronetURLRequestAdapter::OnCanceled() {
JNIEnv* env = base::android::AttachCurrentThread();
cronet::Java_CronetUrlRequest_onCanceled(env, owner_);
}
void CronetURLRequestAdapter::OnDestroyed() {
JNIEnv* env = base::android::AttachCurrentThread();
cronet::Java_CronetUrlRequest_onNativeAdapterDestroyed(env, owner_);
// |this| adapter will be destroyed by the owner after return from this call.
}
void CronetURLRequestAdapter::OnStatus(
const base::android::ScopedJavaGlobalRef<jobject>& status_listener_ref,
net::LoadState load_status) {
JNIEnv* env = base::android::AttachCurrentThread();
cronet::Java_CronetUrlRequest_onStatus(env, owner_, status_listener_ref,
load_status);
}
void CronetURLRequestAdapter::OnMetricsCollected(
const base::Time& start_time,
const base::TimeTicks& start_ticks,
const base::TimeTicks& dns_start,
const base::TimeTicks& dns_end,
const base::TimeTicks& connect_start,
const base::TimeTicks& connect_end,
const base::TimeTicks& ssl_start,
const base::TimeTicks& ssl_end,
const base::TimeTicks& send_start,
const base::TimeTicks& send_end,
const base::TimeTicks& push_start,
const base::TimeTicks& push_end,
const base::TimeTicks& receive_headers_end,
const base::TimeTicks& request_end,
bool socket_reused,
int64_t sent_bytes_count,
int64_t received_bytes_count) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_CronetUrlRequest_onMetricsCollected(
env, owner_,
metrics_util::ConvertTime(start_ticks, start_ticks, start_time),
metrics_util::ConvertTime(dns_start, start_ticks, start_time),
metrics_util::ConvertTime(dns_end, start_ticks, start_time),
metrics_util::ConvertTime(connect_start, start_ticks, start_time),
metrics_util::ConvertTime(connect_end, start_ticks, start_time),
metrics_util::ConvertTime(ssl_start, start_ticks, start_time),
metrics_util::ConvertTime(ssl_end, start_ticks, start_time),
metrics_util::ConvertTime(send_start, start_ticks, start_time),
metrics_util::ConvertTime(send_end, start_ticks, start_time),
metrics_util::ConvertTime(push_start, start_ticks, start_time),
metrics_util::ConvertTime(push_end, start_ticks, start_time),
metrics_util::ConvertTime(receive_headers_end, start_ticks, start_time),
metrics_util::ConvertTime(request_end, start_ticks, start_time),
socket_reused ? JNI_TRUE : JNI_FALSE, sent_bytes_count,
received_bytes_count);
}
} // namespace cronet

View File

@ -0,0 +1,167 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_CRONET_ANDROID_CRONET_URL_REQUEST_ADAPTER_H_
#define COMPONENTS_CRONET_ANDROID_CRONET_URL_REQUEST_ADAPTER_H_
#include <jni.h>
#include <memory>
#include <string>
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
#include "components/cronet/cronet_url_request.h"
#include "url/gurl.h"
namespace net {
enum LoadState;
class UploadDataStream;
} // namespace net
namespace cronet {
class CronetURLRequestContextAdapter;
class TestUtil;
// An adapter from Java CronetUrlRequest object to native CronetURLRequest.
// Created and configured from a Java thread. Start, ReadData, and Destroy are
// posted to network thread and all callbacks into the Java CronetUrlRequest are
// done on the network thread. Java CronetUrlRequest is expected to initiate the
// next step like FollowDeferredRedirect, ReadData or Destroy. Public methods
// can be called on any thread.
class CronetURLRequestAdapter : public CronetURLRequest::Callback {
public:
// Bypasses cache if |jdisable_cache| is true. If context is not set up to
// use cache, |jdisable_cache| has no effect. |jdisable_connection_migration|
// causes connection migration to be disabled for this request if true. If
// global connection migration flag is not enabled,
// |jdisable_connection_migration| has no effect.
CronetURLRequestAdapter(CronetURLRequestContextAdapter* context,
JNIEnv* env,
jobject jurl_request,
const GURL& url,
net::RequestPriority priority,
jboolean jdisable_cache,
jboolean jdisable_connection_migration,
jboolean jenable_metrics,
jboolean jtraffic_stats_tag_set,
jint jtraffic_stats_tag,
jboolean jtraffic_stats_uid_set,
jint jtraffic_stats_uid,
net::Idempotency idempotency);
CronetURLRequestAdapter(const CronetURLRequestAdapter&) = delete;
CronetURLRequestAdapter& operator=(const CronetURLRequestAdapter&) = delete;
~CronetURLRequestAdapter() override;
// Methods called prior to Start are never called on network thread.
// Sets the request method GET, POST etc.
jboolean SetHttpMethod(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jstring>& jmethod);
// Adds a header to the request before it starts.
jboolean AddRequestHeader(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jstring>& jname,
const base::android::JavaParamRef<jstring>& jvalue);
// Adds a request body to the request before it starts.
void SetUpload(std::unique_ptr<net::UploadDataStream> upload);
// Starts the request.
void Start(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller);
void GetStatus(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jobject>& jstatus_listener);
// Follows redirect.
void FollowDeferredRedirect(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller);
// Reads more data.
jboolean ReadData(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jobject>& jbyte_buffer,
jint jposition,
jint jcapacity);
// Releases all resources for the request and deletes the object itself.
// |jsend_on_canceled| indicates if Java onCanceled callback should be
// issued to indicate when no more callbacks will be issued.
void Destroy(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
jboolean jsend_on_canceled);
// CronetURLRequest::Callback implementations:
void OnReceivedRedirect(const std::string& new_location,
int http_status_code,
const std::string& http_status_text,
const net::HttpResponseHeaders* headers,
bool was_cached,
const std::string& negotiated_protocol,
const std::string& proxy_server,
int64_t received_byte_count) override;
void OnResponseStarted(int http_status_code,
const std::string& http_status_text,
const net::HttpResponseHeaders* headers,
bool was_cached,
const std::string& negotiated_protocol,
const std::string& proxy_server,
int64_t received_byte_count) override;
void OnReadCompleted(scoped_refptr<net::IOBuffer> buffer,
int bytes_read,
int64_t received_byte_count) override;
void OnSucceeded(int64_t received_byte_count) override;
void OnError(int net_error,
int quic_error,
const std::string& error_string,
int64_t received_byte_count) override;
void OnCanceled() override;
void OnDestroyed() override;
void OnMetricsCollected(const base::Time& request_start_time,
const base::TimeTicks& request_start,
const base::TimeTicks& dns_start,
const base::TimeTicks& dns_end,
const base::TimeTicks& connect_start,
const base::TimeTicks& connect_end,
const base::TimeTicks& ssl_start,
const base::TimeTicks& ssl_end,
const base::TimeTicks& send_start,
const base::TimeTicks& send_end,
const base::TimeTicks& push_start,
const base::TimeTicks& push_end,
const base::TimeTicks& receive_headers_end,
const base::TimeTicks& request_end,
bool socket_reused,
int64_t sent_bytes_count,
int64_t received_bytes_count) override;
void OnStatus(
const base::android::ScopedJavaGlobalRef<jobject>& status_listener_ref,
net::LoadState load_status);
private:
friend class TestUtil;
// Native Cronet URL Request that owns |this|.
raw_ptr<CronetURLRequest> request_;
// Java object that owns this CronetURLRequestContextAdapter.
base::android::ScopedJavaGlobalRef<jobject> owner_;
};
} // namespace cronet
#endif // COMPONENTS_CRONET_ANDROID_CRONET_URL_REQUEST_ADAPTER_H_

View File

@ -0,0 +1,357 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/cronet/android/cronet_url_request_context_adapter.h"
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <limits>
#include <map>
#include <set>
#include <utility>
#include <vector>
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/base64.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/cronet/android/buildflags.h"
#include "components/cronet/android/cronet_jni_headers/CronetUrlRequestContext_jni.h"
#include "components/cronet/android/cronet_library_loader.h"
#include "components/cronet/cronet_prefs_manager.h"
#include "components/cronet/host_cache_persistence_manager.h"
#include "components/cronet/url_request_context_config.h"
#include "components/metrics/library_support/histogram_manager.h"
#include "net/base/load_flags.h"
#include "net/base/logging_network_change_observer.h"
#include "net/base/net_errors.h"
#include "net/base/network_delegate_impl.h"
#include "net/base/url_util.h"
#include "net/cert/caching_cert_verifier.h"
#include "net/cert/cert_verifier.h"
#include "net/cookies/cookie_monster.h"
#include "net/http/http_auth_handler_factory.h"
#include "net/log/file_net_log_observer.h"
#include "net/log/net_log_util.h"
#include "net/nqe/network_quality_estimator_params.h"
#include "net/proxy_resolution/proxy_config_service_android.h"
#include "net/proxy_resolution/proxy_resolution_service.h"
#include "net/third_party/quiche/src/quic/core/quic_versions.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_builder.h"
#include "net/url_request/url_request_interceptor.h"
#if BUILDFLAG(INTEGRATED_MODE)
#include "components/cronet/android/cronet_integrated_mode_state.h"
#endif
using base::android::JavaParamRef;
using base::android::ScopedJavaLocalRef;
namespace {
// Helper method that takes a Java string that can be null, in which case it
// will get converted to an empty string.
std::string ConvertNullableJavaStringToUTF8(JNIEnv* env,
const JavaParamRef<jstring>& jstr) {
std::string str;
if (!jstr.is_null())
base::android::ConvertJavaStringToUTF8(env, jstr, &str);
return str;
}
} // namespace
namespace cronet {
CronetURLRequestContextAdapter::CronetURLRequestContextAdapter(
std::unique_ptr<URLRequestContextConfig> context_config) {
// Create context and pass ownership of |this| (self) to the context.
std::unique_ptr<CronetURLRequestContextAdapter> self(this);
#if BUILDFLAG(INTEGRATED_MODE)
// Create CronetURLRequestContext running in integrated network task runner.
context_ =
new CronetURLRequestContext(std::move(context_config), std::move(self),
GetIntegratedModeNetworkTaskRunner());
#else
context_ =
new CronetURLRequestContext(std::move(context_config), std::move(self));
#endif
}
CronetURLRequestContextAdapter::~CronetURLRequestContextAdapter() = default;
void CronetURLRequestContextAdapter::InitRequestContextOnInitThread(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller) {
jcronet_url_request_context_.Reset(env, jcaller);
context_->InitRequestContextOnInitThread();
}
void CronetURLRequestContextAdapter::ConfigureNetworkQualityEstimatorForTesting(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller,
jboolean use_local_host_requests,
jboolean use_smaller_responses,
jboolean disable_offline_check) {
context_->ConfigureNetworkQualityEstimatorForTesting(
use_local_host_requests == JNI_TRUE, use_smaller_responses == JNI_TRUE,
disable_offline_check == JNI_TRUE);
}
void CronetURLRequestContextAdapter::ProvideRTTObservations(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller,
bool should) {
context_->ProvideRTTObservations(should == JNI_TRUE);
}
void CronetURLRequestContextAdapter::ProvideThroughputObservations(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller,
bool should) {
context_->ProvideThroughputObservations(should == JNI_TRUE);
}
void CronetURLRequestContextAdapter::OnInitNetworkThread() {
JNIEnv* env = base::android::AttachCurrentThread();
Java_CronetUrlRequestContext_initNetworkThread(env,
jcronet_url_request_context_);
}
void CronetURLRequestContextAdapter::OnDestroyNetworkThread() {
// The |context_| is destroyed.
context_ = nullptr;
}
void CronetURLRequestContextAdapter::OnEffectiveConnectionTypeChanged(
net::EffectiveConnectionType effective_connection_type) {
Java_CronetUrlRequestContext_onEffectiveConnectionTypeChanged(
base::android::AttachCurrentThread(), jcronet_url_request_context_,
effective_connection_type);
}
void CronetURLRequestContextAdapter::OnRTTOrThroughputEstimatesComputed(
int32_t http_rtt_ms,
int32_t transport_rtt_ms,
int32_t downstream_throughput_kbps) {
Java_CronetUrlRequestContext_onRTTOrThroughputEstimatesComputed(
base::android::AttachCurrentThread(), jcronet_url_request_context_,
http_rtt_ms, transport_rtt_ms, downstream_throughput_kbps);
}
void CronetURLRequestContextAdapter::OnRTTObservation(
int32_t rtt_ms,
int32_t timestamp_ms,
net::NetworkQualityObservationSource source) {
Java_CronetUrlRequestContext_onRttObservation(
base::android::AttachCurrentThread(), jcronet_url_request_context_,
rtt_ms, timestamp_ms, source);
}
void CronetURLRequestContextAdapter::OnThroughputObservation(
int32_t throughput_kbps,
int32_t timestamp_ms,
net::NetworkQualityObservationSource source) {
Java_CronetUrlRequestContext_onThroughputObservation(
base::android::AttachCurrentThread(), jcronet_url_request_context_,
throughput_kbps, timestamp_ms, source);
}
void CronetURLRequestContextAdapter::OnStopNetLogCompleted() {
Java_CronetUrlRequestContext_stopNetLogCompleted(
base::android::AttachCurrentThread(), jcronet_url_request_context_);
}
void CronetURLRequestContextAdapter::Destroy(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller) {
// Deleting |context_| on client thread will post cleanup onto network thread,
// which will in turn delete |this| on network thread.
delete context_;
}
net::URLRequestContext* CronetURLRequestContextAdapter::GetURLRequestContext() {
return context_->GetURLRequestContext();
}
void CronetURLRequestContextAdapter::PostTaskToNetworkThread(
const base::Location& posted_from,
base::OnceClosure callback) {
context_->PostTaskToNetworkThread(posted_from, std::move(callback));
}
bool CronetURLRequestContextAdapter::IsOnNetworkThread() const {
return context_->IsOnNetworkThread();
}
bool CronetURLRequestContextAdapter::StartNetLogToFile(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller,
const JavaParamRef<jstring>& jfile_name,
jboolean jlog_all) {
std::string file_name(
base::android::ConvertJavaStringToUTF8(env, jfile_name));
return context_->StartNetLogToFile(file_name, jlog_all == JNI_TRUE);
}
void CronetURLRequestContextAdapter::StartNetLogToDisk(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller,
const JavaParamRef<jstring>& jdir_name,
jboolean jlog_all,
jint jmax_size) {
std::string dir_name(base::android::ConvertJavaStringToUTF8(env, jdir_name));
context_->StartNetLogToDisk(dir_name, jlog_all == JNI_TRUE, jmax_size);
}
void CronetURLRequestContextAdapter::StopNetLog(
JNIEnv* env,
const JavaParamRef<jobject>& jcaller) {
context_->StopNetLog();
}
int CronetURLRequestContextAdapter::default_load_flags() const {
return context_->default_load_flags();
}
// Create a URLRequestContextConfig from the given parameters.
static jlong JNI_CronetUrlRequestContext_CreateRequestContextConfig(
JNIEnv* env,
const JavaParamRef<jstring>& juser_agent,
const JavaParamRef<jstring>& jstorage_path,
jboolean jquic_enabled,
const JavaParamRef<jstring>& jquic_default_user_agent_id,
jboolean jhttp2_enabled,
jboolean jbrotli_enabled,
jboolean jdisable_cache,
jint jhttp_cache_mode,
jlong jhttp_cache_max_size,
const JavaParamRef<jstring>& jexperimental_quic_connection_options,
jlong jmock_cert_verifier,
jboolean jenable_network_quality_estimator,
jboolean jbypass_public_key_pinning_for_local_trust_anchors,
jint jnetwork_thread_priority) {
std::unique_ptr<URLRequestContextConfig> url_request_context_config =
URLRequestContextConfig::CreateURLRequestContextConfig(
jquic_enabled,
ConvertNullableJavaStringToUTF8(env, jquic_default_user_agent_id),
jhttp2_enabled, jbrotli_enabled,
static_cast<URLRequestContextConfig::HttpCacheType>(jhttp_cache_mode),
jhttp_cache_max_size, jdisable_cache,
ConvertNullableJavaStringToUTF8(env, jstorage_path),
/* accept_languages */ std::string(),
ConvertNullableJavaStringToUTF8(env, juser_agent),
ConvertNullableJavaStringToUTF8(
env, jexperimental_quic_connection_options),
base::WrapUnique(
reinterpret_cast<net::CertVerifier*>(jmock_cert_verifier)),
jenable_network_quality_estimator,
jbypass_public_key_pinning_for_local_trust_anchors,
jnetwork_thread_priority >= -20 && jnetwork_thread_priority <= 19
? absl::optional<double>(jnetwork_thread_priority)
: absl::optional<double>());
return reinterpret_cast<jlong>(url_request_context_config.release());
}
// Add a QUIC hint to a URLRequestContextConfig.
static void JNI_CronetUrlRequestContext_AddQuicHint(
JNIEnv* env,
jlong jurl_request_context_config,
const JavaParamRef<jstring>& jhost,
jint jport,
jint jalternate_port) {
URLRequestContextConfig* config =
reinterpret_cast<URLRequestContextConfig*>(jurl_request_context_config);
config->quic_hints.push_back(
std::make_unique<URLRequestContextConfig::QuicHint>(
base::android::ConvertJavaStringToUTF8(env, jhost), jport,
jalternate_port));
}
// Add a public key pin to URLRequestContextConfig.
// |jhost| is the host to apply the pin to.
// |jhashes| is an array of jbyte[32] representing SHA256 key hashes.
// |jinclude_subdomains| indicates if pin should be applied to subdomains.
// |jexpiration_time| is the time that the pin expires, in milliseconds since
// Jan. 1, 1970, midnight GMT.
static void JNI_CronetUrlRequestContext_AddPkp(
JNIEnv* env,
jlong jurl_request_context_config,
const JavaParamRef<jstring>& jhost,
const JavaParamRef<jobjectArray>& jhashes,
jboolean jinclude_subdomains,
jlong jexpiration_time) {
URLRequestContextConfig* config =
reinterpret_cast<URLRequestContextConfig*>(jurl_request_context_config);
std::unique_ptr<URLRequestContextConfig::Pkp> pkp(
new URLRequestContextConfig::Pkp(
base::android::ConvertJavaStringToUTF8(env, jhost),
jinclude_subdomains,
base::Time::UnixEpoch() + base::Milliseconds(jexpiration_time)));
for (auto bytes_array : jhashes.ReadElements<jbyteArray>()) {
static_assert(std::is_pod<net::SHA256HashValue>::value,
"net::SHA256HashValue is not POD");
static_assert(sizeof(net::SHA256HashValue) * CHAR_BIT == 256,
"net::SHA256HashValue contains overhead");
if (env->GetArrayLength(bytes_array.obj()) !=
sizeof(net::SHA256HashValue)) {
LOG(ERROR) << "Unable to add public key hash value.";
continue;
}
jbyte* bytes = env->GetByteArrayElements(bytes_array.obj(), nullptr);
net::HashValue hash(*reinterpret_cast<net::SHA256HashValue*>(bytes));
pkp->pin_hashes.push_back(hash);
env->ReleaseByteArrayElements(bytes_array.obj(), bytes, JNI_ABORT);
}
config->pkp_list.push_back(std::move(pkp));
}
// Creates RequestContextAdater if config is valid URLRequestContextConfig,
// returns 0 otherwise.
static jlong JNI_CronetUrlRequestContext_CreateRequestContextAdapter(
JNIEnv* env,
jlong jconfig) {
std::unique_ptr<URLRequestContextConfig> context_config(
reinterpret_cast<URLRequestContextConfig*>(jconfig));
CronetURLRequestContextAdapter* context_adapter =
new CronetURLRequestContextAdapter(std::move(context_config));
return reinterpret_cast<jlong>(context_adapter);
}
static jint JNI_CronetUrlRequestContext_SetMinLogLevel(
JNIEnv* env,
jint jlog_level) {
jint old_log_level = static_cast<jint>(logging::GetMinLogLevel());
// MinLogLevel is global, shared by all URLRequestContexts.
logging::SetMinLogLevel(static_cast<int>(jlog_level));
return old_log_level;
}
static ScopedJavaLocalRef<jbyteArray>
JNI_CronetUrlRequestContext_GetHistogramDeltas(JNIEnv* env) {
std::vector<uint8_t> data;
if (!metrics::HistogramManager::GetInstance()->GetDeltas(&data))
return ScopedJavaLocalRef<jbyteArray>();
return base::android::ToJavaByteArray(env, data.data(), data.size());
}
} // namespace cronet

View File

@ -0,0 +1,155 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_CRONET_ANDROID_CRONET_URL_REQUEST_CONTEXT_ADAPTER_H_
#define COMPONENTS_CRONET_ANDROID_CRONET_URL_REQUEST_CONTEXT_ADAPTER_H_
#include <jni.h>
#include <stdint.h>
#include <memory>
#include "base/android/scoped_java_ref.h"
#include "base/callback.h"
#include "base/containers/queue.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/threading/thread.h"
#include "components/cronet/cronet_url_request_context.h"
#include "components/prefs/json_pref_store.h"
#include "net/nqe/effective_connection_type.h"
#include "net/nqe/effective_connection_type_observer.h"
#include "net/nqe/network_quality_estimator.h"
#include "net/nqe/network_quality_observation_source.h"
#include "net/nqe/rtt_throughput_estimates_observer.h"
namespace net {
class NetLog;
class URLRequestContext;
} // namespace net
namespace cronet {
class TestUtil;
struct URLRequestContextConfig;
// Adapter between Java CronetUrlRequestContext and CronetURLRequestContext.
class CronetURLRequestContextAdapter
: public CronetURLRequestContext::Callback {
public:
explicit CronetURLRequestContextAdapter(
std::unique_ptr<URLRequestContextConfig> context_config);
CronetURLRequestContextAdapter(const CronetURLRequestContextAdapter&) =
delete;
CronetURLRequestContextAdapter& operator=(
const CronetURLRequestContextAdapter&) = delete;
~CronetURLRequestContextAdapter() override;
// Called on init Java thread to initialize URLRequestContext.
void InitRequestContextOnInitThread(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller);
// Releases all resources for the request context and deletes the object.
// Blocks until network thread is destroyed after running all pending tasks.
void Destroy(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller);
// Posts a task that might depend on the context being initialized
// to the network thread.
void PostTaskToNetworkThread(const base::Location& posted_from,
base::OnceClosure callback);
bool IsOnNetworkThread() const;
net::URLRequestContext* GetURLRequestContext();
// TODO(xunjieli): Keep only one version of StartNetLog().
// Starts NetLog logging to file. This can be called on any thread.
// Return false if |jfile_name| cannot be opened.
bool StartNetLogToFile(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jstring>& jfile_name,
jboolean jlog_all);
// Starts NetLog logging to disk with a bounded amount of disk space. This
// can be called on any thread.
void StartNetLogToDisk(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jstring>& jdir_name,
jboolean jlog_all,
jint jmax_size);
// Stops NetLog logging to file. This can be called on any thread. This will
// flush any remaining writes to disk.
void StopNetLog(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller);
// Default net::LOAD flags used to create requests.
int default_load_flags() const;
// Called on init Java thread to initialize URLRequestContext.
void InitRequestContextOnInitThread();
// Configures the network quality estimator to observe requests to localhost,
// to use smaller responses when estimating throughput, and to disable the
// device offline checks when computing the effective connection type or when
// writing the prefs. This should only be used for testing. This can be
// called only after the network quality estimator has been enabled.
void ConfigureNetworkQualityEstimatorForTesting(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
jboolean use_local_host_requests,
jboolean use_smaller_responses,
jboolean disable_offline_check);
// Request that RTT and/or throughput observations should or should not be
// provided by the network quality estimator.
void ProvideRTTObservations(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
bool should);
void ProvideThroughputObservations(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
bool should);
CronetURLRequestContext* cronet_url_request_context() const {
return context_;
}
// CronetURLRequestContext::Callback
void OnInitNetworkThread() override;
void OnDestroyNetworkThread() override;
void OnEffectiveConnectionTypeChanged(
net::EffectiveConnectionType effective_connection_type) override;
void OnRTTOrThroughputEstimatesComputed(
int32_t http_rtt_ms,
int32_t transport_rtt_ms,
int32_t downstream_throughput_kbps) override;
void OnRTTObservation(int32_t rtt_ms,
int32_t timestamp_ms,
net::NetworkQualityObservationSource source) override;
void OnThroughputObservation(
int32_t throughput_kbps,
int32_t timestamp_ms,
net::NetworkQualityObservationSource source) override;
void OnStopNetLogCompleted() override;
private:
friend class TestUtil;
// Native Cronet URL Request Context.
raw_ptr<CronetURLRequestContext> context_;
// Java object that owns this CronetURLRequestContextAdapter.
base::android::ScopedJavaGlobalRef<jobject> jcronet_url_request_context_;
};
} // namespace cronet
#endif // COMPONENTS_CRONET_ANDROID_CRONET_URL_REQUEST_CONTEXT_ADAPTER_H_

View File

@ -0,0 +1,211 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import android.content.Context;
import org.chromium.net.CronetEngine;
import org.chromium.net.ExperimentalCronetEngine;
import org.chromium.net.UrlRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Controller for fake Cronet implementation. Allows a test to setup responses for
* {@link UrlRequest}s. If multiple {@link ResponseMatcher}s match a specific request, the first
* {@link ResponseMatcher} added takes precedence.
*/
public final class FakeCronetController {
// List of FakeCronetEngines so that FakeCronetEngine can be accessed when created with
// the {@link FakeCronetProvider}.
private static final List<CronetEngine> sInstances =
Collections.synchronizedList(new ArrayList<>());
// List of ResponseMatchers to be checked for a response to a request in place of a server.
private final List<ResponseMatcher> mResponseMatchers =
Collections.synchronizedList(new ArrayList<>());
/**
* Creates a fake {@link CronetEngine.Builder} that creates {@link CronetEngine}s that return
* fake {@link UrlRequests}. Once built, the {@link CronetEngine}'s {@link UrlRequest}s will
* retrieve responses from this {@link FakeCronetController}.
*
* @param context the Android context to build the fake {@link CronetEngine} from.
* @return a fake CronetEngine.Builder that uses this {@link FakeCronetController} to manage
* responses once it is built.
*/
public CronetEngine.Builder newFakeCronetEngineBuilder(Context context) {
FakeCronetEngine.Builder builder = new FakeCronetEngine.Builder(context);
builder.setController(this);
// FakeCronetEngine.Builder is not actually a CronetEngine.Builder, so construct one with
// the child of CronetEngine.Builder: ExperimentalCronetEngine.Builder.
return new ExperimentalCronetEngine.Builder(builder);
}
/**
* Adds a {@link UrlResponseMatcher} that will respond to the provided URL with the provided
* {@link FakeUrlResponse}. Equivalent to:
* addResponseMatcher(new UrlResponseMatcher(url, response)).
*
* @param response a {@link FakeUrlResponse} to respond with
* @param url a url for which the response should be returned
*/
public void addResponseForUrl(FakeUrlResponse response, String url) {
addResponseMatcher(new UrlResponseMatcher(url, response));
}
/**
* Adds a {@link ResponseMatcher} to the list of {@link ResponseMatcher}s.
*
* @param matcher the {@link ResponseMatcher} that should be matched against a request
*/
public void addResponseMatcher(ResponseMatcher matcher) {
mResponseMatchers.add(matcher);
}
/**
* Removes a specific {@link ResponseMatcher} from the list of {@link ResponseMatcher}s.
*
* @param matcher the {@link ResponseMatcher} to remove
*/
public void removeResponseMatcher(ResponseMatcher matcher) {
mResponseMatchers.remove(matcher);
}
/**
* Removes all {@link ResponseMatcher}s from the list of {@link ResponseMatcher}s.
*/
public void clearResponseMatchers() {
mResponseMatchers.clear();
}
/**
* Adds a {@link FakeUrlResponse} to the list of responses that will redirect a
* {@link UrlRequest} to the specified URL.
*
* @param redirectLocation the URL to redirect the {@link UrlRequest} to
* @param url the URL that will trigger the redirect
*/
public void addRedirectResponse(String redirectLocation, String url) {
FakeUrlResponse redirectResponse = new FakeUrlResponse.Builder()
.setHttpStatusCode(302)
.addHeader("location", redirectLocation)
.build();
addResponseForUrl(redirectResponse, url);
}
/**
* Adds an {@link FakeUrlResponse} that fails with the specified HTTP code for the specified
* URL.
*
* @param statusCode the code for the {@link FakeUrlResponse}
* @param url the URL that should trigger the error response when requested by a
* {@link UrlRequest}
* @throws IllegalArgumentException if the HTTP status code is not an error code (code >= 400)
*/
public void addHttpErrorResponse(int statusCode, String url) {
addResponseForUrl(getFailedResponse(statusCode), url);
}
// TODO(kirchman): Create a function to add a response that takes a CronetException.
/**
* Adds a successful 200 code {@link FakeUrlResponse} that will match the specified
* URL when requested by a {@link UrlRequest}.
*
* @param url the URL that triggers the successful response
* @param body the body of the response as a byte array
*/
public void addSuccessResponse(String url, byte[] body) {
addResponseForUrl(new FakeUrlResponse.Builder().setResponseBody(body).build(), url);
}
/**
* Returns the {@link CronetEngineController} for a specified {@link CronetEngine}. This method
* should be used in conjunction with {@link FakeCronetController.getInstances}.
*
* @param engine the fake {@link CronetEngine} to get the controller for.
* @return the controller for the specified fake {@link CronetEngine}.
*/
public static FakeCronetController getControllerForFakeEngine(CronetEngine engine) {
if (engine instanceof FakeCronetEngine) {
FakeCronetEngine fakeEngine = (FakeCronetEngine) engine;
return fakeEngine.getController();
}
throw new IllegalArgumentException("Provided CronetEngine is not a fake CronetEngine");
}
/**
* Returns all created fake instances of {@link CronetEngine} that have not been shut down with
* {@link CronetEngine.shutdown()} in order of creation. Can be used to retrieve a controller
* in conjunction with {@link FakeCronetController.getControllerForFakeEngine}.
*
* @return a list of all fake {@link CronetEngine}s that have been created
*/
public static List<CronetEngine> getFakeCronetEngines() {
synchronized (sInstances) {
return new ArrayList<>(sInstances);
}
}
/**
* Removes a fake {@link CronetEngine} from the list of {@link CronetEngine} instances.
*
* @param cronetEngine the instance to remove
*/
static void removeFakeCronetEngine(CronetEngine cronetEngine) {
sInstances.remove(cronetEngine);
}
/**
* Add a CronetEngine to the list of CronetEngines.
*
* @param engine the {@link CronetEngine} to add
*/
static void addFakeCronetEngine(FakeCronetEngine engine) {
sInstances.add(engine);
}
/**
* Gets a response for specified request details if there is one, or a "404" failed response
* if there is no {@link ResponseMatcher} with a {@link FakeUrlResponse} for this request.
*
* @param url the URL that the {@link UrlRequest} is connecting to
* @param httpMethod the HTTP method that the {@link UrlRequest} is using to connect with
* @param headers the headers supplied by the {@link UrlRequest}
* @param body the body of the fake HTTP request
* @return a {@link FakeUrlResponse} if there is one, or a failed "404" response if none found
*/
FakeUrlResponse getResponse(
String url, String httpMethod, List<Map.Entry<String, String>> headers, byte[] body) {
synchronized (mResponseMatchers) {
for (ResponseMatcher responseMatcher : mResponseMatchers) {
FakeUrlResponse matchedResponse =
responseMatcher.getMatchingResponse(url, httpMethod, headers, body);
if (matchedResponse != null) {
return matchedResponse;
}
}
}
return getFailedResponse(404);
}
/**
* Creates and returns a failed response with the specified HTTP status code.
*
* @param statusCode the HTTP code that the returned response will have
* @return a {@link FakeUrlResponse} with the specified code
*/
private static FakeUrlResponse getFailedResponse(int statusCode) {
if (statusCode < 400) {
throw new IllegalArgumentException(
"Expected HTTP error code (code >= 400), but was: " + statusCode);
}
return new FakeUrlResponse.Builder().setHttpStatusCode(statusCode).build();
}
}

View File

@ -0,0 +1,302 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import android.content.Context;
import androidx.annotation.GuardedBy;
import org.chromium.net.BidirectionalStream;
import org.chromium.net.CronetEngine;
import org.chromium.net.ExperimentalBidirectionalStream;
import org.chromium.net.NetworkQualityRttListener;
import org.chromium.net.NetworkQualityThroughputListener;
import org.chromium.net.RequestFinishedInfo;
import org.chromium.net.UrlRequest;
import org.chromium.net.impl.CronetEngineBase;
import org.chromium.net.impl.CronetEngineBuilderImpl;
import org.chromium.net.impl.ImplVersion;
import org.chromium.net.impl.UrlRequestBase;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandlerFactory;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Fake {@link CronetEngine}. This implements CronetEngine.
*/
final class FakeCronetEngine extends CronetEngineBase {
/**
* Builds a {@link FakeCronetEngine}. This implements CronetEngine.Builder.
*/
static class Builder extends CronetEngineBuilderImpl {
private FakeCronetController mController;
/**
* Builder for {@link FakeCronetEngine}.
*
* @param context Android {@link Context}.
*/
Builder(Context context) {
super(context);
}
@Override
public FakeCronetEngine build() {
return new FakeCronetEngine(this);
}
void setController(FakeCronetController controller) {
mController = controller;
}
}
private final FakeCronetController mController;
private final ExecutorService mExecutorService;
private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mIsShutdown;
@GuardedBy("mLock")
private int mActiveRequestCount;
/**
* Creates a {@link FakeCronetEngine}. Used when {@link FakeCronetEngine} is created with the
* {@link FakeCronetEngine.Builder}.
*
* @param builder a {@link CronetEngineBuilderImpl} to build this {@link CronetEngine}
* implementation from.
*/
private FakeCronetEngine(FakeCronetEngine.Builder builder) {
if (builder.mController != null) {
mController = builder.mController;
} else {
mController = new FakeCronetController();
}
mExecutorService = new ThreadPoolExecutor(
/* corePoolSize= */ 1,
/* maximumPoolSize= */ 5,
/* keepAliveTime= */ 50, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
new ThreadFactory() {
@Override
public Thread newThread(final Runnable r) {
return Executors.defaultThreadFactory().newThread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("FakeCronetEngine");
r.run();
}
});
}
});
FakeCronetController.addFakeCronetEngine(this);
}
/**
* Gets the controller associated with this instance that will be used for responses to
* {@link UrlRequest}s.
*
* @return the {@link FakeCronetCntroller} that controls this {@link FakeCronetEngine}.
*/
FakeCronetController getController() {
return mController;
}
@Override
public ExperimentalBidirectionalStream.Builder newBidirectionalStreamBuilder(
String url, BidirectionalStream.Callback callback, Executor executor) {
synchronized (mLock) {
if (mIsShutdown) {
throw new IllegalStateException(
"This instance of CronetEngine has been shutdown and can no longer be "
+ "used.");
}
throw new UnsupportedOperationException(
"The bidirectional stream API is not supported by the Fake implementation "
+ "of CronetEngine.");
}
}
@Override
public String getVersionString() {
return "FakeCronet/" + ImplVersion.getCronetVersionWithLastChange();
}
@Override
public void shutdown() {
synchronized (mLock) {
if (mActiveRequestCount != 0) {
throw new IllegalStateException("Cannot shutdown with active requests.");
} else {
mIsShutdown = true;
}
}
mExecutorService.shutdown();
FakeCronetController.removeFakeCronetEngine(this);
}
@Override
public void startNetLogToFile(String fileName, boolean logAll) {}
@Override
public void startNetLogToDisk(String dirPath, boolean logAll, int maxSize) {}
@Override
public void stopNetLog() {}
@Override
public byte[] getGlobalMetricsDeltas() {
return new byte[0];
}
@Override
public int getEffectiveConnectionType() {
return EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
}
@Override
public int getHttpRttMs() {
return CONNECTION_METRIC_UNKNOWN;
}
@Override
public int getTransportRttMs() {
return CONNECTION_METRIC_UNKNOWN;
}
@Override
public int getDownstreamThroughputKbps() {
return CONNECTION_METRIC_UNKNOWN;
}
@Override
public void configureNetworkQualityEstimatorForTesting(boolean useLocalHostRequests,
boolean useSmallerResponses, boolean disableOfflineCheck) {}
@Override
public void addRttListener(NetworkQualityRttListener listener) {}
@Override
public void removeRttListener(NetworkQualityRttListener listener) {}
@Override
public void addThroughputListener(NetworkQualityThroughputListener listener) {}
@Override
public void removeThroughputListener(NetworkQualityThroughputListener listener) {}
@Override
public void addRequestFinishedListener(RequestFinishedInfo.Listener listener) {}
@Override
public void removeRequestFinishedListener(RequestFinishedInfo.Listener listener) {}
// TODO(crbug.com/669707) Instantiate a fake CronetHttpUrlConnection wrapping a FakeUrlRequest
// here.
@Override
public URLConnection openConnection(URL url) throws IOException {
throw new UnsupportedOperationException(
"The openConnection API is not supported by the Fake implementation of "
+ "CronetEngine.");
}
@Override
public URLConnection openConnection(URL url, Proxy proxy) throws IOException {
throw new UnsupportedOperationException(
"The openConnection API is not supported by the Fake implementation of "
+ "CronetEngine.");
}
@Override
public URLStreamHandlerFactory createURLStreamHandlerFactory() {
throw new UnsupportedOperationException(
"The URLStreamHandlerFactory API is not supported by the Fake implementation of "
+ "CronetEngine.");
}
@Override
protected UrlRequestBase createRequest(String url, UrlRequest.Callback callback,
Executor userExecutor, int priority, Collection<Object> connectionAnnotations,
boolean disableCache, boolean disableConnectionMigration, boolean allowDirectExecutor,
boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet,
int trafficStatsUid, RequestFinishedInfo.Listener requestFinishedListener,
int idempotency) {
synchronized (mLock) {
if (mIsShutdown) {
throw new IllegalStateException(
"This instance of CronetEngine has been shutdown and can no longer be "
+ "used.");
}
return new FakeUrlRequest(callback, userExecutor, mExecutorService, url,
allowDirectExecutor, trafficStatsTagSet, trafficStatsTag, trafficStatsUidSet,
trafficStatsUid, mController, this);
}
}
@Override
protected ExperimentalBidirectionalStream createBidirectionalStream(String url,
BidirectionalStream.Callback callback, Executor executor, String httpMethod,
List<Map.Entry<String, String>> requestHeaders, @StreamPriority int priority,
boolean delayRequestHeadersUntilFirstFlush, Collection<Object> connectionAnnotations,
boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet,
int trafficStatsUid) {
synchronized (mLock) {
if (mIsShutdown) {
throw new IllegalStateException(
"This instance of CronetEngine has been shutdown and can no longer be "
+ "used.");
}
throw new UnsupportedOperationException(
"The BidirectionalStream API is not supported by the Fake implementation of "
+ "CronetEngine.");
}
}
/**
* Mark request as started to prevent shutdown when there are active
* requests, only if the engine is not shutdown.
*
* @return true if the engine is not shutdown and the request is marked as started.
*/
boolean startRequest() {
synchronized (mLock) {
if (!mIsShutdown) {
mActiveRequestCount++;
return true;
}
return false;
}
}
/**
* Mark request as finished to allow shutdown when there are no active
* requests.
*/
void onRequestDestroyed() {
synchronized (mLock) {
// Sanity check. We should not be able to shutdown if there are still running requests.
if (mIsShutdown) {
throw new IllegalStateException(
"This instance of CronetEngine was shutdown. All requests must have been "
+ "complete.");
}
mActiveRequestCount--;
}
}
}

View File

@ -0,0 +1,70 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import android.content.Context;
import org.chromium.net.CronetEngine;
import org.chromium.net.CronetProvider;
import org.chromium.net.ExperimentalCronetEngine;
import org.chromium.net.ICronetEngineBuilder;
import org.chromium.net.impl.ImplVersion;
import java.util.Arrays;
/**
* Implementation of {@link CronetProvider} that creates {@link CronetEngine.Builder}
* for building the Fake implementation of {@link CronetEngine}.
* {@hide}
*/
public class FakeCronetProvider extends CronetProvider {
/**
* String returned by {@link CronetProvider#getName} for {@link CronetProvider}
* that provides the fake Cronet implementation.
*/
public static final String PROVIDER_NAME_FAKE = "Fake-Cronet-Provider";
/**
* Constructs a {@link FakeCronetProvider}.
*
* @param context Android context to use
*/
public FakeCronetProvider(Context context) {
super(context);
}
@Override
public CronetEngine.Builder createBuilder() {
ICronetEngineBuilder impl = new FakeCronetEngine.Builder(mContext);
return new ExperimentalCronetEngine.Builder(impl);
}
@Override
public String getName() {
return PROVIDER_NAME_FAKE;
}
@Override
public String getVersion() {
return ImplVersion.getCronetVersion();
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public int hashCode() {
return Arrays.hashCode(new Object[] {FakeCronetProvider.class, mContext});
}
@Override
public boolean equals(Object other) {
return other == this
|| (other instanceof FakeCronetProvider
&& this.mContext.equals(((FakeCronetProvider) other).mContext));
}
}

View File

@ -0,0 +1,747 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import android.util.Log;
import androidx.annotation.GuardedBy;
import androidx.annotation.VisibleForTesting;
import org.chromium.net.CronetException;
import org.chromium.net.InlineExecutionProhibitedException;
import org.chromium.net.UploadDataProvider;
import org.chromium.net.UrlResponseInfo;
import org.chromium.net.impl.CallbackExceptionImpl;
import org.chromium.net.impl.CronetExceptionImpl;
import org.chromium.net.impl.JavaUploadDataSinkBase;
import org.chromium.net.impl.JavaUrlRequestUtils;
import org.chromium.net.impl.JavaUrlRequestUtils.CheckedRunnable;
import org.chromium.net.impl.JavaUrlRequestUtils.DirectPreventingExecutor;
import org.chromium.net.impl.JavaUrlRequestUtils.State;
import org.chromium.net.impl.Preconditions;
import org.chromium.net.impl.UrlRequestBase;
import org.chromium.net.impl.UrlResponseInfoImpl;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
/**
* Fake UrlRequest that retrieves responses from the associated FakeCronetController. Used for
* testing Cronet usage on Android.
*/
final class FakeUrlRequest extends UrlRequestBase {
private static final int DEFAULT_UPLOAD_BUFFER_SIZE = 8192;
// Used for logging errors.
private static final String TAG = FakeUrlRequest.class.getSimpleName();
// Callback used to report responses to the client.
private final Callback mCallback;
// The {@link Executor} provided by the user to be used for callbacks.
private final Executor mUserExecutor;
// The {@link Executor} provided by the engine used to break up callback loops.
private final Executor mExecutor;
// The {@link FakeCronetController} that will provide responses for this request.
private final FakeCronetController mFakeCronetController;
// The fake {@link CronetEngine} that should be notified when this request starts and stops.
private final FakeCronetEngine mFakeCronetEngine;
// Source of thread safety for this class.
private final Object mLock = new Object();
// True if direct execution is allowed for this request.
private final boolean mAllowDirectExecutor;
// The chain of URL's this request has received.
@GuardedBy("mLock")
private final List<String> mUrlChain = new ArrayList<>();
// The list of HTTP headers used by this request to establish a connection.
@GuardedBy("mLock")
private final ArrayList<Map.Entry<String, String>> mAllHeadersList = new ArrayList<>();
// The current URL this request is connecting to.
@GuardedBy("mLock")
private String mCurrentUrl;
// The {@link FakeUrlResponse} for the current URL.
@GuardedBy("mLock")
private FakeUrlResponse mCurrentFakeResponse;
// The body of the request from UploadDataProvider.
@GuardedBy("mLock")
private byte[] mRequestBody;
// The {@link UploadDataProvider} to retrieve a request body from.
@GuardedBy("mLock")
private UploadDataProvider mUploadDataProvider;
// The executor to call the {@link UploadDataProvider}'s callback methods with.
@GuardedBy("mLock")
private Executor mUploadExecutor;
// The {@link UploadDataSink} for the {@link UploadDataProvider}.
@GuardedBy("mLock")
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
FakeDataSink mFakeDataSink;
// The {@link UrlResponseInfo} for the current request.
@GuardedBy("mLock")
private UrlResponseInfo mUrlResponseInfo;
// The response from the current request that needs to be sent.
@GuardedBy("mLock")
private ByteBuffer mResponse;
// The HTTP method used by this request to establish a connection.
@GuardedBy("mLock")
private String mHttpMethod;
// True after the {@link UploadDataProvider} for this request has been closed.
@GuardedBy("mLock")
private boolean mUploadProviderClosed;
@GuardedBy("mLock")
@State
private int mState = State.NOT_STARTED;
/**
* Holds a subset of StatusValues - {@link State#STARTED} can represent
* {@link Status#SENDING_REQUEST} or {@link Status#WAITING_FOR_RESPONSE}. While the distinction
* isn't needed to implement the logic in this class, it is needed to implement
* {@link #getStatus(StatusListener)}.
*/
@StatusValues
private volatile int mAdditionalStatusDetails = Status.INVALID;
/**
* Used to map from HTTP status codes to the corresponding human-readable text.
*/
private static final Map<Integer, String> HTTP_STATUS_CODE_TO_TEXT;
static {
Map<Integer, String> httpCodeMap = new HashMap<>();
httpCodeMap.put(100, "Continue");
httpCodeMap.put(101, "Switching Protocols");
httpCodeMap.put(102, "Processing");
httpCodeMap.put(103, "Early Hints");
httpCodeMap.put(200, "OK");
httpCodeMap.put(201, "Created");
httpCodeMap.put(202, "Accepted");
httpCodeMap.put(203, "Non-Authoritative Information");
httpCodeMap.put(204, "No Content");
httpCodeMap.put(205, "Reset Content");
httpCodeMap.put(206, "Partial Content");
httpCodeMap.put(207, "Multi-Status");
httpCodeMap.put(208, "Already Reported");
httpCodeMap.put(226, "IM Used");
httpCodeMap.put(300, "Multiple Choices");
httpCodeMap.put(301, "Moved Permanently");
httpCodeMap.put(302, "Found");
httpCodeMap.put(303, "See Other");
httpCodeMap.put(304, "Not Modified");
httpCodeMap.put(305, "Use Proxy");
httpCodeMap.put(306, "Unused");
httpCodeMap.put(307, "Temporary Redirect");
httpCodeMap.put(308, "Permanent Redirect");
httpCodeMap.put(400, "Bad Request");
httpCodeMap.put(401, "Unauthorized");
httpCodeMap.put(402, "Payment Required");
httpCodeMap.put(403, "Forbidden");
httpCodeMap.put(404, "Not Found");
httpCodeMap.put(405, "Method Not Allowed");
httpCodeMap.put(406, "Not Acceptable");
httpCodeMap.put(407, "Proxy Authentication Required");
httpCodeMap.put(408, "Request Timeout");
httpCodeMap.put(409, "Conflict");
httpCodeMap.put(410, "Gone");
httpCodeMap.put(411, "Length Required");
httpCodeMap.put(412, "Precondition Failed");
httpCodeMap.put(413, "Payload Too Large");
httpCodeMap.put(414, "URI Too Long");
httpCodeMap.put(415, "Unsupported Media Type");
httpCodeMap.put(416, "Range Not Satisfiable");
httpCodeMap.put(417, "Expectation Failed");
httpCodeMap.put(421, "Misdirected Request");
httpCodeMap.put(422, "Unprocessable Entity");
httpCodeMap.put(423, "Locked");
httpCodeMap.put(424, "Failed Dependency");
httpCodeMap.put(425, "Too Early");
httpCodeMap.put(426, "Upgrade Required");
httpCodeMap.put(428, "Precondition Required");
httpCodeMap.put(429, "Too Many Requests");
httpCodeMap.put(431, "Request Header Fields Too Large");
httpCodeMap.put(451, "Unavailable For Legal Reasons");
httpCodeMap.put(500, "Internal Server Error");
httpCodeMap.put(501, "Not Implemented");
httpCodeMap.put(502, "Bad Gateway");
httpCodeMap.put(503, "Service Unavailable");
httpCodeMap.put(504, "Gateway Timeout");
httpCodeMap.put(505, "HTTP Version Not Supported");
httpCodeMap.put(506, "Variant Also Negotiates");
httpCodeMap.put(507, "Insufficient Storage");
httpCodeMap.put(508, "Loop Denied");
httpCodeMap.put(510, "Not Extended");
httpCodeMap.put(511, "Network Authentication Required");
HTTP_STATUS_CODE_TO_TEXT = Collections.unmodifiableMap(httpCodeMap);
}
FakeUrlRequest(Callback callback, Executor userExecutor, Executor executor, String url,
boolean allowDirectExecutor, boolean trafficStatsTagSet, int trafficStatsTag,
final boolean trafficStatsUidSet, final int trafficStatsUid,
FakeCronetController fakeCronetController, FakeCronetEngine fakeCronetEngine) {
if (url == null) {
throw new NullPointerException("URL is required");
}
if (callback == null) {
throw new NullPointerException("Listener is required");
}
if (executor == null) {
throw new NullPointerException("Executor is required");
}
mCallback = callback;
mUserExecutor =
allowDirectExecutor ? userExecutor : new DirectPreventingExecutor(userExecutor);
mExecutor = executor;
mCurrentUrl = url;
mFakeCronetController = fakeCronetController;
mFakeCronetEngine = fakeCronetEngine;
mAllowDirectExecutor = allowDirectExecutor;
}
@Override
public void setUploadDataProvider(UploadDataProvider uploadDataProvider, Executor executor) {
if (uploadDataProvider == null) {
throw new NullPointerException("Invalid UploadDataProvider.");
}
synchronized (mLock) {
if (!checkHasContentTypeHeader()) {
throw new IllegalArgumentException(
"Requests with upload data must have a Content-Type.");
}
checkNotStarted();
if (mHttpMethod == null) {
mHttpMethod = "POST";
}
mUploadExecutor =
mAllowDirectExecutor ? executor : new DirectPreventingExecutor(executor);
mUploadDataProvider = uploadDataProvider;
}
}
@Override
public void setHttpMethod(String method) {
synchronized (mLock) {
checkNotStarted();
if (method == null) {
throw new NullPointerException("Method is required.");
}
if ("OPTIONS".equalsIgnoreCase(method) || "GET".equalsIgnoreCase(method)
|| "HEAD".equalsIgnoreCase(method) || "POST".equalsIgnoreCase(method)
|| "PUT".equalsIgnoreCase(method) || "DELETE".equalsIgnoreCase(method)
|| "TRACE".equalsIgnoreCase(method) || "PATCH".equalsIgnoreCase(method)) {
mHttpMethod = method;
} else {
throw new IllegalArgumentException("Invalid http method: " + method);
}
}
}
@Override
public void addHeader(String header, String value) {
synchronized (mLock) {
checkNotStarted();
mAllHeadersList.add(new AbstractMap.SimpleEntry<String, String>(header, value));
}
}
/**
* Verifies that the request is not already started and throws an exception if it is.
*/
@GuardedBy("mLock")
private void checkNotStarted() {
if (mState != State.NOT_STARTED) {
throw new IllegalStateException("Request is already started. State is: " + mState);
}
}
@Override
public void start() {
synchronized (mLock) {
if (mFakeCronetEngine.startRequest()) {
boolean transitionedState = false;
try {
transitionStates(State.NOT_STARTED, State.STARTED);
mAdditionalStatusDetails = Status.CONNECTING;
transitionedState = true;
} finally {
if (!transitionedState) {
cleanup();
}
}
mUrlChain.add(mCurrentUrl);
if (mUploadDataProvider != null) {
mFakeDataSink =
new FakeDataSink(mUploadExecutor, mExecutor, mUploadDataProvider);
mFakeDataSink.start(/* firstTime= */ true);
} else {
fakeConnect();
}
} else {
throw new IllegalStateException("This request's CronetEngine is already shutdown.");
}
}
}
/**
* Fakes a request to a server by retrieving a response to this {@link UrlRequest} from the
* {@link FakeCronetController}.
*/
@GuardedBy("mLock")
private void fakeConnect() {
mAdditionalStatusDetails = Status.WAITING_FOR_RESPONSE;
mCurrentFakeResponse = mFakeCronetController.getResponse(
mCurrentUrl, mHttpMethod, mAllHeadersList, mRequestBody);
int responseCode = mCurrentFakeResponse.getHttpStatusCode();
mUrlResponseInfo = new UrlResponseInfoImpl(
Collections.unmodifiableList(new ArrayList<>(mUrlChain)), responseCode,
getDescriptionByCode(responseCode), mCurrentFakeResponse.getAllHeadersList(),
mCurrentFakeResponse.getWasCached(), mCurrentFakeResponse.getNegotiatedProtocol(),
mCurrentFakeResponse.getProxyServer(),
mCurrentFakeResponse.getResponseBody().length);
mResponse = ByteBuffer.wrap(mCurrentFakeResponse.getResponseBody());
// Check for a redirect.
if (responseCode >= 300 && responseCode < 400) {
processRedirectResponse();
} else {
closeUploadDataProvider();
final UrlResponseInfo info = mUrlResponseInfo;
transitionStates(State.STARTED, State.AWAITING_READ);
executeCheckedRunnable(new CheckedRunnable() {
@Override
public void run() throws Exception {
mCallback.onResponseStarted(FakeUrlRequest.this, info);
}
});
}
}
/**
* Retrieves the redirect location from the response headers and responds to the
* {@link UrlRequest.Callback#onRedirectReceived} method. Adds the redirect URL to the chain.
*
* @param url the URL that the {@link FakeUrlResponse} redirected this request to
*/
@GuardedBy("mLock")
private void processRedirectResponse() {
transitionStates(State.STARTED, State.REDIRECT_RECEIVED);
if (mUrlResponseInfo.getAllHeaders().get("location") == null) {
// Response did not have a location header, so this request must fail.
final String prevUrl = mCurrentUrl;
mUserExecutor.execute(new Runnable() {
@Override
public void run() {
tryToFailWithException(new CronetExceptionImpl(
"Request failed due to bad redirect HTTP headers",
new IllegalStateException("Response recieved from URL: " + prevUrl
+ " was a redirect, but lacked a location header.")));
}
});
return;
}
String pendingRedirectUrl =
URI.create(mCurrentUrl)
.resolve(mUrlResponseInfo.getAllHeaders().get("location").get(0))
.toString();
mCurrentUrl = pendingRedirectUrl;
mUrlChain.add(mCurrentUrl);
transitionStates(State.REDIRECT_RECEIVED, State.AWAITING_FOLLOW_REDIRECT);
final UrlResponseInfo info = mUrlResponseInfo;
mExecutor.execute(new Runnable() {
@Override
public void run() {
executeCheckedRunnable(new CheckedRunnable() {
@Override
public void run() throws Exception {
mCallback.onRedirectReceived(FakeUrlRequest.this, info, pendingRedirectUrl);
}
});
}
});
}
@Override
public void read(ByteBuffer buffer) {
// Entering {@link #State.READING} is somewhat redundant because the entire response is
// already acquired. We should still transition so that the fake {@link UrlRequest} follows
// the same state flow as a real request.
Preconditions.checkHasRemaining(buffer);
Preconditions.checkDirect(buffer);
synchronized (mLock) {
transitionStates(State.AWAITING_READ, State.READING);
final UrlResponseInfo info = mUrlResponseInfo;
if (mResponse.hasRemaining()) {
transitionStates(State.READING, State.AWAITING_READ);
fillBufferWithResponse(buffer);
mExecutor.execute(new Runnable() {
@Override
public void run() {
executeCheckedRunnable(new CheckedRunnable() {
@Override
public void run() throws Exception {
mCallback.onReadCompleted(FakeUrlRequest.this, info, buffer);
}
});
}
});
} else {
if (setTerminalState(State.COMPLETE)) {
mUserExecutor.execute(new Runnable() {
@Override
public void run() {
mCallback.onSucceeded(FakeUrlRequest.this, info);
}
});
}
}
}
}
/**
* Puts as much of the remaining response as will fit into the {@link ByteBuffer} and removes
* that part of the string from the response left to send.
*
* @param buffer the {@link ByteBuffer} to put the response into
* @return the buffer with the response that we want to send back in it
*/
@GuardedBy("mLock")
private void fillBufferWithResponse(ByteBuffer buffer) {
final int maxTransfer = Math.min(buffer.remaining(), mResponse.remaining());
ByteBuffer temp = mResponse.duplicate();
temp.limit(temp.position() + maxTransfer);
buffer.put(temp);
mResponse.position(mResponse.position() + maxTransfer);
}
@Override
public void followRedirect() {
synchronized (mLock) {
transitionStates(State.AWAITING_FOLLOW_REDIRECT, State.STARTED);
if (mFakeDataSink != null) {
mFakeDataSink = new FakeDataSink(mUploadExecutor, mExecutor, mUploadDataProvider);
mFakeDataSink.start(/* firstTime= */ false);
} else {
fakeConnect();
}
}
}
@Override
public void cancel() {
synchronized (mLock) {
final UrlResponseInfo info = mUrlResponseInfo;
if (setTerminalState(State.CANCELLED)) {
mUserExecutor.execute(new Runnable() {
@Override
public void run() {
mCallback.onCanceled(FakeUrlRequest.this, info);
}
});
}
}
}
@Override
public void getStatus(final StatusListener listener) {
synchronized (mLock) {
int extraStatus = mAdditionalStatusDetails;
@StatusValues
final int status;
switch (mState) {
case State.ERROR:
case State.COMPLETE:
case State.CANCELLED:
case State.NOT_STARTED:
status = Status.INVALID;
break;
case State.STARTED:
status = extraStatus;
break;
case State.REDIRECT_RECEIVED:
case State.AWAITING_FOLLOW_REDIRECT:
case State.AWAITING_READ:
status = Status.IDLE;
break;
case State.READING:
status = Status.READING_RESPONSE;
break;
default:
throw new IllegalStateException("Switch is exhaustive: " + mState);
}
mUserExecutor.execute(new Runnable() {
@Override
public void run() {
listener.onStatus(status);
}
});
}
}
@Override
public boolean isDone() {
synchronized (mLock) {
return mState == State.COMPLETE || mState == State.ERROR || mState == State.CANCELLED;
}
}
/**
* Swaps from the expected state to a new state. If the swap fails, and it's not
* due to an earlier error or cancellation, throws an exception.
*/
@GuardedBy("mLock")
private void transitionStates(@State int expected, @State int newState) {
if (mState == expected) {
mState = newState;
} else {
if (!(mState == State.CANCELLED || mState == State.ERROR)) {
throw new IllegalStateException(
"Invalid state transition - expected " + expected + " but was " + mState);
}
}
}
/**
* Calls the callback's onFailed method if this request is not complete. Should be executed on
* the {@code mUserExecutor}, unless the error is a {@link InlineExecutionProhibitedException}
* produced by the {@code mUserExecutor}.
*
* @param e the {@link CronetException} that the request should pass to the callback.
*
*/
private void tryToFailWithException(CronetException e) {
synchronized (mLock) {
if (setTerminalState(State.ERROR)) {
mCallback.onFailed(FakeUrlRequest.this, mUrlResponseInfo, e);
}
}
}
/**
* Execute a {@link CheckedRunnable} and call the {@link UrlRequest.Callback#onFailed} method
* if there is an exception and we can change to {@link State.ERROR}. Used to communicate with
* the {@link UrlRequest.Callback} methods using the executor provided by the constructor. This
* should be the last call in the critical section. If this is not the last call in a critical
* section, we risk modifying shared resources in a recursive call to another method
* guarded by the {@code mLock}. This is because in Java synchronized blocks are reentrant.
*
* @param checkedRunnable the runnable to execute
*/
private void executeCheckedRunnable(JavaUrlRequestUtils.CheckedRunnable checkedRunnable) {
try {
mUserExecutor.execute(new Runnable() {
@Override
public void run() {
try {
checkedRunnable.run();
} catch (Exception e) {
tryToFailWithException(new CallbackExceptionImpl(
"Exception received from UrlRequest.Callback", e));
}
}
});
} catch (InlineExecutionProhibitedException e) {
// Don't try to fail using the {@code mUserExecutor} because it produced this error.
tryToFailWithException(
new CronetExceptionImpl("Exception posting task to executor", e));
}
}
/**
* Check the current state and if the request is started, but not complete, failed, or
* cancelled, change to the terminal state and call {@link FakeCronetEngine#onDestroyed}. This
* method ensures {@link FakeCronetEngine#onDestroyed} is only called once.
*
* @param terminalState the terminal state to set; one of {@link State.ERROR},
* {@link State.COMPLETE}, or {@link State.CANCELLED}
* @return true if the terminal state has been set.
*/
@GuardedBy("mLock")
private boolean setTerminalState(@State int terminalState) {
switch (mState) {
case State.NOT_STARTED:
throw new IllegalStateException("Can't enter terminal state before start");
case State.ERROR: // fallthrough
case State.COMPLETE: // fallthrough
case State.CANCELLED:
return false; // Already in a terminal state
default: {
mState = terminalState;
cleanup();
return true;
}
}
}
@GuardedBy("mLock")
private void cleanup() {
closeUploadDataProvider();
mFakeCronetEngine.onRequestDestroyed();
}
/**
* Executed only once after the request has finished using the {@link UploadDataProvider}.
* Closes the {@link UploadDataProvider} if it exists and has not already been closed.
*/
@GuardedBy("mLock")
private void closeUploadDataProvider() {
if (mUploadDataProvider != null && !mUploadProviderClosed) {
try {
mUploadExecutor.execute(uploadErrorSetting(new CheckedRunnable() {
@Override
public void run() throws Exception {
synchronized (mLock) {
mUploadDataProvider.close();
mUploadProviderClosed = true;
}
}
}));
} catch (RejectedExecutionException e) {
Log.e(TAG, "Exception when closing uploadDataProvider", e);
}
}
}
/**
* Wraps a {@link CheckedRunnable} in a runnable that will attempt to fail the request if there
* is an exception.
*
* @param delegate the {@link CheckedRunnable} to try to run
* @return a {@link Runnable} that wraps the delegate runnable.
*/
private Runnable uploadErrorSetting(final CheckedRunnable delegate) {
return new Runnable() {
@Override
public void run() {
try {
delegate.run();
} catch (Throwable t) {
enterUploadErrorState(t);
}
}
};
}
/**
* Fails the request with an error. Called when uploading the request body using an
* {@link UploadDataProvider} fails.
*
* @param error the error that caused this request to fail which should be returned to the
* {@link UrlRequest.Callback}
*/
private void enterUploadErrorState(final Throwable error) {
synchronized (mLock) {
mUserExecutor.execute(new Runnable() {
@Override
public void run() {
tryToFailWithException(new CronetExceptionImpl(
"Exception received from UploadDataProvider", error));
}
});
}
}
/**
* Adapted from {@link JavaUrlRequest.OutputStreamDataSink}. Stores the received message in a
* {@link ByteArrayOutputStream} and transfers it to the {@code mRequestBody} when the response
* has been fully acquired.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
final class FakeDataSink extends JavaUploadDataSinkBase {
private final ByteArrayOutputStream mTotalUploadStream = new ByteArrayOutputStream();
FakeDataSink(final Executor userExecutor, Executor executor, UploadDataProvider provider) {
super(userExecutor, executor, provider);
}
@Override
public Runnable getErrorSettingRunnable(JavaUrlRequestUtils.CheckedRunnable runnable) {
return new Runnable() {
@Override
public void run() {
try {
runnable.run();
} catch (Throwable t) {
mUserExecutor.execute(new Runnable() {
@Override
public void run() {
tryToFailWithException(new CronetExceptionImpl("System error", t));
}
});
}
}
};
}
@Override
protected Runnable getUploadErrorSettingRunnable(
JavaUrlRequestUtils.CheckedRunnable runnable) {
return uploadErrorSetting(runnable);
}
@Override
protected void processUploadError(final Throwable error) {
enterUploadErrorState(error);
}
@Override
protected int processSuccessfulRead(ByteBuffer buffer) throws IOException {
mTotalUploadStream.write(buffer.array(), buffer.arrayOffset(), buffer.remaining());
return buffer.remaining();
}
/**
* Terminates the upload stage of the request. Writes the received bytes to the byte array:
* {@code mRequestBody}. Connects to the current URL for this request.
*/
@Override
protected void finish() throws IOException {
synchronized (mLock) {
mRequestBody = mTotalUploadStream.toByteArray();
fakeConnect();
}
}
@Override
protected void initializeRead() throws IOException {
// Nothing to do before every read in this implementation.
}
@Override
protected void initializeStart(long totalBytes) {
// Nothing to do to initialize the upload in this implementation.
}
}
/**
* Verifies that the "content-type" header is present. Must be checked before an
* {@link UploadDataProvider} is premitted to be set.
*
* @return true if the "content-type" header is present in the request headers.
*/
@GuardedBy("mLock")
private boolean checkHasContentTypeHeader() {
for (Map.Entry<String, String> entry : mAllHeadersList) {
if (entry.getKey().equalsIgnoreCase("content-type")) {
return true;
}
}
return false;
}
/**
* Gets a human readable description for a HTTP status code.
*
* @param code the code to retrieve the status for
* @return the HTTP status text as a string
*/
private static String getDescriptionByCode(Integer code) {
return HTTP_STATUS_CODE_TO_TEXT.containsKey(code) ? HTTP_STATUS_CODE_TO_TEXT.get(code)
: "Unassigned";
}
}

View File

@ -0,0 +1,296 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import org.chromium.net.UrlResponseInfo;
import java.io.UnsupportedEncodingException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
// TODO(kirchman): Update this to explain inter-class usage once other classes land.
/**
*
* Fake response model for UrlRequest used by Fake Cronet.
*/
public class FakeUrlResponse {
private final int mHttpStatusCode;
// Entries to mAllHeadersList should never be mutated.
private final List<Map.Entry<String, String>> mAllHeadersList;
private final boolean mWasCached;
private final String mNegotiatedProtocol;
private final String mProxyServer;
private final byte[] mResponseBody;
private static <T extends Object> T getNullableOrDefault(T nullableObject, T defaultObject) {
if (nullableObject != null) {
return nullableObject;
}
return defaultObject;
}
/**
* Constructs a {@link FakeUrlResponse} from a {@link FakeUrlResponse.Builder}.
* @param builder the {@link FakeUrlResponse.Builder} to create the response from
*/
private FakeUrlResponse(Builder builder) {
mHttpStatusCode = builder.mHttpStatusCode;
mAllHeadersList = Collections.unmodifiableList(new ArrayList<>(builder.mAllHeadersList));
mWasCached = builder.mWasCached;
mNegotiatedProtocol = builder.mNegotiatedProtocol;
mProxyServer = builder.mProxyServer;
mResponseBody = builder.mResponseBody;
}
/**
* Constructs a {@link FakeUrlResponse} from a {@link UrlResponseInfo}. All nullable fields in
* the {@link UrlResponseInfo} are initialized to the default value if the provided value is
* null.
*
* @param info the {@link UrlResponseInfo} used to initialize this object's fields
*/
public FakeUrlResponse(UrlResponseInfo info) {
mHttpStatusCode = info.getHttpStatusCode();
mAllHeadersList = Collections.unmodifiableList(new ArrayList<>(info.getAllHeadersAsList()));
mWasCached = info.wasCached();
mNegotiatedProtocol = getNullableOrDefault(
info.getNegotiatedProtocol(), Builder.DEFAULT_NEGOTIATED_PROTOCOL);
mProxyServer = getNullableOrDefault(info.getProxyServer(), Builder.DEFAULT_PROXY_SERVER);
mResponseBody = Builder.DEFAULT_RESPONSE_BODY;
}
/**
* Builds a {@link FakeUrlResponse}.
*/
public static class Builder {
private static final int DEFAULT_HTTP_STATUS_CODE = 200;
private static final List<Map.Entry<String, String>> INTERNAL_INITIAL_HEADERS_LIST =
new ArrayList<>();
private static final boolean DEFAULT_WAS_CACHED = false;
private static final String DEFAULT_NEGOTIATED_PROTOCOL = "";
private static final String DEFAULT_PROXY_SERVER = "";
private static final byte[] DEFAULT_RESPONSE_BODY = new byte[0];
private int mHttpStatusCode = DEFAULT_HTTP_STATUS_CODE;
// Entries to mAllHeadersList should never be mutated.
private List<Map.Entry<String, String>> mAllHeadersList =
new ArrayList<>(INTERNAL_INITIAL_HEADERS_LIST);
private boolean mWasCached = DEFAULT_WAS_CACHED;
private String mNegotiatedProtocol = DEFAULT_NEGOTIATED_PROTOCOL;
private String mProxyServer = DEFAULT_PROXY_SERVER;
private byte[] mResponseBody = DEFAULT_RESPONSE_BODY;
/**
* Constructs a {@link FakeUrlResponse.Builder} with the default parameters.
*/
public Builder() {}
/**
* Constructs a {@link FakeUrlResponse.Builder} from a source {@link FakeUrlResponse}.
*
* @param source a {@link FakeUrlResponse} to copy into this {@link FakeUrlResponse.Builder}
*/
private Builder(FakeUrlResponse source) {
mHttpStatusCode = source.getHttpStatusCode();
mAllHeadersList = new ArrayList<>(source.getAllHeadersList());
mWasCached = source.getWasCached();
mNegotiatedProtocol = source.getNegotiatedProtocol();
mProxyServer = source.getProxyServer();
mResponseBody = source.getResponseBody();
}
/**
* Sets the HTTP status code. The default value is 200.
*
* @param httpStatusCode for {@link UrlResponseInfo.getHttpStatusCode()}
* @return the builder with the corresponding HTTP status code set
*/
public Builder setHttpStatusCode(int httpStatusCode) {
mHttpStatusCode = httpStatusCode;
return this;
}
/**
* Adds a response header to built {@link FakeUrlResponse}s.
*
* @param name the name of the header key, for example, "location" for a redirect header
* @param value the header value
* @return the builder with the corresponding header set
*/
public Builder addHeader(String name, String value) {
mAllHeadersList.add(new AbstractMap.SimpleEntry<>(name, value));
return this;
}
/**
* Sets result of {@link UrlResponseInfo.wasCached()}. The default wasCached value is false.
*
* @param wasCached for {@link UrlResponseInfo.wasCached()}
* @return the builder with the corresponding wasCached field set
*/
public Builder setWasCached(boolean wasCached) {
mWasCached = wasCached;
return this;
}
/**
* Sets result of {@link UrlResponseInfo.getNegotiatedProtocol()}. The default negotiated
* protocol is an empty string.
*
* @param negotiatedProtocol for {@link UrlResponseInfo.getNegotiatedProtocol()}
* @return the builder with the corresponding negotiatedProtocol field set
*/
public Builder setNegotiatedProtocol(String negotiatedProtocol) {
mNegotiatedProtocol = negotiatedProtocol;
return this;
}
/**
* Sets result of {@link UrlResponseInfo.getProxyServer()}. The default proxy server is an
* empty string.
*
* @param proxyServer for {@link UrlResponseInfo.getProxyServer()}
* @return the builder with the corresponding proxyServer field set
*/
public Builder setProxyServer(String proxyServer) {
mProxyServer = proxyServer;
return this;
}
/**
* Sets the response body for a response. The default response body is an empty byte array.
*
* @param body all the information the server returns
* @return the builder with the corresponding responseBody field set
*/
public Builder setResponseBody(byte[] body) {
mResponseBody = body;
return this;
}
/**
* Constructs a {@link FakeUrlResponse} from this {@link FakeUrlResponse.Builder}.
*
* @return a FakeUrlResponse with all fields set according to this builder
*/
public FakeUrlResponse build() {
return new FakeUrlResponse(this);
}
}
/**
* Returns the HTTP status code.
*
* @return the HTTP status code.
*/
int getHttpStatusCode() {
return mHttpStatusCode;
}
/**
* Returns an unmodifiable list of the response header key and value pairs.
*
* @return an unmodifiable list of response header key and value pairs
*/
List<Map.Entry<String, String>> getAllHeadersList() {
return mAllHeadersList;
}
/**
* Returns the wasCached value for this response.
*
* @return the wasCached value for this response
*/
boolean getWasCached() {
return mWasCached;
}
/**
* Returns the protocol (for example 'quic/1+spdy/3') negotiated with the server.
*
* @return the protocol negotiated with the server
*/
String getNegotiatedProtocol() {
return mNegotiatedProtocol;
}
/**
* Returns the proxy server that was used for the request.
*
* @return the proxy server that was used for the request
*/
String getProxyServer() {
return mProxyServer;
}
/**
* Returns the body of the response as a byte array. Used for {@link UrlRequest.Callback}
* {@code read()} callback.
*
* @return the response body
*/
byte[] getResponseBody() {
return mResponseBody;
}
/**
* Returns a mutable builder representation of this {@link FakeUrlResponse}
*
* @return a {@link FakeUrlResponse.Builder} with all fields copied from this instance.
*/
public Builder toBuilder() {
return new Builder(this);
}
@Override
public boolean equals(Object otherObj) {
if (!(otherObj instanceof FakeUrlResponse)) {
return false;
}
FakeUrlResponse other = (FakeUrlResponse) otherObj;
return (mHttpStatusCode == other.mHttpStatusCode
&& mAllHeadersList.equals(other.mAllHeadersList) && mWasCached == other.mWasCached
&& mNegotiatedProtocol.equals(other.mNegotiatedProtocol)
&& mProxyServer.equals(other.mProxyServer)
&& Arrays.equals(mResponseBody, other.mResponseBody));
}
@Override
public int hashCode() {
return Objects.hash(mHttpStatusCode, mAllHeadersList, mWasCached, mNegotiatedProtocol,
mProxyServer, Arrays.hashCode(mResponseBody));
}
@Override
public String toString() {
StringBuilder outputString = new StringBuilder();
outputString.append("HTTP Status Code: " + mHttpStatusCode);
outputString.append(" Headers: " + mAllHeadersList.toString());
outputString.append(" Was Cached: " + mWasCached);
outputString.append(" Negotiated Protocol: " + mNegotiatedProtocol);
outputString.append(" Proxy Server: " + mProxyServer);
outputString.append(" Response Body ");
try {
String bodyString = new String(mResponseBody, "UTF-8");
outputString.append("(UTF-8): " + bodyString);
} catch (UnsupportedEncodingException e) {
outputString.append("(hexadecimal): " + getHexStringFromBytes(mResponseBody));
}
return outputString.toString();
}
private String getHexStringFromBytes(byte[] bytes) {
StringBuilder bytesToHexStringBuilder = new StringBuilder();
for (byte b : mResponseBody) {
bytesToHexStringBuilder.append(String.format("%02x", b));
}
return bytesToHexStringBuilder.toString();
}
}

View File

@ -0,0 +1,30 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import androidx.annotation.Nullable;
import org.chromium.net.UrlRequest;
import java.util.List;
import java.util.Map;
/**
* An interface for matching {@link UrlRequest}s to {@link FakeUrlResponse}s.
*/
public interface ResponseMatcher {
/**
* Optionally gets a response based on the request parameters.
*
* @param url the URL the {@link UrlRequest} is connecting to
* @param httpMethod the HTTP method the {@link UrlRequest} is connecting with
* @param headers the {@link UrlRequest} headers
* @param body the body of the request
* @return a {@link FakeUrlResponse} if there is a matching response, or {@code null} otherwise
*/
@Nullable
FakeUrlResponse getMatchingResponse(
String url, String httpMethod, List<Map.Entry<String, String>> headers, byte[] body);
}

View File

@ -0,0 +1,41 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import org.chromium.net.UrlRequest;
import java.util.List;
import java.util.Map;
/**
* A {@link ResponseMatcher} that matches {@link UrlRequest}s with a particular URL.
*/
public class UrlResponseMatcher implements ResponseMatcher {
private final String mUrl;
private final FakeUrlResponse mResponse;
/**
* Constructs a {@link UrlResponseMatcher} that responds to requests for URL {@code url} with
* {@code response}.
* @param url the URL that the response should be returned for
* @param response the response to return if the URL matches the request's URL
*/
public UrlResponseMatcher(String url, FakeUrlResponse response) {
if (url == null) {
throw new NullPointerException("URL is required.");
}
if (response == null) {
throw new NullPointerException("Response is required.");
}
mUrl = url;
mResponse = response;
}
@Override
public FakeUrlResponse getMatchingResponse(
String url, String httpMethod, List<Map.Entry<String, String>> headers, byte[] body) {
return mUrl.equals(url) ? mResponse : null;
}
}

View File

@ -0,0 +1,255 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.net.CronetEngine;
import org.chromium.net.impl.JavaCronetEngineBuilderImpl;
import java.util.AbstractMap;
import java.util.List;
import java.util.Map;
/**
* Test functionality of {@link FakeCronetController}.
*/
@RunWith(AndroidJUnit4.class)
public class FakeCronetControllerTest {
Context mContext;
FakeCronetController mFakeCronetController;
@Before
public void setUp() {
mContext = InstrumentationRegistry.getTargetContext();
mFakeCronetController = new FakeCronetController();
}
@Test
@SmallTest
public void testGetFakeCronetEnginesStartsEmpty() {
List<CronetEngine> engines = FakeCronetController.getFakeCronetEngines();
assertEquals(0, engines.size());
}
@Test
@SmallTest
public void testGetFakeCronetEnginesIncludesCreatedEngineInOrder() {
// Create an instance with the controller.
CronetEngine engine = mFakeCronetController.newFakeCronetEngineBuilder(mContext).build();
// Create an instance with the provider.
FakeCronetProvider provider = new FakeCronetProvider(mContext);
CronetEngine providerEngine = provider.createBuilder().build();
List<CronetEngine> engines = FakeCronetController.getFakeCronetEngines();
assertTrue(engines.contains(engine));
assertTrue(engines.contains(providerEngine));
assertEquals(engine, engines.get(0));
assertEquals(providerEngine, engines.get(1));
}
@Test
@SmallTest
public void testGetControllerGetsCorrectController() {
// Create an instance with the controller.
CronetEngine engine = mFakeCronetController.newFakeCronetEngineBuilder(mContext).build();
CronetEngine engine2 = mFakeCronetController.newFakeCronetEngineBuilder(mContext).build();
// Create two engines with a second controller.
FakeCronetController newController = new FakeCronetController();
CronetEngine newControllerEngine =
newController.newFakeCronetEngineBuilder(mContext).build();
CronetEngine newControllerEngine2 =
newController.newFakeCronetEngineBuilder(mContext).build();
// Create an instance with the provider.
FakeCronetProvider provider = new FakeCronetProvider(mContext);
CronetEngine providerEngine = provider.createBuilder().build();
assertEquals(
mFakeCronetController, FakeCronetController.getControllerForFakeEngine(engine));
assertEquals(
mFakeCronetController, FakeCronetController.getControllerForFakeEngine(engine2));
assertEquals(newController,
FakeCronetController.getControllerForFakeEngine(newControllerEngine));
assertEquals(newController,
FakeCronetController.getControllerForFakeEngine(newControllerEngine2));
// TODO(kirchman): Test which controller the provider-created engine uses once the fake
// UrlRequest class has been implemented.
assertNotEquals(mFakeCronetController,
FakeCronetController.getControllerForFakeEngine(providerEngine));
assertNotEquals(
newController, FakeCronetController.getControllerForFakeEngine(providerEngine));
assertNotNull(FakeCronetController.getControllerForFakeEngine(providerEngine));
}
@Test
@SmallTest
public void testAddNonFakeCronetEngineNotAllowed() {
CronetEngine javaEngine = new JavaCronetEngineBuilderImpl(mContext).build();
try {
FakeCronetController.getControllerForFakeEngine(javaEngine);
fail("Should not be able to get a controller for a non-fake CronetEngine.");
} catch (IllegalArgumentException e) {
assertEquals("Provided CronetEngine is not a fake CronetEngine", e.getMessage());
}
}
@Test
@SmallTest
public void testShutdownRemovesCronetEngine() {
CronetEngine engine = mFakeCronetController.newFakeCronetEngineBuilder(mContext).build();
CronetEngine engine2 = mFakeCronetController.newFakeCronetEngineBuilder(mContext).build();
List<CronetEngine> engines = FakeCronetController.getFakeCronetEngines();
assertTrue(engines.contains(engine));
assertTrue(engines.contains(engine2));
engine.shutdown();
engines = FakeCronetController.getFakeCronetEngines();
assertFalse(engines.contains(engine));
assertTrue(engines.contains(engine2));
}
@Test
@SmallTest
public void testResponseMatchersConsultedInOrderOfAddition() {
String url = "url";
FakeUrlResponse response =
new FakeUrlResponse.Builder().setResponseBody("body text".getBytes()).build();
ResponseMatcher matcher = new UrlResponseMatcher(url, response);
mFakeCronetController.addResponseMatcher(matcher);
mFakeCronetController.addSuccessResponse(url, "different text".getBytes());
FakeUrlResponse foundResponse =
mFakeCronetController.getResponse(new String(url), null, null, null);
assertEquals(response, foundResponse);
}
@Test
@SmallTest
public void testRemoveResponseMatcher() {
String url = "url";
FakeUrlResponse response =
new FakeUrlResponse.Builder().setResponseBody("body text".getBytes()).build();
ResponseMatcher matcher = new UrlResponseMatcher(url, response);
mFakeCronetController.addResponseMatcher(matcher);
mFakeCronetController.removeResponseMatcher(matcher);
FakeUrlResponse foundResponse = mFakeCronetController.getResponse(url, null, null, null);
assertEquals(404, foundResponse.getHttpStatusCode());
assertNotEquals(response, foundResponse);
}
@Test
@SmallTest
public void testClearResponseMatchers() {
String url = "url";
FakeUrlResponse response =
new FakeUrlResponse.Builder().setResponseBody("body text".getBytes()).build();
ResponseMatcher matcher = new UrlResponseMatcher(url, response);
mFakeCronetController.addResponseMatcher(matcher);
mFakeCronetController.clearResponseMatchers();
FakeUrlResponse foundResponse = mFakeCronetController.getResponse(url, null, null, null);
assertEquals(404, foundResponse.getHttpStatusCode());
assertNotEquals(response, foundResponse);
}
@Test
@SmallTest
public void testAddUrlResponseMatcher() {
String url = "url";
FakeUrlResponse response =
new FakeUrlResponse.Builder().setResponseBody("body text".getBytes()).build();
mFakeCronetController.addResponseForUrl(response, url);
FakeUrlResponse foundResponse = mFakeCronetController.getResponse(url, null, null, null);
assertEquals(foundResponse, response);
}
@Test
@SmallTest
public void testDefaultResponseIs404() {
FakeUrlResponse foundResponse = mFakeCronetController.getResponse("url", null, null, null);
assertEquals(404, foundResponse.getHttpStatusCode());
}
@Test
@SmallTest
public void testAddRedirectResponse() {
String url = "url";
String location = "/TEST_REDIRECT_LOCATION";
mFakeCronetController.addRedirectResponse(location, url);
FakeUrlResponse foundResponse = mFakeCronetController.getResponse("url", null, null, null);
Map.Entry<String, String> headerEntry = new AbstractMap.SimpleEntry<>("location", location);
assertTrue(foundResponse.getAllHeadersList().contains(headerEntry));
assertTrue(foundResponse.getHttpStatusCode() >= 300);
assertTrue(foundResponse.getHttpStatusCode() < 400);
}
@Test
@SmallTest
public void testAddErrorResponse() {
String url = "url";
int httpStatusCode = 400;
mFakeCronetController.addHttpErrorResponse(httpStatusCode, url);
FakeUrlResponse foundResponse = mFakeCronetController.getResponse(url, null, null, null);
assertEquals(foundResponse.getHttpStatusCode(), httpStatusCode);
}
@Test
@SmallTest
public void testAddErrorResponseWithNonErrorCodeThrowsException() {
int nonErrorCode = 200;
try {
mFakeCronetController.addHttpErrorResponse(nonErrorCode, "url");
fail("Should not be able to add an error response with a non-error code.");
} catch (IllegalArgumentException e) {
assertEquals("Expected HTTP error code (code >= 400), but was: " + nonErrorCode,
e.getMessage());
}
}
@Test
@SmallTest
public void testAddSuccessResponse() {
String url = "url";
String body = "TEST_BODY";
mFakeCronetController.addSuccessResponse(url, body.getBytes());
FakeUrlResponse foundResponse = mFakeCronetController.getResponse(url, null, null, null);
assertTrue(foundResponse.getHttpStatusCode() >= 200);
assertTrue(foundResponse.getHttpStatusCode() < 300);
assertEquals(body, new String(foundResponse.getResponseBody()));
}
}

View File

@ -0,0 +1,291 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.net.CronetException;
import org.chromium.net.UrlRequest;
import org.chromium.net.UrlResponseInfo;
import org.chromium.net.impl.ImplVersion;
import java.net.Proxy;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Test functionality of {@link FakeCronetEngine}.
*/
@RunWith(AndroidJUnit4.class)
public class FakeCronetEngineTest {
Context mContext;
FakeCronetEngine mFakeCronetEngine;
UrlRequest.Callback mCallback;
ExecutorService mExecutor;
@Before
public void setUp() {
mContext = InstrumentationRegistry.getTargetContext();
mFakeCronetEngine =
(FakeCronetEngine) new FakeCronetProvider(mContext).createBuilder().build();
mCallback = new UrlRequest.Callback() {
@Override
public void onRedirectReceived(
UrlRequest request, UrlResponseInfo info, String newLocationUrl) {}
@Override
public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {}
@Override
public void onReadCompleted(
UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) {}
@Override
public void onSucceeded(UrlRequest request, UrlResponseInfo info) {}
@Override
public void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) {}
@Override
public void onCanceled(UrlRequest request, UrlResponseInfo info) {}
};
mExecutor = Executors.newSingleThreadExecutor();
}
@Test
@SmallTest
public void testShutdownEngineThrowsExceptionWhenApiCalled() {
mFakeCronetEngine.shutdown();
try {
mFakeCronetEngine.newUrlRequestBuilder("", mCallback, mExecutor).build();
fail("newUrlRequestBuilder API not checked for shutdown engine.");
} catch (IllegalStateException e) {
assertEquals(
"This instance of CronetEngine has been shutdown and can no longer be used.",
e.getMessage());
}
}
@Test
@SmallTest
public void testShutdownEngineThrowsExceptionWhenBidirectionalStreamApiCalled() {
mFakeCronetEngine.shutdown();
try {
mFakeCronetEngine.newBidirectionalStreamBuilder("", null, null);
fail("newBidirectionalStreamBuilder API not checked for shutdown engine.");
} catch (IllegalStateException e) {
assertEquals(
"This instance of CronetEngine has been shutdown and can no longer be used.",
e.getMessage());
}
}
@Test
@SmallTest
public void testExceptionForNewBidirectionalStreamApi() {
try {
mFakeCronetEngine.newBidirectionalStreamBuilder("", null, null);
fail("newBidirectionalStreamBuilder API should not be available.");
} catch (UnsupportedOperationException e) {
assertEquals("The bidirectional stream API is not supported by the Fake implementation "
+ "of CronetEngine.",
e.getMessage());
}
}
@Test
@SmallTest
public void testExceptionForOpenConnectionApi() {
try {
mFakeCronetEngine.openConnection(null);
fail("openConnection API should not be available.");
} catch (Exception e) {
assertEquals("The openConnection API is not supported by the Fake implementation of "
+ "CronetEngine.",
e.getMessage());
}
}
@Test
@SmallTest
public void testExceptionForOpenConnectionApiWithProxy() {
try {
mFakeCronetEngine.openConnection(null, Proxy.NO_PROXY);
fail("openConnection API should not be available.");
} catch (Exception e) {
assertEquals("The openConnection API is not supported by the Fake implementation of "
+ "CronetEngine.",
e.getMessage());
}
}
@Test
@SmallTest
public void testExceptionForCreateStreamHandlerFactoryApi() {
try {
mFakeCronetEngine.createURLStreamHandlerFactory();
fail("createURLStreamHandlerFactory API should not be available.");
} catch (UnsupportedOperationException e) {
assertEquals(
"The URLStreamHandlerFactory API is not supported by the Fake implementation of"
+ " CronetEngine.",
e.getMessage());
}
}
@Test
@SmallTest
public void testGetVersionString() {
assertEquals("FakeCronet/" + ImplVersion.getCronetVersionWithLastChange(),
mFakeCronetEngine.getVersionString());
}
@Test
@SmallTest
public void testStartNetLogToFile() {
mFakeCronetEngine.startNetLogToFile("", false);
}
@Test
@SmallTest
public void testStartNetLogToDisk() {
mFakeCronetEngine.startNetLogToDisk("", false, 0);
}
@Test
@SmallTest
public void testStopNetLog() {
mFakeCronetEngine.stopNetLog();
}
@Test
@SmallTest
public void testGetGlobalMetricsDeltas() {
assertTrue(mFakeCronetEngine.getGlobalMetricsDeltas().length == 0);
}
@Test
@SmallTest
public void testGetEffectiveConnectionType() {
assertEquals(FakeCronetEngine.EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
mFakeCronetEngine.getEffectiveConnectionType());
}
@Test
@SmallTest
public void testGetHttpRttMs() {
assertEquals(FakeCronetEngine.CONNECTION_METRIC_UNKNOWN, mFakeCronetEngine.getHttpRttMs());
}
@Test
@SmallTest
public void testGetTransportRttMs() {
assertEquals(
FakeCronetEngine.CONNECTION_METRIC_UNKNOWN, mFakeCronetEngine.getTransportRttMs());
}
@Test
@SmallTest
public void testGetDownstreamThroughputKbps() {
assertEquals(FakeCronetEngine.CONNECTION_METRIC_UNKNOWN,
mFakeCronetEngine.getDownstreamThroughputKbps());
}
@Test
@SmallTest
public void testConfigureNetworkQualityEstimatorForTesting() {
mFakeCronetEngine.configureNetworkQualityEstimatorForTesting(false, false, false);
}
@Test
@SmallTest
public void testAddRttListener() {
mFakeCronetEngine.addRttListener(null);
}
@Test
@SmallTest
public void testRemoveRttListener() {
mFakeCronetEngine.removeRttListener(null);
}
@Test
@SmallTest
public void testAddThroughputListener() {
mFakeCronetEngine.addThroughputListener(null);
}
@Test
@SmallTest
public void testRemoveThroughputListener() {
mFakeCronetEngine.removeThroughputListener(null);
}
@Test
@SmallTest
public void testAddRequestFinishedListener() {
mFakeCronetEngine.addRequestFinishedListener(null);
}
@Test
@SmallTest
public void testRemoveRequestFinishedListener() {
mFakeCronetEngine.removeRequestFinishedListener(null);
}
@Test
@SmallTest
public void testShutdownBlockedWhenRequestCountNotZero() {
// Start a request and verify the engine can't be shutdown.
assertTrue(mFakeCronetEngine.startRequest());
try {
mFakeCronetEngine.shutdown();
fail("Shutdown not checked for active requests.");
} catch (IllegalStateException e) {
assertEquals("Cannot shutdown with active requests.", e.getMessage());
}
// Finish the request and verify the engine can be shutdown.
mFakeCronetEngine.onRequestDestroyed();
mFakeCronetEngine.shutdown();
}
@Test
@SmallTest
public void testCantStartRequestAfterEngineShutdown() {
mFakeCronetEngine.shutdown();
assertFalse(mFakeCronetEngine.startRequest());
}
@Test
@SmallTest
public void testCantDecrementOnceShutdown() {
mFakeCronetEngine.shutdown();
try {
mFakeCronetEngine.onRequestDestroyed();
fail("onRequestDestroyed not checked for shutdown engine");
} catch (IllegalStateException e) {
assertEquals("This instance of CronetEngine was shutdown. All requests must have been "
+ "complete.",
e.getMessage());
}
}
}

View File

@ -0,0 +1,67 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.net.impl.ImplVersion;
/**
* Test functionality of {@link FakeCronetProvider}.
*/
@RunWith(AndroidJUnit4.class)
public class FakeCronetProviderTest {
Context mContext;
FakeCronetProvider mProvider;
@Before
public void setUp() {
mContext = InstrumentationRegistry.getTargetContext();
mProvider = new FakeCronetProvider(mContext);
}
@Test
@SmallTest
public void testGetName() {
String expectedName = "Fake-Cronet-Provider";
assertEquals(expectedName, mProvider.getName());
}
@Test
@SmallTest
public void testGetVersion() {
assertEquals(ImplVersion.getCronetVersion(), mProvider.getVersion());
}
@Test
@SmallTest
public void testIsEnabled() {
assertTrue(mProvider.isEnabled());
}
@Test
@SmallTest
public void testHashCode() {
FakeCronetProvider otherProvider = new FakeCronetProvider(mContext);
assertEquals(otherProvider.hashCode(), mProvider.hashCode());
}
@Test
@SmallTest
public void testEquals() {
assertTrue(mProvider.equals(new FakeCronetProvider(mContext)));
}
}

View File

@ -0,0 +1,219 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import android.support.test.runner.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.net.UrlResponseInfo;
import org.chromium.net.impl.UrlResponseInfoImpl;
import java.io.UnsupportedEncodingException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Test functionality of FakeUrlResponse.
*/
@RunWith(AndroidJUnit4.class)
public class FakeUrlResponseTest {
private static final int TEST_HTTP_STATUS_CODE = 201;
private static final String TEST_HEADER_NAME = "name";
private static final String TEST_HEADER_VALUE = "value";
private static final boolean TEST_WAS_CACHED = true;
private static final String TEST_NEGOTIATED_PROTOCOL = "test_negotiated_protocol";
private static final String TEST_PROXY_SERVER = "test_proxy_server";
private static final String TEST_BODY = "test_body";
List<Map.Entry<String, String>> mTestHeaders;
AbstractMap.SimpleEntry<String, String> mTestHeaderEntry;
FakeUrlResponse mTestResponse;
@Before
public void setUp() {
mTestHeaders = new ArrayList<>();
mTestHeaderEntry = new AbstractMap.SimpleEntry<>(TEST_HEADER_NAME, TEST_HEADER_VALUE);
mTestHeaders.add(mTestHeaderEntry);
mTestResponse = new FakeUrlResponse.Builder()
.setHttpStatusCode(TEST_HTTP_STATUS_CODE)
.setWasCached(TEST_WAS_CACHED)
.addHeader(TEST_HEADER_NAME, TEST_HEADER_VALUE)
.setNegotiatedProtocol(TEST_NEGOTIATED_PROTOCOL)
.setProxyServer(TEST_PROXY_SERVER)
.setResponseBody(TEST_BODY.getBytes())
.build();
}
@Test
@SmallTest
public void testAddHeader() {
FakeUrlResponse response = new FakeUrlResponse.Builder()
.addHeader(TEST_HEADER_NAME, TEST_HEADER_VALUE)
.build();
List<Map.Entry<String, String>> responseHeadersList = response.getAllHeadersList();
// mTestHeaderEntry is header entry of TEST_HEADER_NAME, TEST_HEADER_VALUE.
assertTrue(responseHeadersList.contains(mTestHeaderEntry));
}
@Test
@SmallTest
public void testEquals() {
FakeUrlResponse responseEqualToTestResponse = mTestResponse.toBuilder().build();
FakeUrlResponse responseNotEqualToTestResponse =
mTestResponse.toBuilder().setResponseBody("Not equal".getBytes()).build();
assertEquals(mTestResponse, mTestResponse);
assertEquals(mTestResponse, responseEqualToTestResponse);
assertNotEquals(mTestResponse, responseNotEqualToTestResponse);
}
@Test
@SmallTest
public void testResponseBodyIsSame() {
try {
FakeUrlResponse responseWithBodySetAsBytes =
mTestResponse.toBuilder().setResponseBody(TEST_BODY.getBytes("UTF-8")).build();
assertTrue(Arrays.equals(
mTestResponse.getResponseBody(), responseWithBodySetAsBytes.getResponseBody()));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(
"Exception occurred while encoding response body: " + TEST_BODY);
}
}
@Test
@SmallTest
public void testHeadersNotShared() {
FakeUrlResponse.Builder responseBuilder = new FakeUrlResponse.Builder();
FakeUrlResponse response = responseBuilder.build();
FakeUrlResponse responseWithHeader =
responseBuilder.addHeader(TEST_HEADER_NAME, TEST_HEADER_VALUE).build();
List<Map.Entry<String, String>> responseHeadersList = response.getAllHeadersList();
List<Map.Entry<String, String>> responseHeadersListWithHeader =
responseWithHeader.getAllHeadersList();
assertNotEquals(responseHeadersListWithHeader, responseHeadersList);
}
@Test
@SmallTest
public void testSettingAllHeadersCopiesHeaderList() {
String nameNotInOriginalList = "nameNotInOriginalList";
String valueNotInOriginalList = "valueNotInOriginalList";
AbstractMap.SimpleEntry<String, String> entryNotInOriginalList =
new AbstractMap.SimpleEntry<>(nameNotInOriginalList, valueNotInOriginalList);
FakeUrlResponse testResponseWithHeader =
mTestResponse.toBuilder()
.addHeader(nameNotInOriginalList, valueNotInOriginalList)
.build();
assertFalse(mTestHeaders.contains(entryNotInOriginalList));
assertTrue(testResponseWithHeader.getAllHeadersList().contains(entryNotInOriginalList));
}
@Test
@SmallTest
public void testHashCodeReturnsSameIntForEqualObjects() {
FakeUrlResponse responseEqualToTest = mTestResponse.toBuilder().build();
assertEquals(mTestResponse.hashCode(), mTestResponse.hashCode());
assertEquals(mTestResponse.hashCode(), responseEqualToTest.hashCode());
// Two non-equivalent values can map to the same hashCode.
}
@Test
@SmallTest
public void testToString() {
String expectedString = "HTTP Status Code: " + TEST_HTTP_STATUS_CODE
+ " Headers: " + mTestHeaders.toString() + " Was Cached: " + TEST_WAS_CACHED
+ " Negotiated Protocol: " + TEST_NEGOTIATED_PROTOCOL
+ " Proxy Server: " + TEST_PROXY_SERVER + " Response Body (UTF-8): " + TEST_BODY;
String responseToString = mTestResponse.toString();
assertEquals(expectedString, responseToString);
}
@Test
@SmallTest
public void testGetResponseWithUrlResponseInfo() {
UrlResponseInfo info = new UrlResponseInfoImpl(new ArrayList<>(), TEST_HTTP_STATUS_CODE, "",
mTestHeaders, TEST_WAS_CACHED, TEST_NEGOTIATED_PROTOCOL, TEST_PROXY_SERVER, 0);
FakeUrlResponse expectedResponse = new FakeUrlResponse.Builder()
.setHttpStatusCode(TEST_HTTP_STATUS_CODE)
.addHeader(TEST_HEADER_NAME, TEST_HEADER_VALUE)
.setWasCached(TEST_WAS_CACHED)
.setNegotiatedProtocol(TEST_NEGOTIATED_PROTOCOL)
.setProxyServer(TEST_PROXY_SERVER)
.build();
FakeUrlResponse constructedResponse = new FakeUrlResponse(info);
assertEquals(expectedResponse, constructedResponse);
}
@Test
@SmallTest
public void testGetResponesWithNullUrlResponseInfoGetsDefault() {
// Set params that cannot be null in UrlResponseInfo in the expected response so that the
// parameters found in the constructed response from UrlResponseInfo are the same
// as the expected.
FakeUrlResponse expectedResponse = new FakeUrlResponse.Builder()
.setHttpStatusCode(TEST_HTTP_STATUS_CODE)
.setWasCached(TEST_WAS_CACHED)
.addHeader(TEST_HEADER_NAME, TEST_HEADER_VALUE)
.build();
// UnmodifiableList cannot be null.
UrlResponseInfo info = new UrlResponseInfoImpl(/* UrlChain */ new ArrayList<>(),
TEST_HTTP_STATUS_CODE, null, mTestHeaders, TEST_WAS_CACHED, null, null, 0);
FakeUrlResponse constructedResponse = new FakeUrlResponse(info);
assertEquals(expectedResponse, constructedResponse);
}
@Test
@SmallTest
public void testInternalInitialHeadersListCantBeModified() {
FakeUrlResponse defaultResponseWithHeader =
new FakeUrlResponse.Builder()
.addHeader(TEST_HEADER_NAME, TEST_HEADER_VALUE)
.build();
FakeUrlResponse defaultResponse = new FakeUrlResponse.Builder().build();
assertNotEquals(
defaultResponse.getAllHeadersList(), defaultResponseWithHeader.getAllHeadersList());
}
@Test
@SmallTest
public void testUrlResponseInfoHeadersMapIsCaseInsensitve() {
UrlResponseInfo info = new UrlResponseInfoImpl(new ArrayList<>(), 200, "OK",
mTestResponse.getAllHeadersList(), mTestResponse.getWasCached(),
mTestResponse.getNegotiatedProtocol(), mTestResponse.getProxyServer(),
mTestResponse.getResponseBody().length);
Map infoMap = info.getAllHeaders();
assertTrue(infoMap.containsKey(TEST_HEADER_NAME.toLowerCase(Locale.ROOT)));
assertTrue(infoMap.containsKey(TEST_HEADER_NAME.toUpperCase(Locale.ROOT)));
}
}

View File

@ -0,0 +1,75 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import android.support.test.runner.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Test functionality of UrlResponseMatcher.
*/
@RunWith(AndroidJUnit4.class)
public class UrlResponseMatcherTest {
@Test
@SmallTest
public void testCheckUrlNotNull() {
try {
UrlResponseMatcher matcher =
new UrlResponseMatcher(null, new FakeUrlResponse.Builder().build());
fail("URL not null-checked");
} catch (NullPointerException e) {
assertEquals("URL is required.", e.getMessage());
}
}
@Test
@SmallTest
public void testCheckResponseNotNull() {
try {
UrlResponseMatcher matcher = new UrlResponseMatcher("url", null);
fail("Response not null-checked");
} catch (NullPointerException e) {
assertEquals("Response is required.", e.getMessage());
}
}
@Test
@SmallTest
public void testGetMatchingUrlResponse() {
String url = "url";
FakeUrlResponse response =
new FakeUrlResponse.Builder().setResponseBody("TestBody".getBytes()).build();
ResponseMatcher matcher = new UrlResponseMatcher(url, response);
FakeUrlResponse found = matcher.getMatchingResponse(url, null, null, null);
assertNotNull(found);
assertEquals(found, response);
}
@Test
@SmallTest
public void testGetResponseWithBadUrlReturnsNull() {
String url = "url";
String urlWithoutResponse = "NO_RESPONSE";
FakeUrlResponse response =
new FakeUrlResponse.Builder().setResponseBody("TestBody".getBytes()).build();
ResponseMatcher matcher = new UrlResponseMatcher(url, response);
FakeUrlResponse notFound =
matcher.getMatchingResponse(urlWithoutResponse, null, null, null);
assertNull(notFound);
}
}

View File

@ -0,0 +1,41 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/cronet/android/io_buffer_with_byte_buffer.h"
#include "base/check_op.h"
namespace cronet {
IOBufferWithByteBuffer::IOBufferWithByteBuffer(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jbyte_buffer,
void* byte_buffer_data,
jint position,
jint limit)
: net::WrappedIOBuffer(static_cast<char*>(byte_buffer_data) + position),
byte_buffer_(env, jbyte_buffer),
initial_position_(position),
initial_limit_(limit) {
DCHECK(byte_buffer_data);
DCHECK_EQ(env->GetDirectBufferAddress(jbyte_buffer), byte_buffer_data);
}
IOBufferWithByteBuffer::~IOBufferWithByteBuffer() {}
ByteBufferWithIOBuffer::ByteBufferWithIOBuffer(
JNIEnv* env,
scoped_refptr<net::IOBuffer> io_buffer,
int io_buffer_len)
: io_buffer_(std::move(io_buffer)), io_buffer_len_(io_buffer_len) {
// An intermediate ScopedJavaLocalRef is needed here to release the local
// reference created by env->NewDirectByteBuffer().
base::android::ScopedJavaLocalRef<jobject> java_buffer(
env, env->NewDirectByteBuffer(io_buffer_->data(), io_buffer_len_));
byte_buffer_.Reset(env, java_buffer.obj());
}
ByteBufferWithIOBuffer::~ByteBufferWithIOBuffer() {}
} // namespace cronet

View File

@ -0,0 +1,80 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_CRONET_ANDROID_IO_BUFFER_WITH_BYTE_BUFFER_H_
#define COMPONENTS_CRONET_ANDROID_IO_BUFFER_WITH_BYTE_BUFFER_H_
#include <jni.h>
#include "base/android/scoped_java_ref.h"
#include "net/base/io_buffer.h"
namespace cronet {
// net::WrappedIOBuffer subclass for a buffer owned by a Java ByteBuffer. Keeps
// the ByteBuffer alive until destroyed. Uses WrappedIOBuffer because data() is
// owned by the embedder.
class IOBufferWithByteBuffer : public net::WrappedIOBuffer {
public:
// Creates a buffer wrapping the Java ByteBuffer |jbyte_buffer|.
// |byte_buffer_data| points to the memory backed by the ByteBuffer, and
// |position| is the index of the first byte of data inside of the buffer.
// |limit| is the the index of the first element that should not be read or
// written, preserved to verify that buffer is not changed externally during
// networking operations.
IOBufferWithByteBuffer(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jbyte_buffer,
void* byte_buffer_data,
jint position,
jint limit);
IOBufferWithByteBuffer(const IOBufferWithByteBuffer&) = delete;
IOBufferWithByteBuffer& operator=(const IOBufferWithByteBuffer&) = delete;
jint initial_position() const { return initial_position_; }
jint initial_limit() const { return initial_limit_; }
const base::android::JavaRef<jobject>& byte_buffer() const {
return byte_buffer_;
}
private:
~IOBufferWithByteBuffer() override;
base::android::ScopedJavaGlobalRef<jobject> byte_buffer_;
const jint initial_position_;
const jint initial_limit_;
};
// Represents a Java direct ByteBuffer backed by a net::IOBuffer. Keeps both the
// net::IOBuffer and the Java ByteBuffer object alive until destroyed.
class ByteBufferWithIOBuffer {
public:
ByteBufferWithIOBuffer(JNIEnv* env,
scoped_refptr<net::IOBuffer> io_buffer,
int io_buffer_len);
ByteBufferWithIOBuffer(const ByteBufferWithIOBuffer&) = delete;
ByteBufferWithIOBuffer& operator=(const ByteBufferWithIOBuffer&) = delete;
~ByteBufferWithIOBuffer();
const net::IOBuffer* io_buffer() const { return io_buffer_.get(); }
int io_buffer_len() const { return io_buffer_len_; }
const base::android::JavaRef<jobject>& byte_buffer() const {
return byte_buffer_;
}
private:
scoped_refptr<net::IOBuffer> io_buffer_;
int io_buffer_len_;
base::android::ScopedJavaGlobalRef<jobject> byte_buffer_;
};
} // namespace cronet
#endif // COMPONENTS_CRONET_ANDROID_IO_BUFFER_WITH_BYTE_BUFFER_H_

View File

@ -0,0 +1,156 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import android.annotation.SuppressLint;
import org.chromium.net.BidirectionalStream;
import org.chromium.net.CronetEngine;
import org.chromium.net.ExperimentalBidirectionalStream;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* Implementation of {@link ExperimentalBidirectionalStream.Builder}.
*/
public class BidirectionalStreamBuilderImpl extends ExperimentalBidirectionalStream.Builder {
// All fields are temporary storage of ExperimentalBidirectionalStream configuration to be
// copied to CronetBidirectionalStream.
// CronetEngine to create the stream.
private final CronetEngineBase mCronetEngine;
// URL to request.
private final String mUrl;
// Callback to receive progress callbacks.
private final BidirectionalStream.Callback mCallback;
// Executor on which callbacks will be invoked.
private final Executor mExecutor;
// List of request headers, stored as header field name and value pairs.
private final ArrayList<Map.Entry<String, String>> mRequestHeaders = new ArrayList<>();
// HTTP method for the request. Default to POST.
private String mHttpMethod = "POST";
// Priority of the stream. Default is medium.
@CronetEngineBase.StreamPriority
private int mPriority = STREAM_PRIORITY_MEDIUM;
private boolean mDelayRequestHeadersUntilFirstFlush;
// Request reporting annotations.
private Collection<Object> mRequestAnnotations;
private boolean mTrafficStatsTagSet;
private int mTrafficStatsTag;
private boolean mTrafficStatsUidSet;
private int mTrafficStatsUid;
/**
* 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 the URL for the generated stream
* @param callback the {@link BidirectionalStream.Callback} object that gets invoked upon
* different events
* occuring
* @param executor the {@link Executor} on which {@code callback} methods will be invoked
* @param cronetEngine the {@link CronetEngine} used to create the stream
*/
BidirectionalStreamBuilderImpl(String url, BidirectionalStream.Callback callback,
Executor executor, CronetEngineBase cronetEngine) {
super();
if (url == null) {
throw new NullPointerException("URL is required.");
}
if (callback == null) {
throw new NullPointerException("Callback is required.");
}
if (executor == null) {
throw new NullPointerException("Executor is required.");
}
if (cronetEngine == null) {
throw new NullPointerException("CronetEngine is required.");
}
mUrl = url;
mCallback = callback;
mExecutor = executor;
mCronetEngine = cronetEngine;
}
@Override
public BidirectionalStreamBuilderImpl setHttpMethod(String method) {
if (method == null) {
throw new NullPointerException("Method is required.");
}
mHttpMethod = method;
return this;
}
@Override
public BidirectionalStreamBuilderImpl addHeader(String header, String value) {
if (header == null) {
throw new NullPointerException("Invalid header name.");
}
if (value == null) {
throw new NullPointerException("Invalid header value.");
}
mRequestHeaders.add(new AbstractMap.SimpleImmutableEntry<>(header, value));
return this;
}
@Override
public BidirectionalStreamBuilderImpl setPriority(
@CronetEngineBase.StreamPriority int priority) {
mPriority = priority;
return this;
}
@Override
public BidirectionalStreamBuilderImpl delayRequestHeadersUntilFirstFlush(
boolean delayRequestHeadersUntilFirstFlush) {
mDelayRequestHeadersUntilFirstFlush = delayRequestHeadersUntilFirstFlush;
return this;
}
@Override
public ExperimentalBidirectionalStream.Builder addRequestAnnotation(Object annotation) {
if (annotation == null) {
throw new NullPointerException("Invalid metrics annotation.");
}
if (mRequestAnnotations == null) {
mRequestAnnotations = new ArrayList<Object>();
}
mRequestAnnotations.add(annotation);
return this;
}
@Override
public ExperimentalBidirectionalStream.Builder setTrafficStatsTag(int tag) {
mTrafficStatsTagSet = true;
mTrafficStatsTag = tag;
return this;
}
@Override
public ExperimentalBidirectionalStream.Builder setTrafficStatsUid(int uid) {
mTrafficStatsUidSet = true;
mTrafficStatsUid = uid;
return this;
}
@Override
@SuppressLint("WrongConstant") // TODO(jbudorick): Remove this after rolling to the N SDK.
public ExperimentalBidirectionalStream build() {
return mCronetEngine.createBidirectionalStream(mUrl, mCallback, mExecutor, mHttpMethod,
mRequestHeaders, mPriority, mDelayRequestHeadersUntilFirstFlush,
mRequestAnnotations, mTrafficStatsTagSet, mTrafficStatsTag, mTrafficStatsUidSet,
mTrafficStatsUid);
}
}

View File

@ -0,0 +1,32 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import androidx.annotation.VisibleForTesting;
import org.chromium.net.NetError;
/**
* Used in {@link CronetBidirectionalStream}. Implements {@link NetworkExceptionImpl}.
*/
@VisibleForTesting
public class BidirectionalStreamNetworkException extends NetworkExceptionImpl {
public BidirectionalStreamNetworkException(
String message, int errorCode, int cronetInternalErrorCode) {
super(message, errorCode, cronetInternalErrorCode);
}
@Override
public boolean immediatelyRetryable() {
switch (mCronetInternalErrorCode) {
case NetError.ERR_HTTP2_PING_FAILED:
case NetError.ERR_QUIC_HANDSHAKE_FAILED:
assert mErrorCode == ERROR_OTHER;
return true;
default:
return super.immediatelyRetryable();
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import org.chromium.net.CallbackException;
/**
* An implementation of {@link CallbackException}.
*/
public class CallbackExceptionImpl extends CallbackException {
public CallbackExceptionImpl(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,850 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeClassQualifiedName;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.net.BidirectionalStream;
import org.chromium.net.CallbackException;
import org.chromium.net.CronetException;
import org.chromium.net.ExperimentalBidirectionalStream;
import org.chromium.net.NetworkException;
import org.chromium.net.RequestFinishedInfo;
import org.chromium.net.RequestPriority;
import org.chromium.net.UrlResponseInfo;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import javax.annotation.concurrent.GuardedBy;
/**
* {@link BidirectionalStream} implementation using Chromium network stack.
* All @CalledByNative methods are called on the native network thread
* and post tasks with callback calls onto Executor. Upon returning from callback, the native
* stream is called on Executor thread and posts native tasks to the native network thread.
*/
@JNINamespace("cronet")
@VisibleForTesting
public class CronetBidirectionalStream extends ExperimentalBidirectionalStream {
/**
* States of BidirectionalStream are tracked in mReadState and mWriteState.
* The write state is separated out as it changes independently of the read state.
* There is one initial state: State.NOT_STARTED. There is one normal final state:
* State.SUCCESS, reached after State.READING_DONE and State.WRITING_DONE. There are two
* exceptional final states: State.CANCELED and State.ERROR, which can be reached from
* any other non-final state.
*/
@IntDef({State.NOT_STARTED, State.STARTED, State.WAITING_FOR_READ, State.READING,
State.READING_DONE, State.CANCELED, State.ERROR, State.SUCCESS, State.WAITING_FOR_FLUSH,
State.WRITING, State.WRITING_DONE})
@Retention(RetentionPolicy.SOURCE)
private @interface State {
/* Initial state, stream not started. */
int NOT_STARTED = 0;
/*
* Stream started, request headers are being sent if mDelayRequestHeadersUntilNextFlush
* is not set to true.
*/
int STARTED = 1;
/* Waiting for {@code read()} to be called. */
int WAITING_FOR_READ = 2;
/* Reading from the remote, {@code onReadCompleted()} callback will be called when done. */
int READING = 3;
/* There is no more data to read and stream is half-closed by the remote side. */
int READING_DONE = 4;
/* Stream is canceled. */
int CANCELED = 5;
/* Error has occurred, stream is closed. */
int ERROR = 6;
/* Reading and writing are done, and the stream is closed successfully. */
int SUCCESS = 7;
/* Waiting for {@code CronetBidirectionalStreamJni.get().sendRequestHeaders()} or {@code
CronetBidirectionalStreamJni.get().writevData()} to be called. */
int WAITING_FOR_FLUSH = 8;
/* Writing to the remote, {@code onWritevCompleted()} callback will be called when done. */
int WRITING = 9;
/* There is no more data to write and stream is half-closed by the local side. */
int WRITING_DONE = 10;
}
private final CronetUrlRequestContext mRequestContext;
private final Executor mExecutor;
private final VersionSafeCallbacks.BidirectionalStreamCallback mCallback;
private final String mInitialUrl;
private final int mInitialPriority;
private final String mInitialMethod;
private final String mRequestHeaders[];
private final boolean mDelayRequestHeadersUntilFirstFlush;
private final Collection<Object> mRequestAnnotations;
private final boolean mTrafficStatsTagSet;
private final int mTrafficStatsTag;
private final boolean mTrafficStatsUidSet;
private final int mTrafficStatsUid;
private CronetException mException;
/*
* Synchronizes access to mNativeStream, mReadState and mWriteState.
*/
private final Object mNativeStreamLock = new Object();
@GuardedBy("mNativeStreamLock")
// Pending write data.
private LinkedList<ByteBuffer> mPendingData;
@GuardedBy("mNativeStreamLock")
// Flush data queue that should be pushed to the native stack when the previous
// CronetBidirectionalStreamJni.get().writevData completes.
private LinkedList<ByteBuffer> mFlushData;
@GuardedBy("mNativeStreamLock")
// Whether an end-of-stream flag is passed in through write().
private boolean mEndOfStreamWritten;
@GuardedBy("mNativeStreamLock")
// Whether request headers have been sent.
private boolean mRequestHeadersSent;
@GuardedBy("mNativeStreamLock")
// Metrics information. Obtained when request succeeds, fails or is canceled.
private RequestFinishedInfo.Metrics mMetrics;
/* Native BidirectionalStream object, owned by CronetBidirectionalStream. */
@GuardedBy("mNativeStreamLock")
private long mNativeStream;
/**
* Read state is tracking reading flow.
* / <--- READING <--- \
* | |
* \ /
* NOT_STARTED -> STARTED --> WAITING_FOR_READ -> READING_DONE -> SUCCESS
*/
@GuardedBy("mNativeStreamLock")
private @State int mReadState = State.NOT_STARTED;
/**
* Write state is tracking writing flow.
* / <--- WRITING <--- \
* | |
* \ /
* NOT_STARTED -> STARTED --> WAITING_FOR_FLUSH -> WRITING_DONE -> SUCCESS
*/
@GuardedBy("mNativeStreamLock")
private @State int mWriteState = State.NOT_STARTED;
// Only modified on the network thread.
private UrlResponseInfoImpl mResponseInfo;
/*
* OnReadCompleted callback is repeatedly invoked when each read is completed, so it
* is cached as a member variable.
*/
// Only modified on the network thread.
private OnReadCompletedRunnable mOnReadCompletedTask;
private Runnable mOnDestroyedCallbackForTesting;
private final class OnReadCompletedRunnable implements Runnable {
// Buffer passed back from current invocation of onReadCompleted.
ByteBuffer mByteBuffer;
// End of stream flag from current invocation of onReadCompleted.
boolean mEndOfStream;
@Override
public void run() {
try {
// Null out mByteBuffer, to pass buffer ownership to callback or release if done.
ByteBuffer buffer = mByteBuffer;
mByteBuffer = null;
boolean maybeOnSucceeded = false;
synchronized (mNativeStreamLock) {
if (isDoneLocked()) {
return;
}
if (mEndOfStream) {
mReadState = State.READING_DONE;
maybeOnSucceeded = (mWriteState == State.WRITING_DONE);
} else {
mReadState = State.WAITING_FOR_READ;
}
}
mCallback.onReadCompleted(
CronetBidirectionalStream.this, mResponseInfo, buffer, mEndOfStream);
if (maybeOnSucceeded) {
maybeOnSucceededOnExecutor();
}
} catch (Exception e) {
onCallbackException(e);
}
}
}
private final class OnWriteCompletedRunnable implements Runnable {
// Buffer passed back from current invocation of onWriteCompleted.
private ByteBuffer mByteBuffer;
// End of stream flag from current call to write.
private final boolean mEndOfStream;
OnWriteCompletedRunnable(ByteBuffer buffer, boolean endOfStream) {
mByteBuffer = buffer;
mEndOfStream = endOfStream;
}
@Override
public void run() {
try {
// Null out mByteBuffer, to pass buffer ownership to callback or release if done.
ByteBuffer buffer = mByteBuffer;
mByteBuffer = null;
boolean maybeOnSucceeded = false;
synchronized (mNativeStreamLock) {
if (isDoneLocked()) {
return;
}
if (mEndOfStream) {
mWriteState = State.WRITING_DONE;
maybeOnSucceeded = (mReadState == State.READING_DONE);
}
}
mCallback.onWriteCompleted(
CronetBidirectionalStream.this, mResponseInfo, buffer, mEndOfStream);
if (maybeOnSucceeded) {
maybeOnSucceededOnExecutor();
}
} catch (Exception e) {
onCallbackException(e);
}
}
}
CronetBidirectionalStream(CronetUrlRequestContext requestContext, String url,
@CronetEngineBase.StreamPriority int priority, Callback callback, Executor executor,
String httpMethod, List<Map.Entry<String, String>> requestHeaders,
boolean delayRequestHeadersUntilNextFlush, Collection<Object> requestAnnotations,
boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet,
int trafficStatsUid) {
mRequestContext = requestContext;
mInitialUrl = url;
mInitialPriority = convertStreamPriority(priority);
mCallback = new VersionSafeCallbacks.BidirectionalStreamCallback(callback);
mExecutor = executor;
mInitialMethod = httpMethod;
mRequestHeaders = stringsFromHeaderList(requestHeaders);
mDelayRequestHeadersUntilFirstFlush = delayRequestHeadersUntilNextFlush;
mPendingData = new LinkedList<>();
mFlushData = new LinkedList<>();
mRequestAnnotations = requestAnnotations;
mTrafficStatsTagSet = trafficStatsTagSet;
mTrafficStatsTag = trafficStatsTag;
mTrafficStatsUidSet = trafficStatsUidSet;
mTrafficStatsUid = trafficStatsUid;
}
@Override
public void start() {
synchronized (mNativeStreamLock) {
if (mReadState != State.NOT_STARTED) {
throw new IllegalStateException("Stream is already started.");
}
try {
mNativeStream = CronetBidirectionalStreamJni.get().createBidirectionalStream(
CronetBidirectionalStream.this,
mRequestContext.getUrlRequestContextAdapter(),
!mDelayRequestHeadersUntilFirstFlush,
mRequestContext.hasRequestFinishedListener(), mTrafficStatsTagSet,
mTrafficStatsTag, mTrafficStatsUidSet, mTrafficStatsUid);
mRequestContext.onRequestStarted();
// Non-zero startResult means an argument error.
int startResult = CronetBidirectionalStreamJni.get().start(mNativeStream,
CronetBidirectionalStream.this, mInitialUrl, mInitialPriority,
mInitialMethod, mRequestHeaders, !doesMethodAllowWriteData(mInitialMethod));
if (startResult == -1) {
throw new IllegalArgumentException("Invalid http method " + mInitialMethod);
}
if (startResult > 0) {
int headerPos = startResult - 1;
throw new IllegalArgumentException("Invalid header "
+ mRequestHeaders[headerPos] + "=" + mRequestHeaders[headerPos + 1]);
}
mReadState = mWriteState = State.STARTED;
} catch (RuntimeException e) {
// If there's an exception, clean up and then throw the
// exception to the caller.
destroyNativeStreamLocked(false);
throw e;
}
}
}
@Override
public void read(ByteBuffer buffer) {
synchronized (mNativeStreamLock) {
Preconditions.checkHasRemaining(buffer);
Preconditions.checkDirect(buffer);
if (mReadState != State.WAITING_FOR_READ) {
throw new IllegalStateException("Unexpected read attempt.");
}
if (isDoneLocked()) {
return;
}
if (mOnReadCompletedTask == null) {
mOnReadCompletedTask = new OnReadCompletedRunnable();
}
mReadState = State.READING;
if (!CronetBidirectionalStreamJni.get().readData(mNativeStream,
CronetBidirectionalStream.this, buffer, buffer.position(),
buffer.limit())) {
// Still waiting on read. This is just to have consistent
// behavior with the other error cases.
mReadState = State.WAITING_FOR_READ;
throw new IllegalArgumentException("Unable to call native read");
}
}
}
@Override
public void write(ByteBuffer buffer, boolean endOfStream) {
synchronized (mNativeStreamLock) {
Preconditions.checkDirect(buffer);
if (!buffer.hasRemaining() && !endOfStream) {
throw new IllegalArgumentException("Empty buffer before end of stream.");
}
if (mEndOfStreamWritten) {
throw new IllegalArgumentException("Write after writing end of stream.");
}
if (isDoneLocked()) {
return;
}
mPendingData.add(buffer);
if (endOfStream) {
mEndOfStreamWritten = true;
}
}
}
@Override
public void flush() {
synchronized (mNativeStreamLock) {
if (isDoneLocked()
|| (mWriteState != State.WAITING_FOR_FLUSH && mWriteState != State.WRITING)) {
return;
}
if (mPendingData.isEmpty() && mFlushData.isEmpty()) {
// If there is no pending write when flush() is called, see if
// request headers need to be flushed.
if (!mRequestHeadersSent) {
mRequestHeadersSent = true;
CronetBidirectionalStreamJni.get().sendRequestHeaders(
mNativeStream, CronetBidirectionalStream.this);
if (!doesMethodAllowWriteData(mInitialMethod)) {
mWriteState = State.WRITING_DONE;
}
}
return;
}
assert !mPendingData.isEmpty() || !mFlushData.isEmpty();
// Move buffers from mPendingData to the flushing queue.
if (!mPendingData.isEmpty()) {
mFlushData.addAll(mPendingData);
mPendingData.clear();
}
if (mWriteState == State.WRITING) {
// If there is a write already pending, wait until onWritevCompleted is
// called before pushing data to the native stack.
return;
}
sendFlushDataLocked();
}
}
// Helper method to send buffers in mFlushData. Caller needs to acquire
// mNativeStreamLock and make sure mWriteState is WAITING_FOR_FLUSH and
// mFlushData queue isn't empty.
@SuppressWarnings("GuardedByChecker")
private void sendFlushDataLocked() {
assert mWriteState == State.WAITING_FOR_FLUSH;
int size = mFlushData.size();
ByteBuffer[] buffers = new ByteBuffer[size];
int[] positions = new int[size];
int[] limits = new int[size];
for (int i = 0; i < size; i++) {
ByteBuffer buffer = mFlushData.poll();
buffers[i] = buffer;
positions[i] = buffer.position();
limits[i] = buffer.limit();
}
assert mFlushData.isEmpty();
assert buffers.length >= 1;
mWriteState = State.WRITING;
mRequestHeadersSent = true;
if (!CronetBidirectionalStreamJni.get().writevData(mNativeStream,
CronetBidirectionalStream.this, buffers, positions, limits,
mEndOfStreamWritten && mPendingData.isEmpty())) {
// Still waiting on flush. This is just to have consistent
// behavior with the other error cases.
mWriteState = State.WAITING_FOR_FLUSH;
throw new IllegalArgumentException("Unable to call native writev.");
}
}
/**
* Returns a read-only copy of {@code mPendingData} for testing.
*/
@VisibleForTesting
public List<ByteBuffer> getPendingDataForTesting() {
synchronized (mNativeStreamLock) {
List<ByteBuffer> pendingData = new LinkedList<ByteBuffer>();
for (ByteBuffer buffer : mPendingData) {
pendingData.add(buffer.asReadOnlyBuffer());
}
return pendingData;
}
}
/**
* Returns a read-only copy of {@code mFlushData} for testing.
*/
@VisibleForTesting
public List<ByteBuffer> getFlushDataForTesting() {
synchronized (mNativeStreamLock) {
List<ByteBuffer> flushData = new LinkedList<ByteBuffer>();
for (ByteBuffer buffer : mFlushData) {
flushData.add(buffer.asReadOnlyBuffer());
}
return flushData;
}
}
@Override
public void cancel() {
synchronized (mNativeStreamLock) {
if (isDoneLocked() || mReadState == State.NOT_STARTED) {
return;
}
mReadState = mWriteState = State.CANCELED;
destroyNativeStreamLocked(true);
}
}
@Override
public boolean isDone() {
synchronized (mNativeStreamLock) {
return isDoneLocked();
}
}
@GuardedBy("mNativeStreamLock")
private boolean isDoneLocked() {
return mReadState != State.NOT_STARTED && mNativeStream == 0;
}
/*
* Runs an onSucceeded callback if both Read and Write sides are closed.
*/
private void maybeOnSucceededOnExecutor() {
synchronized (mNativeStreamLock) {
if (isDoneLocked()) {
return;
}
if (!(mWriteState == State.WRITING_DONE && mReadState == State.READING_DONE)) {
return;
}
mReadState = mWriteState = State.SUCCESS;
// Destroy native stream first, so UrlRequestContext could be shut
// down from the listener.
destroyNativeStreamLocked(false);
}
try {
mCallback.onSucceeded(CronetBidirectionalStream.this, mResponseInfo);
} catch (Exception e) {
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onSucceeded method", e);
}
}
@SuppressWarnings("unused")
@CalledByNative
private void onStreamReady(final boolean requestHeadersSent) {
postTaskToExecutor(new Runnable() {
@Override
public void run() {
synchronized (mNativeStreamLock) {
if (isDoneLocked()) {
return;
}
mRequestHeadersSent = requestHeadersSent;
mReadState = State.WAITING_FOR_READ;
if (!doesMethodAllowWriteData(mInitialMethod) && mRequestHeadersSent) {
mWriteState = State.WRITING_DONE;
} else {
mWriteState = State.WAITING_FOR_FLUSH;
}
}
try {
mCallback.onStreamReady(CronetBidirectionalStream.this);
} catch (Exception e) {
onCallbackException(e);
}
}
});
}
/**
* Called when the final set of headers, after all redirects,
* is received. Can only be called once for each stream.
*/
@SuppressWarnings("unused")
@CalledByNative
private void onResponseHeadersReceived(int httpStatusCode, String negotiatedProtocol,
String[] headers, long receivedByteCount) {
try {
mResponseInfo = prepareResponseInfoOnNetworkThread(
httpStatusCode, negotiatedProtocol, headers, receivedByteCount);
} catch (Exception e) {
failWithException(new CronetExceptionImpl("Cannot prepare ResponseInfo", null));
return;
}
postTaskToExecutor(new Runnable() {
@Override
public void run() {
synchronized (mNativeStreamLock) {
if (isDoneLocked()) {
return;
}
mReadState = State.WAITING_FOR_READ;
}
try {
mCallback.onResponseHeadersReceived(
CronetBidirectionalStream.this, mResponseInfo);
} catch (Exception e) {
onCallbackException(e);
}
}
});
}
@SuppressWarnings("unused")
@CalledByNative
private void onReadCompleted(final ByteBuffer byteBuffer, int bytesRead, int initialPosition,
int initialLimit, long receivedByteCount) {
mResponseInfo.setReceivedByteCount(receivedByteCount);
if (byteBuffer.position() != initialPosition || byteBuffer.limit() != initialLimit) {
failWithException(
new CronetExceptionImpl("ByteBuffer modified externally during read", null));
return;
}
if (bytesRead < 0 || initialPosition + bytesRead > initialLimit) {
failWithException(new CronetExceptionImpl("Invalid number of bytes read", null));
return;
}
byteBuffer.position(initialPosition + bytesRead);
assert mOnReadCompletedTask.mByteBuffer == null;
mOnReadCompletedTask.mByteBuffer = byteBuffer;
mOnReadCompletedTask.mEndOfStream = (bytesRead == 0);
postTaskToExecutor(mOnReadCompletedTask);
}
@SuppressWarnings("unused")
@CalledByNative
private void onWritevCompleted(final ByteBuffer[] byteBuffers, int[] initialPositions,
int[] initialLimits, boolean endOfStream) {
assert byteBuffers.length == initialPositions.length;
assert byteBuffers.length == initialLimits.length;
synchronized (mNativeStreamLock) {
if (isDoneLocked()) return;
mWriteState = State.WAITING_FOR_FLUSH;
// Flush if there is anything in the flush queue mFlushData.
if (!mFlushData.isEmpty()) {
sendFlushDataLocked();
}
}
for (int i = 0; i < byteBuffers.length; i++) {
ByteBuffer buffer = byteBuffers[i];
if (buffer.position() != initialPositions[i] || buffer.limit() != initialLimits[i]) {
failWithException(new CronetExceptionImpl(
"ByteBuffer modified externally during write", null));
return;
}
// Current implementation always writes the complete buffer.
buffer.position(buffer.limit());
postTaskToExecutor(new OnWriteCompletedRunnable(buffer,
// Only set endOfStream flag if this buffer is the last in byteBuffers.
endOfStream && i == byteBuffers.length - 1));
}
}
@SuppressWarnings("unused")
@CalledByNative
private void onResponseTrailersReceived(String[] trailers) {
final UrlResponseInfo.HeaderBlock trailersBlock =
new UrlResponseInfoImpl.HeaderBlockImpl(headersListFromStrings(trailers));
postTaskToExecutor(new Runnable() {
@Override
public void run() {
synchronized (mNativeStreamLock) {
if (isDoneLocked()) {
return;
}
}
try {
mCallback.onResponseTrailersReceived(
CronetBidirectionalStream.this, mResponseInfo, trailersBlock);
} catch (Exception e) {
onCallbackException(e);
}
}
});
}
@SuppressWarnings("unused")
@CalledByNative
private void onError(int errorCode, int nativeError, int nativeQuicError, String errorString,
long receivedByteCount) {
if (mResponseInfo != null) {
mResponseInfo.setReceivedByteCount(receivedByteCount);
}
if (errorCode == NetworkException.ERROR_QUIC_PROTOCOL_FAILED
|| errorCode == NetworkException.ERROR_NETWORK_CHANGED) {
failWithException(
new QuicExceptionImpl("Exception in BidirectionalStream: " + errorString,
errorCode, nativeError, nativeQuicError));
} else {
failWithException(new BidirectionalStreamNetworkException(
"Exception in BidirectionalStream: " + errorString, errorCode, nativeError));
}
}
/**
* Called when request is canceled, no callbacks will be called afterwards.
*/
@SuppressWarnings("unused")
@CalledByNative
private void onCanceled() {
postTaskToExecutor(new Runnable() {
@Override
public void run() {
try {
mCallback.onCanceled(CronetBidirectionalStream.this, mResponseInfo);
} catch (Exception e) {
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onCanceled method", e);
}
}
});
}
/**
* Called by the native code to report metrics just before the native adapter is destroyed.
*/
@SuppressWarnings("unused")
@CalledByNative
private void onMetricsCollected(long requestStartMs, long dnsStartMs, long dnsEndMs,
long connectStartMs, long connectEndMs, long sslStartMs, long sslEndMs,
long sendingStartMs, long sendingEndMs, long pushStartMs, long pushEndMs,
long responseStartMs, long requestEndMs, boolean socketReused, long sentByteCount,
long receivedByteCount) {
synchronized (mNativeStreamLock) {
if (mMetrics != null) {
throw new IllegalStateException("Metrics collection should only happen once.");
}
mMetrics = new CronetMetrics(requestStartMs, dnsStartMs, dnsEndMs, connectStartMs,
connectEndMs, sslStartMs, sslEndMs, sendingStartMs, sendingEndMs, pushStartMs,
pushEndMs, responseStartMs, requestEndMs, socketReused, sentByteCount,
receivedByteCount);
assert mReadState == mWriteState;
assert (mReadState == State.SUCCESS) || (mReadState == State.ERROR)
|| (mReadState == State.CANCELED);
int finishedReason;
if (mReadState == State.SUCCESS) {
finishedReason = RequestFinishedInfo.SUCCEEDED;
} else if (mReadState == State.CANCELED) {
finishedReason = RequestFinishedInfo.CANCELED;
} else {
finishedReason = RequestFinishedInfo.FAILED;
}
final RequestFinishedInfo requestFinishedInfo = new RequestFinishedInfoImpl(mInitialUrl,
mRequestAnnotations, mMetrics, finishedReason, mResponseInfo, mException);
mRequestContext.reportRequestFinished(requestFinishedInfo);
}
}
@VisibleForTesting
public void setOnDestroyedCallbackForTesting(Runnable onDestroyedCallbackForTesting) {
mOnDestroyedCallbackForTesting = onDestroyedCallbackForTesting;
}
private static boolean doesMethodAllowWriteData(String methodName) {
return !methodName.equals("GET") && !methodName.equals("HEAD");
}
private static ArrayList<Map.Entry<String, String>> headersListFromStrings(String[] headers) {
ArrayList<Map.Entry<String, String>> headersList = new ArrayList<>(headers.length / 2);
for (int i = 0; i < headers.length; i += 2) {
headersList.add(new AbstractMap.SimpleImmutableEntry<>(headers[i], headers[i + 1]));
}
return headersList;
}
private static String[] stringsFromHeaderList(List<Map.Entry<String, String>> headersList) {
String headersArray[] = new String[headersList.size() * 2];
int i = 0;
for (Map.Entry<String, String> requestHeader : headersList) {
headersArray[i++] = requestHeader.getKey();
headersArray[i++] = requestHeader.getValue();
}
return headersArray;
}
private static int convertStreamPriority(@CronetEngineBase.StreamPriority int priority) {
switch (priority) {
case Builder.STREAM_PRIORITY_IDLE:
return RequestPriority.IDLE;
case Builder.STREAM_PRIORITY_LOWEST:
return RequestPriority.LOWEST;
case Builder.STREAM_PRIORITY_LOW:
return RequestPriority.LOW;
case Builder.STREAM_PRIORITY_MEDIUM:
return RequestPriority.MEDIUM;
case Builder.STREAM_PRIORITY_HIGHEST:
return RequestPriority.HIGHEST;
default:
throw new IllegalArgumentException("Invalid stream priority.");
}
}
/**
* Posts task to application Executor. Used for callbacks
* and other tasks that should not be executed on network thread.
*/
private void postTaskToExecutor(Runnable task) {
try {
mExecutor.execute(task);
} catch (RejectedExecutionException failException) {
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception posting task to executor",
failException);
// If posting a task throws an exception, then there is no choice
// but to destroy the stream without invoking the callback.
synchronized (mNativeStreamLock) {
mReadState = mWriteState = State.ERROR;
destroyNativeStreamLocked(false);
}
}
}
private UrlResponseInfoImpl prepareResponseInfoOnNetworkThread(int httpStatusCode,
String negotiatedProtocol, String[] headers, long receivedByteCount) {
UrlResponseInfoImpl responseInfo = new UrlResponseInfoImpl(Arrays.asList(mInitialUrl),
httpStatusCode, "", headersListFromStrings(headers), false, negotiatedProtocol,
null, receivedByteCount);
return responseInfo;
}
@GuardedBy("mNativeStreamLock")
private void destroyNativeStreamLocked(boolean sendOnCanceled) {
Log.i(CronetUrlRequestContext.LOG_TAG, "destroyNativeStreamLocked " + this.toString());
if (mNativeStream == 0) {
return;
}
CronetBidirectionalStreamJni.get().destroy(
mNativeStream, CronetBidirectionalStream.this, sendOnCanceled);
mRequestContext.onRequestDestroyed();
mNativeStream = 0;
if (mOnDestroyedCallbackForTesting != null) {
mOnDestroyedCallbackForTesting.run();
}
}
/**
* Fails the stream with an exception. Only called on the Executor.
*/
private void failWithExceptionOnExecutor(CronetException e) {
mException = e;
// Do not call into mCallback if request is complete.
synchronized (mNativeStreamLock) {
if (isDoneLocked()) {
return;
}
mReadState = mWriteState = State.ERROR;
destroyNativeStreamLocked(false);
}
try {
mCallback.onFailed(this, mResponseInfo, e);
} catch (Exception failException) {
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception notifying of failed request",
failException);
}
}
/**
* If callback method throws an exception, stream gets canceled
* and exception is reported via onFailed callback.
* Only called on the Executor.
*/
private void onCallbackException(Exception e) {
CallbackException streamError =
new CallbackExceptionImpl("CalledByNative method has thrown an exception", e);
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in CalledByNative method", e);
failWithExceptionOnExecutor(streamError);
}
/**
* Fails the stream with an exception. Can be called on any thread.
*/
private void failWithException(final CronetException exception) {
postTaskToExecutor(new Runnable() {
@Override
public void run() {
failWithExceptionOnExecutor(exception);
}
});
}
@NativeMethods
interface Natives {
// Native methods are implemented in cronet_bidirectional_stream_adapter.cc.
long createBidirectionalStream(CronetBidirectionalStream caller,
long urlRequestContextAdapter, boolean sendRequestHeadersAutomatically,
boolean enableMetricsCollection, boolean trafficStatsTagSet, int trafficStatsTag,
boolean trafficStatsUidSet, int trafficStatsUid);
@NativeClassQualifiedName("CronetBidirectionalStreamAdapter")
int start(long nativePtr, CronetBidirectionalStream caller, String url, int priority,
String method, String[] headers, boolean endOfStream);
@NativeClassQualifiedName("CronetBidirectionalStreamAdapter")
void sendRequestHeaders(long nativePtr, CronetBidirectionalStream caller);
@NativeClassQualifiedName("CronetBidirectionalStreamAdapter")
boolean readData(long nativePtr, CronetBidirectionalStream caller, ByteBuffer byteBuffer,
int position, int limit);
@NativeClassQualifiedName("CronetBidirectionalStreamAdapter")
boolean writevData(long nativePtr, CronetBidirectionalStream caller, ByteBuffer[] buffers,
int[] positions, int[] limits, boolean endOfStream);
@NativeClassQualifiedName("CronetBidirectionalStreamAdapter")
void destroy(long nativePtr, CronetBidirectionalStream caller, boolean sendOnCanceled);
}
}

View File

@ -0,0 +1,130 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import org.chromium.net.BidirectionalStream;
import org.chromium.net.ExperimentalBidirectionalStream;
import org.chromium.net.ExperimentalCronetEngine;
import org.chromium.net.ExperimentalUrlRequest;
import org.chromium.net.RequestFinishedInfo;
import org.chromium.net.UrlRequest;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* Base class of {@link CronetUrlRequestContext} and {@link JavaCronetEngine} that contains
* shared logic.
*/
public abstract class CronetEngineBase extends ExperimentalCronetEngine {
/**
* Creates a {@link UrlRequest} object. All callbacks will
* be called on {@code executor}'s thread. {@code executor} must not run
* tasks on the current thread to prevent blocking networking operations
* and causing exceptions during shutdown.
*
* @param url {@link URL} for the request.
* @param callback callback object that gets invoked on different events.
* @param executor {@link Executor} on which all callbacks will be invoked.
* @param priority priority of the request which should be one of the
* {@link UrlRequest.Builder#REQUEST_PRIORITY_IDLE REQUEST_PRIORITY_*}
* values.
* @param requestAnnotations Objects to pass on to
* {@link org.chromium.net.RequestFinishedInfo.Listener}.
* @param disableCache disables cache for the request.
* If context is not set up to use cache this param has no effect.
* @param disableConnectionMigration disables connection migration for this
* request if it is enabled for the session.
* @param allowDirectExecutor whether executors used by this request are permitted
* to execute submitted tasks inline.
* @param trafficStatsTagSet {@code true} if {@code trafficStatsTag} represents a TrafficStats
* tag to apply to sockets used to perform this request.
* @param trafficStatsTag TrafficStats tag to apply to sockets used to perform this request.
* @param trafficStatsUidSet {@code true} if {@code trafficStatsUid} represents a UID to
* attribute traffic used to perform this request.
* @param trafficStatsUid UID to attribute traffic used to perform this request.
* @param requestFinishedListener callback to get invoked with metrics when request is finished.
* Set to {@code null} if not used.
* @param idempotency idempotency of the request which should be one of the
* {@link ExperimentalUrlRequest.Builder#DEFAULT_IDEMPOTENCY IDEMPOTENT NOT_IDEMPOTENT}
* values.
* @return new request.
*/
protected abstract UrlRequestBase createRequest(String url, UrlRequest.Callback callback,
Executor executor, @RequestPriority int priority, Collection<Object> requestAnnotations,
boolean disableCache, boolean disableConnectionMigration, boolean allowDirectExecutor,
boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet,
int trafficStatsUid, @Nullable RequestFinishedInfo.Listener requestFinishedListener,
@Idempotency int idempotency);
/**
* Creates a {@link BidirectionalStream} object. {@code callback} methods will
* be invoked on {@code executor}. {@code executor} must not run
* tasks on the current thread to prevent blocking networking operations
* and causing exceptions during shutdown.
*
* @param url the URL for the stream
* @param callback the object whose methods get invoked upon different events
* @param executor the {@link Executor} on which all callbacks will be called
* @param httpMethod the HTTP method to use for the stream
* @param requestHeaders the list of request headers
* @param priority priority of the stream which should be one of the
* {@link BidirectionalStream.Builder#STREAM_PRIORITY_IDLE STREAM_PRIORITY_*}
* values.
* @param delayRequestHeadersUntilFirstFlush whether to delay sending request
* headers until flush() is called, and try to combine them
* with the next data frame.
* @param requestAnnotations Objects to pass on to
* {@link org.chromium.net.RequestFinishedInfo.Listener}.
* @param trafficStatsTagSet {@code true} if {@code trafficStatsTag} represents a TrafficStats
* tag to apply to sockets used to perform this request.
* @param trafficStatsTag TrafficStats tag to apply to sockets used to perform this request.
* @param trafficStatsUidSet {@code true} if {@code trafficStatsUid} represents a UID to
* attribute traffic used to perform this request.
* @param trafficStatsUid UID to attribute traffic used to perform this request.
* @return a new stream.
*/
protected abstract ExperimentalBidirectionalStream createBidirectionalStream(String url,
BidirectionalStream.Callback callback, Executor executor, String httpMethod,
List<Map.Entry<String, String>> requestHeaders, @StreamPriority int priority,
boolean delayRequestHeadersUntilFirstFlush, Collection<Object> requestAnnotations,
boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet,
int trafficStatsUid);
@Override
public ExperimentalUrlRequest.Builder newUrlRequestBuilder(
String url, UrlRequest.Callback callback, Executor executor) {
return new UrlRequestBuilderImpl(url, callback, executor, this);
}
@IntDef({UrlRequest.Builder.REQUEST_PRIORITY_IDLE, UrlRequest.Builder.REQUEST_PRIORITY_LOWEST,
UrlRequest.Builder.REQUEST_PRIORITY_LOW, UrlRequest.Builder.REQUEST_PRIORITY_MEDIUM,
UrlRequest.Builder.REQUEST_PRIORITY_HIGHEST})
@Retention(RetentionPolicy.SOURCE)
public @interface RequestPriority {}
@IntDef({
BidirectionalStream.Builder.STREAM_PRIORITY_IDLE,
BidirectionalStream.Builder.STREAM_PRIORITY_LOWEST,
BidirectionalStream.Builder.STREAM_PRIORITY_LOW,
BidirectionalStream.Builder.STREAM_PRIORITY_MEDIUM,
BidirectionalStream.Builder.STREAM_PRIORITY_HIGHEST,
})
@Retention(RetentionPolicy.SOURCE)
public @interface StreamPriority {}
@IntDef({ExperimentalUrlRequest.Builder.DEFAULT_IDEMPOTENCY,
ExperimentalUrlRequest.Builder.IDEMPOTENT,
ExperimentalUrlRequest.Builder.NOT_IDEMPOTENT})
@Retention(RetentionPolicy.SOURCE)
public @interface Idempotency {}
}

View File

@ -0,0 +1,407 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import static android.os.Process.THREAD_PRIORITY_LOWEST;
import android.content.Context;
import android.util.Base64;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import org.chromium.net.CronetEngine;
import org.chromium.net.ICronetEngineBuilder;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.IDN;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
* Implementation of {@link ICronetEngineBuilder}.
*/
public abstract class CronetEngineBuilderImpl extends ICronetEngineBuilder {
/**
* A hint that a host supports QUIC.
*/
public static class QuicHint {
// The host.
final String mHost;
// Port of the server that supports QUIC.
final int mPort;
// Alternate protocol port.
final int mAlternatePort;
QuicHint(String host, int port, int alternatePort) {
mHost = host;
mPort = port;
mAlternatePort = alternatePort;
}
}
/**
* A public key pin.
*/
public static class Pkp {
// Host to pin for.
final String mHost;
// Array of SHA-256 hashes of keys.
final byte[][] mHashes;
// Should pin apply to subdomains?
final boolean mIncludeSubdomains;
// When the pin expires.
final Date mExpirationDate;
Pkp(String host, byte[][] hashes, boolean includeSubdomains, Date expirationDate) {
mHost = host;
mHashes = hashes;
mIncludeSubdomains = includeSubdomains;
mExpirationDate = expirationDate;
}
}
private static final Pattern INVALID_PKP_HOST_NAME = Pattern.compile("^[0-9\\.]*$");
private static final int INVALID_THREAD_PRIORITY = THREAD_PRIORITY_LOWEST + 1;
// Private fields are simply storage of configuration for the resulting CronetEngine.
// See setters below for verbose descriptions.
private final Context mApplicationContext;
private final List<QuicHint> mQuicHints = new LinkedList<>();
private final List<Pkp> mPkps = new LinkedList<>();
private boolean mPublicKeyPinningBypassForLocalTrustAnchorsEnabled;
private String mUserAgent;
private String mStoragePath;
private boolean mQuicEnabled;
private boolean mHttp2Enabled;
private boolean mBrotiEnabled;
private boolean mDisableCache;
private int mHttpCacheMode;
private long mHttpCacheMaxSize;
private String mExperimentalOptions;
protected long mMockCertVerifier;
private boolean mNetworkQualityEstimatorEnabled;
private int mThreadPriority = INVALID_THREAD_PRIORITY;
/**
* Default config enables SPDY and QUIC, disables SDCH and HTTP cache.
* @param context Android {@link Context} for engine to use.
*/
public CronetEngineBuilderImpl(Context context) {
mApplicationContext = context.getApplicationContext();
enableQuic(true);
enableHttp2(true);
enableBrotli(false);
enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISABLED, 0);
enableNetworkQualityEstimator(false);
enablePublicKeyPinningBypassForLocalTrustAnchors(true);
}
@Override
public String getDefaultUserAgent() {
return UserAgent.from(mApplicationContext);
}
@Override
public CronetEngineBuilderImpl setUserAgent(String userAgent) {
mUserAgent = userAgent;
return this;
}
String getUserAgent() {
return mUserAgent;
}
@Override
public CronetEngineBuilderImpl setStoragePath(String value) {
if (!new File(value).isDirectory()) {
throw new IllegalArgumentException("Storage path must be set to existing directory");
}
mStoragePath = value;
return this;
}
String storagePath() {
return mStoragePath;
}
@Override
public CronetEngineBuilderImpl setLibraryLoader(CronetEngine.Builder.LibraryLoader loader) {
// |CronetEngineBuilderImpl| is an abstract class that is used by concrete builder
// implementations, including the Java Cronet engine builder; therefore, the implementation
// of this method should be "no-op". Subclasses that care about the library loader
// should override this method.
return this;
}
/**
* Default implementation of the method that returns {@code null}.
*
* @return {@code null}.
*/
VersionSafeCallbacks.LibraryLoader libraryLoader() {
return null;
}
@Override
public CronetEngineBuilderImpl enableQuic(boolean value) {
mQuicEnabled = value;
return this;
}
boolean quicEnabled() {
return mQuicEnabled;
}
/**
* Constructs default QUIC User Agent Id string including application name
* and Cronet version. Returns empty string if QUIC is not enabled.
*
* @return QUIC User Agent ID string.
*/
String getDefaultQuicUserAgentId() {
return mQuicEnabled ? UserAgent.getQuicUserAgentIdFrom(mApplicationContext) : "";
}
@Override
public CronetEngineBuilderImpl enableHttp2(boolean value) {
mHttp2Enabled = value;
return this;
}
boolean http2Enabled() {
return mHttp2Enabled;
}
@Override
public CronetEngineBuilderImpl enableSdch(boolean value) {
return this;
}
@Override
public CronetEngineBuilderImpl enableBrotli(boolean value) {
mBrotiEnabled = value;
return this;
}
boolean brotliEnabled() {
return mBrotiEnabled;
}
@IntDef({CronetEngine.Builder.HTTP_CACHE_DISABLED, CronetEngine.Builder.HTTP_CACHE_IN_MEMORY,
CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, CronetEngine.Builder.HTTP_CACHE_DISK})
@Retention(RetentionPolicy.SOURCE)
public @interface HttpCacheSetting {}
@Override
public CronetEngineBuilderImpl enableHttpCache(@HttpCacheSetting int cacheMode, long maxSize) {
if (cacheMode == CronetEngine.Builder.HTTP_CACHE_DISK
|| cacheMode == CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP) {
if (storagePath() == null) {
throw new IllegalArgumentException("Storage path must be set");
}
} else {
if (storagePath() != null) {
throw new IllegalArgumentException("Storage path must not be set");
}
}
mDisableCache = (cacheMode == CronetEngine.Builder.HTTP_CACHE_DISABLED
|| cacheMode == CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP);
mHttpCacheMaxSize = maxSize;
switch (cacheMode) {
case CronetEngine.Builder.HTTP_CACHE_DISABLED:
mHttpCacheMode = HttpCacheType.DISABLED;
break;
case CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP:
case CronetEngine.Builder.HTTP_CACHE_DISK:
mHttpCacheMode = HttpCacheType.DISK;
break;
case CronetEngine.Builder.HTTP_CACHE_IN_MEMORY:
mHttpCacheMode = HttpCacheType.MEMORY;
break;
default:
throw new IllegalArgumentException("Unknown cache mode");
}
return this;
}
boolean cacheDisabled() {
return mDisableCache;
}
long httpCacheMaxSize() {
return mHttpCacheMaxSize;
}
int httpCacheMode() {
return mHttpCacheMode;
}
@Override
public CronetEngineBuilderImpl addQuicHint(String host, int port, int alternatePort) {
if (host.contains("/")) {
throw new IllegalArgumentException("Illegal QUIC Hint Host: " + host);
}
mQuicHints.add(new QuicHint(host, port, alternatePort));
return this;
}
List<QuicHint> quicHints() {
return mQuicHints;
}
@Override
public CronetEngineBuilderImpl addPublicKeyPins(String hostName, Set<byte[]> pinsSha256,
boolean includeSubdomains, Date expirationDate) {
if (hostName == null) {
throw new NullPointerException("The hostname cannot be null");
}
if (pinsSha256 == null) {
throw new NullPointerException("The set of SHA256 pins cannot be null");
}
if (expirationDate == null) {
throw new NullPointerException("The pin expiration date cannot be null");
}
String idnHostName = validateHostNameForPinningAndConvert(hostName);
// Convert the pin to BASE64 encoding to remove duplicates.
Map<String, byte[]> hashes = new HashMap<>();
for (byte[] pinSha256 : pinsSha256) {
if (pinSha256 == null || pinSha256.length != 32) {
throw new IllegalArgumentException("Public key pin is invalid");
}
hashes.put(Base64.encodeToString(pinSha256, 0), pinSha256);
}
// Add new element to PKP list.
mPkps.add(new Pkp(idnHostName, hashes.values().toArray(new byte[hashes.size()][]),
includeSubdomains, expirationDate));
return this;
}
/**
* Returns list of public key pins.
* @return list of public key pins.
*/
List<Pkp> publicKeyPins() {
return mPkps;
}
@Override
public CronetEngineBuilderImpl enablePublicKeyPinningBypassForLocalTrustAnchors(boolean value) {
mPublicKeyPinningBypassForLocalTrustAnchorsEnabled = value;
return this;
}
boolean publicKeyPinningBypassForLocalTrustAnchorsEnabled() {
return mPublicKeyPinningBypassForLocalTrustAnchorsEnabled;
}
/**
* Checks whether a given string represents a valid host name for PKP and converts it
* to ASCII Compatible Encoding representation according to RFC 1122, RFC 1123 and
* RFC 3490. This method is more restrictive than required by RFC 7469. Thus, a host
* that contains digits and the dot character only is considered invalid.
*
* Note: Currently Cronet doesn't have native implementation of host name validation that
* can be used. There is code that parses a provided URL but doesn't ensure its
* correctness. The implementation relies on {@code getaddrinfo} function.
*
* @param hostName host name to check and convert.
* @return true if the string is a valid host name.
* @throws IllegalArgumentException if the the given string does not represent a valid
* hostname.
*/
private static String validateHostNameForPinningAndConvert(String hostName)
throws IllegalArgumentException {
if (INVALID_PKP_HOST_NAME.matcher(hostName).matches()) {
throw new IllegalArgumentException("Hostname " + hostName + " is illegal."
+ " A hostname should not consist of digits and/or dots only.");
}
// Workaround for crash, see crbug.com/634914
if (hostName.length() > 255) {
throw new IllegalArgumentException("Hostname " + hostName + " is too long."
+ " The name of the host does not comply with RFC 1122 and RFC 1123.");
}
try {
return IDN.toASCII(hostName, IDN.USE_STD3_ASCII_RULES);
} catch (IllegalArgumentException ex) {
throw new IllegalArgumentException("Hostname " + hostName + " is illegal."
+ " The name of the host does not comply with RFC 1122 and RFC 1123.");
}
}
@Override
public CronetEngineBuilderImpl setExperimentalOptions(String options) {
mExperimentalOptions = options;
return this;
}
public String experimentalOptions() {
return mExperimentalOptions;
}
/**
* Sets a native MockCertVerifier for testing. See
* {@code MockCertVerifier.createMockCertVerifier} for a method that
* can be used to create a MockCertVerifier.
* @param mockCertVerifier pointer to native MockCertVerifier.
* @return the builder to facilitate chaining.
*/
@VisibleForTesting
public CronetEngineBuilderImpl setMockCertVerifierForTesting(long mockCertVerifier) {
mMockCertVerifier = mockCertVerifier;
return this;
}
long mockCertVerifier() {
return mMockCertVerifier;
}
/**
* @return true if the network quality estimator has been enabled for
* this builder.
*/
boolean networkQualityEstimatorEnabled() {
return mNetworkQualityEstimatorEnabled;
}
@Override
public CronetEngineBuilderImpl enableNetworkQualityEstimator(boolean value) {
mNetworkQualityEstimatorEnabled = value;
return this;
}
@Override
public CronetEngineBuilderImpl setThreadPriority(int priority) {
if (priority > THREAD_PRIORITY_LOWEST || priority < -20) {
throw new IllegalArgumentException("Thread priority invalid");
}
mThreadPriority = priority;
return this;
}
/**
* @return thread priority provided by user, or {@code defaultThreadPriority} if none provided.
*/
int threadPriority(int defaultThreadPriority) {
return mThreadPriority == INVALID_THREAD_PRIORITY ? defaultThreadPriority : mThreadPriority;
}
/**
* Returns {@link Context} for builder.
*
* @return {@link Context} for builder.
*/
Context getContext() {
return mApplicationContext;
}
}

View File

@ -0,0 +1,16 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import org.chromium.net.CronetException;
/**
* Implements {@link CronetException}.
*/
public class CronetExceptionImpl extends CronetException {
public CronetExceptionImpl(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,192 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import android.content.Context;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.net.NetworkChangeNotifier;
/**
* CronetLibraryLoader loads and initializes native library on init thread.
*/
@JNINamespace("cronet")
@VisibleForTesting
public class CronetLibraryLoader {
// Synchronize initialization.
private static final Object sLoadLock = new Object();
private static final String LIBRARY_NAME = "cronet." + ImplVersion.getCronetVersion();
private static final String TAG = CronetLibraryLoader.class.getSimpleName();
// Thread used for initialization work and processing callbacks for
// long-lived global singletons. This thread lives forever as things like
// the global singleton NetworkChangeNotifier live on it and are never killed.
private static final HandlerThread sInitThread = new HandlerThread("CronetInit");
// Has library loading commenced? Setting guarded by sLoadLock.
private static volatile boolean sLibraryLoaded = IntegratedModeState.INTEGRATED_MODE_ENABLED;
// Has ensureInitThreadInitialized() completed?
private static volatile boolean sInitThreadInitDone;
// Block calling native methods until this ConditionVariable opens to indicate loadLibrary()
// is completed and native methods have been registered.
private static final ConditionVariable sWaitForLibLoad = new ConditionVariable();
/**
* Ensure that native library is loaded and initialized. Can be called from
* any thread, the load and initialization is performed on init thread.
*/
public static void ensureInitialized(
Context applicationContext, final CronetEngineBuilderImpl builder) {
synchronized (sLoadLock) {
if (!sInitThreadInitDone) {
if (!IntegratedModeState.INTEGRATED_MODE_ENABLED) {
// In integrated mode, application context should be initialized by the host.
ContextUtils.initApplicationContext(applicationContext);
}
if (!sInitThread.isAlive()) {
sInitThread.start();
}
postToInitThread(new Runnable() {
@Override
public void run() {
ensureInitializedOnInitThread();
}
});
}
if (!sLibraryLoaded) {
if (builder.libraryLoader() != null) {
builder.libraryLoader().loadLibrary(LIBRARY_NAME);
} else {
System.loadLibrary(LIBRARY_NAME);
}
String implVersion = ImplVersion.getCronetVersion();
if (!implVersion.equals(CronetLibraryLoaderJni.get().getCronetVersion())) {
throw new RuntimeException(String.format("Expected Cronet version number %s, "
+ "actual version number %s.",
implVersion, CronetLibraryLoaderJni.get().getCronetVersion()));
}
Log.i(TAG, "Cronet version: %s, arch: %s", implVersion,
System.getProperty("os.arch"));
sLibraryLoaded = true;
sWaitForLibLoad.open();
}
}
}
/**
* Returns {@code true} if running on the initialization thread.
*/
private static boolean onInitThread() {
return sInitThread.getLooper() == Looper.myLooper();
}
/**
* Ensure that the init thread initialization has completed. Can only be called from
* the init thread. Ensures that the NetworkChangeNotifier is initialzied and the
* init thread native MessageLoop is initialized.
*/
static void ensureInitializedOnInitThread() {
assert onInitThread();
if (sInitThreadInitDone) {
return;
}
if (IntegratedModeState.INTEGRATED_MODE_ENABLED) {
assert NetworkChangeNotifier.isInitialized();
} else {
NetworkChangeNotifier.init();
// Registers to always receive network notifications. Note
// that this call is fine for Cronet because Cronet
// embedders do not have API access to create network change
// observers. Existing observers in the net stack do not
// perform expensive work.
NetworkChangeNotifier.registerToReceiveNotificationsAlways();
// Wait for loadLibrary() to complete so JNI is registered.
sWaitForLibLoad.block();
}
assert sLibraryLoaded;
// registerToReceiveNotificationsAlways() is called before the native
// NetworkChangeNotifierAndroid is created, so as to avoid receiving
// the undesired initial network change observer notification, which
// will cause active requests to fail with ERR_NETWORK_CHANGED.
CronetLibraryLoaderJni.get().cronetInitOnInitThread();
sInitThreadInitDone = true;
}
/**
* Run {@code r} on the initialization thread.
*/
public static void postToInitThread(Runnable r) {
if (onInitThread()) {
r.run();
} else {
new Handler(sInitThread.getLooper()).post(r);
}
}
/**
* Called from native library to get default user agent constructed
* using application context. May be called on any thread.
*
* Expects that ContextUtils.initApplicationContext() was called already
* either by some testing framework or an embedder constructing a Java
* CronetEngine via CronetEngine.Builder.build().
*/
@CalledByNative
private static String getDefaultUserAgent() {
return UserAgent.from(ContextUtils.getApplicationContext());
}
/**
* Called from native library to ensure that library is initialized.
* May be called on any thread, but initialization is performed on
* this.sInitThread.
*
* Expects that ContextUtils.initApplicationContext() was called already
* either by some testing framework or an embedder constructing a Java
* CronetEngine via CronetEngine.Builder.build().
*
* TODO(mef): In the long term this should be changed to some API with
* lower overhead like CronetEngine.Builder.loadNativeCronet().
*/
@CalledByNative
private static void ensureInitializedFromNative() {
// Called by native, so native library is already loaded.
// It is possible that loaded native library is not regular
// "libcronet.xyz.so" but test library that statically links
// native code like "libcronet_unittests.so".
synchronized (sLoadLock) {
sLibraryLoaded = true;
sWaitForLibLoad.open();
}
// The application context must already be initialized
// using ContextUtils.initApplicationContext().
Context applicationContext = ContextUtils.getApplicationContext();
assert applicationContext != null;
ensureInitialized(applicationContext, null);
}
@CalledByNative
private static void setNetworkThreadPriorityOnNetworkThread(int priority) {
Process.setThreadPriority(priority);
}
@NativeMethods
interface Natives {
// Native methods are implemented in cronet_library_loader.cc.
void cronetInitOnInitThread();
String getCronetVersion();
}
}

View File

@ -0,0 +1,244 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.net.RequestFinishedInfo;
import java.util.Date;
/**
* Implementation of {@link RequestFinishedInfo.Metrics}.
*/
@VisibleForTesting
public final class CronetMetrics extends RequestFinishedInfo.Metrics {
private final long mRequestStartMs;
private final long mDnsStartMs;
private final long mDnsEndMs;
private final long mConnectStartMs;
private final long mConnectEndMs;
private final long mSslStartMs;
private final long mSslEndMs;
private final long mSendingStartMs;
private final long mSendingEndMs;
private final long mPushStartMs;
private final long mPushEndMs;
private final long mResponseStartMs;
private final long mRequestEndMs;
private final boolean mSocketReused;
// TODO(mgersh): Delete after the switch to the new API http://crbug.com/629194
@Nullable
private final Long mTtfbMs;
// TODO(mgersh): Delete after the switch to the new API http://crbug.com/629194
@Nullable
private final Long mTotalTimeMs;
@Nullable
private final Long mSentByteCount;
@Nullable
private final Long mReceivedByteCount;
@Nullable
private static Date toDate(long timestamp) {
if (timestamp != -1) {
return new Date(timestamp);
}
return null;
}
private static boolean checkOrder(long start, long end) {
// If end doesn't exist, start can be anything, including also not existing
// If end exists, start must also exist and be before end
return (end >= start && start != -1) || end == -1;
}
/**
* Old-style constructor
* TODO(mgersh): Delete after the switch to the new API http://crbug.com/629194
*/
public CronetMetrics(@Nullable Long ttfbMs, @Nullable Long totalTimeMs,
@Nullable Long sentByteCount, @Nullable Long receivedByteCount) {
mTtfbMs = ttfbMs;
mTotalTimeMs = totalTimeMs;
mSentByteCount = sentByteCount;
mReceivedByteCount = receivedByteCount;
// Everything else is -1 (translates to null) for now
mRequestStartMs = -1;
mDnsStartMs = -1;
mDnsEndMs = -1;
mConnectStartMs = -1;
mConnectEndMs = -1;
mSslStartMs = -1;
mSslEndMs = -1;
mSendingStartMs = -1;
mSendingEndMs = -1;
mPushStartMs = -1;
mPushEndMs = -1;
mResponseStartMs = -1;
mRequestEndMs = -1;
mSocketReused = false;
}
/**
* New-style constructor
*/
public CronetMetrics(long requestStartMs, long dnsStartMs, long dnsEndMs, long connectStartMs,
long connectEndMs, long sslStartMs, long sslEndMs, long sendingStartMs,
long sendingEndMs, long pushStartMs, long pushEndMs, long responseStartMs,
long requestEndMs, boolean socketReused, long sentByteCount, long receivedByteCount) {
// Check that no end times are before corresponding start times,
// or exist when start time doesn't.
assert checkOrder(dnsStartMs, dnsEndMs);
assert checkOrder(connectStartMs, connectEndMs);
assert checkOrder(sslStartMs, sslEndMs);
assert checkOrder(sendingStartMs, sendingEndMs);
assert checkOrder(pushStartMs, pushEndMs);
// requestEnd always exists, so just check that it's after start
assert requestEndMs >= responseStartMs;
// Spot-check some of the other orderings
assert dnsStartMs >= requestStartMs || dnsStartMs == -1;
assert sendingStartMs >= requestStartMs || sendingStartMs == -1;
assert sslStartMs >= connectStartMs || sslStartMs == -1;
assert responseStartMs >= sendingStartMs || responseStartMs == -1;
mRequestStartMs = requestStartMs;
mDnsStartMs = dnsStartMs;
mDnsEndMs = dnsEndMs;
mConnectStartMs = connectStartMs;
mConnectEndMs = connectEndMs;
mSslStartMs = sslStartMs;
mSslEndMs = sslEndMs;
mSendingStartMs = sendingStartMs;
mSendingEndMs = sendingEndMs;
mPushStartMs = pushStartMs;
mPushEndMs = pushEndMs;
mResponseStartMs = responseStartMs;
mRequestEndMs = requestEndMs;
mSocketReused = socketReused;
mSentByteCount = sentByteCount;
mReceivedByteCount = receivedByteCount;
// TODO(mgersh): delete these after embedders stop using them http://crbug.com/629194
if (requestStartMs != -1 && responseStartMs != -1) {
mTtfbMs = responseStartMs - requestStartMs;
} else {
mTtfbMs = null;
}
if (requestStartMs != -1 && requestEndMs != -1) {
mTotalTimeMs = requestEndMs - requestStartMs;
} else {
mTotalTimeMs = null;
}
}
@Nullable
@Override
public Date getRequestStart() {
return toDate(mRequestStartMs);
}
@Nullable
@Override
public Date getDnsStart() {
return toDate(mDnsStartMs);
}
@Nullable
@Override
public Date getDnsEnd() {
return toDate(mDnsEndMs);
}
@Nullable
@Override
public Date getConnectStart() {
return toDate(mConnectStartMs);
}
@Nullable
@Override
public Date getConnectEnd() {
return toDate(mConnectEndMs);
}
@Nullable
@Override
public Date getSslStart() {
return toDate(mSslStartMs);
}
@Nullable
@Override
public Date getSslEnd() {
return toDate(mSslEndMs);
}
@Nullable
@Override
public Date getSendingStart() {
return toDate(mSendingStartMs);
}
@Nullable
@Override
public Date getSendingEnd() {
return toDate(mSendingEndMs);
}
@Nullable
@Override
public Date getPushStart() {
return toDate(mPushStartMs);
}
@Nullable
@Override
public Date getPushEnd() {
return toDate(mPushEndMs);
}
@Nullable
@Override
public Date getResponseStart() {
return toDate(mResponseStartMs);
}
@Nullable
@Override
public Date getRequestEnd() {
return toDate(mRequestEndMs);
}
@Override
public boolean getSocketReused() {
return mSocketReused;
}
@Nullable
@Override
public Long getTtfbMs() {
return mTtfbMs;
}
@Nullable
@Override
public Long getTotalTimeMs() {
return mTotalTimeMs;
}
@Nullable
@Override
public Long getSentByteCount() {
return mSentByteCount;
}
@Nullable
@Override
public Long getReceivedByteCount() {
return mReceivedByteCount;
}
}

View File

@ -0,0 +1,419 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import android.annotation.SuppressLint;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeClassQualifiedName;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.net.UploadDataProvider;
import org.chromium.net.UploadDataSink;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.GuardedBy;
/**
* CronetUploadDataStream handles communication between an upload body
* encapsulated in the embedder's {@link UploadDataSink} and a C++
* UploadDataStreamAdapter, which it owns. It's attached to a {@link
* CronetUrlRequest}'s during the construction of request's native C++ objects
* on the network thread, though it's created on one of the embedder's threads.
* It is called by the UploadDataStreamAdapter on the network thread, but calls
* into the UploadDataSink and the UploadDataStreamAdapter on the Executor
* passed into its constructor.
*/
@JNINamespace("cronet")
@VisibleForTesting
public final class CronetUploadDataStream extends UploadDataSink {
private static final String TAG = CronetUploadDataStream.class.getSimpleName();
// These are never changed, once a request starts.
private final Executor mExecutor;
private final VersionSafeCallbacks.UploadDataProviderWrapper mDataProvider;
private final CronetUrlRequest mRequest;
private long mLength;
private long mRemainingLength;
private long mByteBufferLimit;
// Reusable read task, to reduce redundant memory allocation.
private final Runnable mReadTask = new Runnable() {
@Override
public void run() {
synchronized (mLock) {
if (mUploadDataStreamAdapter == 0) {
return;
}
checkState(UserCallback.NOT_IN_CALLBACK);
if (mByteBuffer == null) {
throw new IllegalStateException("Unexpected readData call. Buffer is null");
}
mInWhichUserCallback = UserCallback.READ;
}
try {
checkCallingThread();
assert mByteBuffer.position() == 0;
mDataProvider.read(CronetUploadDataStream.this, mByteBuffer);
} catch (Exception exception) {
onError(exception);
}
}
};
// ByteBuffer created in the native code and passed to
// UploadDataProvider for reading. It is only valid from the
// call to mDataProvider.read until onError or onReadSucceeded.
private ByteBuffer mByteBuffer;
// Lock that protects all subsequent variables. The adapter has to be
// protected to ensure safe shutdown, mReading and mRewinding are protected
// to robustly detect getting read/rewind results more often than expected.
private final Object mLock = new Object();
// Native adapter object, owned by the CronetUploadDataStream. It's only
// deleted after the native UploadDataStream object is destroyed. All access
// to the adapter is synchronized, for safe usage and cleanup.
@GuardedBy("mLock")
private long mUploadDataStreamAdapter;
@IntDef({UserCallback.READ, UserCallback.REWIND, UserCallback.GET_LENGTH,
UserCallback.NOT_IN_CALLBACK})
@Retention(RetentionPolicy.SOURCE)
private @interface UserCallback {
int READ = 0;
int REWIND = 1;
int GET_LENGTH = 2;
int NOT_IN_CALLBACK = 3;
}
@GuardedBy("mLock")
private @UserCallback int mInWhichUserCallback = UserCallback.NOT_IN_CALLBACK;
@GuardedBy("mLock")
private boolean mDestroyAdapterPostponed;
private Runnable mOnDestroyedCallbackForTesting;
/**
* Constructs a CronetUploadDataStream.
* @param dataProvider the UploadDataProvider to read data from.
* @param executor the Executor to execute UploadDataProvider tasks.
*/
public CronetUploadDataStream(
UploadDataProvider dataProvider, Executor executor, CronetUrlRequest request) {
mExecutor = executor;
mDataProvider = new VersionSafeCallbacks.UploadDataProviderWrapper(dataProvider);
mRequest = request;
}
/**
* Called by native code to make the UploadDataProvider read data into
* {@code byteBuffer}.
*/
@SuppressWarnings("unused")
@CalledByNative
void readData(ByteBuffer byteBuffer) {
mByteBuffer = byteBuffer;
mByteBufferLimit = byteBuffer.limit();
postTaskToExecutor(mReadTask);
}
// TODO(mmenke): Consider implementing a cancel method.
// currently wait for any pending read to complete.
/**
* Called by native code to make the UploadDataProvider rewind upload data.
*/
@SuppressWarnings("unused")
@CalledByNative
void rewind() {
Runnable task = new Runnable() {
@Override
public void run() {
synchronized (mLock) {
if (mUploadDataStreamAdapter == 0) {
return;
}
checkState(UserCallback.NOT_IN_CALLBACK);
mInWhichUserCallback = UserCallback.REWIND;
}
try {
checkCallingThread();
mDataProvider.rewind(CronetUploadDataStream.this);
} catch (Exception exception) {
onError(exception);
}
}
};
postTaskToExecutor(task);
}
private void checkCallingThread() {
mRequest.checkCallingThread();
}
@GuardedBy("mLock")
private void checkState(@UserCallback int mode) {
if (mInWhichUserCallback != mode) {
throw new IllegalStateException(
"Expected " + mode + ", but was " + mInWhichUserCallback);
}
}
/**
* Called when the native UploadDataStream is destroyed. At this point,
* the native adapter needs to be destroyed, but only after any pending
* read operation completes, as the adapter owns the read buffer.
*/
@SuppressWarnings("unused")
@CalledByNative
void onUploadDataStreamDestroyed() {
destroyAdapter();
}
/**
* Helper method called when an exception occurred. This method resets
* states and propagates the error to the request.
*/
private void onError(Throwable exception) {
final boolean sendClose;
synchronized (mLock) {
if (mInWhichUserCallback == UserCallback.NOT_IN_CALLBACK) {
throw new IllegalStateException(
"There is no read or rewind or length check in progress.");
}
sendClose = mInWhichUserCallback == UserCallback.GET_LENGTH;
mInWhichUserCallback = UserCallback.NOT_IN_CALLBACK;
mByteBuffer = null;
destroyAdapterIfPostponed();
}
// Failure before length is obtained means that the request has failed before the
// adapter has been initialized. Close the UploadDataProvider. This is safe to call
// here since failure during getLength can only happen on the user's executor.
if (sendClose) {
try {
mDataProvider.close();
} catch (Exception e) {
Log.e(TAG, "Failure closing data provider", e);
}
}
// Just fail the request - simpler to fail directly, and
// UploadDataStream only supports failing during initialization, not
// while reading. The request is smart enough to handle the case where
// it was already canceled by the embedder.
mRequest.onUploadException(exception);
}
@Override
@SuppressLint("DefaultLocale")
public void onReadSucceeded(boolean lastChunk) {
synchronized (mLock) {
checkState(UserCallback.READ);
if (mByteBufferLimit != mByteBuffer.limit()) {
throw new IllegalStateException("ByteBuffer limit changed");
}
if (lastChunk && mLength >= 0) {
throw new IllegalArgumentException("Non-chunked upload can't have last chunk");
}
int bytesRead = mByteBuffer.position();
mRemainingLength -= bytesRead;
if (mRemainingLength < 0 && mLength >= 0) {
throw new IllegalArgumentException(
String.format("Read upload data length %d exceeds expected length %d",
mLength - mRemainingLength, mLength));
}
mByteBuffer.position(0);
mByteBuffer = null;
mInWhichUserCallback = UserCallback.NOT_IN_CALLBACK;
destroyAdapterIfPostponed();
// Request may been canceled already.
if (mUploadDataStreamAdapter == 0) {
return;
}
CronetUploadDataStreamJni.get().onReadSucceeded(
mUploadDataStreamAdapter, CronetUploadDataStream.this, bytesRead, lastChunk);
}
}
@Override
public void onReadError(Exception exception) {
synchronized (mLock) {
checkState(UserCallback.READ);
onError(exception);
}
}
@Override
public void onRewindSucceeded() {
synchronized (mLock) {
checkState(UserCallback.REWIND);
mInWhichUserCallback = UserCallback.NOT_IN_CALLBACK;
mRemainingLength = mLength;
// Request may been canceled already.
if (mUploadDataStreamAdapter == 0) {
return;
}
CronetUploadDataStreamJni.get().onRewindSucceeded(
mUploadDataStreamAdapter, CronetUploadDataStream.this);
}
}
@Override
public void onRewindError(Exception exception) {
synchronized (mLock) {
checkState(UserCallback.REWIND);
onError(exception);
}
}
/**
* Posts task to application Executor.
*/
void postTaskToExecutor(Runnable task) {
try {
mExecutor.execute(task);
} catch (Throwable e) {
// Just fail the request. The request is smart enough to handle the
// case where it was already canceled by the embedder.
mRequest.onUploadException(e);
}
}
/**
* The adapter is owned by the CronetUploadDataStream, so it can be
* destroyed safely when there is no pending read; however, destruction is
* initiated by the destruction of the native UploadDataStream.
*/
private void destroyAdapter() {
synchronized (mLock) {
if (mInWhichUserCallback == UserCallback.READ) {
// Wait for the read to complete before destroy the adapter.
mDestroyAdapterPostponed = true;
return;
}
if (mUploadDataStreamAdapter == 0) {
return;
}
CronetUploadDataStreamJni.get().destroy(mUploadDataStreamAdapter);
mUploadDataStreamAdapter = 0;
if (mOnDestroyedCallbackForTesting != null) {
mOnDestroyedCallbackForTesting.run();
}
}
postTaskToExecutor(new Runnable() {
@Override
public void run() {
try {
checkCallingThread();
mDataProvider.close();
} catch (Exception e) {
Log.e(TAG, "Exception thrown when closing", e);
}
}
});
}
/**
* Destroys the native adapter if the destruction is postponed due to a
* pending read, which has since completed. Caller needs to be on executor
* thread.
*/
private void destroyAdapterIfPostponed() {
synchronized (mLock) {
if (mInWhichUserCallback == UserCallback.READ) {
throw new IllegalStateException(
"Method should not be called when read has not completed.");
}
if (mDestroyAdapterPostponed) {
destroyAdapter();
}
}
}
/**
* Initializes upload length by getting it from data provider. Submits to
* the user's executor thread to allow getLength() to block and/or report errors.
* If data provider throws an exception, then it is reported to the request.
* No native calls to urlRequest are allowed as this is done before request
* start, so native object may not exist.
*/
void initializeWithRequest() {
synchronized (mLock) {
mInWhichUserCallback = UserCallback.GET_LENGTH;
}
try {
mRequest.checkCallingThread();
mLength = mDataProvider.getLength();
mRemainingLength = mLength;
} catch (Throwable t) {
onError(t);
}
synchronized (mLock) {
mInWhichUserCallback = UserCallback.NOT_IN_CALLBACK;
}
}
/**
* Creates native objects and attaches them to the underlying request
* adapter object. Always called on executor thread.
*/
void attachNativeAdapterToRequest(final long requestAdapter) {
synchronized (mLock) {
mUploadDataStreamAdapter = CronetUploadDataStreamJni.get().attachUploadDataToRequest(
CronetUploadDataStream.this, requestAdapter, mLength);
}
}
/**
* Creates a native CronetUploadDataStreamAdapter and
* CronetUploadDataStream for testing.
* @return the address of the native CronetUploadDataStream object.
*/
@VisibleForTesting
public long createUploadDataStreamForTesting() throws IOException {
synchronized (mLock) {
mUploadDataStreamAdapter = CronetUploadDataStreamJni.get().createAdapterForTesting(
CronetUploadDataStream.this);
mLength = mDataProvider.getLength();
mRemainingLength = mLength;
return CronetUploadDataStreamJni.get().createUploadDataStreamForTesting(
CronetUploadDataStream.this, mLength, mUploadDataStreamAdapter);
}
}
@VisibleForTesting
void setOnDestroyedCallbackForTesting(Runnable onDestroyedCallbackForTesting) {
mOnDestroyedCallbackForTesting = onDestroyedCallbackForTesting;
}
// Native methods are implemented in upload_data_stream_adapter.cc.
@NativeMethods
interface Natives {
long attachUploadDataToRequest(
CronetUploadDataStream caller, long urlRequestAdapter, long length);
long createAdapterForTesting(CronetUploadDataStream caller);
long createUploadDataStreamForTesting(
CronetUploadDataStream caller, long length, long adapter);
@NativeClassQualifiedName("CronetUploadDataStreamAdapter")
void onReadSucceeded(
long nativePtr, CronetUploadDataStream caller, int bytesRead, boolean finalChunk);
@NativeClassQualifiedName("CronetUploadDataStreamAdapter")
void onRewindSucceeded(long nativePtr, CronetUploadDataStream caller);
@NativeClassQualifiedName("CronetUploadDataStreamAdapter")
void destroy(long nativePtr);
}
}

View File

@ -0,0 +1,878 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNIAdditionalImport;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeClassQualifiedName;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.net.CallbackException;
import org.chromium.net.CronetException;
import org.chromium.net.Idempotency;
import org.chromium.net.InlineExecutionProhibitedException;
import org.chromium.net.NetworkException;
import org.chromium.net.RequestFinishedInfo;
import org.chromium.net.RequestPriority;
import org.chromium.net.UploadDataProvider;
import org.chromium.net.UrlRequest;
import java.nio.ByteBuffer;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import javax.annotation.concurrent.GuardedBy;
/**
* UrlRequest using Chromium HTTP stack implementation. Could be accessed from
* any thread on Executor. Cancel can be called from any thread.
* All @CallByNative methods are called on native network thread
* and post tasks with listener calls onto Executor. Upon return from listener
* callback native request adapter is called on executive thread and posts
* native tasks to native network thread. Because Cancel could be called from
* any thread it is protected by mUrlRequestAdapterLock.
*/
@JNINamespace("cronet")
// Qualifies VersionSafeCallbacks.UrlRequestStatusListener which is used in onStatus, a JNI method.
@JNIAdditionalImport(VersionSafeCallbacks.class)
@VisibleForTesting
public final class CronetUrlRequest extends UrlRequestBase {
private final boolean mAllowDirectExecutor;
/* Native adapter object, owned by UrlRequest. */
@GuardedBy("mUrlRequestAdapterLock")
private long mUrlRequestAdapter;
@GuardedBy("mUrlRequestAdapterLock")
private boolean mStarted;
@GuardedBy("mUrlRequestAdapterLock")
private boolean mWaitingOnRedirect;
@GuardedBy("mUrlRequestAdapterLock")
private boolean mWaitingOnRead;
/*
* Synchronize access to mUrlRequestAdapter, mStarted, mWaitingOnRedirect,
* and mWaitingOnRead.
*/
private final Object mUrlRequestAdapterLock = new Object();
private final CronetUrlRequestContext mRequestContext;
private final Executor mExecutor;
/*
* URL chain contains the URL currently being requested, and
* all URLs previously requested. New URLs are added before
* mCallback.onRedirectReceived is called.
*/
private final List<String> mUrlChain = new ArrayList<String>();
private final VersionSafeCallbacks.UrlRequestCallback mCallback;
private final String mInitialUrl;
private final int mPriority;
private final int mIdempotency;
private String mInitialMethod;
private final HeadersList mRequestHeaders = new HeadersList();
private final Collection<Object> mRequestAnnotations;
private final boolean mDisableCache;
private final boolean mDisableConnectionMigration;
private final boolean mTrafficStatsTagSet;
private final int mTrafficStatsTag;
private final boolean mTrafficStatsUidSet;
private final int mTrafficStatsUid;
private final VersionSafeCallbacks.RequestFinishedInfoListener mRequestFinishedListener;
private CronetUploadDataStream mUploadDataStream;
private UrlResponseInfoImpl mResponseInfo;
// These three should only be updated once with mUrlRequestAdapterLock held. They are read on
// UrlRequest.Callback's and RequestFinishedInfo.Listener's executors after the last update.
@RequestFinishedInfoImpl.FinishedReason
private int mFinishedReason;
private CronetException mException;
private CronetMetrics mMetrics;
/*
* Listener callback is repeatedly invoked when each read is completed, so it
* is cached as a member variable.
*/
private OnReadCompletedRunnable mOnReadCompletedTask;
@GuardedBy("mUrlRequestAdapterLock")
private Runnable mOnDestroyedCallbackForTesting;
private static final class HeadersList extends ArrayList<Map.Entry<String, String>> {}
private final class OnReadCompletedRunnable implements Runnable {
// Buffer passed back from current invocation of onReadCompleted.
ByteBuffer mByteBuffer;
@Override
public void run() {
checkCallingThread();
// Null out mByteBuffer, to pass buffer ownership to callback or release if done.
ByteBuffer buffer = mByteBuffer;
mByteBuffer = null;
try {
synchronized (mUrlRequestAdapterLock) {
if (isDoneLocked()) {
return;
}
mWaitingOnRead = true;
}
mCallback.onReadCompleted(CronetUrlRequest.this, mResponseInfo, buffer);
} catch (Exception e) {
onCallbackException(e);
}
}
}
CronetUrlRequest(CronetUrlRequestContext requestContext, String url, int priority,
UrlRequest.Callback callback, Executor executor, Collection<Object> requestAnnotations,
boolean disableCache, boolean disableConnectionMigration, boolean allowDirectExecutor,
boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet,
int trafficStatsUid, RequestFinishedInfo.Listener requestFinishedListener,
int idempotency) {
if (url == null) {
throw new NullPointerException("URL is required");
}
if (callback == null) {
throw new NullPointerException("Listener is required");
}
if (executor == null) {
throw new NullPointerException("Executor is required");
}
mAllowDirectExecutor = allowDirectExecutor;
mRequestContext = requestContext;
mInitialUrl = url;
mUrlChain.add(url);
mPriority = convertRequestPriority(priority);
mCallback = new VersionSafeCallbacks.UrlRequestCallback(callback);
mExecutor = executor;
mRequestAnnotations = requestAnnotations;
mDisableCache = disableCache;
mDisableConnectionMigration = disableConnectionMigration;
mTrafficStatsTagSet = trafficStatsTagSet;
mTrafficStatsTag = trafficStatsTag;
mTrafficStatsUidSet = trafficStatsUidSet;
mTrafficStatsUid = trafficStatsUid;
mRequestFinishedListener = requestFinishedListener != null
? new VersionSafeCallbacks.RequestFinishedInfoListener(requestFinishedListener)
: null;
mIdempotency = convertIdempotency(idempotency);
}
@Override
public void setHttpMethod(String method) {
checkNotStarted();
if (method == null) {
throw new NullPointerException("Method is required.");
}
mInitialMethod = method;
}
@Override
public void addHeader(String header, String value) {
checkNotStarted();
if (header == null) {
throw new NullPointerException("Invalid header name.");
}
if (value == null) {
throw new NullPointerException("Invalid header value.");
}
mRequestHeaders.add(new AbstractMap.SimpleImmutableEntry<String, String>(header, value));
}
@Override
public void setUploadDataProvider(UploadDataProvider uploadDataProvider, Executor executor) {
if (uploadDataProvider == null) {
throw new NullPointerException("Invalid UploadDataProvider.");
}
if (mInitialMethod == null) {
mInitialMethod = "POST";
}
mUploadDataStream = new CronetUploadDataStream(uploadDataProvider, executor, this);
}
@Override
public void start() {
synchronized (mUrlRequestAdapterLock) {
checkNotStarted();
try {
mUrlRequestAdapter = CronetUrlRequestJni.get().createRequestAdapter(
CronetUrlRequest.this, mRequestContext.getUrlRequestContextAdapter(),
mInitialUrl, mPriority, mDisableCache, mDisableConnectionMigration,
mRequestContext.hasRequestFinishedListener()
|| mRequestFinishedListener != null,
mTrafficStatsTagSet, mTrafficStatsTag, mTrafficStatsUidSet,
mTrafficStatsUid, mIdempotency);
mRequestContext.onRequestStarted();
if (mInitialMethod != null) {
if (!CronetUrlRequestJni.get().setHttpMethod(
mUrlRequestAdapter, CronetUrlRequest.this, mInitialMethod)) {
throw new IllegalArgumentException("Invalid http method " + mInitialMethod);
}
}
boolean hasContentType = false;
for (Map.Entry<String, String> header : mRequestHeaders) {
if (header.getKey().equalsIgnoreCase("Content-Type")
&& !header.getValue().isEmpty()) {
hasContentType = true;
}
if (!CronetUrlRequestJni.get().addRequestHeader(mUrlRequestAdapter,
CronetUrlRequest.this, header.getKey(), header.getValue())) {
throw new IllegalArgumentException(
"Invalid header " + header.getKey() + "=" + header.getValue());
}
}
if (mUploadDataStream != null) {
if (!hasContentType) {
throw new IllegalArgumentException(
"Requests with upload data must have a Content-Type.");
}
mStarted = true;
mUploadDataStream.postTaskToExecutor(new Runnable() {
@Override
public void run() {
mUploadDataStream.initializeWithRequest();
synchronized (mUrlRequestAdapterLock) {
if (isDoneLocked()) {
return;
}
mUploadDataStream.attachNativeAdapterToRequest(mUrlRequestAdapter);
startInternalLocked();
}
}
});
return;
}
} catch (RuntimeException e) {
// If there's an exception, cleanup and then throw the exception to the caller.
// start() is synchronized so we do not acquire mUrlRequestAdapterLock here.
destroyRequestAdapterLocked(RequestFinishedInfo.FAILED);
throw e;
}
mStarted = true;
startInternalLocked();
}
}
/*
* Starts fully configured request. Could execute on UploadDataProvider executor.
* Caller is expected to ensure that request isn't canceled and mUrlRequestAdapter is valid.
*/
@GuardedBy("mUrlRequestAdapterLock")
private void startInternalLocked() {
CronetUrlRequestJni.get().start(mUrlRequestAdapter, CronetUrlRequest.this);
}
@Override
public void followRedirect() {
synchronized (mUrlRequestAdapterLock) {
if (!mWaitingOnRedirect) {
throw new IllegalStateException("No redirect to follow.");
}
mWaitingOnRedirect = false;
if (isDoneLocked()) {
return;
}
CronetUrlRequestJni.get().followDeferredRedirect(
mUrlRequestAdapter, CronetUrlRequest.this);
}
}
@Override
public void read(ByteBuffer buffer) {
Preconditions.checkHasRemaining(buffer);
Preconditions.checkDirect(buffer);
synchronized (mUrlRequestAdapterLock) {
if (!mWaitingOnRead) {
throw new IllegalStateException("Unexpected read attempt.");
}
mWaitingOnRead = false;
if (isDoneLocked()) {
return;
}
if (!CronetUrlRequestJni.get().readData(mUrlRequestAdapter, CronetUrlRequest.this,
buffer, buffer.position(), buffer.limit())) {
// Still waiting on read. This is just to have consistent
// behavior with the other error cases.
mWaitingOnRead = true;
throw new IllegalArgumentException("Unable to call native read");
}
}
}
@Override
public void cancel() {
synchronized (mUrlRequestAdapterLock) {
if (isDoneLocked() || !mStarted) {
return;
}
destroyRequestAdapterLocked(RequestFinishedInfo.CANCELED);
}
}
@Override
public boolean isDone() {
synchronized (mUrlRequestAdapterLock) {
return isDoneLocked();
}
}
@GuardedBy("mUrlRequestAdapterLock")
private boolean isDoneLocked() {
return mStarted && mUrlRequestAdapter == 0;
}
@Override
public void getStatus(UrlRequest.StatusListener unsafeListener) {
final VersionSafeCallbacks.UrlRequestStatusListener listener =
new VersionSafeCallbacks.UrlRequestStatusListener(unsafeListener);
synchronized (mUrlRequestAdapterLock) {
if (mUrlRequestAdapter != 0) {
CronetUrlRequestJni.get().getStatus(
mUrlRequestAdapter, CronetUrlRequest.this, listener);
return;
}
}
Runnable task = new Runnable() {
@Override
public void run() {
listener.onStatus(UrlRequest.Status.INVALID);
}
};
postTaskToExecutor(task);
}
@VisibleForTesting
public void setOnDestroyedCallbackForTesting(Runnable onDestroyedCallbackForTesting) {
synchronized (mUrlRequestAdapterLock) {
mOnDestroyedCallbackForTesting = onDestroyedCallbackForTesting;
}
}
@VisibleForTesting
public void setOnDestroyedUploadCallbackForTesting(
Runnable onDestroyedUploadCallbackForTesting) {
mUploadDataStream.setOnDestroyedCallbackForTesting(onDestroyedUploadCallbackForTesting);
}
@VisibleForTesting
public long getUrlRequestAdapterForTesting() {
synchronized (mUrlRequestAdapterLock) {
return mUrlRequestAdapter;
}
}
/**
* Posts task to application Executor. Used for Listener callbacks
* and other tasks that should not be executed on network thread.
*/
private void postTaskToExecutor(Runnable task) {
try {
mExecutor.execute(task);
} catch (RejectedExecutionException failException) {
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception posting task to executor",
failException);
// If posting a task throws an exception, then we fail the request. This exception could
// be permanent (executor shutdown), transient (AbortPolicy, or CallerRunsPolicy with
// direct execution not permitted), or caused by the runnables we submit if
// mUserExecutor is a direct executor and direct execution is not permitted. In the
// latter two cases, there is at least have a chance to inform the embedder of the
// request's failure, since failWithException does not enforce that onFailed() is not
// executed inline.
failWithException(
new CronetExceptionImpl("Exception posting task to executor", failException));
}
}
private static int convertRequestPriority(int priority) {
switch (priority) {
case Builder.REQUEST_PRIORITY_IDLE:
return RequestPriority.IDLE;
case Builder.REQUEST_PRIORITY_LOWEST:
return RequestPriority.LOWEST;
case Builder.REQUEST_PRIORITY_LOW:
return RequestPriority.LOW;
case Builder.REQUEST_PRIORITY_MEDIUM:
return RequestPriority.MEDIUM;
case Builder.REQUEST_PRIORITY_HIGHEST:
return RequestPriority.HIGHEST;
default:
return RequestPriority.MEDIUM;
}
}
private static int convertIdempotency(int idempotency) {
switch (idempotency) {
case Builder.DEFAULT_IDEMPOTENCY:
return Idempotency.DEFAULT_IDEMPOTENCY;
case Builder.IDEMPOTENT:
return Idempotency.IDEMPOTENT;
case Builder.NOT_IDEMPOTENT:
return Idempotency.NOT_IDEMPOTENT;
default:
return Idempotency.DEFAULT_IDEMPOTENCY;
}
}
private UrlResponseInfoImpl prepareResponseInfoOnNetworkThread(int httpStatusCode,
String httpStatusText, String[] headers, boolean wasCached, String negotiatedProtocol,
String proxyServer, long receivedByteCount) {
HeadersList headersList = new HeadersList();
for (int i = 0; i < headers.length; i += 2) {
headersList.add(new AbstractMap.SimpleImmutableEntry<String, String>(
headers[i], headers[i + 1]));
}
return new UrlResponseInfoImpl(new ArrayList<String>(mUrlChain), httpStatusCode,
httpStatusText, headersList, wasCached, negotiatedProtocol, proxyServer,
receivedByteCount);
}
private void checkNotStarted() {
synchronized (mUrlRequestAdapterLock) {
if (mStarted || isDoneLocked()) {
throw new IllegalStateException("Request is already started.");
}
}
}
/**
* Helper method to set final status of CronetUrlRequest and clean up the
* native request adapter.
*/
@GuardedBy("mUrlRequestAdapterLock")
private void destroyRequestAdapterLocked(
@RequestFinishedInfoImpl.FinishedReason int finishedReason) {
assert mException == null || finishedReason == RequestFinishedInfo.FAILED;
mFinishedReason = finishedReason;
if (mUrlRequestAdapter == 0) {
return;
}
mRequestContext.onRequestDestroyed();
// Posts a task to destroy the native adapter.
CronetUrlRequestJni.get().destroy(mUrlRequestAdapter, CronetUrlRequest.this,
finishedReason == RequestFinishedInfo.CANCELED);
mUrlRequestAdapter = 0;
}
/**
* If callback method throws an exception, request gets canceled
* and exception is reported via onFailed listener callback.
* Only called on the Executor.
*/
private void onCallbackException(Exception e) {
CallbackException requestError =
new CallbackExceptionImpl("Exception received from UrlRequest.Callback", e);
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in CalledByNative method", e);
failWithException(requestError);
}
/**
* Called when UploadDataProvider encounters an error.
*/
void onUploadException(Throwable e) {
CallbackException uploadError =
new CallbackExceptionImpl("Exception received from UploadDataProvider", e);
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in upload method", e);
failWithException(uploadError);
}
/**
* Fails the request with an exception on any thread.
*/
private void failWithException(final CronetException exception) {
synchronized (mUrlRequestAdapterLock) {
if (isDoneLocked()) {
return;
}
assert mException == null;
mException = exception;
destroyRequestAdapterLocked(RequestFinishedInfo.FAILED);
}
// onFailed will be invoked from onNativeAdapterDestroyed() to ensure metrics collection.
}
////////////////////////////////////////////////
// Private methods called by the native code.
// Always called on network thread.
////////////////////////////////////////////////
/**
* Called before following redirects. The redirect will only be followed if
* {@link #followRedirect()} is called. If the redirect response has a body, it will be ignored.
* This will only be called between start and onResponseStarted.
*
* @param newLocation Location where request is redirected.
* @param httpStatusCode from redirect response
* @param receivedByteCount count of bytes received for redirect response
* @param headers an array of response headers with keys at the even indices
* followed by the corresponding values at the odd indices.
*/
@SuppressWarnings("unused")
@CalledByNative
private void onRedirectReceived(final String newLocation, int httpStatusCode,
String httpStatusText, String[] headers, boolean wasCached, String negotiatedProtocol,
String proxyServer, long receivedByteCount) {
final UrlResponseInfoImpl responseInfo =
prepareResponseInfoOnNetworkThread(httpStatusCode, httpStatusText, headers,
wasCached, negotiatedProtocol, proxyServer, receivedByteCount);
// Have to do this after creating responseInfo.
mUrlChain.add(newLocation);
Runnable task = new Runnable() {
@Override
public void run() {
checkCallingThread();
synchronized (mUrlRequestAdapterLock) {
if (isDoneLocked()) {
return;
}
mWaitingOnRedirect = true;
}
try {
mCallback.onRedirectReceived(CronetUrlRequest.this, responseInfo, newLocation);
} catch (Exception e) {
onCallbackException(e);
}
}
};
postTaskToExecutor(task);
}
/**
* Called when the final set of headers, after all redirects,
* is received. Can only be called once for each request.
*/
@SuppressWarnings("unused")
@CalledByNative
private void onResponseStarted(int httpStatusCode, String httpStatusText, String[] headers,
boolean wasCached, String negotiatedProtocol, String proxyServer,
long receivedByteCount) {
mResponseInfo = prepareResponseInfoOnNetworkThread(httpStatusCode, httpStatusText, headers,
wasCached, negotiatedProtocol, proxyServer, receivedByteCount);
Runnable task = new Runnable() {
@Override
public void run() {
checkCallingThread();
synchronized (mUrlRequestAdapterLock) {
if (isDoneLocked()) {
return;
}
mWaitingOnRead = true;
}
try {
mCallback.onResponseStarted(CronetUrlRequest.this, mResponseInfo);
} catch (Exception e) {
onCallbackException(e);
}
}
};
postTaskToExecutor(task);
}
/**
* Called whenever data is received. The ByteBuffer remains
* valid only until listener callback. Or if the callback
* pauses the request, it remains valid until the request is resumed.
* Cancelling the request also invalidates the buffer.
*
* @param byteBuffer ByteBuffer containing received data, starting at
* initialPosition. Guaranteed to have at least one read byte. Its
* limit has not yet been updated to reflect the bytes read.
* @param bytesRead Number of bytes read.
* @param initialPosition Original position of byteBuffer when passed to
* read(). Used as a minimal check that the buffer hasn't been
* modified while reading from the network.
* @param initialLimit Original limit of byteBuffer when passed to
* read(). Used as a minimal check that the buffer hasn't been
* modified while reading from the network.
* @param receivedByteCount number of bytes received.
*/
@SuppressWarnings("unused")
@CalledByNative
private void onReadCompleted(final ByteBuffer byteBuffer, int bytesRead, int initialPosition,
int initialLimit, long receivedByteCount) {
mResponseInfo.setReceivedByteCount(receivedByteCount);
if (byteBuffer.position() != initialPosition || byteBuffer.limit() != initialLimit) {
failWithException(
new CronetExceptionImpl("ByteBuffer modified externally during read", null));
return;
}
if (mOnReadCompletedTask == null) {
mOnReadCompletedTask = new OnReadCompletedRunnable();
}
byteBuffer.position(initialPosition + bytesRead);
mOnReadCompletedTask.mByteBuffer = byteBuffer;
postTaskToExecutor(mOnReadCompletedTask);
}
/**
* Called when request is completed successfully, no callbacks will be
* called afterwards.
*
* @param receivedByteCount number of bytes received.
*/
@SuppressWarnings("unused")
@CalledByNative
private void onSucceeded(long receivedByteCount) {
mResponseInfo.setReceivedByteCount(receivedByteCount);
Runnable task = new Runnable() {
@Override
public void run() {
synchronized (mUrlRequestAdapterLock) {
if (isDoneLocked()) {
return;
}
// Destroy adapter first, so request context could be shut
// down from the listener.
destroyRequestAdapterLocked(RequestFinishedInfo.SUCCEEDED);
}
try {
mCallback.onSucceeded(CronetUrlRequest.this, mResponseInfo);
maybeReportMetrics();
} catch (Exception e) {
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onSucceeded method", e);
}
}
};
postTaskToExecutor(task);
}
/**
* Called when error has occurred, no callbacks will be called afterwards.
*
* @param errorCode Error code represented by {@code UrlRequestError} that should be mapped
* to one of {@link NetworkException#ERROR_HOSTNAME_NOT_RESOLVED
* NetworkException.ERROR_*}.
* @param nativeError native net error code.
* @param errorString textual representation of the error code.
* @param receivedByteCount number of bytes received.
*/
@SuppressWarnings("unused")
@CalledByNative
private void onError(int errorCode, int nativeError, int nativeQuicError, String errorString,
long receivedByteCount) {
if (mResponseInfo != null) {
mResponseInfo.setReceivedByteCount(receivedByteCount);
}
if (errorCode == NetworkException.ERROR_QUIC_PROTOCOL_FAILED
|| errorCode == NetworkException.ERROR_NETWORK_CHANGED) {
failWithException(new QuicExceptionImpl("Exception in CronetUrlRequest: " + errorString,
errorCode, nativeError, nativeQuicError));
} else {
int javaError = mapUrlRequestErrorToApiErrorCode(errorCode);
failWithException(new NetworkExceptionImpl(
"Exception in CronetUrlRequest: " + errorString, javaError, nativeError));
}
}
/**
* Called when request is canceled, no callbacks will be called afterwards.
*/
@SuppressWarnings("unused")
@CalledByNative
private void onCanceled() {
Runnable task = new Runnable() {
@Override
public void run() {
try {
mCallback.onCanceled(CronetUrlRequest.this, mResponseInfo);
maybeReportMetrics();
} catch (Exception e) {
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onCanceled method", e);
}
}
};
postTaskToExecutor(task);
}
/**
* Called by the native code when request status is fetched from the
* native stack.
*/
@SuppressWarnings("unused")
@CalledByNative
private void onStatus(
final VersionSafeCallbacks.UrlRequestStatusListener listener, final int loadState) {
Runnable task = new Runnable() {
@Override
public void run() {
listener.onStatus(convertLoadState(loadState));
}
};
postTaskToExecutor(task);
}
/**
* Called by the native code on the network thread to report metrics. Happens before
* onSucceeded, onError and onCanceled.
*/
@SuppressWarnings("unused")
@CalledByNative
private void onMetricsCollected(long requestStartMs, long dnsStartMs, long dnsEndMs,
long connectStartMs, long connectEndMs, long sslStartMs, long sslEndMs,
long sendingStartMs, long sendingEndMs, long pushStartMs, long pushEndMs,
long responseStartMs, long requestEndMs, boolean socketReused, long sentByteCount,
long receivedByteCount) {
synchronized (mUrlRequestAdapterLock) {
if (mMetrics != null) {
throw new IllegalStateException("Metrics collection should only happen once.");
}
mMetrics = new CronetMetrics(requestStartMs, dnsStartMs, dnsEndMs, connectStartMs,
connectEndMs, sslStartMs, sslEndMs, sendingStartMs, sendingEndMs, pushStartMs,
pushEndMs, responseStartMs, requestEndMs, socketReused, sentByteCount,
receivedByteCount);
}
// Metrics are reported to RequestFinishedListener when the final UrlRequest.Callback has
// been invoked.
}
/**
* Called when the native adapter is destroyed.
*/
@SuppressWarnings("unused")
@CalledByNative
private void onNativeAdapterDestroyed() {
synchronized (mUrlRequestAdapterLock) {
if (mOnDestroyedCallbackForTesting != null) {
mOnDestroyedCallbackForTesting.run();
}
// mException is set when an error is encountered (in native code via onError or in
// Java code). If mException is not null, notify the mCallback and report metrics.
if (mException == null) {
return;
}
}
Runnable task = new Runnable() {
@Override
public void run() {
try {
mCallback.onFailed(CronetUrlRequest.this, mResponseInfo, mException);
maybeReportMetrics();
} catch (Exception e) {
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onFailed method", e);
}
}
};
try {
mExecutor.execute(task);
} catch (RejectedExecutionException e) {
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception posting task to executor", e);
}
}
/** Enforces prohibition of direct execution. */
void checkCallingThread() {
if (!mAllowDirectExecutor && mRequestContext.isNetworkThread(Thread.currentThread())) {
throw new InlineExecutionProhibitedException();
}
}
private int mapUrlRequestErrorToApiErrorCode(int errorCode) {
switch (errorCode) {
case UrlRequestError.HOSTNAME_NOT_RESOLVED:
return NetworkException.ERROR_HOSTNAME_NOT_RESOLVED;
case UrlRequestError.INTERNET_DISCONNECTED:
return NetworkException.ERROR_INTERNET_DISCONNECTED;
case UrlRequestError.NETWORK_CHANGED:
return NetworkException.ERROR_NETWORK_CHANGED;
case UrlRequestError.TIMED_OUT:
return NetworkException.ERROR_TIMED_OUT;
case UrlRequestError.CONNECTION_CLOSED:
return NetworkException.ERROR_CONNECTION_CLOSED;
case UrlRequestError.CONNECTION_TIMED_OUT:
return NetworkException.ERROR_CONNECTION_TIMED_OUT;
case UrlRequestError.CONNECTION_REFUSED:
return NetworkException.ERROR_CONNECTION_REFUSED;
case UrlRequestError.CONNECTION_RESET:
return NetworkException.ERROR_CONNECTION_RESET;
case UrlRequestError.ADDRESS_UNREACHABLE:
return NetworkException.ERROR_ADDRESS_UNREACHABLE;
case UrlRequestError.QUIC_PROTOCOL_FAILED:
return NetworkException.ERROR_QUIC_PROTOCOL_FAILED;
case UrlRequestError.OTHER:
return NetworkException.ERROR_OTHER;
default:
Log.e(CronetUrlRequestContext.LOG_TAG, "Unknown error code: " + errorCode);
return errorCode;
}
}
// Maybe report metrics. This method should only be called on Callback's executor thread and
// after Callback's onSucceeded, onFailed and onCanceled.
private void maybeReportMetrics() {
if (mMetrics != null) {
final RequestFinishedInfo requestInfo = new RequestFinishedInfoImpl(mInitialUrl,
mRequestAnnotations, mMetrics, mFinishedReason, mResponseInfo, mException);
mRequestContext.reportRequestFinished(requestInfo);
if (mRequestFinishedListener != null) {
try {
mRequestFinishedListener.getExecutor().execute(new Runnable() {
@Override
public void run() {
mRequestFinishedListener.onRequestFinished(requestInfo);
}
});
} catch (RejectedExecutionException failException) {
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception posting task to executor",
failException);
}
}
}
}
// Native methods are implemented in cronet_url_request_adapter.cc.
@NativeMethods
interface Natives {
long createRequestAdapter(CronetUrlRequest caller, long urlRequestContextAdapter,
String url, int priority, boolean disableCache, boolean disableConnectionMigration,
boolean enableMetrics, boolean trafficStatsTagSet, int trafficStatsTag,
boolean trafficStatsUidSet, int trafficStatsUid, int idempotency);
@NativeClassQualifiedName("CronetURLRequestAdapter")
boolean setHttpMethod(long nativePtr, CronetUrlRequest caller, String method);
@NativeClassQualifiedName("CronetURLRequestAdapter")
boolean addRequestHeader(
long nativePtr, CronetUrlRequest caller, String name, String value);
@NativeClassQualifiedName("CronetURLRequestAdapter")
void start(long nativePtr, CronetUrlRequest caller);
@NativeClassQualifiedName("CronetURLRequestAdapter")
void followDeferredRedirect(long nativePtr, CronetUrlRequest caller);
@NativeClassQualifiedName("CronetURLRequestAdapter")
boolean readData(long nativePtr, CronetUrlRequest caller, ByteBuffer byteBuffer,
int position, int capacity);
@NativeClassQualifiedName("CronetURLRequestAdapter")
void destroy(long nativePtr, CronetUrlRequest caller, boolean sendOnCanceled);
@NativeClassQualifiedName("CronetURLRequestAdapter")
void getStatus(long nativePtr, CronetUrlRequest caller,
VersionSafeCallbacks.UrlRequestStatusListener listener);
}
}

View File

@ -0,0 +1,759 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import android.os.ConditionVariable;
import android.os.Process;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.Log;
import org.chromium.base.ObserverList;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeClassQualifiedName;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.annotations.UsedByReflection;
import org.chromium.net.BidirectionalStream;
import org.chromium.net.EffectiveConnectionType;
import org.chromium.net.ExperimentalBidirectionalStream;
import org.chromium.net.NetworkQualityRttListener;
import org.chromium.net.NetworkQualityThroughputListener;
import org.chromium.net.RequestFinishedInfo;
import org.chromium.net.RttThroughputValues;
import org.chromium.net.UrlRequest;
import org.chromium.net.urlconnection.CronetHttpURLConnection;
import org.chromium.net.urlconnection.CronetURLStreamHandlerFactory;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandlerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.concurrent.GuardedBy;
/**
* CronetEngine using Chromium HTTP stack implementation.
*/
@JNINamespace("cronet")
@UsedByReflection("CronetEngine.java")
@VisibleForTesting
public class CronetUrlRequestContext extends CronetEngineBase {
private static final int LOG_NONE = 3; // LOG(FATAL), no VLOG.
private static final int LOG_DEBUG = -1; // LOG(FATAL...INFO), VLOG(1)
private static final int LOG_VERBOSE = -2; // LOG(FATAL...INFO), VLOG(2)
static final String LOG_TAG = CronetUrlRequestContext.class.getSimpleName();
/**
* Synchronize access to mUrlRequestContextAdapter and shutdown routine.
*/
private final Object mLock = new Object();
private final ConditionVariable mInitCompleted = new ConditionVariable(false);
private final AtomicInteger mActiveRequestCount = new AtomicInteger(0);
@GuardedBy("mLock")
private long mUrlRequestContextAdapter;
/**
* This field is accessed without synchronization, but only for the purposes of reference
* equality comparison with other threads. If such a comparison is performed on the network
* thread, then there is a happens-before edge between the write of this field and the
* subsequent read; if it's performed on another thread, then observing a value of null won't
* change the result of the comparison.
*/
private Thread mNetworkThread;
private final boolean mNetworkQualityEstimatorEnabled;
/**
* Locks operations on network quality listeners, because listener
* addition and removal may occur on a different thread from notification.
*/
private final Object mNetworkQualityLock = new Object();
/**
* Locks operations on the list of RequestFinishedInfo.Listeners, because operations can happen
* on any thread. This should be used for fine-grained locking only. In particular, don't call
* any UrlRequest methods that acquire mUrlRequestAdapterLock while holding this lock.
*/
private final Object mFinishedListenerLock = new Object();
/**
* Current effective connection type as computed by the network quality
* estimator.
*/
@GuardedBy("mNetworkQualityLock")
private int mEffectiveConnectionType = EffectiveConnectionType.TYPE_UNKNOWN;
/**
* Current estimate of the HTTP RTT (in milliseconds) computed by the
* network quality estimator.
*/
@GuardedBy("mNetworkQualityLock")
private int mHttpRttMs = RttThroughputValues.INVALID_RTT_THROUGHPUT;
/**
* Current estimate of the transport RTT (in milliseconds) computed by the
* network quality estimator.
*/
@GuardedBy("mNetworkQualityLock")
private int mTransportRttMs = RttThroughputValues.INVALID_RTT_THROUGHPUT;
/**
* Current estimate of the downstream throughput (in kilobits per second)
* computed by the network quality estimator.
*/
@GuardedBy("mNetworkQualityLock")
private int mDownstreamThroughputKbps = RttThroughputValues.INVALID_RTT_THROUGHPUT;
@GuardedBy("mNetworkQualityLock")
private final ObserverList<VersionSafeCallbacks.NetworkQualityRttListenerWrapper>
mRttListenerList =
new ObserverList<VersionSafeCallbacks.NetworkQualityRttListenerWrapper>();
@GuardedBy("mNetworkQualityLock")
private final ObserverList<VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper>
mThroughputListenerList =
new ObserverList<VersionSafeCallbacks
.NetworkQualityThroughputListenerWrapper>();
@GuardedBy("mFinishedListenerLock")
private final Map<RequestFinishedInfo.Listener,
VersionSafeCallbacks.RequestFinishedInfoListener> mFinishedListenerMap =
new HashMap<RequestFinishedInfo.Listener,
VersionSafeCallbacks.RequestFinishedInfoListener>();
private final ConditionVariable mStopNetLogCompleted = new ConditionVariable();
/** Set of storage paths currently in use. */
@GuardedBy("sInUseStoragePaths")
private static final HashSet<String> sInUseStoragePaths = new HashSet<String>();
/** Storage path used by this context. */
private final String mInUseStoragePath;
/**
* True if a NetLog observer is active.
*/
@GuardedBy("mLock")
private boolean mIsLogging;
/**
* True if NetLog is being shutdown.
*/
@GuardedBy("mLock")
private boolean mIsStoppingNetLog;
@UsedByReflection("CronetEngine.java")
public CronetUrlRequestContext(final CronetEngineBuilderImpl builder) {
mRttListenerList.disableThreadAsserts();
mThroughputListenerList.disableThreadAsserts();
mNetworkQualityEstimatorEnabled = builder.networkQualityEstimatorEnabled();
CronetLibraryLoader.ensureInitialized(builder.getContext(), builder);
if (!IntegratedModeState.INTEGRATED_MODE_ENABLED) {
CronetUrlRequestContextJni.get().setMinLogLevel(getLoggingLevel());
}
if (builder.httpCacheMode() == HttpCacheType.DISK) {
mInUseStoragePath = builder.storagePath();
synchronized (sInUseStoragePaths) {
if (!sInUseStoragePaths.add(mInUseStoragePath)) {
throw new IllegalStateException("Disk cache storage path already in use");
}
}
} else {
mInUseStoragePath = null;
}
synchronized (mLock) {
mUrlRequestContextAdapter =
CronetUrlRequestContextJni.get().createRequestContextAdapter(
createNativeUrlRequestContextConfig(builder));
if (mUrlRequestContextAdapter == 0) {
throw new NullPointerException("Context Adapter creation failed.");
}
}
// Init native Chromium URLRequestContext on init thread.
CronetLibraryLoader.postToInitThread(new Runnable() {
@Override
public void run() {
CronetLibraryLoader.ensureInitializedOnInitThread();
synchronized (mLock) {
// mUrlRequestContextAdapter is guaranteed to exist until
// initialization on init and network threads completes and
// initNetworkThread is called back on network thread.
CronetUrlRequestContextJni.get().initRequestContextOnInitThread(
mUrlRequestContextAdapter, CronetUrlRequestContext.this);
}
}
});
}
@VisibleForTesting
public static long createNativeUrlRequestContextConfig(CronetEngineBuilderImpl builder) {
final long urlRequestContextConfig =
CronetUrlRequestContextJni.get().createRequestContextConfig(builder.getUserAgent(),
builder.storagePath(), builder.quicEnabled(),
builder.getDefaultQuicUserAgentId(), builder.http2Enabled(),
builder.brotliEnabled(), builder.cacheDisabled(), builder.httpCacheMode(),
builder.httpCacheMaxSize(), builder.experimentalOptions(),
builder.mockCertVerifier(), builder.networkQualityEstimatorEnabled(),
builder.publicKeyPinningBypassForLocalTrustAnchorsEnabled(),
builder.threadPriority(Process.THREAD_PRIORITY_BACKGROUND));
if (urlRequestContextConfig == 0) {
throw new IllegalArgumentException("Experimental options parsing failed.");
}
for (CronetEngineBuilderImpl.QuicHint quicHint : builder.quicHints()) {
CronetUrlRequestContextJni.get().addQuicHint(urlRequestContextConfig, quicHint.mHost,
quicHint.mPort, quicHint.mAlternatePort);
}
for (CronetEngineBuilderImpl.Pkp pkp : builder.publicKeyPins()) {
CronetUrlRequestContextJni.get().addPkp(urlRequestContextConfig, pkp.mHost, pkp.mHashes,
pkp.mIncludeSubdomains, pkp.mExpirationDate.getTime());
}
return urlRequestContextConfig;
}
@Override
public ExperimentalBidirectionalStream.Builder newBidirectionalStreamBuilder(
String url, BidirectionalStream.Callback callback, Executor executor) {
return new BidirectionalStreamBuilderImpl(url, callback, executor, this);
}
@Override
public UrlRequestBase createRequest(String url, UrlRequest.Callback callback, Executor executor,
int priority, Collection<Object> requestAnnotations, boolean disableCache,
boolean disableConnectionMigration, boolean allowDirectExecutor,
boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet,
int trafficStatsUid, RequestFinishedInfo.Listener requestFinishedListener,
int idempotency) {
synchronized (mLock) {
checkHaveAdapter();
return new CronetUrlRequest(this, url, priority, callback, executor, requestAnnotations,
disableCache, disableConnectionMigration, allowDirectExecutor,
trafficStatsTagSet, trafficStatsTag, trafficStatsUidSet, trafficStatsUid,
requestFinishedListener, idempotency);
}
}
@Override
protected ExperimentalBidirectionalStream createBidirectionalStream(String url,
BidirectionalStream.Callback callback, Executor executor, String httpMethod,
List<Map.Entry<String, String>> requestHeaders, @StreamPriority int priority,
boolean delayRequestHeadersUntilFirstFlush, Collection<Object> requestAnnotations,
boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet,
int trafficStatsUid) {
synchronized (mLock) {
checkHaveAdapter();
return new CronetBidirectionalStream(this, url, priority, callback, executor,
httpMethod, requestHeaders, delayRequestHeadersUntilFirstFlush,
requestAnnotations, trafficStatsTagSet, trafficStatsTag, trafficStatsUidSet,
trafficStatsUid);
}
}
@Override
public String getVersionString() {
return "Cronet/" + ImplVersion.getCronetVersionWithLastChange();
}
@Override
public void shutdown() {
if (mInUseStoragePath != null) {
synchronized (sInUseStoragePaths) {
sInUseStoragePaths.remove(mInUseStoragePath);
}
}
synchronized (mLock) {
checkHaveAdapter();
if (mActiveRequestCount.get() != 0) {
throw new IllegalStateException("Cannot shutdown with active requests.");
}
// Destroying adapter stops the network thread, so it cannot be
// called on network thread.
if (Thread.currentThread() == mNetworkThread) {
throw new IllegalThreadStateException("Cannot shutdown from network thread.");
}
}
// Wait for init to complete on init and network thread (without lock,
// so other thread could access it).
mInitCompleted.block();
// If not logging, this is a no-op.
stopNetLog();
synchronized (mLock) {
// It is possible that adapter is already destroyed on another thread.
if (!haveRequestContextAdapter()) {
return;
}
CronetUrlRequestContextJni.get().destroy(
mUrlRequestContextAdapter, CronetUrlRequestContext.this);
mUrlRequestContextAdapter = 0;
}
}
@Override
public void startNetLogToFile(String fileName, boolean logAll) {
synchronized (mLock) {
checkHaveAdapter();
if (mIsLogging) {
return;
}
if (!CronetUrlRequestContextJni.get().startNetLogToFile(mUrlRequestContextAdapter,
CronetUrlRequestContext.this, fileName, logAll)) {
throw new RuntimeException("Unable to start NetLog");
}
mIsLogging = true;
}
}
@Override
public void startNetLogToDisk(String dirPath, boolean logAll, int maxSize) {
synchronized (mLock) {
checkHaveAdapter();
if (mIsLogging) {
return;
}
CronetUrlRequestContextJni.get().startNetLogToDisk(mUrlRequestContextAdapter,
CronetUrlRequestContext.this, dirPath, logAll, maxSize);
mIsLogging = true;
}
}
@Override
public void stopNetLog() {
synchronized (mLock) {
checkHaveAdapter();
if (!mIsLogging || mIsStoppingNetLog) {
return;
}
CronetUrlRequestContextJni.get().stopNetLog(
mUrlRequestContextAdapter, CronetUrlRequestContext.this);
mIsStoppingNetLog = true;
}
mStopNetLogCompleted.block();
mStopNetLogCompleted.close();
synchronized (mLock) {
mIsStoppingNetLog = false;
mIsLogging = false;
}
}
@CalledByNative
public void stopNetLogCompleted() {
mStopNetLogCompleted.open();
}
// This method is intentionally non-static to ensure Cronet native library
// is loaded by class constructor.
@Override
public byte[] getGlobalMetricsDeltas() {
return CronetUrlRequestContextJni.get().getHistogramDeltas();
}
@Override
public int getEffectiveConnectionType() {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
return convertConnectionTypeToApiValue(mEffectiveConnectionType);
}
}
@Override
public int getHttpRttMs() {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
return mHttpRttMs != RttThroughputValues.INVALID_RTT_THROUGHPUT
? mHttpRttMs
: CONNECTION_METRIC_UNKNOWN;
}
}
@Override
public int getTransportRttMs() {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
return mTransportRttMs != RttThroughputValues.INVALID_RTT_THROUGHPUT
? mTransportRttMs
: CONNECTION_METRIC_UNKNOWN;
}
}
@Override
public int getDownstreamThroughputKbps() {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
return mDownstreamThroughputKbps != RttThroughputValues.INVALID_RTT_THROUGHPUT
? mDownstreamThroughputKbps
: CONNECTION_METRIC_UNKNOWN;
}
}
@VisibleForTesting
@Override
public void configureNetworkQualityEstimatorForTesting(boolean useLocalHostRequests,
boolean useSmallerResponses, boolean disableOfflineCheck) {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mLock) {
checkHaveAdapter();
CronetUrlRequestContextJni.get().configureNetworkQualityEstimatorForTesting(
mUrlRequestContextAdapter, CronetUrlRequestContext.this, useLocalHostRequests,
useSmallerResponses, disableOfflineCheck);
}
}
@Override
public void addRttListener(NetworkQualityRttListener listener) {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
if (mRttListenerList.isEmpty()) {
synchronized (mLock) {
checkHaveAdapter();
CronetUrlRequestContextJni.get().provideRTTObservations(
mUrlRequestContextAdapter, CronetUrlRequestContext.this, true);
}
}
mRttListenerList.addObserver(
new VersionSafeCallbacks.NetworkQualityRttListenerWrapper(listener));
}
}
@Override
public void removeRttListener(NetworkQualityRttListener listener) {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
if (mRttListenerList.removeObserver(
new VersionSafeCallbacks.NetworkQualityRttListenerWrapper(listener))) {
if (mRttListenerList.isEmpty()) {
synchronized (mLock) {
checkHaveAdapter();
CronetUrlRequestContextJni.get().provideRTTObservations(
mUrlRequestContextAdapter, CronetUrlRequestContext.this, false);
}
}
}
}
}
@Override
public void addThroughputListener(NetworkQualityThroughputListener listener) {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
if (mThroughputListenerList.isEmpty()) {
synchronized (mLock) {
checkHaveAdapter();
CronetUrlRequestContextJni.get().provideThroughputObservations(
mUrlRequestContextAdapter, CronetUrlRequestContext.this, true);
}
}
mThroughputListenerList.addObserver(
new VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper(listener));
}
}
@Override
public void removeThroughputListener(NetworkQualityThroughputListener listener) {
if (!mNetworkQualityEstimatorEnabled) {
throw new IllegalStateException("Network quality estimator must be enabled");
}
synchronized (mNetworkQualityLock) {
if (mThroughputListenerList.removeObserver(
new VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper(
listener))) {
if (mThroughputListenerList.isEmpty()) {
synchronized (mLock) {
checkHaveAdapter();
CronetUrlRequestContextJni.get().provideThroughputObservations(
mUrlRequestContextAdapter, CronetUrlRequestContext.this, false);
}
}
}
}
}
@Override
public void addRequestFinishedListener(RequestFinishedInfo.Listener listener) {
synchronized (mFinishedListenerLock) {
mFinishedListenerMap.put(
listener, new VersionSafeCallbacks.RequestFinishedInfoListener(listener));
}
}
@Override
public void removeRequestFinishedListener(RequestFinishedInfo.Listener listener) {
synchronized (mFinishedListenerLock) {
mFinishedListenerMap.remove(listener);
}
}
boolean hasRequestFinishedListener() {
synchronized (mFinishedListenerLock) {
return !mFinishedListenerMap.isEmpty();
}
}
@Override
public URLConnection openConnection(URL url) {
return openConnection(url, Proxy.NO_PROXY);
}
@Override
public URLConnection openConnection(URL url, Proxy proxy) {
if (proxy.type() != Proxy.Type.DIRECT) {
throw new UnsupportedOperationException();
}
String protocol = url.getProtocol();
if ("http".equals(protocol) || "https".equals(protocol)) {
return new CronetHttpURLConnection(url, this);
}
throw new UnsupportedOperationException("Unexpected protocol:" + protocol);
}
@Override
public URLStreamHandlerFactory createURLStreamHandlerFactory() {
return new CronetURLStreamHandlerFactory(this);
}
/**
* Mark request as started to prevent shutdown when there are active
* requests.
*/
void onRequestStarted() {
mActiveRequestCount.incrementAndGet();
}
/**
* Mark request as finished to allow shutdown when there are no active
* requests.
*/
void onRequestDestroyed() {
mActiveRequestCount.decrementAndGet();
}
@VisibleForTesting
public long getUrlRequestContextAdapter() {
synchronized (mLock) {
checkHaveAdapter();
return mUrlRequestContextAdapter;
}
}
@GuardedBy("mLock")
private void checkHaveAdapter() throws IllegalStateException {
if (!haveRequestContextAdapter()) {
throw new IllegalStateException("Engine is shut down.");
}
}
@GuardedBy("mLock")
private boolean haveRequestContextAdapter() {
return mUrlRequestContextAdapter != 0;
}
/**
* @return loggingLevel see {@link #LOG_NONE}, {@link #LOG_DEBUG} and
* {@link #LOG_VERBOSE}.
*/
private int getLoggingLevel() {
int loggingLevel;
if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
loggingLevel = LOG_VERBOSE;
} else if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
loggingLevel = LOG_DEBUG;
} else {
loggingLevel = LOG_NONE;
}
return loggingLevel;
}
private static int convertConnectionTypeToApiValue(@EffectiveConnectionType int type) {
switch (type) {
case EffectiveConnectionType.TYPE_OFFLINE:
return EFFECTIVE_CONNECTION_TYPE_OFFLINE;
case EffectiveConnectionType.TYPE_SLOW_2G:
return EFFECTIVE_CONNECTION_TYPE_SLOW_2G;
case EffectiveConnectionType.TYPE_2G:
return EFFECTIVE_CONNECTION_TYPE_2G;
case EffectiveConnectionType.TYPE_3G:
return EFFECTIVE_CONNECTION_TYPE_3G;
case EffectiveConnectionType.TYPE_4G:
return EFFECTIVE_CONNECTION_TYPE_4G;
case EffectiveConnectionType.TYPE_UNKNOWN:
return EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
default:
throw new RuntimeException(
"Internal Error: Illegal EffectiveConnectionType value " + type);
}
}
@SuppressWarnings("unused")
@CalledByNative
private void initNetworkThread() {
mNetworkThread = Thread.currentThread();
mInitCompleted.open();
if (!IntegratedModeState.INTEGRATED_MODE_ENABLED) {
// In integrated mode, network thread is shared from the host.
// Cronet shouldn't change the property of the thread.
Thread.currentThread().setName("ChromiumNet");
}
}
@SuppressWarnings("unused")
@CalledByNative
private void onEffectiveConnectionTypeChanged(int effectiveConnectionType) {
synchronized (mNetworkQualityLock) {
// Convert the enum returned by the network quality estimator to an enum of type
// EffectiveConnectionType.
mEffectiveConnectionType = effectiveConnectionType;
}
}
@SuppressWarnings("unused")
@CalledByNative
private void onRTTOrThroughputEstimatesComputed(
final int httpRttMs, final int transportRttMs, final int downstreamThroughputKbps) {
synchronized (mNetworkQualityLock) {
mHttpRttMs = httpRttMs;
mTransportRttMs = transportRttMs;
mDownstreamThroughputKbps = downstreamThroughputKbps;
}
}
@SuppressWarnings("unused")
@CalledByNative
private void onRttObservation(final int rttMs, final long whenMs, final int source) {
synchronized (mNetworkQualityLock) {
for (final VersionSafeCallbacks.NetworkQualityRttListenerWrapper listener :
mRttListenerList) {
Runnable task = new Runnable() {
@Override
public void run() {
listener.onRttObservation(rttMs, whenMs, source);
}
};
postObservationTaskToExecutor(listener.getExecutor(), task);
}
}
}
@SuppressWarnings("unused")
@CalledByNative
private void onThroughputObservation(
final int throughputKbps, final long whenMs, final int source) {
synchronized (mNetworkQualityLock) {
for (final VersionSafeCallbacks.NetworkQualityThroughputListenerWrapper listener :
mThroughputListenerList) {
Runnable task = new Runnable() {
@Override
public void run() {
listener.onThroughputObservation(throughputKbps, whenMs, source);
}
};
postObservationTaskToExecutor(listener.getExecutor(), task);
}
}
}
void reportRequestFinished(final RequestFinishedInfo requestInfo) {
ArrayList<VersionSafeCallbacks.RequestFinishedInfoListener> currentListeners;
synchronized (mFinishedListenerLock) {
if (mFinishedListenerMap.isEmpty()) return;
currentListeners = new ArrayList<VersionSafeCallbacks.RequestFinishedInfoListener>(
mFinishedListenerMap.values());
}
for (final VersionSafeCallbacks.RequestFinishedInfoListener listener : currentListeners) {
Runnable task = new Runnable() {
@Override
public void run() {
listener.onRequestFinished(requestInfo);
}
};
postObservationTaskToExecutor(listener.getExecutor(), task);
}
}
private static void postObservationTaskToExecutor(Executor executor, Runnable task) {
try {
executor.execute(task);
} catch (RejectedExecutionException failException) {
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception posting task to executor",
failException);
}
}
public boolean isNetworkThread(Thread thread) {
return thread == mNetworkThread;
}
// Native methods are implemented in cronet_url_request_context_adapter.cc.
@NativeMethods
interface Natives {
long createRequestContextConfig(String userAgent, String storagePath, boolean quicEnabled,
String quicUserAgentId, boolean http2Enabled, boolean brotliEnabled,
boolean disableCache, int httpCacheMode, long httpCacheMaxSize,
String experimentalOptions, long mockCertVerifier,
boolean enableNetworkQualityEstimator,
boolean bypassPublicKeyPinningForLocalTrustAnchors, int networkThreadPriority);
void addQuicHint(long urlRequestContextConfig, String host, int port, int alternatePort);
void addPkp(long urlRequestContextConfig, String host, byte[][] hashes,
boolean includeSubdomains, long expirationTime);
long createRequestContextAdapter(long urlRequestContextConfig);
int setMinLogLevel(int loggingLevel);
byte[] getHistogramDeltas();
@NativeClassQualifiedName("CronetURLRequestContextAdapter")
void destroy(long nativePtr, CronetUrlRequestContext caller);
@NativeClassQualifiedName("CronetURLRequestContextAdapter")
boolean startNetLogToFile(
long nativePtr, CronetUrlRequestContext caller, String fileName, boolean logAll);
@NativeClassQualifiedName("CronetURLRequestContextAdapter")
void startNetLogToDisk(long nativePtr, CronetUrlRequestContext caller, String dirPath,
boolean logAll, int maxSize);
@NativeClassQualifiedName("CronetURLRequestContextAdapter")
void stopNetLog(long nativePtr, CronetUrlRequestContext caller);
@NativeClassQualifiedName("CronetURLRequestContextAdapter")
void initRequestContextOnInitThread(long nativePtr, CronetUrlRequestContext caller);
@NativeClassQualifiedName("CronetURLRequestContextAdapter")
void configureNetworkQualityEstimatorForTesting(long nativePtr,
CronetUrlRequestContext caller, boolean useLocalHostRequests,
boolean useSmallerResponses, boolean disableOfflineCheck);
@NativeClassQualifiedName("CronetURLRequestContextAdapter")
void provideRTTObservations(long nativePtr, CronetUrlRequestContext caller, boolean should);
@NativeClassQualifiedName("CronetURLRequestContextAdapter")
void provideThroughputObservations(
long nativePtr, CronetUrlRequestContext caller, boolean should);
}
}

View File

@ -0,0 +1,33 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
// Version based on chrome/VERSION.
public class ImplVersion {
private static final String CRONET_VERSION = "@MAJOR@.@MINOR@.@BUILD@.@PATCH@";
private static final int API_LEVEL = @API_LEVEL@;
private static final String LAST_CHANGE = "@LASTCHANGE@";
/**
* Private constructor. All members of this class should be static.
*/
private ImplVersion() {}
public static String getCronetVersionWithLastChange() {
return CRONET_VERSION + "@" + LAST_CHANGE.substring(0, 8);
}
public static int getApiLevel() {
return API_LEVEL;
}
public static String getCronetVersion() {
return CRONET_VERSION;
}
public static String getLastChange() {
return LAST_CHANGE;
}
}

View File

@ -0,0 +1,77 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import androidx.annotation.NonNull;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Adapts an {@link InputStream} into a {@link ReadableByteChannel}, exactly like
* {@link java.nio.channels.Channels#newChannel(InputStream)} does, but more efficiently, since it
* does not allocate a temporary buffer if it doesn't have to, and it freely takes advantage of
* {@link FileInputStream}'s trivial conversion to {@link java.nio.channels.FileChannel}.
*/
final class InputStreamChannel implements ReadableByteChannel {
private static final int MAX_TMP_BUFFER_SIZE = 16384;
private static final int MIN_TMP_BUFFER_SIZE = 4096;
private final InputStream mInputStream;
private final AtomicBoolean mIsOpen = new AtomicBoolean(true);
private InputStreamChannel(@NonNull InputStream inputStream) {
mInputStream = inputStream;
}
static ReadableByteChannel wrap(@NonNull InputStream inputStream) {
if (inputStream instanceof FileInputStream) {
return ((FileInputStream) inputStream).getChannel();
}
return new InputStreamChannel(inputStream);
}
@Override
public int read(ByteBuffer dst) throws IOException {
final int read;
if (dst.hasArray()) {
read = mInputStream.read(
dst.array(), dst.arrayOffset() + dst.position(), dst.remaining());
if (read > 0) {
dst.position(dst.position() + read);
}
} else {
// Since we're allocating a buffer for every read, we want to choose a good size - on
// Android, the only case where a ByteBuffer won't have a backing byte[] is if it was
// created wrapping a void * in native code, or if it represents a memory-mapped file.
// Especially in the latter case, we want to avoid allocating a buffer that could be
// very large.
final int possibleToRead = Math.min(
Math.max(mInputStream.available(), MIN_TMP_BUFFER_SIZE), dst.remaining());
final int reasonableToRead = Math.min(MAX_TMP_BUFFER_SIZE, possibleToRead);
byte[] tmpBuf = new byte[reasonableToRead];
read = mInputStream.read(tmpBuf);
if (read > 0) {
dst.put(tmpBuf, 0, read);
}
}
return read;
}
@Override
public boolean isOpen() {
return mIsOpen.get();
}
@Override
public void close() throws IOException {
if (mIsOpen.compareAndSet(true, false)) {
mInputStream.close();
}
}
}

View File

@ -0,0 +1,21 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
/**
* This template file provides the flags of Cronet integrated mode for Java side.
*/
public class IntegratedModeState {
// A boolean flag indicating whether integrated mode is enabled. In integrated mode, CronetEngine
// would use the shared network task runner by other Chromium-based clients like webview, Chrome
// Android, Cronet without self-initialization.
public static final boolean INTEGRATED_MODE_ENABLED =
#if defined(INTEGRATED_MODE)
true;
#else
false;
#endif
}

View File

@ -0,0 +1,186 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.os.Process.THREAD_PRIORITY_MORE_FAVORABLE;
import org.chromium.net.BidirectionalStream;
import org.chromium.net.ExperimentalBidirectionalStream;
import org.chromium.net.NetworkQualityRttListener;
import org.chromium.net.NetworkQualityThroughputListener;
import org.chromium.net.RequestFinishedInfo;
import org.chromium.net.UrlRequest;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* {@link java.net.HttpURLConnection} backed CronetEngine.
*
* <p>Does not support netlogs, transferred data measurement, bidistream, cache, or priority.
*/
public final class JavaCronetEngine extends CronetEngineBase {
private final String mUserAgent;
private final ExecutorService mExecutorService;
public JavaCronetEngine(CronetEngineBuilderImpl builder) {
// On android, all background threads (and all threads that are part
// of background processes) are put in a cgroup that is allowed to
// consume up to 5% of CPU - these worker threads spend the vast
// majority of their time waiting on I/O, so making them contend with
// background applications for a slice of CPU doesn't make much sense.
// We want to hurry up and get idle.
final int threadPriority =
builder.threadPriority(THREAD_PRIORITY_BACKGROUND + THREAD_PRIORITY_MORE_FAVORABLE);
this.mUserAgent = builder.getUserAgent();
this.mExecutorService = new ThreadPoolExecutor(10, 20, 50, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
@Override
public Thread newThread(final Runnable r) {
return Executors.defaultThreadFactory().newThread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("JavaCronetEngine");
android.os.Process.setThreadPriority(threadPriority);
r.run();
}
});
}
});
}
@Override
public UrlRequestBase createRequest(String url, UrlRequest.Callback callback, Executor executor,
int priority, Collection<Object> connectionAnnotations, boolean disableCache,
boolean disableConnectionMigration, boolean allowDirectExecutor,
boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet,
int trafficStatsUid, RequestFinishedInfo.Listener requestFinishedListener,
int idempotency) {
return new JavaUrlRequest(callback, mExecutorService, executor, url, mUserAgent,
allowDirectExecutor, trafficStatsTagSet, trafficStatsTag, trafficStatsUidSet,
trafficStatsUid);
}
@Override
protected ExperimentalBidirectionalStream createBidirectionalStream(String url,
BidirectionalStream.Callback callback, Executor executor, String httpMethod,
List<Map.Entry<String, String>> requestHeaders, @StreamPriority int priority,
boolean delayRequestHeadersUntilFirstFlush, Collection<Object> connectionAnnotations,
boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet,
int trafficStatsUid) {
throw new UnsupportedOperationException(
"Can't create a bidi stream - httpurlconnection doesn't have those APIs");
}
@Override
public ExperimentalBidirectionalStream.Builder newBidirectionalStreamBuilder(
String url, BidirectionalStream.Callback callback, Executor executor) {
throw new UnsupportedOperationException(
"The bidirectional stream API is not supported by the Java implementation "
+ "of Cronet Engine");
}
@Override
public String getVersionString() {
return "CronetHttpURLConnection/" + ImplVersion.getCronetVersionWithLastChange();
}
@Override
public void shutdown() {
mExecutorService.shutdown();
}
@Override
public void startNetLogToFile(String fileName, boolean logAll) {}
@Override
public void startNetLogToDisk(String dirPath, boolean logAll, int maxSize) {}
@Override
public void stopNetLog() {}
@Override
public byte[] getGlobalMetricsDeltas() {
return new byte[0];
}
@Override
public int getEffectiveConnectionType() {
return EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
}
@Override
public int getHttpRttMs() {
return CONNECTION_METRIC_UNKNOWN;
}
@Override
public int getTransportRttMs() {
return CONNECTION_METRIC_UNKNOWN;
}
@Override
public int getDownstreamThroughputKbps() {
return CONNECTION_METRIC_UNKNOWN;
}
@Override
public void configureNetworkQualityEstimatorForTesting(boolean useLocalHostRequests,
boolean useSmallerResponses, boolean disableOfflineCheck) {}
@Override
public void addRttListener(NetworkQualityRttListener listener) {}
@Override
public void removeRttListener(NetworkQualityRttListener listener) {}
@Override
public void addThroughputListener(NetworkQualityThroughputListener listener) {}
@Override
public void removeThroughputListener(NetworkQualityThroughputListener listener) {}
@Override
public void addRequestFinishedListener(RequestFinishedInfo.Listener listener) {}
@Override
public void removeRequestFinishedListener(RequestFinishedInfo.Listener listener) {}
@Override
public URLConnection openConnection(URL url) throws IOException {
return url.openConnection();
}
@Override
public URLConnection openConnection(URL url, Proxy proxy) throws IOException {
return url.openConnection(proxy);
}
@Override
public URLStreamHandlerFactory createURLStreamHandlerFactory() {
// Returning null causes this factory to pass though, which ends up using the platform's
// implementation.
return new URLStreamHandlerFactory() {
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
return null;
}
};
}
}

View File

@ -0,0 +1,32 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import android.content.Context;
import org.chromium.net.ExperimentalCronetEngine;
import org.chromium.net.ICronetEngineBuilder;
/**
* Implementation of {@link ICronetEngineBuilder} that builds Java-based Cronet engine.
*/
public class JavaCronetEngineBuilderImpl extends CronetEngineBuilderImpl {
/**
* Builder for Platform Cronet Engine.
*
* @param context Android {@link Context} for engine to use.
*/
public JavaCronetEngineBuilderImpl(Context context) {
super(context);
}
@Override
public ExperimentalCronetEngine build() {
if (getUserAgent() == null) {
setUserAgent(getDefaultUserAgent());
}
return new JavaCronetEngine(this);
}
}

View File

@ -0,0 +1,62 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import android.content.Context;
import org.chromium.net.CronetEngine;
import org.chromium.net.CronetProvider;
import org.chromium.net.ExperimentalCronetEngine;
import org.chromium.net.ICronetEngineBuilder;
import java.util.Arrays;
/**
* Implementation of {@link CronetProvider} that creates {@link CronetEngine.Builder}
* for building the Java-based implementation of {@link CronetEngine}.
*/
public class JavaCronetProvider extends CronetProvider {
/**
* Constructor.
*
* @param context Android context to use.
*/
public JavaCronetProvider(Context context) {
super(context);
}
@Override
public CronetEngine.Builder createBuilder() {
ICronetEngineBuilder impl = new JavaCronetEngineBuilderImpl(mContext);
return new ExperimentalCronetEngine.Builder(impl);
}
@Override
public String getName() {
return CronetProvider.PROVIDER_NAME_FALLBACK;
}
@Override
public String getVersion() {
return ImplVersion.getCronetVersion();
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public int hashCode() {
return Arrays.hashCode(new Object[] {JavaCronetProvider.class, mContext});
}
@Override
public boolean equals(Object other) {
return other == this
|| (other instanceof JavaCronetProvider
&& this.mContext.equals(((JavaCronetProvider) other).mContext));
}
}

View File

@ -0,0 +1,255 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import androidx.annotation.IntDef;
import org.chromium.net.UploadDataProvider;
import org.chromium.net.UploadDataSink;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.util.Locale;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Base class for Java UrlRequest implementations of UploadDataSink. Handles asynchronicity and
* manages the executors for this upload.
*/
public abstract class JavaUploadDataSinkBase extends UploadDataSink {
@IntDef({SinkState.AWAITING_READ_RESULT, SinkState.AWAITING_REWIND_RESULT, SinkState.UPLOADING,
SinkState.NOT_STARTED})
@Retention(RetentionPolicy.SOURCE)
@interface SinkState {
int AWAITING_READ_RESULT = 0;
int AWAITING_REWIND_RESULT = 1;
int UPLOADING = 2;
int NOT_STARTED = 3;
}
public static final int DEFAULT_UPLOAD_BUFFER_SIZE = 8192;
private final AtomicInteger /*SinkState*/ mSinkState = new AtomicInteger(SinkState.NOT_STARTED);
private final Executor mUserUploadExecutor;
private final Executor mExecutor;
private final UploadDataProvider mUploadProvider;
private ByteBuffer mBuffer;
/** This holds the total bytes to send (the content-length). -1 if unknown. */
private long mTotalBytes;
/** This holds the bytes written so far */
private long mWrittenBytes;
public JavaUploadDataSinkBase(
final Executor userExecutor, Executor executor, UploadDataProvider provider) {
mUserUploadExecutor = new Executor() {
@Override
public void execute(Runnable runnable) {
try {
userExecutor.execute(runnable);
} catch (RejectedExecutionException e) {
processUploadError(e);
}
}
};
mExecutor = executor;
mUploadProvider = provider;
}
@Override
public void onReadSucceeded(final boolean finalChunk) {
if (!mSinkState.compareAndSet(/* expected= */ SinkState.AWAITING_READ_RESULT,
/* updated= */ SinkState.UPLOADING)) {
throw new IllegalStateException(
"onReadSucceeded() called when not awaiting a read result; in state: "
+ mSinkState.get());
}
mExecutor.execute(getErrorSettingRunnable(new JavaUrlRequestUtils.CheckedRunnable() {
@Override
public void run() throws Exception {
mBuffer.flip();
if (mTotalBytes != -1 && mTotalBytes - mWrittenBytes < mBuffer.remaining()) {
processUploadError(
new IllegalArgumentException(String.format(Locale.getDefault(),
"Read upload data length %d exceeds expected length %d",
mWrittenBytes + mBuffer.remaining(), mTotalBytes)));
return;
}
mWrittenBytes += processSuccessfulRead(mBuffer);
if (mWrittenBytes < mTotalBytes || (mTotalBytes == -1 && !finalChunk)) {
mBuffer.clear();
mSinkState.set(SinkState.AWAITING_READ_RESULT);
executeOnUploadExecutor(new JavaUrlRequestUtils.CheckedRunnable() {
@Override
public void run() throws Exception {
mUploadProvider.read(JavaUploadDataSinkBase.this, mBuffer);
}
});
} else if (mTotalBytes == -1) {
finish();
} else if (mTotalBytes == mWrittenBytes) {
finish();
} else {
processUploadError(
new IllegalArgumentException(String.format(Locale.getDefault(),
"Read upload data length %d exceeds expected length %d",
mWrittenBytes, mTotalBytes)));
}
}
}));
}
@Override
public void onRewindSucceeded() {
if (!mSinkState.compareAndSet(/* expected= */ SinkState.AWAITING_REWIND_RESULT,
/* updated= */ SinkState.UPLOADING)) {
throw new IllegalStateException(
"onRewindSucceeded() called when not awaiting a rewind; in state: "
+ mSinkState.get());
}
startRead();
}
@Override
public void onReadError(Exception exception) {
processUploadError(exception);
}
@Override
public void onRewindError(Exception exception) {
processUploadError(exception);
}
private void startRead() {
mExecutor.execute(getErrorSettingRunnable(new JavaUrlRequestUtils.CheckedRunnable() {
@Override
public void run() throws Exception {
initializeRead();
mSinkState.set(SinkState.AWAITING_READ_RESULT);
executeOnUploadExecutor(new JavaUrlRequestUtils.CheckedRunnable() {
@Override
public void run() throws Exception {
mUploadProvider.read(JavaUploadDataSinkBase.this, mBuffer);
}
});
}
}));
}
/**
* Helper method to execute a checked runnable on the upload executor and process any errors
* that occur as upload errors.
*
* @param runnable the runnable to attempt to run and check for errors
*/
private void executeOnUploadExecutor(JavaUrlRequestUtils.CheckedRunnable runnable) {
try {
mUserUploadExecutor.execute(getUploadErrorSettingRunnable(runnable));
} catch (RejectedExecutionException e) {
processUploadError(e);
}
}
/**
* Starts the upload. This method can be called multiple times. If it is not the first time it
* is called the {@link UploadDataProvider} must rewind.
*
* @param firstTime true if this is the first time this {@link UploadDataSink} has started an
* upload
*/
public void start(final boolean firstTime) {
executeOnUploadExecutor(new JavaUrlRequestUtils.CheckedRunnable() {
@Override
public void run() throws Exception {
mTotalBytes = mUploadProvider.getLength();
if (mTotalBytes == 0) {
finish();
} else {
// If we know how much data we have to upload, and it's small, we can save
// memory by allocating a reasonably sized buffer to read into.
if (mTotalBytes > 0 && mTotalBytes < DEFAULT_UPLOAD_BUFFER_SIZE) {
// Allocate one byte more than necessary, to detect callers uploading
// more bytes than they specified in length.
mBuffer = ByteBuffer.allocateDirect((int) mTotalBytes + 1);
} else {
mBuffer = ByteBuffer.allocateDirect(DEFAULT_UPLOAD_BUFFER_SIZE);
}
initializeStart(mTotalBytes);
if (firstTime) {
startRead();
} else {
mSinkState.set(SinkState.AWAITING_REWIND_RESULT);
mUploadProvider.rewind(JavaUploadDataSinkBase.this);
}
}
}
});
}
/**
* Gets a runnable that checks for errors and processes them by setting an error state when
* executing a {@link CheckedRunnable}.
*
* @param runnable The runnable to run.
* @return a runnable that checks for errors
*/
protected abstract Runnable getErrorSettingRunnable(
JavaUrlRequestUtils.CheckedRunnable runnable);
/**
* Gets a runnable that checks for errors and processes them by setting an upload error state
* when executing a {@link CheckedRunnable}.
*
* @param runnable The runnable to run.
* @return a runnable that checks for errors
*/
protected abstract Runnable getUploadErrorSettingRunnable(
JavaUrlRequestUtils.CheckedRunnable runnable);
/**
* Processes an error encountered while uploading data.
*
* @param error the {@link Throwable} to process
*/
protected abstract void processUploadError(final Throwable error);
/**
* Called when a successful read has occurred and there is new data in the {@code mBuffer} to
* process.
*
* @return the number of bytes processed in this read
* @throws IOException
*/
protected abstract int processSuccessfulRead(ByteBuffer buffer) throws IOException;
/**
* Finishes this upload. Called when the upload is complete.
*
* @throws IOException
*/
protected abstract void finish() throws IOException;
/**
* Initializes the {@link UploadDataSink} before each call to {@code read} in the
* {@link UploadDataProvider}.
*
* @throws IOException
*/
protected abstract void initializeRead() throws IOException;
/**
* Initializes the {@link UploadDataSink} at the start of the upload.
*
* @param totalBytes the total number of bytes to be retrieved in this upload
*/
protected abstract void initializeStart(long totalBytes);
}

View File

@ -0,0 +1,901 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import android.annotation.TargetApi;
import android.net.TrafficStats;
import android.os.Build;
import android.util.Log;
import androidx.annotation.Nullable;
import org.chromium.net.CronetException;
import org.chromium.net.InlineExecutionProhibitedException;
import org.chromium.net.ThreadStatsUid;
import org.chromium.net.UploadDataProvider;
import org.chromium.net.UrlResponseInfo;
import org.chromium.net.impl.JavaUrlRequestUtils.CheckedRunnable;
import org.chromium.net.impl.JavaUrlRequestUtils.DirectPreventingExecutor;
import org.chromium.net.impl.JavaUrlRequestUtils.State;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.concurrent.GuardedBy;
/**
* Pure java UrlRequest, backed by {@link HttpURLConnection}.
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) // TrafficStats only available on ICS
final class JavaUrlRequest extends UrlRequestBase {
private static final String X_ANDROID = "X-Android";
private static final String X_ANDROID_SELECTED_TRANSPORT = "X-Android-Selected-Transport";
private static final String TAG = JavaUrlRequest.class.getSimpleName();
private static final int DEFAULT_CHUNK_LENGTH =
JavaUploadDataSinkBase.DEFAULT_UPLOAD_BUFFER_SIZE;
private static final String USER_AGENT = "User-Agent";
private final AsyncUrlRequestCallback mCallbackAsync;
private final Executor mExecutor;
private final String mUserAgent;
private final Map<String, String> mRequestHeaders =
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private final List<String> mUrlChain = new ArrayList<>();
/**
* This is the source of thread safety in this class - no other synchronization is performed.
* By compare-and-swapping from one state to another, we guarantee that operations aren't
* running concurrently. Only the winner of a CAS proceeds.
*
* <p>A caller can lose a CAS for three reasons - user error (two calls to read() without
* waiting for the read to succeed), runtime error (network code or user code throws an
* exception), or cancellation.
*/
private final AtomicInteger /* State */ mState = new AtomicInteger(State.NOT_STARTED);
private final AtomicBoolean mUploadProviderClosed = new AtomicBoolean(false);
private final boolean mAllowDirectExecutor;
/* These don't change with redirects */
private String mInitialMethod;
private VersionSafeCallbacks.UploadDataProviderWrapper mUploadDataProvider;
private Executor mUploadExecutor;
/**
* Holds a subset of StatusValues - {@link State#STARTED} can represent
* {@link Status#SENDING_REQUEST} or {@link Status#WAITING_FOR_RESPONSE}. While the distinction
* isn't needed to implement the logic in this class, it is needed to implement
* {@link #getStatus(StatusListener)}.
*
* <p>Concurrency notes - this value is not atomically updated with mState, so there is some
* risk that we'd get an inconsistent snapshot of both - however, it also happens that this
* value is only used with the STARTED state, so it's inconsequential.
*/
@StatusValues
private volatile int mAdditionalStatusDetails = Status.INVALID;
/* These change with redirects. */
private String mCurrentUrl;
@Nullable
private ReadableByteChannel mResponseChannel; // Only accessed on mExecutor.
private UrlResponseInfoImpl mUrlResponseInfo;
private String mPendingRedirectUrl;
private HttpURLConnection mCurrentUrlConnection; // Only accessed on mExecutor.
private OutputStreamDataSink mOutputStreamDataSink; // Only accessed on mExecutor.
// Executor that runs one task at a time on an underlying Executor.
// NOTE: Do not use to wrap user supplied Executor as lock is held while underlying execute()
// is called.
private static final class SerializingExecutor implements Executor {
private final Executor mUnderlyingExecutor;
private final Runnable mRunTasks = new Runnable() {
@Override
public void run() {
Runnable task;
synchronized (mTaskQueue) {
if (mRunning) {
return;
}
task = mTaskQueue.pollFirst();
mRunning = task != null;
}
while (task != null) {
boolean threw = true;
try {
task.run();
threw = false;
} finally {
synchronized (mTaskQueue) {
if (threw) {
// If task.run() threw, this method will abort without looping
// again, so repost to keep running tasks.
mRunning = false;
try {
mUnderlyingExecutor.execute(mRunTasks);
} catch (RejectedExecutionException e) {
// Give up if a task run at shutdown throws.
}
} else {
task = mTaskQueue.pollFirst();
mRunning = task != null;
}
}
}
}
}
};
// Queue of tasks to run. Tasks are added to the end and taken from the front.
// Synchronized on itself.
@GuardedBy("mTaskQueue")
private final ArrayDeque<Runnable> mTaskQueue = new ArrayDeque<>();
// Indicates if mRunTasks is actively running tasks. Synchronized on mTaskQueue.
@GuardedBy("mTaskQueue")
private boolean mRunning;
SerializingExecutor(Executor underlyingExecutor) {
mUnderlyingExecutor = underlyingExecutor;
}
@Override
public void execute(Runnable command) {
synchronized (mTaskQueue) {
mTaskQueue.addLast(command);
try {
mUnderlyingExecutor.execute(mRunTasks);
} catch (RejectedExecutionException e) {
// If shutting down, do not add new tasks to the queue.
mTaskQueue.removeLast();
}
}
};
}
/**
* @param executor The executor used for reading and writing from sockets
* @param userExecutor The executor used to dispatch to {@code callback}
*/
JavaUrlRequest(Callback callback, final Executor executor, Executor userExecutor, String url,
String userAgent, boolean allowDirectExecutor, boolean trafficStatsTagSet,
int trafficStatsTag, final boolean trafficStatsUidSet, final int trafficStatsUid) {
if (url == null) {
throw new NullPointerException("URL is required");
}
if (callback == null) {
throw new NullPointerException("Listener is required");
}
if (executor == null) {
throw new NullPointerException("Executor is required");
}
if (userExecutor == null) {
throw new NullPointerException("userExecutor is required");
}
this.mAllowDirectExecutor = allowDirectExecutor;
this.mCallbackAsync = new AsyncUrlRequestCallback(callback, userExecutor);
final int trafficStatsTagToUse =
trafficStatsTagSet ? trafficStatsTag : TrafficStats.getThreadStatsTag();
this.mExecutor = new SerializingExecutor(new Executor() {
@Override
public void execute(final Runnable command) {
executor.execute(new Runnable() {
@Override
public void run() {
int oldTag = TrafficStats.getThreadStatsTag();
TrafficStats.setThreadStatsTag(trafficStatsTagToUse);
if (trafficStatsUidSet) {
ThreadStatsUid.set(trafficStatsUid);
}
try {
command.run();
} finally {
if (trafficStatsUidSet) {
ThreadStatsUid.clear();
}
TrafficStats.setThreadStatsTag(oldTag);
}
}
});
}
});
this.mCurrentUrl = url;
this.mUserAgent = userAgent;
}
@Override
public void setHttpMethod(String method) {
checkNotStarted();
if (method == null) {
throw new NullPointerException("Method is required.");
}
if ("OPTIONS".equalsIgnoreCase(method) || "GET".equalsIgnoreCase(method)
|| "HEAD".equalsIgnoreCase(method) || "POST".equalsIgnoreCase(method)
|| "PUT".equalsIgnoreCase(method) || "DELETE".equalsIgnoreCase(method)
|| "TRACE".equalsIgnoreCase(method) || "PATCH".equalsIgnoreCase(method)) {
mInitialMethod = method;
} else {
throw new IllegalArgumentException("Invalid http method " + method);
}
}
private void checkNotStarted() {
@State
int state = mState.get();
if (state != State.NOT_STARTED) {
throw new IllegalStateException("Request is already started. State is: " + state);
}
}
@Override
public void addHeader(String header, String value) {
checkNotStarted();
if (!isValidHeaderName(header) || value.contains("\r\n")) {
throw new IllegalArgumentException("Invalid header " + header + "=" + value);
}
if (mRequestHeaders.containsKey(header)) {
mRequestHeaders.remove(header);
}
mRequestHeaders.put(header, value);
}
private boolean isValidHeaderName(String header) {
for (int i = 0; i < header.length(); i++) {
char c = header.charAt(i);
switch (c) {
case '(':
case ')':
case '<':
case '>':
case '@':
case ',':
case ';':
case ':':
case '\\':
case '\'':
case '/':
case '[':
case ']':
case '?':
case '=':
case '{':
case '}':
return false;
default: {
if (Character.isISOControl(c) || Character.isWhitespace(c)) {
return false;
}
}
}
}
return true;
}
@Override
public void setUploadDataProvider(UploadDataProvider uploadDataProvider, Executor executor) {
if (uploadDataProvider == null) {
throw new NullPointerException("Invalid UploadDataProvider.");
}
if (!mRequestHeaders.containsKey("Content-Type")) {
throw new IllegalArgumentException(
"Requests with upload data must have a Content-Type.");
}
checkNotStarted();
if (mInitialMethod == null) {
mInitialMethod = "POST";
}
this.mUploadDataProvider =
new VersionSafeCallbacks.UploadDataProviderWrapper(uploadDataProvider);
if (mAllowDirectExecutor) {
this.mUploadExecutor = executor;
} else {
this.mUploadExecutor = new DirectPreventingExecutor(executor);
}
}
private final class OutputStreamDataSink extends JavaUploadDataSinkBase {
private final HttpURLConnection mUrlConnection;
private final AtomicBoolean mOutputChannelClosed = new AtomicBoolean(false);
private WritableByteChannel mOutputChannel;
private OutputStream mUrlConnectionOutputStream;
OutputStreamDataSink(final Executor userExecutor, Executor executor,
HttpURLConnection urlConnection,
VersionSafeCallbacks.UploadDataProviderWrapper provider) {
super(userExecutor, executor, provider);
mUrlConnection = urlConnection;
}
@Override
protected void initializeRead() throws IOException {
if (mOutputChannel == null) {
mAdditionalStatusDetails = Status.CONNECTING;
mUrlConnection.setDoOutput(true);
mUrlConnection.connect();
mAdditionalStatusDetails = Status.SENDING_REQUEST;
mUrlConnectionOutputStream = mUrlConnection.getOutputStream();
mOutputChannel = Channels.newChannel(mUrlConnectionOutputStream);
}
}
void closeOutputChannel() throws IOException {
if (mOutputChannel != null
&& mOutputChannelClosed.compareAndSet(
/* expected= */ false, /* updated= */ true)) {
mOutputChannel.close();
}
}
@Override
protected void finish() throws IOException {
closeOutputChannel();
fireGetHeaders();
}
@Override
protected void initializeStart(long totalBytes) {
if (totalBytes > 0) {
mUrlConnection.setFixedLengthStreamingMode(totalBytes);
} else {
mUrlConnection.setChunkedStreamingMode(DEFAULT_CHUNK_LENGTH);
}
}
@Override
protected int processSuccessfulRead(ByteBuffer buffer) throws IOException {
int totalBytesProcessed = 0;
while (buffer.hasRemaining()) {
totalBytesProcessed += mOutputChannel.write(buffer);
}
// Forces a chunk to be sent, rather than buffering to the DEFAULT_CHUNK_LENGTH.
// This allows clients to trickle-upload bytes as they become available without
// introducing latency due to buffering.
mUrlConnectionOutputStream.flush();
return totalBytesProcessed;
}
@Override
protected Runnable getErrorSettingRunnable(CheckedRunnable runnable) {
return errorSetting(runnable);
}
@Override
protected Runnable getUploadErrorSettingRunnable(CheckedRunnable runnable) {
return uploadErrorSetting(runnable);
}
@Override
protected void processUploadError(Throwable exception) {
enterUploadErrorState(exception);
}
}
@Override
public void start() {
mAdditionalStatusDetails = Status.CONNECTING;
transitionStates(State.NOT_STARTED, State.STARTED, new Runnable() {
@Override
public void run() {
mUrlChain.add(mCurrentUrl);
fireOpenConnection();
}
});
}
private void enterErrorState(final CronetException error) {
if (setTerminalState(State.ERROR)) {
fireDisconnect();
fireCloseUploadDataProvider();
mCallbackAsync.onFailed(mUrlResponseInfo, error);
}
}
private boolean setTerminalState(@State int error) {
while (true) {
@State
int oldState = mState.get();
switch (oldState) {
case State.NOT_STARTED:
throw new IllegalStateException("Can't enter error state before start");
case State.ERROR: // fallthrough
case State.COMPLETE: // fallthrough
case State.CANCELLED:
return false; // Already in a terminal state
default: {
if (mState.compareAndSet(/* expected= */ oldState, /* updated= */ error)) {
return true;
}
}
}
}
}
/** Ends the request with an error, caused by an exception thrown from user code. */
private void enterUserErrorState(final Throwable error) {
enterErrorState(
new CallbackExceptionImpl("Exception received from UrlRequest.Callback", error));
}
/** Ends the request with an error, caused by an exception thrown from user code. */
private void enterUploadErrorState(final Throwable error) {
enterErrorState(
new CallbackExceptionImpl("Exception received from UploadDataProvider", error));
}
private void enterCronetErrorState(final Throwable error) {
// TODO(clm) mapping from Java exception (UnknownHostException, for example) to net error
// code goes here.
enterErrorState(new CronetExceptionImpl("System error", error));
}
/**
* Atomically swaps from the expected state to a new state. If the swap fails, and it's not
* due to an earlier error or cancellation, throws an exception.
*
* @param afterTransition Callback to run after transition completes successfully.
*/
private void transitionStates(
@State int expected, @State int newState, Runnable afterTransition) {
if (!mState.compareAndSet(expected, newState)) {
@State
int state = mState.get();
if (!(state == State.CANCELLED || state == State.ERROR)) {
throw new IllegalStateException(
"Invalid state transition - expected " + expected + " but was " + state);
}
} else {
afterTransition.run();
}
}
@Override
public void followRedirect() {
transitionStates(State.AWAITING_FOLLOW_REDIRECT, State.STARTED, new Runnable() {
@Override
public void run() {
mCurrentUrl = mPendingRedirectUrl;
mPendingRedirectUrl = null;
fireOpenConnection();
}
});
}
private void fireGetHeaders() {
mAdditionalStatusDetails = Status.WAITING_FOR_RESPONSE;
mExecutor.execute(errorSetting(new CheckedRunnable() {
@Override
public void run() throws Exception {
if (mCurrentUrlConnection == null) {
return; // We've been cancelled
}
final List<Map.Entry<String, String>> headerList = new ArrayList<>();
String selectedTransport = "http/1.1";
String headerKey;
for (int i = 0; (headerKey = mCurrentUrlConnection.getHeaderFieldKey(i)) != null;
i++) {
if (X_ANDROID_SELECTED_TRANSPORT.equalsIgnoreCase(headerKey)) {
selectedTransport = mCurrentUrlConnection.getHeaderField(i);
}
if (!headerKey.startsWith(X_ANDROID)) {
headerList.add(new SimpleEntry<>(
headerKey, mCurrentUrlConnection.getHeaderField(i)));
}
}
int responseCode = mCurrentUrlConnection.getResponseCode();
// Important to copy the list here, because although we never concurrently modify
// the list ourselves, user code might iterate over it while we're redirecting, and
// that would throw ConcurrentModificationException.
mUrlResponseInfo = new UrlResponseInfoImpl(new ArrayList<>(mUrlChain), responseCode,
mCurrentUrlConnection.getResponseMessage(),
Collections.unmodifiableList(headerList), false, selectedTransport, "", 0);
// TODO(clm) actual redirect handling? post -> get and whatnot?
if (responseCode >= 300 && responseCode < 400) {
List<String> locationFields = mUrlResponseInfo.getAllHeaders().get("location");
if (locationFields != null) {
fireRedirectReceived(locationFields.get(0));
return;
}
}
fireCloseUploadDataProvider();
if (responseCode >= 400) {
InputStream inputStream = mCurrentUrlConnection.getErrorStream();
mResponseChannel =
inputStream == null ? null : InputStreamChannel.wrap(inputStream);
mCallbackAsync.onResponseStarted(mUrlResponseInfo);
} else {
mResponseChannel =
InputStreamChannel.wrap(mCurrentUrlConnection.getInputStream());
mCallbackAsync.onResponseStarted(mUrlResponseInfo);
}
}
}));
}
private void fireCloseUploadDataProvider() {
if (mUploadDataProvider != null
&& mUploadProviderClosed.compareAndSet(
/* expected= */ false, /* updated= */ true)) {
try {
mUploadExecutor.execute(uploadErrorSetting(new CheckedRunnable() {
@Override
public void run() throws Exception {
mUploadDataProvider.close();
}
}));
} catch (RejectedExecutionException e) {
Log.e(TAG, "Exception when closing uploadDataProvider", e);
}
}
}
private void fireRedirectReceived(final String locationField) {
transitionStates(State.STARTED, State.REDIRECT_RECEIVED, new Runnable() {
@Override
public void run() {
mPendingRedirectUrl = URI.create(mCurrentUrl).resolve(locationField).toString();
mUrlChain.add(mPendingRedirectUrl);
transitionStates(
State.REDIRECT_RECEIVED, State.AWAITING_FOLLOW_REDIRECT, new Runnable() {
@Override
public void run() {
mCallbackAsync.onRedirectReceived(
mUrlResponseInfo, mPendingRedirectUrl);
}
});
}
});
}
private void fireOpenConnection() {
mExecutor.execute(errorSetting(new CheckedRunnable() {
@Override
public void run() throws Exception {
// If we're cancelled, then our old connection will be disconnected for us and
// we shouldn't open a new one.
if (mState.get() == State.CANCELLED) {
return;
}
final URL url = new URL(mCurrentUrl);
if (mCurrentUrlConnection != null) {
mCurrentUrlConnection.disconnect();
mCurrentUrlConnection = null;
}
mCurrentUrlConnection = (HttpURLConnection) url.openConnection();
mCurrentUrlConnection.setInstanceFollowRedirects(false);
if (!mRequestHeaders.containsKey(USER_AGENT)) {
mRequestHeaders.put(USER_AGENT, mUserAgent);
}
for (Map.Entry<String, String> entry : mRequestHeaders.entrySet()) {
mCurrentUrlConnection.setRequestProperty(entry.getKey(), entry.getValue());
}
if (mInitialMethod == null) {
mInitialMethod = "GET";
}
mCurrentUrlConnection.setRequestMethod(mInitialMethod);
if (mUploadDataProvider != null) {
mOutputStreamDataSink = new OutputStreamDataSink(
mUploadExecutor, mExecutor, mCurrentUrlConnection, mUploadDataProvider);
mOutputStreamDataSink.start(mUrlChain.size() == 1);
} else {
mAdditionalStatusDetails = Status.CONNECTING;
mCurrentUrlConnection.connect();
fireGetHeaders();
}
}
}));
}
private Runnable errorSetting(final CheckedRunnable delegate) {
return new Runnable() {
@Override
public void run() {
try {
delegate.run();
} catch (Throwable t) {
enterCronetErrorState(t);
}
}
};
}
private Runnable userErrorSetting(final CheckedRunnable delegate) {
return new Runnable() {
@Override
public void run() {
try {
delegate.run();
} catch (Throwable t) {
enterUserErrorState(t);
}
}
};
}
private Runnable uploadErrorSetting(final CheckedRunnable delegate) {
return new Runnable() {
@Override
public void run() {
try {
delegate.run();
} catch (Throwable t) {
enterUploadErrorState(t);
}
}
};
}
@Override
public void read(final ByteBuffer buffer) {
Preconditions.checkDirect(buffer);
Preconditions.checkHasRemaining(buffer);
transitionStates(State.AWAITING_READ, State.READING, new Runnable() {
@Override
public void run() {
mExecutor.execute(errorSetting(new CheckedRunnable() {
@Override
public void run() throws Exception {
int read = mResponseChannel == null ? -1 : mResponseChannel.read(buffer);
processReadResult(read, buffer);
}
}));
}
});
}
private void processReadResult(int read, final ByteBuffer buffer) throws IOException {
if (read != -1) {
mCallbackAsync.onReadCompleted(mUrlResponseInfo, buffer);
} else {
if (mResponseChannel != null) {
mResponseChannel.close();
}
if (mState.compareAndSet(
/* expected= */ State.READING, /* updated= */ State.COMPLETE)) {
fireDisconnect();
mCallbackAsync.onSucceeded(mUrlResponseInfo);
}
}
}
private void fireDisconnect() {
mExecutor.execute(new Runnable() {
@Override
public void run() {
if (mOutputStreamDataSink != null) {
try {
mOutputStreamDataSink.closeOutputChannel();
} catch (IOException e) {
Log.e(TAG, "Exception when closing OutputChannel", e);
}
}
if (mCurrentUrlConnection != null) {
mCurrentUrlConnection.disconnect();
mCurrentUrlConnection = null;
}
}
});
}
@Override
public void cancel() {
@State
int oldState = mState.getAndSet(State.CANCELLED);
switch (oldState) {
// We've just scheduled some user code to run. When they perform their next operation,
// they'll observe it and fail. However, if user code is cancelling in response to one
// of these callbacks, we'll never actually cancel!
// TODO(clm) figure out if it's possible to avoid concurrency in user callbacks.
case State.REDIRECT_RECEIVED:
case State.AWAITING_FOLLOW_REDIRECT:
case State.AWAITING_READ:
// User code is waiting on us - cancel away!
case State.STARTED:
case State.READING:
fireDisconnect();
fireCloseUploadDataProvider();
mCallbackAsync.onCanceled(mUrlResponseInfo);
break;
// The rest are all termination cases - we're too late to cancel.
case State.ERROR:
case State.COMPLETE:
case State.CANCELLED:
break;
default:
break;
}
}
@Override
public boolean isDone() {
@State
int state = mState.get();
return state == State.COMPLETE || state == State.ERROR || state == State.CANCELLED;
}
@Override
public void getStatus(StatusListener listener) {
@State
int state = mState.get();
int extraStatus = this.mAdditionalStatusDetails;
@StatusValues
final int status;
switch (state) {
case State.ERROR:
case State.COMPLETE:
case State.CANCELLED:
case State.NOT_STARTED:
status = Status.INVALID;
break;
case State.STARTED:
status = extraStatus;
break;
case State.REDIRECT_RECEIVED:
case State.AWAITING_FOLLOW_REDIRECT:
case State.AWAITING_READ:
status = Status.IDLE;
break;
case State.READING:
status = Status.READING_RESPONSE;
break;
default:
throw new IllegalStateException("Switch is exhaustive: " + state);
}
mCallbackAsync.sendStatus(
new VersionSafeCallbacks.UrlRequestStatusListener(listener), status);
}
/** This wrapper ensures that callbacks are always called on the correct executor */
private final class AsyncUrlRequestCallback {
final VersionSafeCallbacks.UrlRequestCallback mCallback;
final Executor mUserExecutor;
final Executor mFallbackExecutor;
AsyncUrlRequestCallback(Callback callback, final Executor userExecutor) {
this.mCallback = new VersionSafeCallbacks.UrlRequestCallback(callback);
if (mAllowDirectExecutor) {
this.mUserExecutor = userExecutor;
this.mFallbackExecutor = null;
} else {
mUserExecutor = new DirectPreventingExecutor(userExecutor);
mFallbackExecutor = userExecutor;
}
}
void sendStatus(
final VersionSafeCallbacks.UrlRequestStatusListener listener, final int status) {
mUserExecutor.execute(new Runnable() {
@Override
public void run() {
listener.onStatus(status);
}
});
}
void execute(CheckedRunnable runnable) {
try {
mUserExecutor.execute(userErrorSetting(runnable));
} catch (RejectedExecutionException e) {
enterErrorState(new CronetExceptionImpl("Exception posting task to executor", e));
}
}
void onRedirectReceived(final UrlResponseInfo info, final String newLocationUrl) {
execute(new CheckedRunnable() {
@Override
public void run() throws Exception {
mCallback.onRedirectReceived(JavaUrlRequest.this, info, newLocationUrl);
}
});
}
void onResponseStarted(UrlResponseInfo info) {
execute(new CheckedRunnable() {
@Override
public void run() throws Exception {
if (mState.compareAndSet(/* expected= */ State.STARTED,
/* updated= */ State.AWAITING_READ)) {
mCallback.onResponseStarted(JavaUrlRequest.this, mUrlResponseInfo);
}
}
});
}
void onReadCompleted(final UrlResponseInfo info, final ByteBuffer byteBuffer) {
execute(new CheckedRunnable() {
@Override
public void run() throws Exception {
if (mState.compareAndSet(/* expected= */ State.READING,
/* updated= */ State.AWAITING_READ)) {
mCallback.onReadCompleted(JavaUrlRequest.this, info, byteBuffer);
}
}
});
}
void onCanceled(final UrlResponseInfo info) {
closeResponseChannel();
mUserExecutor.execute(new Runnable() {
@Override
public void run() {
try {
mCallback.onCanceled(JavaUrlRequest.this, info);
} catch (Exception exception) {
Log.e(TAG, "Exception in onCanceled method", exception);
}
}
});
}
void onSucceeded(final UrlResponseInfo info) {
mUserExecutor.execute(new Runnable() {
@Override
public void run() {
try {
mCallback.onSucceeded(JavaUrlRequest.this, info);
} catch (Exception exception) {
Log.e(TAG, "Exception in onSucceeded method", exception);
}
}
});
}
void onFailed(final UrlResponseInfo urlResponseInfo, final CronetException e) {
closeResponseChannel();
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
mCallback.onFailed(JavaUrlRequest.this, urlResponseInfo, e);
} catch (Exception exception) {
Log.e(TAG, "Exception in onFailed method", exception);
}
}
};
try {
mUserExecutor.execute(runnable);
} catch (InlineExecutionProhibitedException wasDirect) {
if (mFallbackExecutor != null) {
mFallbackExecutor.execute(runnable);
}
}
}
}
private void closeResponseChannel() {
mExecutor.execute(new Runnable() {
@Override
public void run() {
if (mResponseChannel != null) {
try {
mResponseChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
mResponseChannel = null;
}
}
});
}
}

View File

@ -0,0 +1,121 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import androidx.annotation.IntDef;
import org.chromium.net.InlineExecutionProhibitedException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
/**
* Utilities for Java-based UrlRequest implementations.
* {@hide}
*/
public final class JavaUrlRequestUtils {
/**
* State interface for keeping track of the internal state of a {@link UrlRequest}.
*
* /- AWAITING_FOLLOW_REDIRECT <- REDIRECT_RECEIVED <-\ /- READING <--\
* | | | |
* V / V /
* NOT_STARTED -> STARTED -----------------------------------------------> AWAITING_READ -------
* --> COMPLETE
*
*
*/
@IntDef({State.NOT_STARTED, State.STARTED, State.REDIRECT_RECEIVED,
State.AWAITING_FOLLOW_REDIRECT, State.AWAITING_READ, State.READING, State.ERROR,
State.COMPLETE, State.CANCELLED})
@Retention(RetentionPolicy.SOURCE)
public @interface State {
int NOT_STARTED = 0;
int STARTED = 1;
int REDIRECT_RECEIVED = 2;
int AWAITING_FOLLOW_REDIRECT = 3;
int AWAITING_READ = 4;
int READING = 5;
int ERROR = 6;
int COMPLETE = 7;
int CANCELLED = 8;
}
/**
* Interface used to run commands that could throw an exception. Specifically useful for
* calling {@link UrlRequest.Callback}s on a user-supplied executor.
*/
public interface CheckedRunnable { void run() throws Exception; }
/**
* Executor that detects and throws if its mDelegate runs a submitted runnable inline.
*/
public static final class DirectPreventingExecutor implements Executor {
private final Executor mDelegate;
/**
* Constructs an {@link DirectPreventingExecutor} that executes {@link runnable}s on the
* provided {@link Executor}.
*
* @param delegate the {@link Executor} used to run {@link Runnable}s
*/
public DirectPreventingExecutor(Executor delegate) {
this.mDelegate = delegate;
}
/**
* Executes a {@link Runnable} on this {@link Executor} and throws an exception if it is
* being run on the same thread as the calling thread.
*
* @param command the {@link Runnable} to attempt to run
*/
@Override
public void execute(Runnable command) {
Thread currentThread = Thread.currentThread();
InlineCheckingRunnable runnable = new InlineCheckingRunnable(command, currentThread);
mDelegate.execute(runnable);
// This next read doesn't require synchronization; only the current thread could have
// written to runnable.mExecutedInline.
if (runnable.mExecutedInline != null) {
throw runnable.mExecutedInline;
} else {
// It's possible that this method is being called on an executor, and the runnable
// that was just queued will run on this thread after the current runnable returns.
// By nulling out the mCallingThread field, the InlineCheckingRunnable's current
// thread comparison will not fire.
//
// Java reference assignment is always atomic (no tearing, even on 64-bit VMs, see
// JLS 17.7), but other threads aren't guaranteed to ever see updates without
// something like locking, volatile, or AtomicReferences. We're ok in
// this instance, since this write only needs to be seen in the case that
// InlineCheckingRunnable.run() runs on the same thread as this execute() method.
runnable.mCallingThread = null;
}
}
private static final class InlineCheckingRunnable implements Runnable {
private final Runnable mCommand;
private Thread mCallingThread;
private InlineExecutionProhibitedException mExecutedInline;
private InlineCheckingRunnable(Runnable command, Thread callingThread) {
this.mCommand = command;
this.mCallingThread = callingThread;
}
@Override
public void run() {
if (Thread.currentThread() == mCallingThread) {
// Can't throw directly from here, since the delegate executor could catch this
// exception.
mExecutedInline = new InlineExecutionProhibitedException();
return;
}
mCommand.run();
}
}
}
}

View File

@ -0,0 +1,13 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
// A simple auto-generated interface used to list load states as used by
// org.chromium.net.RequestStatus.
public interface LoadState {
#define LOAD_STATE(x, y) public static final int x = y;
#include "net/base/load_states_list.h"
#undef LOAD_STATE
}

View File

@ -0,0 +1,40 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import android.content.Context;
import org.chromium.net.ExperimentalCronetEngine;
import org.chromium.net.ICronetEngineBuilder;
/**
* Implementation of {@link ICronetEngineBuilder} that builds native Cronet engine.
*/
public class NativeCronetEngineBuilderImpl extends CronetEngineBuilderImpl {
/**
* Builder for Native Cronet Engine.
* Default config enables SPDY, disables QUIC and HTTP cache.
*
* @param context Android {@link Context} for engine to use.
*/
public NativeCronetEngineBuilderImpl(Context context) {
super(context);
}
@Override
public ExperimentalCronetEngine build() {
if (getUserAgent() == null) {
setUserAgent(getDefaultUserAgent());
}
ExperimentalCronetEngine builder = new CronetUrlRequestContext(this);
// Clear MOCK_CERT_VERIFIER reference if there is any, since
// the ownership has been transferred to the engine.
mMockCertVerifier = 0;
return builder;
}
}

View File

@ -0,0 +1,39 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import android.content.Context;
import org.chromium.net.CronetEngine.Builder.LibraryLoader;
import org.chromium.net.ICronetEngineBuilder;
/**
* An extension of {@link NativeCronetEngineBuilderImpl} that implements
* {@link ICronetEngineBuilder#setLibraryLoader}.
*/
public class NativeCronetEngineBuilderWithLibraryLoaderImpl extends NativeCronetEngineBuilderImpl {
private VersionSafeCallbacks.LibraryLoader mLibraryLoader;
/**
* Constructs a builder for Native Cronet Engine.
* Default config enables SPDY, disables QUIC and HTTP cache.
*
* @param context Android {@link Context} for engine to use.
*/
public NativeCronetEngineBuilderWithLibraryLoaderImpl(Context context) {
super(context);
}
@Override
public CronetEngineBuilderImpl setLibraryLoader(LibraryLoader loader) {
mLibraryLoader = new VersionSafeCallbacks.LibraryLoader(loader);
return this;
}
@Override
VersionSafeCallbacks.LibraryLoader libraryLoader() {
return mLibraryLoader;
}
}

View File

@ -0,0 +1,64 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import android.content.Context;
import org.chromium.base.annotations.UsedByReflection;
import org.chromium.net.CronetEngine;
import org.chromium.net.CronetProvider;
import org.chromium.net.ExperimentalCronetEngine;
import org.chromium.net.ICronetEngineBuilder;
import java.util.Arrays;
/**
* Implementation of {@link CronetProvider} that creates {@link CronetEngine.Builder}
* for building the native implementation of {@link CronetEngine}.
*/
public class NativeCronetProvider extends CronetProvider {
/**
* Constructor.
*
* @param context Android context to use.
*/
@UsedByReflection("CronetProvider.java")
public NativeCronetProvider(Context context) {
super(context);
}
@Override
public CronetEngine.Builder createBuilder() {
ICronetEngineBuilder impl = new NativeCronetEngineBuilderWithLibraryLoaderImpl(mContext);
return new ExperimentalCronetEngine.Builder(impl);
}
@Override
public String getName() {
return CronetProvider.PROVIDER_NAME_APP_PACKAGED;
}
@Override
public String getVersion() {
return ImplVersion.getCronetVersion();
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public int hashCode() {
return Arrays.hashCode(new Object[] {NativeCronetProvider.class, mContext});
}
@Override
public boolean equals(Object other) {
return other == this
|| (other instanceof NativeCronetProvider
&& this.mContext.equals(((NativeCronetProvider) other).mContext));
}
}

View File

@ -0,0 +1,74 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import org.chromium.net.NetworkException;
/**
* Implements {@link NetworkException}.
*/
public class NetworkExceptionImpl extends NetworkException {
// Error code, one of ERROR_*
protected final int mErrorCode;
// Cronet internal error code.
protected final int mCronetInternalErrorCode;
/**
* Constructs an exception with a specific error.
*
* @param message explanation of failure.
* @param errorCode error code, one of {@link #ERROR_HOSTNAME_NOT_RESOLVED ERROR_*}.
* @param cronetInternalErrorCode Cronet internal error code, one of
* <a href=https://chromium.googlesource.com/chromium/src/+/main/net/base/net_error_list.h>
* these</a>.
*/
public NetworkExceptionImpl(String message, int errorCode, int cronetInternalErrorCode) {
super(message, null);
assert errorCode > 0 && errorCode < 12;
assert cronetInternalErrorCode < 0;
mErrorCode = errorCode;
mCronetInternalErrorCode = cronetInternalErrorCode;
}
@Override
public int getErrorCode() {
return mErrorCode;
}
@Override
public int getCronetInternalErrorCode() {
return mCronetInternalErrorCode;
}
@Override
public boolean immediatelyRetryable() {
switch (mErrorCode) {
case ERROR_HOSTNAME_NOT_RESOLVED:
case ERROR_INTERNET_DISCONNECTED:
case ERROR_CONNECTION_REFUSED:
case ERROR_ADDRESS_UNREACHABLE:
case ERROR_OTHER:
default:
return false;
case ERROR_NETWORK_CHANGED:
case ERROR_TIMED_OUT:
case ERROR_CONNECTION_CLOSED:
case ERROR_CONNECTION_TIMED_OUT:
case ERROR_CONNECTION_RESET:
return true;
}
}
@Override
public String getMessage() {
StringBuilder b = new StringBuilder(super.getMessage());
b.append(", ErrorCode=").append(mErrorCode);
if (mCronetInternalErrorCode != 0) {
b.append(", InternalErrorCode=").append(mCronetInternalErrorCode);
}
b.append(", Retryable=").append(immediatelyRetryable());
return b.toString();
}
}

View File

@ -0,0 +1,26 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import java.nio.ByteBuffer;
/**
* Utility class to check preconditions.
*/
public final class Preconditions {
private Preconditions() {}
public static void checkDirect(ByteBuffer buffer) {
if (!buffer.isDirect()) {
throw new IllegalArgumentException("byteBuffer must be a direct ByteBuffer.");
}
}
public static void checkHasRemaining(ByteBuffer buffer) {
if (!buffer.hasRemaining()) {
throw new IllegalArgumentException("ByteBuffer is already full.");
}
}
}

View File

@ -0,0 +1,61 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import org.chromium.net.QuicException;
/**
* Implements {@link QuicException}.
*/
public class QuicExceptionImpl extends QuicException {
private final int mQuicDetailedErrorCode;
private final NetworkExceptionImpl mNetworkException;
/**
* Constructs an exception with a specific error.
*
* @param message explanation of failure.
* @param netErrorCode Error code from
* <a href=https://chromium.googlesource.com/chromium/src/+/main/net/base/net_error_list.h>
* this list</a>.
* @param quicDetailedErrorCode Detailed <a href="https://www.chromium.org/quic">QUIC</a> error
* code from <a
* href="https://cs.chromium.org/search/?q=symbol:%5CbQuicErrorCode%5Cb">
* QuicErrorCode</a>.
*/
public QuicExceptionImpl(
String message, int errorCode, int netErrorCode, int quicDetailedErrorCode) {
super(message, null);
mNetworkException = new NetworkExceptionImpl(message, errorCode, netErrorCode);
mQuicDetailedErrorCode = quicDetailedErrorCode;
}
@Override
public String getMessage() {
StringBuilder b = new StringBuilder(mNetworkException.getMessage());
b.append(", QuicDetailedErrorCode=").append(mQuicDetailedErrorCode);
return b.toString();
}
@Override
public int getErrorCode() {
return mNetworkException.getErrorCode();
}
@Override
public int getCronetInternalErrorCode() {
return mNetworkException.getCronetInternalErrorCode();
}
@Override
public boolean immediatelyRetryable() {
return mNetworkException.immediatelyRetryable();
}
@Override
public int getQuicDetailedErrorCode() {
return mQuicDetailedErrorCode;
}
}

View File

@ -0,0 +1,85 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import org.chromium.net.CronetException;
import org.chromium.net.RequestFinishedInfo;
import org.chromium.net.UrlResponseInfo;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.Collections;
/**
* Implements information about a finished request. Passed to {@link RequestFinishedInfo.Listener}.
*/
public class RequestFinishedInfoImpl extends RequestFinishedInfo {
private final String mUrl;
private final Collection<Object> mAnnotations;
private final RequestFinishedInfo.Metrics mMetrics;
@FinishedReason
private final int mFinishedReason;
@Nullable
private final UrlResponseInfo mResponseInfo;
@Nullable
private final CronetException mException;
@IntDef({SUCCEEDED, FAILED, CANCELED})
@Retention(RetentionPolicy.SOURCE)
public @interface FinishedReason {}
public RequestFinishedInfoImpl(String url, Collection<Object> annotations,
RequestFinishedInfo.Metrics metrics, @FinishedReason int finishedReason,
@Nullable UrlResponseInfo responseInfo, @Nullable CronetException exception) {
mUrl = url;
mAnnotations = annotations;
mMetrics = metrics;
mFinishedReason = finishedReason;
mResponseInfo = responseInfo;
mException = exception;
}
@Override
public String getUrl() {
return mUrl;
}
@Override
public Collection<Object> getAnnotations() {
if (mAnnotations == null) {
return Collections.emptyList();
}
return mAnnotations;
}
@Override
public Metrics getMetrics() {
return mMetrics;
}
@Override
@FinishedReason
public int getFinishedReason() {
return mFinishedReason;
}
@Override
@Nullable
public UrlResponseInfo getResponseInfo() {
return mResponseInfo;
}
@Override
@Nullable
public CronetException getException() {
return mException;
}
}

View File

@ -0,0 +1,128 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import androidx.annotation.IntDef;
import org.chromium.net.ExperimentalUrlRequest;
import org.chromium.net.UploadDataProvider;
import org.chromium.net.UrlRequest;
import org.chromium.net.UrlRequest.Status;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
/**
* Base class for classes that implement {@link UrlRequest} including experimental
* features. {@link CronetUrlRequest} and {@link JavaUrlRequest} extends this class.
*/
public abstract class UrlRequestBase extends ExperimentalUrlRequest {
/**
* Sets the HTTP method verb to use for this request. Must be done before
* request has started.
*
* <p>The default when this method is not called is "GET" if the request has
* no body or "POST" if it does.
*
* @param method "GET", "HEAD", "DELETE", "POST" or "PUT".
*/
protected abstract void setHttpMethod(String method);
/**
* Adds a request header. Must be done before request has started.
*
* @param header header name.
* @param value header value.
*/
protected abstract void addHeader(String header, String value);
/**
* Sets upload data provider. Must be done before request has started. May only be
* invoked once per request. Switches method to "POST" if not explicitly
* set. Starting the request will throw an exception if a Content-Type
* header is not set.
*
* @param uploadDataProvider responsible for providing the upload data.
* @param executor All {@code uploadDataProvider} methods will be invoked
* using this {@code Executor}. May optionally be the same
* {@code Executor} the request itself is using.
*/
protected abstract void setUploadDataProvider(
UploadDataProvider uploadDataProvider, Executor executor);
/**
* Possible URL Request statuses.
*/
@IntDef({Status.INVALID, Status.IDLE, Status.WAITING_FOR_STALLED_SOCKET_POOL,
Status.WAITING_FOR_AVAILABLE_SOCKET, Status.WAITING_FOR_DELEGATE,
Status.WAITING_FOR_CACHE, Status.DOWNLOADING_PAC_FILE, Status.RESOLVING_PROXY_FOR_URL,
Status.RESOLVING_HOST_IN_PAC_FILE, Status.ESTABLISHING_PROXY_TUNNEL,
Status.RESOLVING_HOST, Status.CONNECTING, Status.SSL_HANDSHAKE, Status.SENDING_REQUEST,
Status.WAITING_FOR_RESPONSE, Status.READING_RESPONSE})
@Retention(RetentionPolicy.SOURCE)
public @interface StatusValues {}
/**
* Convert a LoadState int to one of values listed above.
* @param loadState a LoadState to convert.
* @return static int Status.
*/
@StatusValues
public static int convertLoadState(int loadState) {
assert loadState >= LoadState.IDLE && loadState <= LoadState.READING_RESPONSE;
switch (loadState) {
case (LoadState.IDLE):
return Status.IDLE;
case (LoadState.WAITING_FOR_STALLED_SOCKET_POOL):
return Status.WAITING_FOR_STALLED_SOCKET_POOL;
case (LoadState.WAITING_FOR_AVAILABLE_SOCKET):
return Status.WAITING_FOR_AVAILABLE_SOCKET;
case (LoadState.WAITING_FOR_DELEGATE):
return Status.WAITING_FOR_DELEGATE;
case (LoadState.WAITING_FOR_CACHE):
return Status.WAITING_FOR_CACHE;
case (LoadState.DOWNLOADING_PAC_FILE):
return Status.DOWNLOADING_PAC_FILE;
case (LoadState.RESOLVING_PROXY_FOR_URL):
return Status.RESOLVING_PROXY_FOR_URL;
case (LoadState.RESOLVING_HOST_IN_PAC_FILE):
return Status.RESOLVING_HOST_IN_PAC_FILE;
case (LoadState.ESTABLISHING_PROXY_TUNNEL):
return Status.ESTABLISHING_PROXY_TUNNEL;
case (LoadState.RESOLVING_HOST):
return Status.RESOLVING_HOST;
case (LoadState.CONNECTING):
return Status.CONNECTING;
case (LoadState.SSL_HANDSHAKE):
return Status.SSL_HANDSHAKE;
case (LoadState.SENDING_REQUEST):
return Status.SENDING_REQUEST;
case (LoadState.WAITING_FOR_RESPONSE):
return Status.WAITING_FOR_RESPONSE;
case (LoadState.READING_RESPONSE):
return Status.READING_RESPONSE;
default:
// A load state is retrieved but there is no corresponding
// request status. This most likely means that the mapping is
// incorrect.
throw new IllegalArgumentException("No request status found.");
}
}
}

View File

@ -0,0 +1,224 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import android.annotation.SuppressLint;
import android.util.Log;
import android.util.Pair;
import org.chromium.net.CronetEngine;
import org.chromium.net.ExperimentalUrlRequest;
import org.chromium.net.RequestFinishedInfo;
import org.chromium.net.UploadDataProvider;
import org.chromium.net.UrlRequest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Executor;
/**
* Implements {@link org.chromium.net.ExperimentalUrlRequest.Builder}.
*/
public class UrlRequestBuilderImpl extends ExperimentalUrlRequest.Builder {
private static final String ACCEPT_ENCODING = "Accept-Encoding";
private static final String TAG = UrlRequestBuilderImpl.class.getSimpleName();
// All fields are temporary storage of ExperimentalUrlRequest configuration to be
// copied to built ExperimentalUrlRequest.
// CronetEngineBase to execute request.
private final CronetEngineBase mCronetEngine;
// URL to request.
private final String mUrl;
// Callback to receive progress callbacks.
private final UrlRequest.Callback mCallback;
// Executor to invoke callback on.
private final Executor mExecutor;
// HTTP method (e.g. GET, POST etc).
private String mMethod;
// List of request headers, stored as header field name and value pairs.
private final ArrayList<Pair<String, String>> mRequestHeaders = new ArrayList<>();
// Disable the cache for just this request.
private boolean mDisableCache;
// Disable connection migration for just this request.
private boolean mDisableConnectionMigration;
// Priority of request. Default is medium.
@CronetEngineBase.RequestPriority
private int mPriority = REQUEST_PRIORITY_MEDIUM;
// Request reporting annotations. Avoid extra object creation if no annotations added.
private Collection<Object> mRequestAnnotations;
// If request is an upload, this provides the request body data.
private UploadDataProvider mUploadDataProvider;
// Executor to call upload data provider back on.
private Executor mUploadDataProviderExecutor;
private boolean mAllowDirectExecutor;
private boolean mTrafficStatsTagSet;
private int mTrafficStatsTag;
private boolean mTrafficStatsUidSet;
private int mTrafficStatsUid;
private RequestFinishedInfo.Listener mRequestFinishedListener;
// Idempotency of the request.
@CronetEngineBase.Idempotency
private int mIdempotency = DEFAULT_IDEMPOTENCY;
/**
* Creates a builder for {@link UrlRequest} objects. All callbacks for
* generated {@link UrlRequest} objects will be invoked on
* {@code executor}'s thread. {@code executor} must not run tasks on the
* current thread 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.
* @param cronetEngine {@link CronetEngine} used to execute this request.
*/
UrlRequestBuilderImpl(String url, UrlRequest.Callback callback, Executor executor,
CronetEngineBase cronetEngine) {
super();
if (url == null) {
throw new NullPointerException("URL is required.");
}
if (callback == null) {
throw new NullPointerException("Callback is required.");
}
if (executor == null) {
throw new NullPointerException("Executor is required.");
}
if (cronetEngine == null) {
throw new NullPointerException("CronetEngine is required.");
}
mUrl = url;
mCallback = callback;
mExecutor = executor;
mCronetEngine = cronetEngine;
}
@Override
public ExperimentalUrlRequest.Builder setHttpMethod(String method) {
if (method == null) {
throw new NullPointerException("Method is required.");
}
mMethod = method;
return this;
}
@Override
public UrlRequestBuilderImpl addHeader(String header, String value) {
if (header == null) {
throw new NullPointerException("Invalid header name.");
}
if (value == null) {
throw new NullPointerException("Invalid header value.");
}
if (ACCEPT_ENCODING.equalsIgnoreCase(header)) {
Log.w(TAG, "It's not necessary to set Accept-Encoding on requests - cronet will do"
+ " this automatically for you, and setting it yourself has no "
+ "effect. See https://crbug.com/581399 for details.",
new Exception());
return this;
}
mRequestHeaders.add(Pair.create(header, value));
return this;
}
@Override
public UrlRequestBuilderImpl disableCache() {
mDisableCache = true;
return this;
}
@Override
public UrlRequestBuilderImpl disableConnectionMigration() {
mDisableConnectionMigration = true;
return this;
}
@Override
public UrlRequestBuilderImpl setPriority(@CronetEngineBase.RequestPriority int priority) {
mPriority = priority;
return this;
}
@Override
public UrlRequestBuilderImpl setIdempotency(@CronetEngineBase.Idempotency int idempotency) {
mIdempotency = idempotency;
return this;
}
@Override
public UrlRequestBuilderImpl setUploadDataProvider(
UploadDataProvider uploadDataProvider, Executor executor) {
if (uploadDataProvider == null) {
throw new NullPointerException("Invalid UploadDataProvider.");
}
if (executor == null) {
throw new NullPointerException("Invalid UploadDataProvider Executor.");
}
if (mMethod == null) {
mMethod = "POST";
}
mUploadDataProvider = uploadDataProvider;
mUploadDataProviderExecutor = executor;
return this;
}
@Override
public UrlRequestBuilderImpl allowDirectExecutor() {
mAllowDirectExecutor = true;
return this;
}
@Override
public UrlRequestBuilderImpl addRequestAnnotation(Object annotation) {
if (annotation == null) {
throw new NullPointerException("Invalid metrics annotation.");
}
if (mRequestAnnotations == null) {
mRequestAnnotations = new ArrayList<>();
}
mRequestAnnotations.add(annotation);
return this;
}
@Override
public UrlRequestBuilderImpl setTrafficStatsTag(int tag) {
mTrafficStatsTagSet = true;
mTrafficStatsTag = tag;
return this;
}
@Override
public UrlRequestBuilderImpl setTrafficStatsUid(int uid) {
mTrafficStatsUidSet = true;
mTrafficStatsUid = uid;
return this;
}
@Override
public UrlRequestBuilderImpl setRequestFinishedListener(RequestFinishedInfo.Listener listener) {
mRequestFinishedListener = listener;
return this;
}
@Override
public UrlRequestBase build() {
@SuppressLint("WrongConstant") // TODO(jbudorick): Remove this after rolling to the N SDK.
final UrlRequestBase request = mCronetEngine.createRequest(mUrl, mCallback, mExecutor,
mPriority, mRequestAnnotations, mDisableCache, mDisableConnectionMigration,
mAllowDirectExecutor, mTrafficStatsTagSet, mTrafficStatsTag, mTrafficStatsUidSet,
mTrafficStatsUid, mRequestFinishedListener, mIdempotency);
if (mMethod != null) {
request.setHttpMethod(mMethod);
}
for (Pair<String, String> header : mRequestHeaders) {
request.addHeader(header.first, header.second);
}
if (mUploadDataProvider != null) {
request.setUploadDataProvider(mUploadDataProvider, mUploadDataProviderExecutor);
}
return request;
}
}

View File

@ -0,0 +1,177 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import org.chromium.net.UrlResponseInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* Implements the container for basic information about a response. Included in
* {@link org.chromium.net.UrlRequest.Callback} callbacks. Each
* {@link org.chromium.net.UrlRequest.Callback#onRedirectReceived onRedirectReceived()}
* callback gets a different copy of {@code UrlResponseInfo} describing a particular
* redirect response.
*/
public final class UrlResponseInfoImpl extends UrlResponseInfo {
private final List<String> mResponseInfoUrlChain;
private final int mHttpStatusCode;
private final String mHttpStatusText;
private final boolean mWasCached;
private final String mNegotiatedProtocol;
private final String mProxyServer;
private final AtomicLong mReceivedByteCount;
private final HeaderBlockImpl mHeaders;
/**
* Unmodifiable container of response headers or trailers.
*/
public static final class HeaderBlockImpl extends HeaderBlock {
private final List<Map.Entry<String, String>> mAllHeadersList;
private Map<String, List<String>> mHeadersMap;
HeaderBlockImpl(List<Map.Entry<String, String>> allHeadersList) {
mAllHeadersList = allHeadersList;
}
@Override
public List<Map.Entry<String, String>> getAsList() {
return mAllHeadersList;
}
@Override
public Map<String, List<String>> getAsMap() {
// This is potentially racy...but races will only result in wasted resource.
if (mHeadersMap != null) {
return mHeadersMap;
}
Map<String, List<String>> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (Map.Entry<String, String> entry : mAllHeadersList) {
List<String> values = new ArrayList<String>();
if (map.containsKey(entry.getKey())) {
values.addAll(map.get(entry.getKey()));
}
values.add(entry.getValue());
map.put(entry.getKey(), Collections.unmodifiableList(values));
}
mHeadersMap = Collections.unmodifiableMap(map);
return mHeadersMap;
}
}
/**
* Creates an implementation of {@link UrlResponseInfo}.
*
* @param urlChain the URL chain. The first entry is the originally requested URL;
* the following entries are redirects followed.
* @param httpStatusCode the HTTP status code.
* @param httpStatusText the HTTP status text of the status line.
* @param allHeadersList list of response header field and value pairs.
* @param wasCached {@code true} if the response came from the cache, {@code false}
* otherwise.
* @param negotiatedProtocol the protocol negotiated with the server.
* @param proxyServer the proxy server that was used for the request.
* @param receivedByteCount minimum count of bytes received from the network to process this
* request.
*/
public UrlResponseInfoImpl(List<String> urlChain, int httpStatusCode, String httpStatusText,
List<Map.Entry<String, String>> allHeadersList, boolean wasCached,
String negotiatedProtocol, String proxyServer, long receivedByteCount) {
mResponseInfoUrlChain = Collections.unmodifiableList(urlChain);
mHttpStatusCode = httpStatusCode;
mHttpStatusText = httpStatusText;
mHeaders = new HeaderBlockImpl(Collections.unmodifiableList(allHeadersList));
mWasCached = wasCached;
mNegotiatedProtocol = negotiatedProtocol;
mProxyServer = proxyServer;
mReceivedByteCount = new AtomicLong(receivedByteCount);
}
/**
* Constructor for backwards compatibility. See main constructor above for more info.
*/
@Deprecated
public UrlResponseInfoImpl(List<String> urlChain, int httpStatusCode, String httpStatusText,
List<Map.Entry<String, String>> allHeadersList, boolean wasCached,
String negotiatedProtocol, String proxyServer) {
this(urlChain, httpStatusCode, httpStatusText, allHeadersList, wasCached,
negotiatedProtocol, proxyServer, 0);
}
@Override
public String getUrl() {
return mResponseInfoUrlChain.get(mResponseInfoUrlChain.size() - 1);
}
@Override
public List<String> getUrlChain() {
return mResponseInfoUrlChain;
}
@Override
public int getHttpStatusCode() {
return mHttpStatusCode;
}
@Override
public String getHttpStatusText() {
return mHttpStatusText;
}
@Override
public List<Map.Entry<String, String>> getAllHeadersAsList() {
return mHeaders.getAsList();
}
@Override
public Map<String, List<String>> getAllHeaders() {
return mHeaders.getAsMap();
}
@Override
public boolean wasCached() {
return mWasCached;
}
@Override
public String getNegotiatedProtocol() {
return mNegotiatedProtocol;
}
@Override
public String getProxyServer() {
return mProxyServer;
}
@Override
public long getReceivedByteCount() {
return mReceivedByteCount.get();
}
@Override
public String toString() {
return String.format(Locale.ROOT, "UrlResponseInfo@[%s][%s]: urlChain = %s, "
+ "httpStatus = %d %s, headers = %s, wasCached = %b, "
+ "negotiatedProtocol = %s, proxyServer= %s, receivedByteCount = %d",
// Prevent asserting on the contents of this string
Integer.toHexString(System.identityHashCode(this)), getUrl(),
getUrlChain().toString(), getHttpStatusCode(), getHttpStatusText(),
getAllHeadersAsList().toString(), wasCached(), getNegotiatedProtocol(),
getProxyServer(), getReceivedByteCount());
}
/**
* Sets mReceivedByteCount. Must not be called after request completion or cancellation.
*/
public void setReceivedByteCount(long currentReceivedByteCount) {
mReceivedByteCount.set(currentReceivedByteCount);
}
}

View File

@ -0,0 +1,103 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import java.util.Locale;
/**
* Constructs a User-Agent string.
*/
public final class UserAgent {
private static final Object sLock = new Object();
private static final int VERSION_CODE_UNINITIALIZED = 0;
private static int sVersionCode = VERSION_CODE_UNINITIALIZED;
private UserAgent() {}
/**
* Constructs a User-Agent string including application name and version,
* system build version, model and Id, and Cronet version.
* @param context the context to fetch the application name and version
* from.
* @return User-Agent string.
*/
public static String from(Context context) {
StringBuilder builder = new StringBuilder();
// Our package name and version.
builder.append(context.getPackageName());
builder.append('/');
builder.append(versionFromContext(context));
// The platform version.
builder.append(" (Linux; U; Android ");
builder.append(Build.VERSION.RELEASE);
builder.append("; ");
builder.append(Locale.getDefault().toString());
String model = Build.MODEL;
if (model.length() > 0) {
builder.append("; ");
builder.append(model);
}
String id = Build.ID;
if (id.length() > 0) {
builder.append("; Build/");
builder.append(id);
}
builder.append(";");
appendCronetVersion(builder);
builder.append(')');
return builder.toString();
}
/**
* Constructs default QUIC User Agent Id string including application name
* and Cronet version.
* @param context the context to fetch the application name from.
* @return User-Agent string.
*/
static String getQuicUserAgentIdFrom(Context context) {
StringBuilder builder = new StringBuilder();
// Application name and cronet version.
builder.append(context.getPackageName());
appendCronetVersion(builder);
return builder.toString();
}
private static int versionFromContext(Context context) {
synchronized (sLock) {
if (sVersionCode == VERSION_CODE_UNINITIALIZED) {
PackageManager packageManager = context.getPackageManager();
String packageName = context.getPackageName();
try {
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
sVersionCode = packageInfo.versionCode;
} catch (NameNotFoundException e) {
throw new IllegalStateException("Cannot determine package version");
}
}
return sVersionCode;
}
}
private static void appendCronetVersion(StringBuilder builder) {
builder.append(" Cronet/");
builder.append(ImplVersion.getCronetVersion());
}
}

Some files were not shown because too many files have changed in this diff Show More