An introduction to the dart:io library
Written by Mads Ager
March 2012 (updated September 2018)
The dart:io library is aimed at code that runs in Flutter and the standalone Dart VM. In this article we will give you a feel for what is currently possible with dart:io by going through a couple of examples.
Dart is a single-threaded programming language. If an operation blocks the Dart thread, the application makes no progress before that operation completes. For scalability it is therefore crucial that no I/O operations block. Instead of blocking on I/O operations, dart:io uses an asynchronous programming model inspired by node.js, EventMachine, and Twisted.
The Dart VM and the event loop
Before we dive into asynchronous I/O operations, it might be useful to explain how the standalone Dart VM operates.
When executing a server-side application, the Dart VM runs in an event loop with an associated event queue of pending asynchronous operations. The VM terminates when it has executed the current code to completion and no more pending operations are in the queue.
One simple way to add an event to the event queue is to schedule a function to be called in the future. You can do this by creating a Timer object. The following example registers a timer with the event queue and then drops off the end of main()
. Because a pending operation is in the event queue, the VM does not terminate at that point. After one second, the timer fires and the code in the argument callback executes. Once that code executes to completion, no more pending operations are in the event queue and the VM terminates.
1 2 3 4 5 6 |
<span class="kwd">import</span> <span class="str">'dart:async'</span><span class="pun">;</span> <span class="typ">void</span><span class="pln"> main</span><span class="pun">()</span> <span class="pun">{</span> <span class="typ">Timer</span><span class="pun">(</span><span class="typ">Duration</span><span class="pun">(</span><span class="pln">seconds</span><span class="pun">:</span> <span class="lit">1</span><span class="pun">),</span> <span class="pun">()</span> <span class="pun">=></span><span class="pln"> print</span><span class="pun">(</span><span class="str">'timer'</span><span class="pun">));</span><span class="pln"> print</span><span class="pun">(</span><span class="str">'end of main'</span><span class="pun">);</span> <span class="pun">}</span> |
Running this example at the command line, we get:
1 2 3 4 |
<span class="gp">$</span> dart timer.dart <span class="go">end of main timer </span> |
Had we made the timer repeating by using the Timer.periodic constructor, the VM would not terminate and would continue to print out ‘timer’ every second.
File system access
The dart:io library provides access to files and directories through the File and Directory classes.
The following example prints its own source code. To determine the location of the source code being executed, we use the Platform class.
1 2 3 4 5 6 7 |
<span class="kwd">import</span> <span class="str">'dart:convert'</span><span class="pun">;</span> <span class="kwd">import</span> <span class="str">'dart:io'</span><span class="pun">;</span> <span class="typ">Future</span><span class="pun"><</span><span class="typ">void</span><span class="pun">></span><span class="pln"> main</span><span class="pun">()</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> file </span><span class="pun">=</span> <span class="typ">File</span><span class="pun">(</span><span class="typ">Platform</span><span class="pun">.</span><span class="pln">script</span><span class="pun">.</span><span class="pln">toFilePath</span><span class="pun">());</span><span class="pln"> print</span><span class="pun">(</span><span class="str">"${await (file.readAsString(encoding: ascii))}"</span><span class="pun">);</span> <span class="pun">}</span> |
Notice that the readAsString()
method is asynchronous; it returns a Future that returns the contents of the file once the file has been read from the underlying system. This asynchronicity allows the Dart thread to perform other work while waiting for the I/O operation to complete.
To illustrate more detailed file operations, let’s change the example to read the contents only up to the first semicolon and then to print that. You could do this in two ways: either open the file for random access, or open a Stream for the file and stream in the data.
Here is a version that opens the file for random access operations. The code opens the file for reading and then reads one byte at a time until it encounters the char code for ;
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<span class="kwd">import</span> <span class="str">'dart:io'</span><span class="pun">;</span> <span class="typ">Future</span><span class="pun"><</span><span class="typ">void</span><span class="pun">></span><span class="pln"> main</span><span class="pun">()</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="kwd">final</span><span class="pln"> semicolon </span><span class="pun">=</span> <span class="str">';'</span><span class="pun">.</span><span class="pln">codeUnitAt</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span> <span class="kwd">var</span><span class="pln"> result </span><span class="pun">=</span> <span class="pun"><</span><span class="typ">int</span><span class="pun">>[];</span> <span class="kwd">final</span><span class="pln"> script </span><span class="pun">=</span> <span class="typ">File</span><span class="pun">(</span><span class="typ">Platform</span><span class="pun">.</span><span class="pln">script</span><span class="pun">.</span><span class="pln">toFilePath</span><span class="pun">());</span> <span class="typ">RandomAccessFile</span><span class="pln"> file </span><span class="pun">=</span> <span class="kwd">await</span><span class="pln"> script</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="pln">mode</span><span class="pun">:</span> <span class="typ">FileMode</span><span class="pun">.</span><span class="pln">read</span><span class="pun">);</span> <span class="com">// Deal with each byte.</span> <span class="kwd">while</span> <span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">final</span><span class="pln"> byte </span><span class="pun">=</span> <span class="kwd">await</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">readByte</span><span class="pun">();</span><span class="pln"> result</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">byte</span><span class="pun">);</span> <span class="kwd">if</span> <span class="pun">(</span><span class="pln">byte </span><span class="pun">==</span><span class="pln"> semicolon</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> print</span><span class="pun">(</span><span class="typ">String</span><span class="pun">.</span><span class="pln">fromCharCodes</span><span class="pun">(</span><span class="pln">result</span><span class="pun">));</span> <span class="kwd">await</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span> <span class="kwd">break</span><span class="pun">;</span> <span class="pun">}</span> <span class="pun">}</span> <span class="pun">}</span> |
When you see a use of async
or await
, you are seeing a Future in action. Both the open()
and readByte()
methods return a Future object.
This code is, of course, a very simple use of random-access operations. Operations are available for writing, seeking to a given position, truncating, and so on.
Let’s implement a version using a stream. The following code opens the file for reading presenting the content as a stream of lists of bytes. Like all streams in Dart you listen on this stream (using await for
) and the data is given in chunks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<span class="kwd">import</span> <span class="str">'dart:io'</span><span class="pun">;</span> <span class="typ">Future</span><span class="pun"><</span><span class="typ">void</span><span class="pun">></span><span class="pln"> main</span><span class="pun">()</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> result </span><span class="pun">=</span> <span class="pun"><</span><span class="typ">int</span><span class="pun">>[];</span> <span class="typ">Stream</span><span class="pun"><</span><span class="typ">List</span><span class="pun"><</span><span class="typ">int</span><span class="pun">>></span><span class="pln"> stream </span><span class="pun">=</span> <span class="typ">File</span><span class="pun">(</span><span class="typ">Platform</span><span class="pun">.</span><span class="pln">script</span><span class="pun">.</span><span class="pln">toFilePath</span><span class="pun">()).</span><span class="pln">openRead</span><span class="pun">();</span> <span class="kwd">final</span><span class="pln"> semicolon </span><span class="pun">=</span> <span class="str">';'</span><span class="pun">.</span><span class="pln">codeUnitAt</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span> <span class="kwd">await</span> <span class="kwd">for</span> <span class="pun">(</span><span class="kwd">var</span><span class="pln"> data </span><span class="kwd">in</span><span class="pln"> stream</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">for</span> <span class="pun">(</span><span class="typ">int</span><span class="pln"> i </span><span class="pun">=</span> <span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun"><</span><span class="pln"> data</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span> <span class="pun">{</span><span class="pln"> result</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">data</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]);</span> <span class="kwd">if</span> <span class="pun">(</span><span class="pln">data</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span> <span class="pun">==</span><span class="pln"> semicolon</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> print</span><span class="pun">(</span><span class="typ">String</span><span class="pun">.</span><span class="pln">fromCharCodes</span><span class="pun">(</span><span class="pln">result</span><span class="pun">));</span> <span class="kwd">return</span><span class="pun">;</span> <span class="pun">}</span> <span class="pun">}</span> <span class="pun">}</span> <span class="pun">}</span> |
The stream subscription is implicitly handled by await for
. Exiting the await for
statement — using break
, return
, or an uncaught exception — cancels the subscription.
Stream<List<int>>
is used in multiple places in dart:io: when working with stdin, files, sockets, HTTP connections, and so on. Similarly, IOSinkobjects are used to stream data to stdout, files, sockets, HTTP connections, and so on.
Interacting with processes
For the simple case, use Process.run() to run a process and collect its output. Use run()
when you don’t need interactive control over the process.
1 2 3 4 5 6 7 8 |
<span class="kwd">import</span> <span class="str">'dart:io'</span><span class="pun">;</span> <span class="typ">Future</span><span class="pun"><</span><span class="typ">void</span><span class="pun">></span><span class="pln"> main</span><span class="pun">()</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="com">// List all files in the current directory,</span> <span class="com">// in UNIX-like operating systems.</span> <span class="typ">ProcessResult</span><span class="pln"> results </span><span class="pun">=</span> <span class="kwd">await</span> <span class="typ">Process</span><span class="pun">.</span><span class="pln">run</span><span class="pun">(</span><span class="str">'ls'</span><span class="pun">,</span> <span class="pun">[</span><span class="str">'-l'</span><span class="pun">]);</span><span class="pln"> print</span><span class="pun">(</span><span class="pln">results</span><span class="pun">.</span><span class="pln">stdout</span><span class="pun">);</span> <span class="pun">}</span> |
You can also start a process by creating a Process object using Process.start().
Once you have a Process object you can interact with the process by writing data to its stdin sink, reading data from its stderr and stdout streams, and killing the process. When the process exits, the exitCode
future completes with the exit code of the process.
The following example runs ls -l
in a separate process and prints the output and the exit code for the process to stdout. Since we are interested in getting lines, we use a Utf8Decoder (which decodes chunks of bytes into strings) followed by a LineSplitter (which splits the strings at line boundaries).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<span class="kwd">import</span> <span class="str">'dart:convert'</span><span class="pun">;</span> <span class="kwd">import</span> <span class="str">'dart:io'</span><span class="pun">;</span> <span class="typ">Future</span><span class="pun"><</span><span class="typ">void</span><span class="pun">></span><span class="pln"> main</span><span class="pun">()</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="kwd">final</span><span class="pln"> process </span><span class="pun">=</span> <span class="kwd">await</span> <span class="typ">Process</span><span class="pun">.</span><span class="pln">start</span><span class="pun">(</span><span class="str">'ls'</span><span class="pun">,</span> <span class="pun">[</span><span class="str">'-l'</span><span class="pun">]);</span> <span class="kwd">var</span><span class="pln"> lineStream </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">stdout</span><span class="pun">.</span><span class="pln">transform</span><span class="pun">(</span><span class="typ">Utf8Decoder</span><span class="pun">()).</span><span class="pln">transform</span><span class="pun">(</span><span class="typ">LineSplitter</span><span class="pun">());</span> <span class="kwd">await</span> <span class="kwd">for</span> <span class="pun">(</span><span class="kwd">var</span><span class="pln"> line </span><span class="kwd">in</span><span class="pln"> lineStream</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> print</span><span class="pun">(</span><span class="pln">line</span><span class="pun">);</span> <span class="pun">}</span> <span class="kwd">await</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">stderr</span><span class="pun">.</span><span class="pln">drain</span><span class="pun">();</span><span class="pln"> print</span><span class="pun">(</span><span class="str">'exit code: ${await process.exitCode}'</span><span class="pun">);</span> <span class="pun">}</span> |
Notice that exitCode
can complete before all of the lines of output have been processed. Also note that we do not explicitly close the process. In order to not leak resources we have to listen to both the stderr and stdout streams. We use await for
to listen to stdout, and stderr.drain()
to listen to (and discard) stderr.
Instead of printing the output to stdout, we can use the streaming classes to pipe the output of the process to a file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<span class="kwd">import</span> <span class="str">'dart:io'</span><span class="pun">;</span> <span class="typ">Future</span><span class="pun"><</span><span class="typ">void</span><span class="pun">></span><span class="pln"> main</span><span class="pun">()</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="kwd">final</span><span class="pln"> output </span><span class="pun">=</span> <span class="typ">File</span><span class="pun">(</span><span class="str">'output.txt'</span><span class="pun">).</span><span class="pln">openWrite</span><span class="pun">();</span> <span class="typ">Process</span><span class="pln"> process </span><span class="pun">=</span> <span class="kwd">await</span> <span class="typ">Process</span><span class="pun">.</span><span class="pln">start</span><span class="pun">(</span><span class="str">'ls'</span><span class="pun">,</span> <span class="pun">[</span><span class="str">'-l'</span><span class="pun">]);</span> <span class="com">// Wait for the process to finish; get the exit code.</span> <span class="kwd">final</span><span class="pln"> exitCode </span><span class="pun">=</span> <span class="pun">(</span><span class="kwd">await</span> <span class="typ">Future</span><span class="pun">.</span><span class="pln">wait</span><span class="pun">([</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">stdout</span><span class="pun">.</span><span class="pln">pipe</span><span class="pun">(</span><span class="pln">output</span><span class="pun">),</span> <span class="com">// Send stdout to file.</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">stderr</span><span class="pun">.</span><span class="pln">drain</span><span class="pun">(),</span> <span class="com">// Discard stderr.</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">exitCode </span><span class="pun">]))[</span><span class="lit">2</span><span class="pun">];</span><span class="pln"> print</span><span class="pun">(</span><span class="str">'exit code: $exitCode'</span><span class="pun">);</span> <span class="pun">}</span> |
Writing web servers
dart:io makes it easy to write HTTP servers and clients. To write a simple web server, all you have to do is create an HttpServer and hook up a listener (using await for
) to its stream of HttpRequest
s.
Here is a simple web server that just answers ‘Hello, world’ to any request.
1 2 3 4 5 6 7 8 9 |
<span class="kwd">import</span> <span class="str">'dart:io'</span><span class="pun">;</span> <span class="typ">Future</span><span class="pun"><</span><span class="typ">void</span><span class="pun">></span><span class="pln"> main</span><span class="pun">()</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="kwd">final</span><span class="pln"> server </span><span class="pun">=</span> <span class="kwd">await</span> <span class="typ">HttpServer</span><span class="pun">.</span><span class="pln">bind</span><span class="pun">(</span><span class="str">'127.0.0.1'</span><span class="pun">,</span> <span class="lit">8082</span><span class="pun">);</span> <span class="kwd">await</span> <span class="kwd">for</span> <span class="pun">(</span><span class="typ">HttpRequest</span><span class="pln"> request </span><span class="kwd">in</span><span class="pln"> server</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">response</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="str">'Hello, world'</span><span class="pun">);</span> <span class="kwd">await</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">response</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span> <span class="pun">}</span> <span class="pun">}</span> |
Running this application and pointing your browser to ‘http://127.0.0.1:8082’ gives you ‘Hello, world’ as expected.
Let’s add a bit more and actually serve files. The base path for every file that we serve will be the location of the script. If no path is specified in a request we will serve index.html. For a request with a path, we will attempt to find the file and serve it. If the file is not found we will respond with a ‘404 Not Found’ status. We make use of the streaming interface to pipe all the data read from a file directly to the response stream.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<span class="kwd">import</span> <span class="str">'dart:io'</span><span class="pun">;</span> <span class="typ">Future</span><span class="pun"><</span><span class="typ">void</span><span class="pun">></span><span class="pln"> runServer</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> basePath</span><span class="pun">)</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="kwd">final</span><span class="pln"> server </span><span class="pun">=</span> <span class="kwd">await</span> <span class="typ">HttpServer</span><span class="pun">.</span><span class="pln">bind</span><span class="pun">(</span><span class="str">'127.0.0.1'</span><span class="pun">,</span> <span class="lit">8082</span><span class="pun">);</span> <span class="kwd">await</span> <span class="kwd">for</span> <span class="pun">(</span><span class="typ">HttpRequest</span><span class="pln"> request </span><span class="kwd">in</span><span class="pln"> server</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">await</span><span class="pln"> handleRequest</span><span class="pun">(</span><span class="pln">basePath</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">);</span> <span class="pun">}</span> <span class="pun">}</span> <span class="typ">Future</span><span class="pun"><</span><span class="typ">void</span><span class="pun">></span><span class="pln"> handleRequest</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> basePath</span><span class="pun">,</span> <span class="typ">HttpRequest</span><span class="pln"> request</span><span class="pun">)</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="kwd">final</span> <span class="typ">String</span><span class="pln"> path </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">uri</span><span class="pun">.</span><span class="pln">toFilePath</span><span class="pun">();</span> <span class="com">// PENDING: Do more security checks here.</span> <span class="kwd">final</span> <span class="typ">String</span><span class="pln"> resultPath </span><span class="pun">=</span><span class="pln"> path </span><span class="pun">==</span> <span class="str">'/'</span> <span class="pun">?</span> <span class="str">'/index.html'</span> <span class="pun">:</span><span class="pln"> path</span><span class="pun">;</span> <span class="kwd">final</span> <span class="typ">File</span><span class="pln"> file </span><span class="pun">=</span> <span class="typ">File</span><span class="pun">(</span><span class="str">'$basePath$resultPath'</span><span class="pun">);</span> <span class="kwd">if</span> <span class="pun">(</span><span class="kwd">await</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">exists</span><span class="pun">())</span> <span class="pun">{</span> <span class="kwd">try</span> <span class="pun">{</span> <span class="kwd">await</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">response</span><span class="pun">.</span><span class="pln">addStream</span><span class="pun">(</span><span class="pln">file</span><span class="pun">.</span><span class="pln">openRead</span><span class="pun">());</span> <span class="pun">}</span> <span class="kwd">catch</span> <span class="pun">(</span><span class="pln">exception</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> print</span><span class="pun">(</span><span class="str">'Error happened: $exception'</span><span class="pun">);</span> <span class="kwd">await</span><span class="pln"> sendInternalError</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">response</span><span class="pun">);</span> <span class="pun">}</span> <span class="pun">}</span> <span class="kwd">else</span> <span class="pun">{</span> <span class="kwd">await</span><span class="pln"> sendNotFound</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">response</span><span class="pun">);</span> <span class="pun">}</span> <span class="pun">}</span> <span class="typ">Future</span><span class="pun"><</span><span class="typ">void</span><span class="pun">></span><span class="pln"> sendInternalError</span><span class="pun">(</span><span class="typ">HttpResponse</span><span class="pln"> response</span><span class="pun">)</span> <span class="kwd">async</span> <span class="pun">{</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">statusCode </span><span class="pun">=</span> <span class="typ">HttpStatus</span><span class="pun">.</span><span class="pln">internalServerError</span><span class="pun">;</span> <span class="kwd">await</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span> <span class="pun">}</span> <span class="typ">Future</span><span class="pun"><</span><span class="typ">void</span><span class="pun">></span><span class="pln"> sendNotFound</span><span class="pun">(</span><span class="typ">HttpResponse</span><span class="pln"> response</span><span class="pun">)</span> <span class="kwd">async</span> <span class="pun">{</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">statusCode </span><span class="pun">=</span> <span class="typ">HttpStatus</span><span class="pun">.</span><span class="pln">notFound</span><span class="pun">;</span> <span class="kwd">await</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span> <span class="pun">}</span> <span class="typ">Future</span><span class="pun"><</span><span class="typ">void</span><span class="pun">></span><span class="pln"> main</span><span class="pun">()</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="com">// Compute base path for the request based on the location of the</span> <span class="com">// script, and then start the server.</span> <span class="kwd">final</span><span class="pln"> script </span><span class="pun">=</span> <span class="typ">File</span><span class="pun">(</span><span class="typ">Platform</span><span class="pun">.</span><span class="pln">script</span><span class="pun">.</span><span class="pln">toFilePath</span><span class="pun">());</span> <span class="kwd">await</span><span class="pln"> runServer</span><span class="pun">(</span><span class="pln">script</span><span class="pun">.</span><span class="pln">parent</span><span class="pun">.</span><span class="pln">path</span><span class="pun">);</span> <span class="pun">}</span> |
Writing HTTP clients is very similar to using the HttpClient class.
Feature requests welcome
The dart:io library is already capable of performing a lot of tasks. For example, the Pub site uses dart:io.
Please give dart:io a spin and let us know what you think. Feature requests are very welcome! When you file a bug or feature request, use dartbug.com. To find reported issues, search for the library-io label.