BREAKING CHANGE: New version of dart:io

648 views
Skip to first unread message

Søren Gjesse

unread,
Feb 21, 2013, 9:26:40 AM2/21/13
to General Dart Discussion
Hi Dartisans,

Some time ago the Dart async library was updated. The Stream class was added and the Future class cleaned up. Since this update the dart:io team have been busy working on making dart:io play nicely with the Dart async library. In fact we have done a full re-design of the dart:io API to use the classes and concepts in the core library and in dart:async. This means that there is almost no callback registration in dart:io any more. All async operations now use streams and futures.

We think this is a great step forward in aligning all of the “dart:” API's and for you to have fewer concepts to learn when using Dart for different tasks.

However this is a breaking change which requires rewriting code using dart:io. The good news is that most of these changes should be mechanical. The following sections describe these changes in more details and give some examples on how to migrate code to the new API. For more information take a look on the API documentation for dart:io.

Streams in dart:io
The classes InputStream and OutputStream are gone and have been replaced with classes implementing IOSink and Stream<List<int>>.

When reading from a Stream<List<int>> just use the listen method which have arguments matching the callbacks previously used. This shows how to migrate code using an InputStream to the streams-based API:

dart:io v1 code
InputStream stream = ...
stream.onData = () {
 var data = request.inputStream.read();
 /* Process data. */
};
stream.onClosed = () {
 /* All data received. */
};
stream.onError = (e) {
 /* Error on input. */
};

dart:io v2 code
Stream<List<int>> stream = ...
stream.listen(
 (data) { /* Process data. */ },
 onDone: () { /* All data received. */ },
 onError: (e) { /* Error on input. */ });

As the InputStream class is now gone so is the StringInputStream for turning a stream of bytes into strings and/or lines. Two new stream transformers StringDecoder and LineTransformer have been added to address this. The StringDecoder transforms a stream of List<int> to a stream of String and the LineTransformer transforms a stream of String to a new stream of String where each string is a line.

dart:io v1 code
InputStream stream = ...
StringInputStream stringStream = new StringInputStream(stream);
stringStream.onLine = () {
 String line = stringStream.readLine();
 /* Do something with line. */
};
stringStream.onClosed = () {
 /* No more lines */
};
stream.onError = (e) {
 /* Error on input. */
};

dart:io v2 code
Stream<<int>> stream = ...
stream
 .transform(new StringDecoder())
 .transform(new LineTransformer())
 .listen((String line) { /* Do something with line. */ },
         onDone: () { /* No more lines */ },
         onError: (e) { /* Error on input. */ });

The IOSink replaces OutputStream and this shows how to migrate code using OutputStream to use an IOSink:

dart:io v1 code
OutputStream stream = …
stream.write([72, 101, 108, 108, 111]);  // Hello
stream.writeString(", world!");
stream.close();

dart:io v2 code
IOSink sink = …
sink.add([72, 101, 108, 108, 111]);  // Hello
sink.addString(", world!");
sink.close();

The IOSink also allows you to pipe directly from a stream.

HTTP
The main changes to the HTTP server and client part of dart:io are the following:

* A new HttpServer listening for requests is created using the static method bind.
* An HttpServer is a stream of HttpRequests.
* The defaultRequestHandler setter and addRequestHandler method on HttpServer are gone.
* The HttpRequest and HttpClientResponse objects implement Stream<List<int>>.
* The HttpClientRequest and HttpResponse objects implement IOSink.

To create a new listening HTTP server use the static method bind which returns a future.

dart:io v1 code
HttpServer server = new HttpServer();
server.defaultRequestHandler = …
server.addRequestHandler(…);
server.listen(“127.0.0.1”, 8080);
// Now the HTTP server is bound and listening for requests.

