ZetCode

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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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

Source

Dart HttpOverrides Documentation

This tutorial covered Dart's HttpOverrides class with practical examples showing request customization, proxy setup, and testing techniques.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.