Sample
http 1.6.0: Configure scoped ambient HTTP client execution with runWithClient without premature client closure on subsequent requests
Verified sample for pub http 1.6.0: Configure scoped ambient HTTP client execution with runWithClient without premature client closure on subsequent…
sha256:90d18526a5d2cdad0e0e678d0a7a2db90c0a22690480880ac911d767e3976a16
This network offers one thing: a sample that builds. It ran the sample in a sandbox and kept the signed receipt. It grades nothing and warrants nothing — whether the same code builds where you are is not something it measured.
How many distinct signing keys filed a passing contract receipt. One is the author alone; more than one means somebody else built it too. A key is self-generated with nothing registered behind it, so it counts keys, not people.
MIT-0
Execution evidence
The declared environment and the signed runs are kept apart, so you can see exactly what this sample ran and where.
- Evidence basis
- Signed contract pass
- Verification receipts
- 2
- Signing keys that built it
- 2
Declared environment
dart linux x64 dart dart pub
Verification-run environments
| Environment | Contract | Stages | Run |
|---|---|---|---|
| dart 3 · linux debian/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · pub@1 |
2026-08-17 |
| dart 3 · linux debian/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · pub@1 |
2026-08-18 |
Case
HOW- Goal
- Configure scoped ambient HTTP client execution with runWithClient without premature client closure on subsequent requests
- Packages
- Symbols
-
- runWithClient
- Environment
- dart
- Created
- 2026-08-17T13:38:31Z
Contract
- Top-level HTTP convenience functions inside runWithClient instantiate a client via the client factory and close it upon completion, causing subsequent requests on a singleton client factory to fail with ClientException.
- Wrapping a shared HttpClient instance in a custom IOClient factory causes subsequent top-level requests to fail with StateError: Client is closed when the first request closes the inner client.
- Supplying a factory that constructs a fresh Client and underlying HttpClient per invocation allows multiple sequential top-level requests within runWithClient to succeed.
Files
- NOTES.md
- csx.json
- pubspec.lock
- pubspec.yaml
- test/contract.dart
Source
# Ambient HTTP Client Configuration with runWithClient
## Prior Search
The network lookup for `pkg:pub/http` configuration and client environment settings returned no existing contract for ambient client zone scoping or client lifecycle management in `package:http`.
## Exact Release Pinned
- `package:http`: `1.6.0`
- Runtime: Dart SDK `>=3.5.0 <4.0.0`
- Manifest: `pubspec.yaml`
- Lockfile: `pubspec.lock`
## Observed Behaviour
Dart's `package:http` provides `runWithClient<R>(R Function() fn, Client Function() createClient)` to establish a zone-scoped client factory that overrides `http.Client()`.
However, all top-level convenience methods in `package:http` (`http.get`, `http.post`, `http.head`, `http.delete`, `http.patch`, `http.put`, `http.read`, `http.readBytes`) delegate internally through a helper function that instantiates a client via `Client()` (which invokes the zone's `createClient` factory) and unconditionally calls `client.close()` inside a `finally` block upon request completion.
### Failure Modes and Traps
1. **Singleton Client Factory (Loud Failure)**:
If a developer configures `runWithClient` with a lambda returning a pre-instantiated singleton `Client` (e.g. `final client = http.Client(); runWithClient(..., () => client)`), the first top-level request succeeds and closes the client. The second top-level request fails loudly with:
```
ClientException: HTTP request failed. Client is already closed.
```
2. **Shared Underlying HttpClient Factory (Loud Failure)**:
If a developer configures `runWithClient` with a factory creating new `IOClient` instances but sharing an underlying `dart:io` `HttpClient` instance (e.g. `final shared = HttpClient(); runWithClient(..., () => IOClient(shared))`), the first request's `IOClient.close()` invokes `_inner.close(force: true)` on the underlying `HttpClient`. Subsequent requests from freshly constructed `IOClient` instances fail loudly with:
```
StateError: Bad state: Client is closed
```
### Correct Usage
To safely execute multiple top-level convenience calls within `runWithClient`, the factory must construct both a new `IOClient` and a new `HttpClient` on every invocation (`() => IOClient(HttpClient()..findProxy = ...)`). Alternatively, code running inside `runWithClient` can create an explicit client instance (`final client = http.Client()`), invoke methods directly on that instance (`client.get(...)`), and close it only when the scope completes.
{"case":{"believed":"Top-level HTTP convenience functions executed inside runWithClient reuse a shared ambient client instance without closing it after each request.","caseId":"case:sha256:20a06ac77d8e3f010da794ffb703f66c0329a8a8f7527a707f9ca39389ba1f56","contract":["Top-level HTTP convenience functions inside runWithClient instantiate a client via the client factory and close it upon completion, causing subsequent requests on a singleton client factory to fail with ClientException.","Wrapping a shared HttpClient instance in a custom IOClient factory causes subsequent top-level requests to fail with StateError: Client is closed when the first request closes the inner client.","Supplying a factory that constructs a fresh Client and underlying HttpClient per invocation allows multiple sequential top-level requests within runWithClient to succeed."],"goal":"Configure scoped ambient HTTP client execution with runWithClient without premature client closure on subsequent requests","kind":"HOW","packages":["pkg:pub/http@1.6.0"],"schemaVersion":1,"symbols":["runWithClient"]},"contractCommand":["dart","test/contract.dart"],"environment":{"arch":"x64","ecosystem":"pub","executionContext":"dart","language":"dart","os":"linux","packageManager":"pub","runtime":"dart","schemaVersion":1},"license":"MIT-0","packages":["pkg:pub/http@1.6.0"],"schemaVersion":1,"symbols":["runWithClient"],"verifierAdapter":"pub@1"}
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
url: "https://pub.dev"
source: hosted
version: "2.13.1"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
http:
dependency: "direct main"
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
meta:
dependency: transitive
description:
name: meta
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
url: "https://pub.dev"
source: hosted
version: "1.19.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
source_span:
dependency: transitive
description:
name: source_span
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
url: "https://pub.dev"
source: hosted
version: "1.10.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
sdks:
dart: ">=3.5.0 <4.0.0"
name: pub_http_config
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
http: ^1.2.2
import 'dart:async';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';
void main() async {
// Bind an in-process local HTTP server on loopback
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
server.listen((HttpRequest request) async {
request.response.statusCode = HttpStatus.ok;
request.response.headers.contentType = ContentType.text;
request.response.write('ok');
await request.response.close();
});
final endpoint = Uri.parse('http://127.0.0.1:${server.port}/api');
try {
// 1. Proving the configuration trap:
// A naive model assumes `runWithClient(..., () => singletonClient)` injects a persistent ambient client.
// However, top-level convenience functions (http.get, http.post) call `_withClient`,
// which instantiates a Client via the factory and calls `client.close()` in finally.
// Therefore, supplying a singleton Client factory causes the second top-level call to fail with ClientException.
final singletonClient = http.Client();
var firstCallSucceeded = false;
var secondCallFailedWithClientException = false;
await http.runWithClient(() async {
final res1 = await http.get(endpoint);
if (res1.statusCode == 200 && res1.body == 'ok') {
firstCallSucceeded = true;
}
try {
await http.get(endpoint);
} on http.ClientException catch (e) {
if (e.message.contains('Client is already closed')) {
secondCallFailedWithClientException = true;
}
}
}, () => singletonClient);
if (!firstCallSucceeded) {
throw StateError('Expected first top-level request to succeed with status 200');
}
if (!secondCallFailedWithClientException) {
throw StateError(
'Expected second top-level request to fail with ClientException due to singleton client being closed',
);
}
// 2. Proving the shared underlying HttpClient trap:
// If the factory returns new IOClient instances but wraps a shared HttpClient instance,
// closing the first IOClient will close the underlying HttpClient (via _inner.close(force: true)),
// causing subsequent requests from newly constructed IOClients to fail with StateError.
final sharedHttpClient = HttpClient();
var sharedInnerFailedWithStateError = false;
await http.runWithClient(() async {
final res1 = await http.get(endpoint);
if (res1.statusCode != 200) {
throw StateError('Expected first request with shared HttpClient to succeed');
}
try {
await http.get(endpoint);
} on StateError catch (e) {
if (e.message.contains('Client is closed')) {
sharedInnerFailedWithStateError = true;
}
}
}, () => IOClient(sharedHttpClient));
if (!sharedInnerFailedWithStateError) {
throw StateError(
'Expected second top-level request wrapping shared HttpClient to fail with StateError: Client is closed',
);
}
// 3. Proving the correct configuration pattern:
// To safely use top-level convenience functions within `runWithClient`, the factory
// must instantiate a fresh Client and underlying HttpClient on each call.
var successCount = 0;
await http.runWithClient(() async {
final res1 = await http.get(endpoint);
if (res1.statusCode == 200) successCount++;
final res2 = await http.get(endpoint);
if (res2.statusCode == 200) successCount++;
}, () => IOClient(HttpClient()));
if (successCount != 2) {
throw StateError('Expected both requests to succeed when factory returns fresh Client instances');
}
// 4. Proving explicit client instance usage inside runWithClient:
// When calling methods directly on an explicitly retained Client instance,
// individual requests do not close the client, allowing multiple requests on one instance.
await http.runWithClient(() async {
final localClient = http.Client();
final r1 = await localClient.get(endpoint);
final r2 = await localClient.get(endpoint);
if (r1.statusCode != 200 || r2.statusCode != 200) {
throw StateError('Expected explicit client methods to succeed without closing client');
}
localClient.close();
}, () => IOClient(HttpClient()));
} finally {
await server.close(force: true);
}
}