dart:io v2 code
HttpServer.bind(“127.0.0.1”, 8080)
   .then((HttpServer server) {
     server.listen(
         (HttpRequest request) {
           // Handle the request.
         });
}

The request routing through defaultRequestHandler and addRequestHandler is gone and any routing to specific request handling methods should be added to the listen handling. The HttpResponse is available as the response property on the HttpRequest object.

For client side HTTP the HttpClient class still exists. The HttpClientConnection class is gone, and instead the request initiation methods get, post, open, etc. now returns a future for the HttpClientRequest object. The HttpClientRequest object has a response property which is a future for the HttpClientResponse. As a convenience the HttpClientRequest.close method also returns the future for the response. This shows how to migrate HTTP client code:

dart:io v1 code
HttpClient client = new HttpClient();
HttpClientConnection connection = client.get(...);
connection.onRequest = (HttpClientRequest request) {
 // Prepare the request.
 request.outputStream.close();
}
connection.onResponse = (HttpClientResponse response) {
 // Process the response.
}

dart:io v2 code
HttpClient client = new HttpClient();
client.get(...)
   .then((HttpClientRequest request) {
     // Prepare the request.
     return request.close();
   })
   .then((HttpClientResponse response) {
     // Process the response.
   });

Web Sockets
The web socket interface has been simplified and now uses the same class for web socket connections on both the client and the server. The WebSocket class is a stream of events.

On the server the web socket handling is implemented as a stream transformer. This transformer can transform a stream of HttpRequests into a stream of WebSockets.

dart:io v1 code
HttpServer server = new HttpServer();
server.listen(...);
WebSocketHandler handler = new WebSocketHandler();
handler.onOpen = (WebSocketConnection connection) {
 connection.onMessage = (Object message) {
   /* Handle message. */
 };
 connection.onClosed = (status, reason) {
   /* Handle closed. */
 };
};
server.defaultRequestHandler = handler.onRequest;

dart:io v2 code
HttpServer.bind(...).then((server) {
 server.transform(new WebSocketTransformer()).listen((WebSocket webSocket) {
   webSocket.listen((event) {
     if (event is MessageEvent) {
       /* Handle message. */
     } else if (event is CloseEvent) {
       /* Handle closed. */
     }
   });
 });

On the client connecting a web socket has become much simpler. Just use the WebSocket static method connect which returns a future for the web socket connection as shown here:

dart:io v1 code
HttpClient client = new HttpClient();
HttpClientConnection conn = client.openUrl(“https://127.0.0.1:8080”);
WebSocketClientConnection wsconn = new WebSocketClientConnection(conn);
wsconn.onMessage = (message) {
 /* Handle message. */
}
wsconn.onClosed = (status, reason) {
 /* Handle closed. */
};

dart:io v2 code
WebSocket.connect("ws://127.0.0.1:8080")
   .then((WebSocket webSocket) {
     webSocket.listen((event) {
       if (event is MessageEvent) {
         /* Handle message. */
       } else if (event is CloseEvent) {
         /* Handle closed. */
       }
     });
   });

Process
The Process class uses Stream<List<int>> for stdout and stderr and IOSink for stdin. The exit code for the process is now available through the exitCode future.

dart:io v1 code
Process process = ...
process.stdout.onData = ...
process.stdout.onDone = ...
process.onExit = (exitCode) { /* do something with exitCode. */ }

dart:io v2 code
Process process = ...
process.stdout.listen(...);
p.exitCode.then((exitCode) { /* do something with exitCode. */ });

Likewise the types of the top level properties stdin, stdout and stderr have been changed. stdio is a Stream<List<int>> and stdout and stderr are IOSinks.

File and directory
Reading and writing a file also uses Stream<List<int>> and IOSink. To read a file change the use of openInputStream to openRead which returns a stream of type Stream<List<int>>. Likewise change the use of openOutputStream to openWrite which returns an IOSink.

The Directory.list function now returns a stream of FileSystemEntity objects. The FileSystemEntity is a superclass of both File and Directory. The previous DirectoryLister where callbacks were set is now gone.

dart:io v1 code
Directory dir = ...
DirectoryLister lister = dir.list();
lister.onDir = (Directory directory) { /* Do something with directory. */ };
lister.onFile = (File file) { /* Do something with file. */ };
lister.onDone = (bool complete) { /* Listing ended.*/ };
lister.onError = (error) { /* Listing error.*/ };


dart:io v2 code
Directory dir = ...
dir.list().listen(
   (FileSystemEntity fse) {
     if (fse is Directory) {
       Directory directory = fse;
       /* Do something with directory. */
     } else if (fse is File) {
       File file = fse;
       /* Do something with file. */
     }
   },
   onDone: () { /* Listing ended.*/ },
   onError: (error) { /* Listing error.*/ });

Sockets
The classes Socket and SecureSocket both implement Stream<List<int>> and IOSink as they support bidirectional communication. This replaces all the reading and writing previously provided through a combination of callbacks, read and write methods, and InputStream and OutputStream objects backed by sockets.

Connecting a socket now uses a static method returning a future instead of a callback.

dart:io v1 code
Socket socket = new Socket(host, port);
socket.onConnect = () { /* Socket connected. */ }

dart:io v2 code
Socket.connect(host, port).then((Socket socket) {
 /* Socket connected. */
};

The classes ServerSocket and SecureServerSocket now uses a static method returning a future when binding to an interface. A listening server socket delivers the socket connections as a stream instead of through the onConnection callback.

dart:io v1 code
ServerSocket socket = new ServerSocket(host, port);
socket.onConnection = (Socket clientSocket) {
 /* Do something with the clientSocket. */
}

dart:io v2 code
ServerSocket.bind(host, port).then((ServerSocket socket) {
 socket.listen((Socket clientSocket) {
   /* Do something with the clientSocket. */
 })
});

Raw sockets
In order to provide a low level socket API we introduced raw sockets. Raw sockets give you access to low-level socket events without giving you data in your hand (think of this as the events you get from epoll on a socket file descriptor on Linux systems). Based on the low-level events you can read out data from the socket and decide when to write more data to the socket. The high-level Socket and ServerSocket classes are built on top of the RawSocket and RawServerSocket classes. Check out the API documentation on the Raw* classes.

Regards,
Søren

Matthew Butler

unread,
Feb 21, 2013, 10:16:00 AM2/21/13
to mi...@dartlang.org
This is really exciting. Especially the RawSockets! This is some much needed love for dart:io.

However do you have any information on availability? Should this be checked out from the separate experimental dart_v2_io branch? Is this getting ready (or already) dropped into bleeding_edge?

Thanks for the very thorough introduction. I've been looking forward to this change and am actually looking forward to updating my code. Should be fun playing with some of the new HttpServer stuff! :)

Kevin Moore

unread,
Feb 21, 2013, 10:17:43 AM2/21/13
to mi...@dartlang.org
http://code.google.com/p/dart/source/detail?r=18820

In bleeding edge as of 3 hours ago. :-)

--
Consider asking HOWTO questions at Stack Overflow: http://stackoverflow.com/tags/dart
 
 

Matthew Butler

unread,
Feb 21, 2013, 10:25:33 AM2/21/13
to mi...@dartlang.org

On Thursday, February 21, 2013 11:17:43 AM UTC-4, Kevin Moore wrote:
http://code.google.com/p/dart/source/detail?r=18820

In bleeding edge as of 3 hours ago. :-)

 Ahh nice! Just need to wait for the bleeding_edge docs to regenerate then ;)

Matt

Mads Ager

unread,
Feb 21, 2013, 10:27:01 AM2/21/13
to General Dart Discussion
When they do and when you start updating your code, please keep the bug reports coming! :-)

Cheers,    -- Mads 

Matthew Butler

unread,
Feb 21, 2013, 10:40:39 AM2/21/13
to mi...@dartlang.org
With pleasure. 

Anyway of getting the bleeding_edge forced to regenerate? I notice it hasn't been done in 3 days (since the 18th for the HttpServer docs at least). Not sure how long it usually is between regenerations of the docs.

Matt

Paul Brauner

unread,
Feb 21, 2013, 10:46:29 AM2/21/13
to General Dart Discussion
1 season for Christopher Eccleston, 3 for David Tennant.

Paul

(Sorry, I had to make that joke)
 
Matt

Aza Tek

unread,
Feb 21, 2013, 11:02:30 AM2/21/13
to mi...@dartlang.org
Guess I'm the only one who feels that 'naming' in the v1 API was more INTUITIVE :(


Matthew Butler

unread,
Feb 21, 2013, 11:19:41 AM2/21/13
to mi...@dartlang.org


On Thursday, February 21, 2013 11:27:01 AM UTC-4, Mads Ager wrote:
First bug filed (yes prior to the docs being generated): http://dartbug.com/8679  -> HttpRequest no longer has a 'path' property

Matt 

Ladislav Thon

unread,
Feb 21, 2013, 12:20:11 PM2/21/13
to General Dart Discussion


> Guess I'm the only one who feels that 'naming' in the v1 API was more INTUITIVE :(

You're not. But it's just a feeling, let's take a look at the API in more detail and play with it before judging.

Honestly, however, I really don't like the Stream<List<int>> vs. IOSink stuff (and shouldn't it be IoSink?).

