dart:io

dart:io untuk berkas, process, socket, dan klien HTTP.

Daftar isi

dart:io adalah library bawaan Dart untuk program yang berjalan di luar browser. Library ini menyediakan kelas untuk berkas dan direktori, eksekusi proses lain, socket TCP, serta klien dan server HTTP, dan hanya tersedia pada VM Dart, sehingga tidak ikut terkompilasi untuk web. Topik ini membahas operasi berkas baca-tulis, penelusuran direktori, menjalankan proses eksternal dengan Process.run, serta pasangan klien dan server HTTP yang berkomunikasi melalui alamat loopback 127.0.0.1, yaitu alamat yang menunjuk ke mesin yang sama. Semua contoh berjalan penuh di terminal dengan Dart 3.13, dan keluarannya dicantumkan apa adanya.

Membaca dan menulis berkas#

Kelas File mewakili satu berkas pada sistem berkas, dan hampir semua metodenya memiliki dua bentuk. Bentuk asinkron seperti writeAsString dan readAsString mengembalikan Future, sehingga panggilan beruntun di dalam fungsi async tidak menghalangi alur program. Bentuk sinkronnya bernama readAsStringSync dan writeAsStringSync, yang berjalan sampai selesai sebelum baris berikutnya dieksekusi. Mode penulisan ditentukan dengan parameter mode, yaitu FileMode.write sebagai bawaan yang mengganti seluruh isi berkas, dan FileMode.append yang menambahkan di akhir berkas.

Contoh berikut menyusun log sensor suhu kecil pada folder sementara /tmp/uji/dart-io. Program memastikan folder tujuannya ada terlebih dahulu melalui Directory.create(recursive: true), menulis tiga baris awal, membaca ulang seluruh isinya, menambah satu baris dengan mode append, lalu membaca isi terbaru per baris dengan readAsLines, yaitu versi readAsString yang mengembalikan List<String>.

dart baca-tulis.dart
import 'dart:io';

Future<void> main() async {
  final folder = Directory('/tmp/uji/dart-io');
  final berkas = File('${folder.path}/sensor.log');

  // memastikan folder tujuan sudah ada sebelum berkas ditulis
  await folder.create(recursive: true);
  // menulis tiga baris log dari awal berkas
  await berkas.writeAsString('08:00 22.4\n09:00 23.1\n10:00 24.8\n');
  final isi = await berkas.readAsString();
  print('isi saat ini:');
  print(isi);

  // menambah satu baris di akhir berkas
  await berkas.writeAsString('11:00 25.3\n', mode: FileMode.append);
  final baris = await berkas.readAsLines();
  print('jumlah baris setelah tambahan: ${baris.length}');
  print('baris terakhir: ${baris.last}');
}
bash
# menjalankan program dari folder topik
dart run baca-tulis.dart
text Keluaran
isi saat ini:
08:00 22.4
09:00 23.1
10:00 24.8

jumlah baris setelah tambahan: 4
baris terakhir: 11:00 25.3

Keluaran memperlihatkan dua kejadian secara berurutan. Pada penulisan pertama, berkas yang belum ada dibuat baru dan diisi tiga baris log. Pada penulisan kedua, FileMode.append menjaga isi lama dan menambahkan baris keempat, sehingga readAsLines melaporkan empat baris dengan baris terakhir 11:00 25.3. Variabel isi dicetak dengan print(isi) dan berisi karakter baris baru di ujungnya, sehingga keluaran menyisakan satu baris kosong sebelum baris berikutnya.

Menelusuri direktori#

Kelas Directory mewakili satu folder beserta operasinya. Metode create membuat folder, dan parameter recursive: true membuat seluruh induknya sekaligus bila belum ada, seperti mkdir -p. Penelusuran isi folder dilakukan dengan list(), yaitu metode yang mengembalikan Stream<FileSystemEntity>, sehingga setiap entitasnya dikonsumsi dengan await for. Entitas yang keluar bertipe File, Directory, atau Link, dan parameter recursive: true pada list() memperluas penelusuran ke seluruh subfolder di dalamnya.

