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

Консоль, файлы и 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!');
}

Разбор:

  • stdout.writeln(...) пишет подсказку в стандартный поток вывода с переводом строки.
  • stdin.readLineSync() читает ввод пользователя синхронно; ?.trim() убирает лишние пробелы.
  • ?? 'Гость' подставляет значение по умолчанию, если ввод пустой или null.
  • stderr.writeln(...) выводит диагностическое сообщение в поток ошибок, что полезно для логирования.
  • 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}');
}

Разбор:

  • Асинхронный main (Future<void> main() async) позволяет использовать await прямо в точке входа.
  • stdout.write('Число: ') печатает приглашение без перевода строки.
  • await stdin.readLine() читает строку без блокировки event loop.
  • int.tryParse(...) безопасно парсит число и возвращает null, если формат неверный.
  • Ветка if (n == null) обрабатывает ошибку ввода и завершает функцию через return.
  • При корректном вводе вычисляется квадрат 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);
}

Разбор:

  • File(from) создаёт объект пути к исходному файлу.
  • await source.exists() проверяет наличие файла до чтения.
  • throw FileSystemException(...) явно сообщает вызывающему коду о проблеме пути.
  • readAsString() загружает весь текст в память, а writeAsString(text) записывает его в новый файл.
  • Функция асинхронная, поэтому операции I/O выполняются через await без блокировки.

Построчное чтение (логи, 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;
}

Разбор:

  • openRead() открывает поток байтов для файла, что подходит для больших данных.
  • transform(utf8.decoder) превращает байты в текст.
  • transform(const LineSplitter()) разбивает поток на отдельные строки.
  • await for (...) перебирает события потока асинхронно и увеличивает счётчик.
  • В итоге функция возвращает общее количество строк без чтения всего файла целиком в память.

Для 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);
}
}

Разбор:

  • Directory('data') создаёт ссылку на каталог относительно текущей рабочей директории.
  • exists() проверяет, есть ли папка, а create(recursive: true) создаёт её вместе с отсутствующими родителями.
  • await for (final entity in dir.list()) асинхронно перечисляет содержимое каталога.
  • print(entity.path) выводит путь каждого найденного файла/подкаталога.
  • Такой шаблон полезен для подготовительного шага утилит, которые работают с локальными данными.

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


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

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

Код ITЗагрузка примера кода…

Разбор:

  • jsonEncode(config) сериализует словарь конфигурации в JSON-строку.
  • writeAsString(json) сохраняет JSON в файл config.json.
  • В loadConfig() проверка exists() предотвращает падение при первом запуске и возвращает {}.
  • jsonDecode(raw) парсит JSON обратно в объект, затем выполняется приведение к Map<String, dynamic>.
  • Это минимальный цикл "сохранить/загрузить" для настроек CLI-приложения.

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


HTTP-клиент

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

Код ITЗагрузка примера кода…

Разбор:

  • HttpClient() создаёт HTTP-клиент низкого уровня из dart:io.
  • getUrl(Uri.parse(...)) формирует GET-запрос, request.close() отправляет его и получает HttpClientResponse.
  • Проверка statusCode != 200 обрабатывает неуспешные ответы как исключение HttpException.
  • response.transform(utf8.decoder).join() собирает тело ответа в строку.
  • jsonDecode(body) разбирает JSON и даёт доступ к полям (data['origin']).
  • finally &#123; client.close(force: true); &#125; гарантирует освобождение сетевых ресурсов.

В приложениях на Flutter чаще берут пакет http или dio — они проще в тестах и повторном использовании. Пример GET и списка на экране — галерея Lab; общий контекст запросов — Fetch / axios (Lab). Для консольного Dart достаточно HttpClient или package:http.

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


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

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

Код ITЗагрузка примера кода…

Разбор:

  • HttpServer.bind(InternetAddress.loopbackIPv4, 8080) поднимает локальный сервер на 127.0.0.1:8080.
  • await for (final request in server) организует бесконечный цикл обработки входящих запросов.
  • Условие request.uri.path == '/health' реализует endpoint проверки состояния.
  • В успешной ветке выставляется HttpStatus.ok и тело ответа 'ok', иначе 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, если цель — пользовательские приложения. Готовые экраны с http и FutureBuilderFlutter — готовые виджеты. Для серверной разработки в экосистеме Dart смотрите также разделы энциклопедии про сети и бэкенд.


Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.