LT

Bob Nystrom

unread,
Feb 21, 2013, 12:37:22 PM2/21/13
to General Dart Discussion

On Thu, Feb 21, 2013 at 9:20 AM, Ladislav Thon <lad...@gmail.com> wrote:
IOSink stuff (and shouldn't it be IoSink?)

After a lot of back and forth, we decided to modify the original style guide rule for acronyms so that two-letter ones are kept capitalized. For what it's worth, this is the guideline .NET uses too.

I believe our APIs are getting increasingly consistent here (see for example, PIHRElement, UIEvent, etc.). If you find places where we get this wrong, please do file bugs and let us know.

Cheers!

- bob

Aza Tek

unread,
Feb 21, 2013, 1:28:02 PM2/21/13
to mi...@dartlang.org
-1
PI is a bad example here because it's a CONSTANT. 
Two, three, four letters; it shouldn't matter. Let's leave all-caps for constants. 

Ladislav Thon

unread,
Feb 21, 2013, 2:41:54 PM2/21/13
to mi...@dartlang.org
IOSink stuff (and shouldn't it be IoSink?)

After a lot of back and forth, we decided to modify the original style guide rule for acronyms so that two-letter ones are kept capitalized. For what it's worth, this is the guideline .NET uses too.

This is so arbitrary it's not even funny.

LT

Bob Nystrom

unread,
Feb 21, 2013, 4:37:34 PM2/21/13
to General Dart Discussion

On Thu, Feb 21, 2013 at 11:41 AM, Ladislav Thon <lad...@gmail.com> wrote:
This is so arbitrary it's not even funny.

It's not arbitrary, it's human. Names are a user interface for a program. When it comes to UI, human preferences and aesthetics are critical. What the .NET folks and the Dart team found was that capitalizing short acronyms like Io and Ui looked distractingly bad. IO and UI are a more humane interface.

- bob

Seth Ladd

unread,
Feb 21, 2013, 5:00:55 PM2/21/13
to General Dart Discussion
Anyway of getting the bleeding_edge forced to regenerate? I notice it hasn't been done in 3 days (since the 18th for the HttpServer docs at least). Not sure how long it usually is between regenerations of the docs.

I kicked the cache... new docs are up! http://api.dartlang.org/docs/bleeding_edge/dart_io.html
 

Matt

Shannon Behrens

unread,
Mar 7, 2013, 4:21:02 PM3/7/13
to General Dart Discussion, Mads Ager, Seth Ladd
I'm really happy with all the improvements to the synchronous APIs! Thank you so much!!! That makes writing batch-type scripts much easier!

-jj


--
Reply all
Reply to author
Forward
0 new messages