Skip to content

Commit

Permalink
feat: dynamic dns resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
Tienisto committed Sep 27, 2024
1 parent fecc5b0 commit 65a50ca
Show file tree
Hide file tree
Showing 10 changed files with 1,069 additions and 292 deletions.
35 changes: 35 additions & 0 deletions benchmark/nodejs/hello.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const http = require('http');

const PORT = 80;
const HOST = '0.0.0.0';

const requestHandler = async (request, response) => {
const body = await getBody(request);

const responseText = 'Hello: ' + body;

response.statusCode = 200;
response.setHeader('Content-Type', 'text/plain');
response.setHeader('Content-Length', responseText.length);
response.end(responseText);
};

// Create HTTPS server with the provided SSL credentials and request handler
const server = http.createServer(requestHandler);

server.listen(PORT, HOST, () => {
console.log(`Server listening on https://${HOST}:${PORT}`);
});

function getBody(request) {
return new Promise((resolve) => {
const bodyParts = [];
let body;
request.on('data', (chunk) => {
bodyParts.push(chunk);
}).on('end', () => {
body = Buffer.concat(bodyParts).toString();
resolve(body)
});
});
}
20 changes: 19 additions & 1 deletion rhttp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ You can override the mapping of hostnames to IP addresses:
```dart
final client = await RhttpClient.create(
settings: const ClientSettings(
dnsSettings: DnsSettings(
dnsSettings: DnsSettings.static(
overrides: {
'example.com': ['127.0.0.1'],
},
Expand All @@ -635,6 +635,24 @@ final client = await RhttpClient.create(
);
```

For a more complex DNS resolution, you can construct a `DnsSettings.dynamic` object:

```dart
final client = await RhttpClient.create(
settings: const ClientSettings(
dnsSettings: DnsSettings.dynamic(
resolver: (String host) async {
if (counter % 2 == 0) {
return ['127.0.0.1'];
} else {
return ['1.2.3.4'];
}
}
),
)
);
```

### ➤ Compatibility Layer