Contoh berikut membuat folder arsip, mengisinya dengan tiga berkas catatan, lalu menyaring entitas bertipe File saja. Nama berkasnya diambil dari segmen terakhir uri setiap entitas dan diurutkan agar keluaran tetap sama setiap kali program dijalankan.

dart daftar-direktori.dart
import 'dart:io';

Future<void> main() async {
  final direktori = Directory('/tmp/uji/dart-io/arsip');

  // membuat folder beserta seluruh induknya bila belum ada
  await direktori.create(recursive: true);
  for (var i = 1; i <= 3; i++) {
    await File('${direktori.path}/catatan-$i.txt').writeAsString('catatan $i\n');
  }

  // menelusuri isi folder dan menyaring berkas saja
  final nama = <String>[];
  await for (final entitas in direktori.list()) {
    if (entitas is File) nama.add(entitas.uri.pathSegments.last);
  }
  nama.sort();
  print('berkas di ${direktori.path}:');
  for (final n in nama) {
    print('- $n');
  }
}
bash
# menjalankan program dari folder topik
dart run daftar-direktori.dart
text Keluaran
berkas di /tmp/uji/dart-io/arsip:
- catatan-1.txt
- catatan-2.txt
- catatan-3.txt

Urutan keluaran tidak berubah karena nama yang terkumpul disortir sebelum dicetak, sedangkan list() sendiri tidak menjamin urutan. Penyaringan bertipe dengan entitas is File diperlukan karena isi folder juga bisa berupa subfolder. Pemanggilan create(recursive: true) tidak menimbulkan error saat folder sudah ada, sehingga program ini dapat dijalankan berulang kali dengan hasil yang sama.

Menjalankan proses lain#

Process.run mengeksekusi program eksternal dan mengembalikan Future<ProcessResult>. Argumen pertamanya adalah nama program, sedangkan argumen program diberikan sebagai List<String> yang dikirim apa adanya tanpa melewati shell, sehingga tidak ada penafsiran karakter khusus. Objek ProcessResult membawa exitCode, stdout, dan stderr, dan exitCode bernilai 0 bila program selesai tanpa masalah.

Contoh berikut menjalankan printf dengan keluaran yang tetap, lalu mengeksekusi ls ke berkas yang tidak ada untuk memperlihatkan proses yang gagal. Kedua kejadian itu sama-sama mengembalikan ProcessResult, hanya saja nilai exitCode dan stderr-nya mencatat kegagalan.

dart proses.dart
import 'dart:io';

Future<void> main() async {
  // Process.run menjalankan program tanpa melewati shell
  final hasil = await Process.run('printf', ['tiga kata di sini\n']);
  print('kode keluar: ${hasil.exitCode}');
  print('keluaran: ${hasil.stdout}');

  // proses yang gagal tetap mengembalikan ProcessResult
  final gagal = await Process.run('ls', ['/tmp/uji/dart-io/tidak-ada.txt']);
  print('kode keluar ls: ${gagal.exitCode}');
  print('pesan error: ${gagal.stderr}');
}
bash
# menjalankan program dari folder topik
dart run proses.dart
text Keluaran
kode keluar: 0
keluaran: tiga kata di sini

kode keluar ls: 2
pesan error: ls: cannot access '/tmp/uji/dart-io/tidak-ada.txt': No such file or directory

ls yang gagal memberi kode keluar 2 dan menuliskan alasannya ke stderr, sedangkan printf selesai dengan kode 0. Pemisahan stdout dan stderr memudahkan program menangkap dua aliran itu secara terpisah, misalnya menyimpan keluaran normal dan mencatat pesan error ke tempat berbeda. Nilai stdout dan stderr pada ProcessResult sudah berbentuk teks karena Process.run menguraikan byte dengan pengodean bawaan, dan parameter stdoutEncoding serta stderrEncoding menggantinya bila keluaran proses bukan teks biasa. Untuk proses yang berjalan lama dan keluarannya mengalir, Process.start memperlihatkan stdout dan stderr sebagai Stream, dan Process.kill menghentikan prosesnya.

