Перейти к основному содержимому

Консоль, файлы и HTTP в Dart

Разработчику Архитектору

Консоль, файлы и HTTP в Dart

Dart часто воспринимают только через Flutter, но тот же язык подходит для консольных утилит, скриптов автоматизации и лёгких серверов. Для этого используют библиотеку dart:io (доступна в Dart VM на десктопе и сервере; в браузерной сборке её нет — там другие API).

Связанные темы: первая программа, асинхронность, архитектура и dart:io.


Консольный ввод и вывод

Вывод в терминал — функция print из dart:core (уже в первой программе). Для потоков без буферизации и stderr:

import 'dart:io';

void main() {
stdout.writeln('Введите имя:');
final name = stdin.readLineSync()?.trim() ?? 'Гость';
stderr.writeln('(отладка: прочитано ${name.length} символов)');
print('Привет, $name!');
}
APIНазначение
stdoutОбычный вывод
stderrСообщения об ошибках и диагностика
stdin.readLineSync()Строка до Enter (синхронно)

В интерактивных CLI предпочтительнее асинхронное чтение, чтобы не блокировать event loop при сложной логике:

Future<void> main() async {
stdout.write('Число: ');
final line = await stdin.readLine();
final n = int.tryParse(line?.trim() ?? '');
if (n == null) {
print('Некорректный ввод');
return;
}
print('Квадрат: ${n * n}');
}

Шаблон проекта: dart create -t console my_tool — см. установку и запуск.


Работа с файлами и каталогами

Классы File, Directory из dart:io описывают пути в файловой системе.

Текст:

import 'dart:io';

Future<void> copyText(String from, String to) async {
final source = File(from);
if (!await source.exists()) {
throw FileSystemException('файл не найден', from);
}
final text = await source.readAsString();
await File(to).writeAsString(text);
}

Построчное чтение (логи, CSV):

Future<int> countLines(String path) async {
var count = 0;
await for (final _ in File(path).openRead().transform(utf8.decoder).transform(const LineSplitter())) {
count++;
}
return count;
}

Для utf8 и LineSplitter нужен import 'dart:convert';.

Каталоги:

Future<void> ensureDataDir() async {
final dir = Directory('data');
if (!await dir.exists()) {
await dir.create(recursive: true);
}
await for (final entity in dir.list()) {
print(entity.path);
}
}

Бинарные данныеreadAsBytes / writeAsBytes; для больших файлов — потоки openRead() / openWrite() без загрузки всего файла в память.


JSON — сохранение и загрузка

Сериализация через dart:convert не требует сторонних пакетов для простых структур:

import 'dart:convert';
import 'dart:io';

Future<void> saveConfig(Map<String, Object?> config) async {
final json = jsonEncode(config);
await File('config.json').writeAsString(json);
}

Future<Map<String, dynamic>> loadConfig() async {
final file = File('config.json');
if (!await file.exists()) return {};
final raw = await file.readAsString();
return jsonDecode(raw) as Map<String, dynamic>;
}

Для сложных моделей в production-проектах генерируют код (json_serializable, freezed); на этапе обучения достаточно ручного Map или record с явным маппингом полей.


HTTP-клиент

Встроенный HttpClient (dart:io) подходит для скриптов и сервисов без лишних зависимостей:

import 'dart:convert';
import 'dart:io';

Future<void> fetchExample() async {
final client = HttpClient();
try {
final request = await client.getUrl(Uri.parse('https://httpbin.org/get'));
final response = await request.close();
if (response.statusCode != 200) {
throw HttpException('код ${response.statusCode}');
}
final body = await response.transform(utf8.decoder).join();
final data = jsonDecode(body) as Map<String, dynamic>;
print(data['origin']);
} finally {
client.close(force: true);
}
}

В приложениях на Flutter чаще берут пакет http или dio — они проще в тестах и повторном использовании. Для консольного Dart достаточно HttpClient или package:http.

Всегда оборачивайте сетевые вызовы в try/catch и закрывайте клиент в finally, как в обработке ошибок.


Минимальный HTTP-сервер

Тот же dart:io позволяет поднять лёгкий сервер для учебных задач и локальных API:

import 'dart:io';

Future<void> main() async {
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 8080);
print('Слушаем http://127.0.0.1:8080');

await for (final request in server) {
if (request.uri.path == '/health') {
request.response
..statusCode = HttpStatus.ok
..write('ok');
} else {
request.response.statusCode = HttpStatus.notFound;
}
await request.response.close();
}
}

Для реального бэкенда обычно выбирают фреймворки уровня Shelf, Serverpod или отдельный стек (Node, Go и т.д.); встроенный HttpServer полезен, чтобы понять цикл «запрос → ответ» без лишних слоёв. Общие принципы HTTP — в справочнике по HTTP энциклопедии.


Асинхронность и изоляты

Чтение файлов и сеть — операции ввода-вывода. Их выполняют через Future и async/await (функции и асинхронность), чтобы не блокировать единственный поток изолята. Тяжёный разбор большого файла можно вынести в Isolate.run, если утилита начинает «подвисать» на больших объёмах данных.


Ограничения платформ

Средаdart:ioПримечание
Консоль, сервер, desktop VMДаОсновной сценарий статьи
Flutter mobile/desktopДаПлюс доступ к нативным API через плагины
Flutter Web / dart2jsНетФайлы и сокеты — через браузерные API и пакеты

Перед переносом консольного скрипта в веб-проект проверьте, что код не импортирует dart:io без условной компиляции.


Типичные ошибки

  • Забыли await при readAsString — получили Future, а не текст.
  • Путь к файлу без Directory.current / Platform.pathSeparator — ломается при смене ОС.
  • Не закрыли HttpClient — утечка сокетов при многократных запросах.
  • Синхронное readLineSync в UI-изоляте Flutter — риск подвисания интерфейса (там только асинхронные API).

Что изучить дальше

После консольного Dart логично углубить паттерны и switch для разбора ответов API и перейти к Flutter, если цель — пользовательские приложения. Для серверной разработки в экосистеме Dart смотрите также разделы энциклопедии про сети и бэкенд.


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).