mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-23 22:06:12 +03:00
Add components
This commit is contained in:
parent
c647d3d363
commit
7743b2c008
271
src/components/cronet/BUILD.gn
Normal file
271
src/components/cronet/BUILD.gn
Normal 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",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
8
src/components/cronet/DEPS
Normal file
8
src/components/cronet/DEPS
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
include_rules = [
|
||||||
|
"+components/grpc_support",
|
||||||
|
"+components/metrics",
|
||||||
|
"+components/prefs",
|
||||||
|
"+net",
|
||||||
|
"+third_party/metrics_proto",
|
||||||
|
"+third_party/zlib",
|
||||||
|
]
|
5
src/components/cronet/DIR_METADATA
Normal file
5
src/components/cronet/DIR_METADATA
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
monorail {
|
||||||
|
component: "Internals>Network>Library"
|
||||||
|
}
|
||||||
|
|
||||||
|
team_email: "net-dev@chromium.org"
|
5
src/components/cronet/OWNERS
Normal file
5
src/components/cronet/OWNERS
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
cleborgne@google.com
|
||||||
|
danstahr@google.com
|
||||||
|
sporeba@google.com
|
||||||
|
torne@chromium.org
|
||||||
|
file://net/OWNERS
|
106
src/components/cronet/PRESUBMIT.py
Normal file
106
src/components/cronet/PRESUBMIT.py
Normal 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)
|
176
src/components/cronet/README.md
Normal file
176
src/components/cronet/README.md
Normal 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
|
0
src/components/cronet/__init__.py
Normal file
0
src/components/cronet/__init__.py
Normal file
1582
src/components/cronet/android/BUILD.gn
Normal file
1582
src/components/cronet/android/BUILD.gn
Normal file
File diff suppressed because it is too large
Load Diff
5
src/components/cronet/android/DEPS
Normal file
5
src/components/cronet/android/DEPS
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
include_rules = [
|
||||||
|
"+components/metrics",
|
||||||
|
"+crypto",
|
||||||
|
"+jni",
|
||||||
|
]
|
1
src/components/cronet/android/OWNERS
Normal file
1
src/components/cronet/android/OWNERS
Normal file
@ -0,0 +1 @@
|
|||||||
|
per-file lint-*.xml=*
|
@ -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:
|
||||||
|
*;
|
||||||
|
};
|
401
src/components/cronet/android/api.txt
Normal file
401
src/components/cronet/android/api.txt
Normal 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
|
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:keep="@string/CronetProviderClassName" />
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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 {}
|
||||||
|
}
|
@ -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() {}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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.
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
1
src/components/cronet/android/api_version.txt
Normal file
1
src/components/cronet/android/api_version.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
14
|
@ -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
|
@ -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_
|
@ -0,0 +1 @@
|
|||||||
|
# Proguard config for apps that depend on cronet_impl_common_java.jar.
|
@ -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);
|
||||||
|
}
|
@ -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
|
||||||
|
|
@ -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);
|
||||||
|
}
|
@ -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
|
29
src/components/cronet/android/cronet_integrated_mode_state.h
Normal file
29
src/components/cronet/android/cronet_integrated_mode_state.h
Normal 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_
|
15
src/components/cronet/android/cronet_jni.cc
Normal file
15
src/components/cronet/android/cronet_jni.cc
Normal 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);
|
||||||
|
}
|
||||||
|
|
221
src/components/cronet/android/cronet_library_loader.cc
Normal file
221
src/components/cronet/android/cronet_library_loader.cc
Normal 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
|
17
src/components/cronet/android/cronet_library_loader.h
Normal file
17
src/components/cronet/android/cronet_library_loader.h
Normal 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_
|
@ -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
|
@ -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_
|
326
src/components/cronet/android/cronet_url_request_adapter.cc
Normal file
326
src/components/cronet/android/cronet_url_request_adapter.cc
Normal 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
|
167
src/components/cronet/android/cronet_url_request_adapter.h
Normal file
167
src/components/cronet/android/cronet_url_request_adapter.h
Normal 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_
|
@ -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
|
@ -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_
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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()));
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
41
src/components/cronet/android/io_buffer_with_byte_buffer.cc
Normal file
41
src/components/cronet/android/io_buffer_with_byte_buffer.cc
Normal 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
|
80
src/components/cronet/android/io_buffer_with_byte_buffer.h
Normal file
80
src/components/cronet/android/io_buffer_with_byte_buffer.h
Normal 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_
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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 {}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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
Loading…
Reference in New Issue
Block a user