Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions pkgs/cronet_http/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 1.10.0-wip

* Add DNS configuration options to `CronetEngine.build`:
`useBuiltInDnsResolver`, `enableStaleDns`, `persistHostCache` and
`persistHostCachePeriod`. Setting `useBuiltInDnsResolver: false` forces the
system DNS resolver, which works around `ERROR_HOSTNAME_NOT_RESOLVED`
failures seen with QUIC enabled on some cellular networks and in background
isolates (https://github.com/dart-lang/http/issues/1217).

## 1.9.0

* Add `CronetEngine.startNetLogToFile` and `CronetEngine.stopNetLog`.
Expand Down
43 changes: 43 additions & 0 deletions pkgs/cronet_http/example/integration_test/cronet_engine_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,48 @@ void testQuicHints() {
});
}

void testDnsOptions() {
group('dnsOptions', () {
late HttpServer server;

setUp(() async {
server = (await HttpServer.bind('localhost', 0))
..listen((request) async {
await request.drain<void>();
request.response.headers.set('Content-Type', 'text/plain');
await request.response.close();
});
});
tearDown(() {
server.close();
});

test('system resolver', () async {
final engine = CronetEngine.build(useBuiltInDnsResolver: false);
final client = CronetClient.fromCronetEngine(engine, closeEngine: true);
final response =
await client.get(Uri.parse('http://localhost:${server.port}'));
expect(response.statusCode, 200);
client.close();
});

test('persistent host cache', () async {
final engine = CronetEngine.build(
cacheMode: CacheMode.disk,
cacheMaxSize: 1024 * 1024,
storagePath: (await Directory.systemTemp.createTemp()).absolute.path,
enableStaleDns: true,
persistHostCache: true,
persistHostCachePeriod: const Duration(seconds: 1));
final client = CronetClient.fromCronetEngine(engine, closeEngine: true);
final response =
await client.get(Uri.parse('http://localhost:${server.port}'));
expect(response.statusCode, 200);
client.close();
});
});
}

void testNetLog() {
group('net log', () {
late HttpServer server;
Expand Down Expand Up @@ -226,6 +268,7 @@ void main() {
testInvalidConfigurations();
testUserAgent();
testQuicHints();
testDnsOptions();
testNetLog();
testEngineClose();
}
1 change: 1 addition & 0 deletions pkgs/cronet_http/jnigen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ classes:
- 'java.net.URL'
- 'java.util.concurrent.Executors'
- 'org.chromium.net.CronetEngine'
- 'org.chromium.net.DnsOptions'
- 'org.chromium.net.CallbackException'
- 'org.chromium.net.CronetException'
- 'org.chromium.net.NetworkException'
Expand Down
60 changes: 59 additions & 1 deletion pkgs/cronet_http/lib/src/cronet_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,30 @@ class CronetEngine {
/// of (host, port, alternativePort) that indicates that the host supports
/// QUIC. Note that [CacheMode.disk] or [CacheMode.diskNoHttp] is needed to
/// take advantage of 0-RTT connection establishment between sessions.
///
/// [useBuiltInDnsResolver] controls whether the engine uses Cronet's
/// built-in DNS resolver instead of the system resolver. The built-in
/// resolver is only used when QUIC is enabled, which it is by default.
/// Setting this to `false` forces the system resolver, which can work
/// around host resolution failures (`ERROR_HOSTNAME_NOT_RESOLVED`) seen
/// with QUIC on some cellular networks and in background isolates.
///
/// [enableStaleDns] controls whether the engine may use expired entries
/// from its host cache while a new resolution is in flight. Like
/// [useBuiltInDnsResolver], this only takes effect when QUIC is enabled.
///
/// [persistHostCache] controls whether the engine's host cache is
/// persisted to disk so that a freshly created engine (for example in a
/// newly spawned isolate) can resolve recently used hosts without a live
/// DNS query. Requires [storagePath] to be set.
///
/// [persistHostCachePeriod] sets how often the host cache is written to
/// disk when [persistHostCache] is `true`.
///
/// See [DnsOptions](https://developer.android.com/develop/connectivity/cronet/reference/org/chromium/net/DnsOptions)
/// for details on the DNS configuration options. On devices whose Cronet
/// implementation does not support DNS options natively, they are applied
/// through Cronet's experimental options fallback.
static CronetEngine build(
{CacheMode? cacheMode,
int? cacheMaxSize,
Expand All @@ -255,7 +279,11 @@ class CronetEngine {
bool? enableQuic,
String? storagePath,
String? userAgent,
List<(String, int, int)>? quicHints}) {
List<(String, int, int)>? quicHints,
bool? useBuiltInDnsResolver,
bool? enableStaleDns,
bool? persistHostCache,
Duration? persistHostCachePeriod}) {
try {
return using((arena) {
final builder = jb.CronetEngine$Builder(
Expand Down Expand Up @@ -308,6 +336,36 @@ class CronetEngine {
}
}

if (useBuiltInDnsResolver != null ||
enableStaleDns != null ||
persistHostCache != null ||
persistHostCachePeriod != null) {
if (jb.DnsOptions.builder() case final dnsOptionsBuilder?) {
dnsOptionsBuilder.releasedBy(arena);
if (useBuiltInDnsResolver != null) {
dnsOptionsBuilder
.useBuiltInDnsResolver(useBuiltInDnsResolver)
?.release();
}
if (enableStaleDns != null) {
dnsOptionsBuilder.enableStaleDns(enableStaleDns)?.release();
}
if (persistHostCache != null) {
dnsOptionsBuilder.persistHostCache(persistHostCache)?.release();
}
if (persistHostCachePeriod != null) {
dnsOptionsBuilder
.setPersistHostCachePeriodMillis(
persistHostCachePeriod.inMilliseconds)
?.release();
}
if (dnsOptionsBuilder.build() case final dnsOptions?) {
dnsOptions.releasedBy(arena);
builder.setDnsOptions(dnsOptions)?.release();
}
}
}

return CronetEngine._(builder.build()!);
});
} on JThrowable catch (e) {
Expand Down
Loading