Dart HttpOverrides
last modified October 22, 2026
The HttpOverrides class in Dart allows customization of HTTP
requests made through the dart:io HttpClient. It is
useful for testing, mocking, and modifying HTTP behavior.
HttpOverrides provides hooks to intercept and modify HTTP requests
globally. This includes client creation, proxy configuration, and certificate
validation.
Basic Definition
HttpOverrides is an abstract class in Dart's dart:io
library. It serves as a hook point for HTTP request customization. The current
override comes either from the enclosing zone or from the process-wide
HttpOverrides.global value.
Key methods include createHttpClient for client creation and
findProxyFromEnvironment for proxy configuration. These can be
overridden. A new HttpClient() consults
HttpOverrides.current at construction time, so clients that already
exist are not affected by a later change to the global override.
Basic HttpOverrides Usage
This example shows how to set up basic HttpOverrides for all HTTP
requests. A small loopback server echoes the User-Agent header back,
so the output proves that the override took effect.
import 'dart:convert';
import 'dart:io';
class MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..userAgent = 'MyCustomAgent/1.0';
}
}
void main() async {
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
server.listen((request) async {
request.response.write(request.headers.value('user-agent'));
await request.response.close();
});
HttpOverrides.global = MyHttpOverrides();
final client = HttpClient();
final request = await client.getUrl(
Uri.parse('http://localhost:${server.port}/'));
final response = await request.close();
final body = await utf8.decoder.bind(response).join();
print('User-Agent seen by the server: $body');
client.close();
await server.close(force: true);
}
We create a custom HttpOverrides class that sets a user agent for
every client it creates. The global instance is set before constructing the
client. The server replies with the header it received, and the program prints
MyCustomAgent/1.0 instead of the default Dart user agent.
$ dart main.dart User-Agent seen by the server: MyCustomAgent/1.0
Custom Certificate Validation
This example demonstrates overriding certificate validation for a single test host. The callback accepts the certificate only when the host matches; everything else still fails validation.
import 'dart:io';
class SelfSignedHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback =
(X509Certificate cert, String host, int port) =>
host == 'self-signed.badssl.com';
}
}
void main() async {
HttpOverrides.global = SelfSignedHttpOverrides();
try {
final client = HttpClient();
final request = await client.getUrl(
Uri.parse('https://self-signed.badssl.com/'));
final response = await request.close();
await response.drain<void>();
print('Successfully connected to self-signed cert site');
client.close();
} catch (e) {
print('Error: $e');
}
}
The callback returns true only for
self-signed.badssl.com. Returning true for every host
disables a core security check and must never be used in production code.
$ dart main.dart Successfully connected to self-signed cert site
Proxy Configuration
This example shows how to configure a proxy for all HTTP requests. The proxy listens on an ephemeral port, which is passed to the override so the example does not depend on a fixed port being free.
import 'dart:convert';
import 'dart:io';
class ProxyHttpOverrides extends HttpOverrides {
final int proxyPort;
ProxyHttpOverrides(this.proxyPort);
@override
String findProxyFromEnvironment(Uri url, Map<String, String>? environment) {
return 'PROXY localhost:$proxyPort';
}
}
void main() async {
final proxy = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
proxy.listen((request) async {
request.response
..headers.contentType = ContentType.text
..write('Response from proxy');
await request.response.close();
});
HttpOverrides.global = ProxyHttpOverrides(proxy.port);
final client = HttpClient();
final request = await client.getUrl(Uri.parse('http://example.com'));
final response = await request.close();
final body = await utf8.decoder.bind(response).join();
print('Request completed via proxy');
print(body);
client.close();
await proxy.close(force: true);
}
We override the proxy configuration to route all requests through a local
proxy server. The findProxyFromEnvironment method returns the proxy
string, and the test server answers the forwarded request. The request URL still
points at example.com; the proxy receives the full URL and answers
it without contacting the network.
$ dart main.dart Request completed via proxy Response from proxy
Request Header Modification
This example demonstrates adding custom headers to all requests. A loopback server reports the headers it received.
import 'dart:convert';
import 'dart:io';
class CustomHeadersHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return _HeaderAddingClient(super.createHttpClient(context));
}
}
class _HeaderAddingClient implements HttpClient {
final HttpClient _inner;
_HeaderAddingClient(this._inner);
@override
Future<HttpClientRequest> getUrl(Uri url) async {
final request = await _inner.getUrl(url);
request.headers.add('X-Custom-Header', 'MyValue');
request.headers.add('Authorization', 'Bearer mytoken');
return request;
}
@override
void close({bool force = false}) => _inner.close(force: force);
@override
dynamic noSuchMethod(Invocation invocation) =>
super.noSuchMethod(invocation);
}
void main() async {
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
server.listen((request) async {
final custom = request.headers.value('x-custom-header');
final auth = request.headers.value('authorization');
request.response.write('$custom|$auth');
await request.response.close();
});
HttpOverrides.global = CustomHeadersHttpOverrides();
final client = HttpClient();
final request = await client.getUrl(
Uri.parse('http://localhost:${server.port}/'));
final response = await request.close();
final body = await utf8.decoder.bind(response).join();
print('Headers seen by the server: $body');
client.close();
await server.close(force: true);
}
HttpOverrides cannot modify request headers directly. We wrap the
client returned by createHttpClient and add headers in
getUrl, so every request created through the wrapper receives them.
The noSuchMethod implementation covers the rest of the
HttpClient interface; only the members used by the example are
implemented explicitly.
$ dart main.dart Headers seen by the server: MyValue|Bearer mytoken
Mocking HTTP Responses
This example shows how to mock HTTP responses for testing purposes.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
class MockHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return _MockHttpClient();
}
}
class _MockHttpClient implements HttpClient {
@override
Future<HttpClientRequest> getUrl(Uri url) async {
return _MockHttpClientRequest();
}
@override
void close({bool force = false}) {}
@override
dynamic noSuchMethod(Invocation invocation) =>
super.noSuchMethod(invocation);
}
class _MockHttpClientRequest implements HttpClientRequest {
@override
Future<HttpClientResponse> close() async {
return _MockHttpClientResponse();
}
@override
dynamic noSuchMethod(Invocation invocation) =>
super.noSuchMethod(invocation);
}
class _MockHttpClientResponse implements HttpClientResponse {
final Stream<List<int>> _body =
Stream.value(utf8.encode('{"message": "Mock response"}'));
@override
int get statusCode => HttpStatus.ok;
@override
bool get isBroadcast => _body.isBroadcast;
@override
StreamSubscription<List<int>> listen(
void Function(List<int> event)? onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) {
return _body.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
@override
dynamic noSuchMethod(Invocation invocation) =>
super.noSuchMethod(invocation);
}
void main() async {
HttpOverrides.global = MockHttpOverrides();
final client = HttpClient();
final request = await client.getUrl(Uri.parse('https://example.com'));
final response = await request.close();
final data = await utf8.decoder.bind(response).join();
print('Mock response: $data');
client.close();
}
We implement a mock HTTP client that returns predefined responses. Only the
members used by this example are implemented; noSuchMethod handles
the rest of the HttpClient interfaces.
$ dart main.dart
Mock response: {"message": "Mock response"}
Scoped Overrides
Setting HttpOverrides.global changes the default for the whole
isolate. In tests it is usually better to run the code in a zone with a
temporary override. HttpOverrides.runWithHttpOverrides restores the
previous override automatically when the callback completes.
import 'dart:convert';
import 'dart:io';
class ScopedHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)..userAgent = 'ScopedAgent/1.0';
}
}
void main() async {
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
server.listen((request) async {
request.response.write(request.headers.value('user-agent'));
await request.response.close();
});
await HttpOverrides.runWithHttpOverrides(() async {
final client = HttpClient();
final request = await client.getUrl(
Uri.parse('http://localhost:${server.port}/'));
final response = await request.close();
final body = await utf8.decoder.bind(response).join();
print('Scoped User-Agent: $body');
client.close();
}, ScopedHttpOverrides());
await server.close(force: true);
}
$ dart main.dart Scoped User-Agent: ScopedAgent/1.0
HttpOverrides.runZoned is a shorthand when the override can be
expressed with callbacks; runWithHttpOverrides accepts a complete
HttpOverrides instance. Both keep the override out of the root
zone.
Best Practices
- Testing only: Use certificate bypass only in test environments, and restrict
badCertificateCallbackto the host under test. - Scope: Prefer
runWithHttpOverridesorrunZonedoverHttpOverrides.global, especially in test suites. - Order: Install the override before constructing the
HttpClient; existing clients keep the implementation they were created with. - Cleanup: Set
HttpOverrides.global = null, or restore the previous value, when testing is done. - Security: Never disable certificate validation globally and never hardcode credentials in production code.
- Performance: Keep overrides lightweight to avoid slowing down every request.
Source
Dart HttpOverrides Documentation
This tutorial covered Dart's HttpOverrides class with practical examples showing request customization, proxy setup, and testing techniques.
Author
List all Dart tutorials.