Isolates look unlikely to serve as a teachable implementation for the remote proxy pattern in Dart. They are relatively simple, but still remain a tad too cumbersome. Last night's exploration of simple streams shows promise, begging the question of how another stream might work—websockets.
I borrow (OK, steal verbatim) the example websocket server from the Dart on the server example:
#!/usr/bin/env dart import 'dart:async'; import 'dart:io'; handleMsg(msg) { print('Message received: $msg'); } main() { runZoned(() async { var server = await HttpServer.bind('localhost', 4040); await for (var req in server) { if (req.uri.path == '/ws') { // Upgrade a HttpRequest to a WebSocket connection. var socket = await WebSocketTransformer.upgrade(req); socket.listen(handleMsg); }; } }, onError: (e) => print("An error occurred: $e")); }I will add my proxy pattern code shortly. For now, I just want to ensure that it works. I save this as
bin/server.dart
, chmod 755
and start it up with ./bin/server.dart
. Nothing crashes, so I assume that I am good to go.In my existing proxy pattern script, I add the appropriate websocket client code:
#!/usr/bin/env dart import 'dart:async'; import 'dart:io'; main() async { var socket = await WebSocket.connect('ws://localhost:4040/ws'); socket.add('Hello, World!'); // ... }That should connect to my running websocket server and add a message to the websocket stream to which the server is currently listening. When I run this client script, I see the following message from the server:
$ ./bin/server.dart Message received: Hello, World!Nice! Web sockets were never all that hard in Dart, but they are getting close to trivial.
So what is it going to take to convert my
ProxyCar
from streams (which were converted last night from isolates) to websockets? Blood sacrifice? The answer is surprisingly little.In my client code, I continue to open the websocket, pass that to a
ProxyCar
, then perform some remote car operations:main() async { var socket = await WebSocket.connect('ws://localhost:4040/ws'); ProxyCar car = new ProxyCar(socket); print("Attempting to drive remote car..."); await car.drive(); print("Car is ${car.state}"); print("--"); print("Attempting to stop remote car..."); await car.stop(); print("Car is ${await car.state}"); }The
ProxyCar
is responsible for listening to this websocket for state responses from the real car. I establish that listener in the constructor:class ProxyCar implements AsyncAuto { Stream _socket, _broadcast; String _state; ProxyCar(this._socket) { _broadcast = _socket.asBroadcastStream(); _broadcast.listen((message) { _state = message; }); } // ... }I get a broadcast version of the websocket so that I can listen to it in multiple locations. Here, I listen for state updates. When I send action messages to the real car, I also listen to the stream for confirmation that the message was received:
class ProxyCar implements AsyncAuto { Stream _socket, _broadcast; String _state; // ... String get state => _state; Future drive() => _send('drive'); Future stop() => _send('stop'); Future _send(message) { _socket.add(message); return _broadcast.first; } }I may reconsider that at some point, just for ease of discussion. For now, I leave it as-is.
The server is using the same library. Instead of the
ProxyCar
instance, it works with the real subject in this pattern: an AsynCar
instance: // ...
if (req.uri.path == '/ws') {
// Upgrade a HttpRequest to a WebSocket connection.
var socket = await WebSocketTransformer.upgrade(req);
new AsyncCar(socket);
};
// ...
Like the ProxyCar
, the AsyncCar
implements the AsyncAuto
interface:// Subject abstract class AsyncAuto implements Automobile { String get state; Future drive(); Future stop(); }If anything, the
AsyncCar
real subject is even simpler than the proxy:class AsyncCar implements AsyncAuto { Stream _socket; Car _car; AsyncCar(this._socket) { _car = new Car(); _socket.listen((message) { print("[AsyncCar] received: $message"); if (message == 'drive') _car.drive(); if (message == 'stop') _car.stop(); _socket.add(state); }); } String get state => _car.state; Future drive() => new Future((){ _car.drive(); }); Future stop() => new Future((){ _car.stop(); }); }It adapts a synchronous
Car
, which it manipulates in response to messages that it receives from the socket. If the websocket message is 'drive'
, then the car
instance is sent the drive message. If the websocket see 'stop'
, stop()
is invoked on car
.And that actually works. Running the client code results in:
$ ./bin/drive.dart Attempting to drive remote car... Car is driving -- Attempting to stop remote car... Car is idleChecking the server output, I see:
$ ./bin/server.dart [AsyncCar] received: drive [AsyncCar] received: stopSo there you have it. I can drive a car over websockets with Dart. And it was pretty darn easy!
Day #70
No comments:
Post a Comment