In Dart's dart:io, what are the steps of a GET request with HttpClient, and why should a command-line script call close() on the client afterwards?
answer
- request object is an IOSink
- closing the request sends it
- response is a byte stream
- idleTimeout defaults to 15 seconds
- HttpServer and Socket sit beside it
basics
~20 sgetUrl() returns an HttpClientRequest; calling close() on it sends the request and returns an HttpClientResponse, a byte stream you decode. HttpClient keeps idle keep-alive connections for idleTimeout (15 s), so client.close() lets a script exit promptly.
solid answer
~40 s`final client = HttpClient();` then `final request = await client.getUrl(uri);` gives an `HttpClientRequest`, which implements `IOSink`: you set headers and optionally write a body. `await request.close()` sends it and completes with an `HttpClientResponse`, which is a `Stream<List<int>>` - you read `statusCode`, then decode the body with `utf8.decoder` and `join()`. The client pools connections: an idle keep-alive connection is kept for `idleTimeout`, 15 seconds by default, and that open connection and its timer can keep a script alive after its work is done. `client.close()` (with `force: false` by default) lets active requests finish and then shuts the pool, so the program exits. On the other side, `HttpServer.bind(address, port)` returns a `Stream<HttpRequest>`, and `Socket.connect` gives a raw TCP `Socket` that is both a `Stream<Uint8List>` and an `IOSink`. dart.dev recommends a higher-level client package for app code.
code
dart · 18 linesimport 'dart:convert';
import 'dart:io';
Future<String> fetchText(Uri uri) async {
final client = HttpClient();
try {
final request = await client.getUrl(uri);
request.headers.set(HttpHeaders.acceptHeader, 'text/plain');
final response = await request.close();
final body = await response.transform(utf8.decoder).join();
if (response.statusCode != HttpStatus.ok) {
throw HttpException('HTTP ${response.statusCode}', uri: uri);
}
return body;
} finally {
client.close();
}
}go deeper
Recall the order: open the request, close it to send, then read the response stream.
Explain that the request is an IOSink, the response a byte stream, and that pooled idle connections delay a script's exit until the client is closed.
Show you manage client lifetime, drain bodies for connection reuse, and choose between raw sockets, HttpClient and a higher-level package.
Decide where raw dart:io networking is justified versus a portable client package, given web targets and testability.
## The networking layers in dart:io `dart:io` offers networking at two levels on native platforms: | Class | Level | Shape | |---|---|---| | `Socket` / `ServerSocket` | raw TCP | `Socket` is a `Stream<Uint8List>` and an `IOSink` | | `HttpClient` | HTTP client | request is an `IOSink`, response is a `Stream<List<int>>` | | `HttpServer` | HTTP server | a `Stream<HttpRequest>`; each request carries an `HttpResponse` | The dart.dev library guide notes that `HttpClient` is platform-dependent and tied to one implementation, and recommends a higher-level client package for app code. Knowing the raw API still matters: it is what those packages wrap on native platforms, and it is what you reach for in a small command-line tool with no dependencies. ## A GET request, step by step 1. **Create the client.** `HttpClient()` owns a pool of connections, keyed by host and port. 2. **Open a request.** `await client.getUrl(uri)` (or `get(host, port, path)`, `postUrl`, `openUrl`) resolves the host, connects or reuses a pooled connection, and returns an `HttpClientRequest`. 3. **Configure it.** The request implements `IOSink`: set headers through `request.headers`, and `write` a body for methods that send one. 4. **Send it.** `await request.close()` finishes the request and completes with an `HttpClientResponse`. Nothing reaches the server until the request is closed. 5. **Read the response.** Check `response.statusCode` and headers, then consume the body stream, for example `await response.transform(utf8.decoder).join()`. Always drain or cancel the body so the connection can be reused. 6. **Close the client** when done. ## Why client.close() matters in a script The client keeps finished connections open for reuse. Each idle connection is held for `idleTimeout`, which defaults to **15 seconds**; `connectionTimeout` defaults to `null`, meaning the OS default. An open socket and a pending timer are work the isolate is still waiting on, so a command-line program can finish its logic and then sit for up to the idle timeout before it exits. `client.close()` fixes that: - `close()` with the default `force: false` keeps the client alive until active connections finish, then shuts everything down. - `close(force: true)` closes active connections immediately; they receive an error. - After either, opening a new connection on that client throws. ## HttpServer and Socket in brief `HttpServer.bind(address, port)` returns a server that is itself a `Stream<HttpRequest>`, so `await for (final request in server)` handles requests one event at a time. Each handler sets `request.response.statusCode`, writes, and must call `response.close()`. The server's own `idleTimeout` for keep-alive connections defaults to 120 seconds. `Socket.connect(host, port, timeout: ...)` opens a TCP connection. You read by listening to it as a byte stream and write through its `IOSink` methods. `SocketOption.tcpNoDelay` can be set with `setOption`. `ServerSocket.bind` accepts incoming sockets as a stream. ## Common mistakes - Forgetting `await request.close()` and wondering why nothing is sent. - Never reading the response body, which keeps the connection from returning to the pool. - Creating a new `HttpClient` per request and never closing any of them. - Reaching for `HttpClient` in Flutter web code, where `dart:io` is unsupported.
- What does close(force: true) on an HttpClient change compared with the default?The default `force: false` lets active connections finish before shutting down. `force: true` closes them immediately to release resources, and those connections receive an error event. In both cases, new connections on that client throw.
- Why is HttpServer something you can use with await for?`HttpServer` implements `Stream<HttpRequest>`, so each incoming request is an event. `await for` handles them in order on the isolate's event loop; each handler must close its `HttpResponse` to finish the reply.
saying these in an interview costs you the question
- Thinks getUrl() sends the request immediately
- Believes the response body arrives as a String without decoding
- Assumes HttpClient closes pooled connections when the last request ends
- Creates a new HttpClient per request and never closes it
- Expects dart:io HttpClient to work in a Flutter web build