Klien dan server HTTP#

Dua kelas HTTP utama pada dart:io adalah HttpClient dan HttpServer. HttpServer.bind membuka port pada alamat tertentu, lalu server mengeluarkan satu HttpRequest untuk setiap permintaan yang masuk. HttpClient bekerja sebaliknya, yaitu membuka permintaan ke alamat lain dan mengembalikan HttpClientResponse berisi kode status serta aliran isi respons. Di bawah keduanya terdapat Socket, yaitu koneksi TCP mentah yang juga dapat digunakan langsung melalui ServerSocket dan Socket bila protokol HTTP terlalu tinggi untuk kebutuhan tertentu.

Server pada contoh berikut sengaja dibuat melayani satu permintaan lalu menutup dirinya, sehingga demo tidak menyisakan proses yang menggantung. Setelah response.close() mengirim jawaban, server.close(force: true) melepaskan port dan program berakhir. Pola satu-permintaan-lalu-berakhir digunakan pada semua demo server dalam topik ini.

dart server.dart
import 'dart:io';

Future<void> main() async {
  final server = await HttpServer.bind('127.0.0.1', 19080);
  await for (final permintaan in server) {
    permintaan.response.headers.contentType = ContentType.json;
    permintaan.response.write('{"sensor":"ruang-utama","suhu":23.1}');
    await permintaan.response.close();
    await server.close(force: true);
  }
}
bash
# menjalankan server satu-permintaan lalu mengambil responsnya dengan curl
dart run server.dart & P=$!
sleep 1
curl -s http://127.0.0.1:19080/suhu
wait $P
text Keluaran
{"sensor":"ruang-utama","suhu":23.1}

Perintah curl memperlihatkan isi respons mentahnya, yaitu satu baris JSON sensor suhu ruangan. Port 19080 dipilih dari rentang tinggi yang bebas digunakan aplikasi biasa, sedangkan alamat 127.0.0.1 membuat server hanya menerima sambungan dari mesin yang sama.

Klien versi Dart menggunakan HttpClient. Metode getUrl menyusun permintaan, dan close() pada permintaan mengirimkannya sekaligus mengembalikan HttpClientResponse. Isi responsnya adalah aliran byte yang diubah menjadi teks dengan utf8.decoder dari dart:convert.

dart klien.dart
import 'dart:convert';
import 'dart:io';

Future<void> main() async {
  final klien = HttpClient();
  final permintaan = await klien.getUrl(Uri.parse('http://127.0.0.1:19080/suhu'));
  final respons = await permintaan.close();
  final isi = await respons.transform(utf8.decoder).join();
  print('status: ${respons.statusCode}');
  print('isi respons: $isi');
  klien.close();
}
bash
# server yang sama diambil lewat HttpClient bawaan Dart
dart run server.dart & P=$!
sleep 1
dart run klien.dart
wait $P
text Keluaran
status: 200
isi respons: {"sensor":"ruang-utama","suhu":23.1}

Kode status 200 dan isi responsnya sama seperti keluaran curl, karena keduanya mengakses server yang sama. Pemanggilan klien.close() menutup klien beserta koneksi yang tersisa, sehingga program berakhir rapi. Untuk program sungguhan, alamat server biasanya berada di luar mesin, tetapi pola permintaannya tidak berubah.

Ringkasan#

  • dart:io menyediakan File, Directory, Process, Socket, HttpClient, dan HttpServer untuk program yang berjalan pada VM Dart, dan tidak tersedia pada target web.
  • Metode berkas memiliki bentuk asinkron yang mengembalikan Future dan bentuk sinkron berakhiran Sync, dengan mode tulis FileMode.write sebagai bawaan dan FileMode.append untuk penambahan.
  • Process.run menjalankan program tanpa shell dan mengembalikan ProcessResult berisi exitCode, stdout, serta stderr.
  • HttpServer.bind mengeluarkan satu HttpRequest per permintaan, sedangkan HttpClient mengembalikan HttpClientResponse berisi kode status dan aliran isi respons.

Referensi#