You can use the `RhttpCompatibleClient` that implements the `Client` of the [http](https://pub.dev/packages/http) package,
Expand Down
20 changes: 13 additions & 7 deletions rhttp/example/lib/dns_resolution.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class MyApp extends StatefulWidget {

class _MyAppState extends State<MyApp> {
HttpTextResponse? response;
int counter = 0;

@override
Widget build(BuildContext context) {
Expand All @@ -33,14 +34,19 @@ class _MyAppState extends State<MyApp> {
ElevatedButton(
onPressed: () async {
try {
counter++;
final res = await Rhttp.get(
'http://example.com:3000',
settings: const ClientSettings(
dnsSettings: DnsSettings(
overrides: {
'example.com': ['127.0.0.1'],
},
fallback: '127.0.0.1'
'http://example.com',
settings: ClientSettings(
dnsSettings: DnsSettings.dynamic(
resolver: (String host) async {
print('Resolving "$host"');
if (counter % 2 == 0) {
return ['127.0.0.1'];
} else {
return ['93.184.215.14'];
}
}
),
),
);
Expand Down
49 changes: 41 additions & 8 deletions rhttp/lib/src/model/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const _keepBaseUrl = '__rhttp_keep__';
const _keepProxySettings = ProxySettings.noProxy();
const _keepRedirectSettings = RedirectSettings.limited(-9999);
const _keepTlsSettings = TlsSettings();
const _keepDnsSettings = DnsSettings();
const _keepDnsSettings = DnsSettings.static();
const _keepTimeoutSettings = TimeoutSettings();

class ClientSettings {
Expand Down Expand Up @@ -194,8 +194,24 @@ class TimeoutSettings {
});
}

/// DNS settings and resolver overrides.
class DnsSettings {
sealed class DnsSettings {
const DnsSettings();

/// Static DNS settings and resolver overrides
/// for simple use cases.
const factory DnsSettings.static({
Map<String, List<String>> overrides,
String? fallback,
}) = StaticDnsSettings._;

/// Dynamic DNS settings and resolver for more complex use cases.
const factory DnsSettings.dynamic({
required Future<List<String>> Function(String host) resolver,
}) = DynamicDnsSettings._;
}

/// Static DNS settings and resolver overrides.
class StaticDnsSettings extends DnsSettings {
/// Overrides the DNS resolver for specific hosts.
/// The key is the host and the value is a list of IP addresses.
final Map<String, List<String>> overrides;
Expand All @@ -204,12 +220,22 @@ class DnsSettings {
/// all requests that don't match any override.
final String? fallback;

const DnsSettings({
const StaticDnsSettings._({
this.overrides = const {},
this.fallback,
});
}

/// Dynamic DNS settings and resolver.
class DynamicDnsSettings extends DnsSettings {
/// The function to resolve the IP address for a host.
final Future<List<String>> Function(String host) resolver;

const DynamicDnsSettings._({
required this.resolver,
});
}

@internal
extension ClientSettingsExt on ClientSettings {
rust_client.ClientSettings toRustType() {
Expand Down Expand Up @@ -292,9 +318,16 @@ extension on ClientCertificate {

extension on DnsSettings {
rust_client.DnsSettings _toRustType() {
return rust_client.DnsSettings(
overrides: overrides,
fallback: fallback,
);
return switch (this) {
StaticDnsSettings s => rust_client.createStaticResolverSync(
settings: rust_client.StaticDnsSettings(
overrides: s.overrides,
fallback: s.fallback,
),
),
DynamicDnsSettings d => rust_client.createDynamicResolverSync(
resolver: d.resolver,
),
};
}
}
52 changes: 32 additions & 20 deletions rhttp/lib/src/rust/api/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,20 @@ import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
part 'client.freezed.dart';

// These functions are ignored because they are not marked as `pub`: `create_client`, `new_default`, `new`
// These types are ignored because they are not used by any `pub` functions: `StaticResolver`
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `resolve`
// These types are ignored because they are not used by any `pub` functions: `DynamicDnsSettings`, `DynamicResolver`, `StaticResolver`
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `resolve`, `resolve`

DnsSettings createStaticResolverSync({required StaticDnsSettings settings}) =>
RustLib.instance.api
.crateApiClientCreateStaticResolverSync(settings: settings);

DnsSettings createDynamicResolverSync(
{required FutureOr<List<String>> Function(String) resolver}) =>
RustLib.instance.api
.crateApiClientCreateDynamicResolverSync(resolver: resolver);

// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<DnsSettings>>
abstract class DnsSettings implements RustOpaqueInterface {}

// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<RequestClient>>
abstract class RequestClient implements RustOpaqueInterface {}
Expand Down Expand Up @@ -83,11 +95,26 @@ class ClientSettings {
dnsSettings == other.dnsSettings;
}

class DnsSettings {
enum ProxySettings {
noProxy,
;
}

@freezed
sealed class RedirectSettings with _$RedirectSettings {
const RedirectSettings._();

const factory RedirectSettings.noRedirect() = RedirectSettings_NoRedirect;
const factory RedirectSettings.limitedRedirects(
int field0,
) = RedirectSettings_LimitedRedirects;
}

class StaticDnsSettings {
final Map<String, List<String>> overrides;
final String? fallback;

const DnsSettings({
const StaticDnsSettings({
required this.overrides,
this.fallback,
});
Expand All @@ -98,27 +125,12 @@ class DnsSettings {
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is DnsSettings &&
other is StaticDnsSettings &&
runtimeType == other.runtimeType &&
overrides == other.overrides &&
fallback == other.fallback;
}

enum ProxySettings {
noProxy,
;
}

@freezed
sealed class RedirectSettings with _$RedirectSettings {
const RedirectSettings._();

const factory RedirectSettings.noRedirect() = RedirectSettings_NoRedirect;
const factory RedirectSettings.limitedRedirects(
int field0,
) = RedirectSettings_LimitedRedirects;
}

class TimeoutSettings {
final Duration? timeout;
final Duration? connectTimeout;
Expand Down
Loading

0 comments on commit 65a50ca

Please sign in to comment.