You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@thrift.apache.org by je...@apache.org on 2015/10/03 01:47:03 UTC

[2/5] thrift git commit: THRIFT-3299 Create an Apache Thrift language binding for Dart (dartlang.org). Client: Dart Patch: Mark Erickson

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/lib/src/transport/t_message_reader.dart
----------------------------------------------------------------------
diff --git a/lib/dart/lib/src/transport/t_message_reader.dart b/lib/dart/lib/src/transport/t_message_reader.dart
new file mode 100644
index 0000000..8ca0708
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_message_reader.dart
@@ -0,0 +1,99 @@
+/// Licensed to the Apache Software Foundation (ASF) under one
+/// or more contributor license agreements. See the NOTICE file
+/// distributed with this work for additional information
+/// regarding copyright ownership. The ASF licenses this file
+/// to you under the Apache License, Version 2.0 (the
+/// "License"); you may not use this file except in compliance
+/// with the License. You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing,
+/// software distributed under the License is distributed on an
+/// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+/// KIND, either express or implied. See the License for the
+/// specific language governing permissions and limitations
+/// under the License.
+
+part of thrift;
+
+/// [TMessageReader] extracts a [TMessage] from bytes.  This is used to allow a
+/// transport to inspect the message seqid and map responses to requests.
+class TMessageReader {
+  final TProtocolFactory protocolFactory;
+
+  final int byteOffset;
+  final _TMessageReaderTransport _transport;
+
+  /// Construct a [MessageReader].  The optional [byteOffset] specifies the
+  /// number of bytes to skip before reading the [TMessage].
+  TMessageReader(this.protocolFactory, {int byteOffset: 0})
+      : _transport = new _TMessageReaderTransport(),
+        this.byteOffset = byteOffset;
+
+  TMessage readMessage(Uint8List bytes) {
+    _transport.reset(bytes, byteOffset);
+    TProtocol protocol = protocolFactory.getProtocol(_transport);
+    TMessage message = protocol.readMessageBegin();
+    _transport.reset(null);
+
+    return message;
+  }
+}
+
+/// An internal class used to support [TMessageReader].
+class _TMessageReaderTransport extends TTransport {
+  _TMessageReaderTransport();
+
+  Iterator<int> _readIterator;
+
+  void reset(Uint8List bytes, [int offset = 0]) {
+    if (bytes == null) {
+      _readIterator = null;
+      return;
+    }
+
+    if (offset > bytes.length) {
+      throw new ArgumentError("The offset exceeds the bytes length");
+    }
+
+    _readIterator = bytes.iterator;
+
+    for (var i = 0; i < offset; i++) {
+      _readIterator.moveNext();
+    }
+  }
+
+  get isOpen => true;
+
+  Future open() => throw new UnsupportedError("Unsupported in MessageReader");
+
+  Future close() => throw new UnsupportedError("Unsupported in MessageReader");
+
+  int read(Uint8List buffer, int offset, int length) {
+    if (buffer == null) {
+      throw new ArgumentError.notNull("buffer");
+    }
+
+    if (offset + length > buffer.length) {
+      throw new ArgumentError("The range exceeds the buffer length");
+    }
+
+    if (_readIterator == null || length <= 0) {
+      return 0;
+    }
+
+    int i = 0;
+    while (i < length && _readIterator.moveNext()) {
+      buffer[offset + i] = _readIterator.current;
+      i++;
+    }
+
+    return i;
+  }
+
+  void write(Uint8List buffer, int offset, int length) =>
+      throw new UnsupportedError("Unsupported in MessageReader");
+
+  Future flush() => throw new UnsupportedError("Unsupported in MessageReader");
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/lib/src/transport/t_socket.dart
----------------------------------------------------------------------
diff --git a/lib/dart/lib/src/transport/t_socket.dart b/lib/dart/lib/src/transport/t_socket.dart
new file mode 100644
index 0000000..74618b6
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_socket.dart
@@ -0,0 +1,38 @@
+/// Licensed to the Apache Software Foundation (ASF) under one
+/// or more contributor license agreements. See the NOTICE file
+/// distributed with this work for additional information
+/// regarding copyright ownership. The ASF licenses this file
+/// to you under the Apache License, Version 2.0 (the
+/// "License"); you may not use this file except in compliance
+/// with the License. You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing,
+/// software distributed under the License is distributed on an
+/// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+/// KIND, either express or implied. See the License for the
+/// specific language governing permissions and limitations
+/// under the License.
+
+part of thrift;
+
+enum TSocketState { CLOSED, OPEN }
+
+abstract class TSocket {
+  Stream<TSocketState> get onState;
+
+  Stream<String> get onError;
+
+  Stream<Uint8List> get onMessage;
+
+  bool get isOpen;
+
+  bool get isClosed;
+
+  Future open();
+
+  Future close();
+
+  void send(Uint8List data);
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/lib/src/transport/t_socket_transport.dart
----------------------------------------------------------------------
diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart
new file mode 100644
index 0000000..ad7e48e
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_socket_transport.dart
@@ -0,0 +1,175 @@
+/// Licensed to the Apache Software Foundation (ASF) under one
+/// or more contributor license agreements. See the NOTICE file
+/// distributed with this work for additional information
+/// regarding copyright ownership. The ASF licenses this file
+/// to you under the Apache License, Version 2.0 (the
+/// "License"); you may not use this file except in compliance
+/// with the License. You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing,
+/// software distributed under the License is distributed on an
+/// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+/// KIND, either express or implied. See the License for the
+/// specific language governing permissions and limitations
+/// under the License.
+
+part of thrift;
+
+/// Socket implementation of [TTransport].
+///
+/// For example:
+///
+///     var transport = new TClientSocketTransport(new TWebSocket(url));
+///     var protocol = new TBinaryProtocol(transport);
+///     var client = new MyThriftServiceClient(protocol);
+///     var result = client.myMethod();
+///
+/// Adapted from the JS WebSocket transport.
+abstract class TSocketTransport extends TBufferedTransport {
+  final Logger log = new Logger('thrift.TSocketTransport');
+
+  final TSocket socket;
+
+  /// A transport using the provided [socket].
+  TSocketTransport(this.socket) {
+    if (socket == null) {
+      throw new ArgumentError.notNull('socket');
+    }
+
+    socket.onError.listen((String e) => log.warning(e));
+    socket.onMessage.listen(handleIncomingMessage);
+  }
+
+  bool get isOpen => socket.isOpen;
+
+  Future open() {
+    _reset(isOpen: true);
+    return socket.open();
+  }
+
+  Future close() {
+    _reset(isOpen: false);
+    return socket.close();
+  }
+
+  /// Make an incoming message available to read from the transport.
+  void handleIncomingMessage(Uint8List messageBytes) {
+    _setReadBuffer(messageBytes);
+  }
+}
+
+/// [TClientSocketTransport] is a basic client socket transport.  It sends
+/// outgoing messages and expects a response.
+///
+/// NOTE: This transport expects a single threaded server, as it will process
+/// responses in FIFO order.
+class TClientSocketTransport extends TSocketTransport {
+  final List<Completer<Uint8List>> _completers = [];
+
+  TClientSocketTransport(TSocket socket) : super(socket);
+
+  Future flush() {
+    Uint8List bytes = _consumeWriteBuffer();
+
+    // Use a sync completer to ensure that the buffer can be read immediately
+    // after the read buffer is set, and avoid a race condition where another
+    // response could overwrite the read buffer.
+    Completer completer = new Completer.sync();
+    _completers.add(completer);
+
+    socket.send(bytes);
+
+    return completer.future;
+  }
+
+  void handleIncomingMessage(Uint8List messageBytes) {
+    super.handleIncomingMessage(messageBytes);
+
+    if (_completers.isNotEmpty) {
+      var completer = _completers.removeAt(0);
+      completer.complete();
+    }
+  }
+}
+
+/// [TAsyncClientSocketTransport] sends outgoing messages and expects an
+/// asynchronous response.
+///
+/// NOTE: This transport uses a [MessageReader] to read a [TMessage] when an
+/// incoming message arrives to correlate a response to a request, using the
+/// seqid.
+class TAsyncClientSocketTransport extends TSocketTransport {
+  static const defaultTimeout = const Duration(seconds: 30);
+
+  final Map<int, Completer<Uint8List>> _completers = {};
+
+  final TMessageReader messageReader;
+
+  final Duration responseTimeout;
+
+  TAsyncClientSocketTransport(TSocket socket, TMessageReader messageReader,
+      {Duration responseTimeout: defaultTimeout})
+      : this.messageReader = messageReader,
+        this.responseTimeout = responseTimeout,
+        super(socket);
+
+  Future flush() {
+    Uint8List bytes = _consumeWriteBuffer();
+    TMessage message = messageReader.readMessage(bytes);
+    int seqid = message.seqid;
+
+    // Use a sync completer to ensure that the buffer can be read immediately
+    // after the read buffer is set, and avoid a race condition where another
+    // response could overwrite the read buffer.
+    Completer completer = new Completer.sync();
+    _completers[seqid] = completer;
+
+    if (responseTimeout != null) {
+      new Future.delayed(responseTimeout, () {
+        var completer = _completers.remove(seqid);
+        if (completer != null) {
+          completer.completeError(
+              new TimeoutException("Response timed out.", responseTimeout));
+        }
+      });
+    }
+
+    socket.send(bytes);
+
+    return completer.future;
+  }
+
+  void handleIncomingMessage(Uint8List messageBytes) {
+    super.handleIncomingMessage(messageBytes);
+
+    TMessage message = messageReader.readMessage(messageBytes);
+    var completer = _completers.remove(message.seqid);
+    if (completer != null) {
+      completer.complete();
+    }
+  }
+}
+
+/// [TServerSocketTransport] listens for incoming messages.  When it sends a
+/// response, it does not expect an acknowledgement.
+class TServerSocketTransport extends TSocketTransport {
+  final StreamController _onIncomingMessageController;
+  Stream get onIncomingMessage => _onIncomingMessageController.stream;
+
+  TServerSocketTransport(TSocket socket)
+      : _onIncomingMessageController = new StreamController.broadcast(),
+        super(socket);
+
+  Future flush() async {
+    Uint8List message = _consumeWriteBuffer();
+    socket.send(message);
+  }
+
+  void handleIncomingMessage(Uint8List messageBytes) {
+    super.handleIncomingMessage(messageBytes);
+
+    _onIncomingMessageController.add(null);
+  }
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/lib/src/transport/t_transport.dart
----------------------------------------------------------------------
diff --git a/lib/dart/lib/src/transport/t_transport.dart b/lib/dart/lib/src/transport/t_transport.dart
new file mode 100644
index 0000000..563d5eb
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_transport.dart
@@ -0,0 +1,70 @@
+/// Licensed to the Apache Software Foundation (ASF) under one
+/// or more contributor license agreements. See the NOTICE file
+/// distributed with this work for additional information
+/// regarding copyright ownership. The ASF licenses this file
+/// to you under the Apache License, Version 2.0 (the
+/// "License"); you may not use this file except in compliance
+/// with the License. You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing,
+/// software distributed under the License is distributed on an
+/// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+/// KIND, either express or implied. See the License for the
+/// specific language governing permissions and limitations
+/// under the License.
+
+part of thrift;
+
+abstract class TTransport {
+  /// Queries whether the transport is open.
+  /// Returns [true] if the transport is open.
+  bool get isOpen;
+
+  /// Opens the transport for reading/writing.
+  /// Throws [TTransportError] if the transport could not be opened.
+  Future open();
+
+  /// Closes the transport.
+  Future close();
+
+  /// Reads up to [length] bytes into [buffer], starting at [offset].
+  /// Returns the number of bytes actually read.
+  /// Throws [TTransportError] if there was an error reading data
+  int read(Uint8List buffer, int offset, int length);
+
+  /// Guarantees that all of [length] bytes are actually read off the transport.
+  /// Returns the number of bytes actually read, which must be equal to
+  /// [length].
+  /// Throws [TTransportError] if there was an error reading data
+  int readAll(Uint8List buffer, int offset, int length) {
+    int got = 0;
+    int ret = 0;
+    while (got < length) {
+      ret = read(buffer, offset + got, length - got);
+      if (ret <= 0) {
+        throw new TTransportError(
+            TTransportErrorType.UNKNOWN,
+            "Cannot read. Remote side has closed. Tried to read $length "
+            "bytes, but only got $got bytes.");
+      }
+      got += ret;
+    }
+    return got;
+  }
+
+  /// Writes up to [len] bytes from the buffer.
+  /// Throws [TTransportError] if there was an error writing data
+  void write(Uint8List buffer, int offset, int length);
+
+  /// Writes the [bytes] to the output.
+  /// Throws [TTransportError] if there was an error writing data
+  void writeAll(Uint8List buffer) {
+    write(buffer, 0, buffer.length);
+  }
+
+  /// Flush any pending data out of a transport buffer.
+  /// Throws [TTransportError] if there was an error writing out data.
+  Future flush();
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/lib/src/transport/t_transport_error.dart
----------------------------------------------------------------------
diff --git a/lib/dart/lib/src/transport/t_transport_error.dart b/lib/dart/lib/src/transport/t_transport_error.dart
new file mode 100644
index 0000000..d3508e0
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_transport_error.dart
@@ -0,0 +1,31 @@
+/// Licensed to the Apache Software Foundation (ASF) under one
+/// or more contributor license agreements. See the NOTICE file
+/// distributed with this work for additional information
+/// regarding copyright ownership. The ASF licenses this file
+/// to you under the Apache License, Version 2.0 (the
+/// "License"); you may not use this file except in compliance
+/// with the License. You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing,
+/// software distributed under the License is distributed on an
+/// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+/// KIND, either express or implied. See the License for the
+/// specific language governing permissions and limitations
+/// under the License.
+
+part of thrift;
+
+class TTransportErrorType {
+  static const int UNKNOWN = 0;
+  static const int NOT_OPEN = 1;
+  static const int ALREADY_OPEN = 2;
+  static const int TIMED_OUT = 3;
+  static const int END_OF_FILE = 4;
+}
+
+class TTransportError extends TError {
+  TTransportError([int type = TTransportErrorType.UNKNOWN, String message = ""])
+      : super(type, message);
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/lib/src/transport/t_transport_factory.dart
----------------------------------------------------------------------
diff --git a/lib/dart/lib/src/transport/t_transport_factory.dart b/lib/dart/lib/src/transport/t_transport_factory.dart
new file mode 100644
index 0000000..7a10461
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_transport_factory.dart
@@ -0,0 +1,27 @@
+/// Licensed to the Apache Software Foundation (ASF) under one
+/// or more contributor license agreements. See the NOTICE file
+/// distributed with this work for additional information
+/// regarding copyright ownership. The ASF licenses this file
+/// to you under the Apache License, Version 2.0 (the
+/// "License"); you may not use this file except in compliance
+/// with the License. You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing,
+/// software distributed under the License is distributed on an
+/// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+/// KIND, either express or implied. See the License for the
+/// specific language governing permissions and limitations
+/// under the License.
+
+part of thrift;
+
+/// Factory class used to create wrapped instance of a [TTransport]. This is
+/// used primarily in servers.
+///
+/// Adapted from the Java version.
+class TTransportFactory {
+  Future<TTransport> getTransport(TTransport transport) =>
+      new Future.value(transport);
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/lib/thrift.dart
----------------------------------------------------------------------
diff --git a/lib/dart/lib/thrift.dart b/lib/dart/lib/thrift.dart
new file mode 100644
index 0000000..2483726
--- /dev/null
+++ b/lib/dart/lib/thrift.dart
@@ -0,0 +1,58 @@
+/// Licensed to the Apache Software Foundation (ASF) under one
+/// or more contributor license agreements. See the NOTICE file
+/// distributed with this work for additional information
+/// regarding copyright ownership. The ASF licenses this file
+/// to you under the Apache License, Version 2.0 (the
+/// "License"); you may not use this file except in compliance
+/// with the License. You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing,
+/// software distributed under the License is distributed on an
+/// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+/// KIND, either express or implied. See the License for the
+/// specific language governing permissions and limitations
+/// under the License.
+
+library thrift;
+
+import 'dart:async';
+import 'dart:convert' show Utf8Codec;
+import 'dart:typed_data' show ByteData;
+import 'dart:typed_data' show Uint8List;
+
+import 'package:crypto/crypto.dart' show CryptoUtils;
+import 'package:http/http.dart' show Client;
+import 'package:logging/logging.dart';
+
+part 'src/t_application_error.dart';
+part 'src/t_base.dart';
+part 'src/t_error.dart';
+part 'src/t_processor.dart';
+
+part 'src/protocol/t_binary_protocol.dart';
+part 'src/protocol/t_field.dart';
+part 'src/protocol/t_json_protocol.dart';
+part 'src/protocol/t_list.dart';
+part 'src/protocol/t_map.dart';
+part 'src/protocol/t_message.dart';
+part 'src/protocol/t_multiplexed_protocol.dart';
+part 'src/protocol/t_protocol.dart';
+part 'src/protocol/t_protocol_decorator.dart';
+part 'src/protocol/t_protocol_error.dart';
+part 'src/protocol/t_protocol_factory.dart';
+part 'src/protocol/t_protocol_util.dart';
+part 'src/protocol/t_set.dart';
+part 'src/protocol/t_struct.dart';
+part 'src/protocol/t_type.dart';
+
+part 'src/transport/t_buffered_transport.dart';
+part 'src/transport/t_framed_transport.dart';
+part 'src/transport/t_http_transport.dart';
+part 'src/transport/t_message_reader.dart';
+part 'src/transport/t_socket.dart';
+part 'src/transport/t_transport.dart';
+part 'src/transport/t_transport_error.dart';
+part 'src/transport/t_transport_factory.dart';
+part 'src/transport/t_socket_transport.dart';

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/lib/thrift_browser.dart
----------------------------------------------------------------------
diff --git a/lib/dart/lib/thrift_browser.dart b/lib/dart/lib/thrift_browser.dart
new file mode 100644
index 0000000..2ebc257
--- /dev/null
+++ b/lib/dart/lib/thrift_browser.dart
@@ -0,0 +1,22 @@
+/// Licensed to the Apache Software Foundation (ASF) under one
+/// or more contributor license agreements. See the NOTICE file
+/// distributed with this work for additional information
+/// regarding copyright ownership. The ASF licenses this file
+/// to you under the Apache License, Version 2.0 (the
+/// "License"); you may not use this file except in compliance
+/// with the License. You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing,
+/// software distributed under the License is distributed on an
+/// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+/// KIND, either express or implied. See the License for the
+/// specific language governing permissions and limitations
+/// under the License.
+
+library thrift_browser;
+
+/// Classes that are only supported in browser applications go here
+
+export 'src/browser/t_web_socket.dart' show TWebSocket;

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/lib/thrift_console.dart
----------------------------------------------------------------------
diff --git a/lib/dart/lib/thrift_console.dart b/lib/dart/lib/thrift_console.dart
new file mode 100644
index 0000000..48a83d1
--- /dev/null
+++ b/lib/dart/lib/thrift_console.dart
@@ -0,0 +1,23 @@
+/// Licensed to the Apache Software Foundation (ASF) under one
+/// or more contributor license agreements. See the NOTICE file
+/// distributed with this work for additional information
+/// regarding copyright ownership. The ASF licenses this file
+/// to you under the Apache License, Version 2.0 (the
+/// "License"); you may not use this file except in compliance
+/// with the License. You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing,
+/// software distributed under the License is distributed on an
+/// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+/// KIND, either express or implied. See the License for the
+/// specific language governing permissions and limitations
+/// under the License.
+
+library thrift_console;
+
+/// Classes that are only supported in console applications go here
+
+export 'src/console/t_tcp_socket.dart' show TTcpSocket;
+export 'src/console/t_web_socket.dart' show TWebSocket;

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/pubspec.yaml
----------------------------------------------------------------------
diff --git a/lib/dart/pubspec.yaml b/lib/dart/pubspec.yaml
new file mode 100644
index 0000000..f64d980
--- /dev/null
+++ b/lib/dart/pubspec.yaml
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# 'License'); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: thrift
+version: 1.0.0-dev
+description: >
+  A Dart library for Apache Thrift
+author: Mark Erickson <ma...@workiva.com>
+homepage: https://github.com/apache/thrift
+documentation: https://github.com/apache/thrift
+environment:
+  sdk: ">=1.12.0 <2.0.0"
+dependencies:
+  crypto: "^0.9.0"
+  http: "^0.11.3"
+  logging: "^0.11.0"
+dev_dependencies:
+  coverage: "^0.7.2"
+  dart_dev: "^1.0.1"
+  dart_style: "^0.2.0"
+  mockito: "^0.11.0"
+  test: "^0.12.0"

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/test/protocol/t_protocol_test.dart
----------------------------------------------------------------------
diff --git a/lib/dart/test/protocol/t_protocol_test.dart b/lib/dart/test/protocol/t_protocol_test.dart
new file mode 100644
index 0000000..88ddd4f
--- /dev/null
+++ b/lib/dart/test/protocol/t_protocol_test.dart
@@ -0,0 +1,374 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+library thrift.test.transport.t_json_protocol_test;
+
+import 'dart:async';
+import 'dart:typed_data' show Uint8List;
+
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+void main() {
+  final message = new TMessage('my message', TMessageType.ONEWAY, 123);
+
+  TProtocol protocol;
+
+  Primitive getPrimitive(int tType) {
+    switch (tType) {
+      case TType.BOOL:
+        return new Primitive(protocol.readBool, protocol.writeBool, false);
+
+      case TType.BYTE:
+        return new Primitive(protocol.readByte, protocol.writeByte, 0);
+
+      case TType.I16:
+        return new Primitive(protocol.readI16, protocol.writeI16, 0);
+
+      case TType.I32:
+        return new Primitive(protocol.readI32, protocol.writeI32, 0);
+
+      case TType.I64:
+        return new Primitive(protocol.readI64, protocol.writeI64, 0);
+
+      case TType.DOUBLE:
+        return new Primitive(protocol.readDouble, protocol.writeDouble, 0);
+
+      case TType.STRING:
+        return new Primitive(protocol.readString, protocol.writeString, '');
+
+      default:
+        throw new UnsupportedError("Unsupported TType $tType");
+    }
+  }
+
+  Future primitiveTest(Primitive primitive, input) async {
+    primitive.write(input);
+    protocol.writeMessageEnd();
+
+    await protocol.transport.flush();
+
+    protocol.readMessageBegin();
+    var output = primitive.read();
+
+    expect(output, input);
+  }
+
+  Future primitiveNullTest(Primitive primitive) async {
+    primitive.write(null);
+    protocol.writeMessageEnd();
+
+    await protocol.transport.flush();
+
+    protocol.readMessageBegin();
+    var output = primitive.read();
+
+    expect(output, primitive.defaultValue);
+  }
+
+  var sharedTests = () {
+    test('Test message', () async {
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      var subject = protocol.readMessageBegin();
+
+      expect(subject.name, message.name);
+      expect(subject.type, message.type);
+      expect(subject.seqid, message.seqid);
+    });
+
+    test('Test struct', () async {
+      var input = new TStruct();
+
+      protocol.writeStructBegin(input);
+      protocol.writeStructEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      var output = protocol.readStructBegin();
+
+      // name is not serialized, see C# version for reference
+      expect(output, isNotNull);
+    });
+
+    test('Test field', () async {
+      var input = new TField('my field', TType.MAP, 123);
+
+      protocol.writeFieldBegin(input);
+      protocol.writeFieldEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      var output = protocol.readFieldBegin();
+
+      // name is not serialized, see C# version for reference
+      expect(output.type, input.type);
+      expect(output.id, input.id);
+    });
+
+    test('Test map', () async {
+      var input = new TMap(TType.STRING, TType.STRUCT, 123);
+
+      protocol.writeMapBegin(input);
+      protocol.writeMapEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      var output = protocol.readMapBegin();
+
+      expect(output.keyType, input.keyType);
+      expect(output.valueType, input.valueType);
+      expect(output.length, input.length);
+    });
+
+    test('Test list', () async {
+      var input = new TList(TType.STRING, 123);
+
+      protocol.writeListBegin(input);
+      protocol.writeListEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      var output = protocol.readListBegin();
+
+      expect(output.elementType, input.elementType);
+      expect(output.length, input.length);
+    });
+
+    test('Test set', () async {
+      var input = new TSet(TType.STRING, 123);
+
+      protocol.writeSetBegin(input);
+      protocol.writeSetEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      var output = protocol.readListBegin();
+
+      expect(output.elementType, input.elementType);
+      expect(output.length, input.length);
+    });
+
+    test('Test bool', () async {
+      await primitiveTest(getPrimitive(TType.BOOL), true);
+    });
+
+    test('Test bool null', () async {
+      await primitiveNullTest(getPrimitive(TType.BOOL));
+    });
+
+    test('Test byte', () async {
+      await primitiveTest(getPrimitive(TType.BYTE), 64);
+    });
+
+    test('Test byte null', () async {
+      await primitiveNullTest(getPrimitive(TType.BYTE));
+    });
+
+    test('Test I16', () async {
+      await primitiveTest(getPrimitive(TType.I16), 32767);
+    });
+
+    test('Test I16 null', () async {
+      await primitiveNullTest(getPrimitive(TType.I16));
+    });
+
+    test('Test I32', () async {
+      await primitiveTest(getPrimitive(TType.I32), 2147483647);
+    });
+
+    test('Test I32 null', () async {
+      await primitiveNullTest(getPrimitive(TType.I32));
+    });
+
+    test('Test I64', () async {
+      await primitiveTest(getPrimitive(TType.I64), 9223372036854775807);
+    });
+
+    test('Test I64 null', () async {
+      await primitiveNullTest(getPrimitive(TType.I64));
+    });
+
+    test('Test double', () async {
+      await primitiveTest(getPrimitive(TType.DOUBLE), 3.1415926);
+    });
+
+    test('Test double null', () async {
+      await primitiveNullTest(getPrimitive(TType.DOUBLE));
+    });
+
+    test('Test string', () async {
+      var input = 'There are only two hard things in computer science: '
+          'cache invalidation, naming things, and off-by-one errors.';
+      await primitiveTest(getPrimitive(TType.STRING), input);
+    });
+
+    test('Test string null', () async {
+      await primitiveNullTest(getPrimitive(TType.STRING));
+    });
+
+    test('Test binary', () async {
+      var input = new Uint8List.fromList(new List.filled(100, 123));
+
+      protocol.writeBinary(input);
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      var output = protocol.readBinary();
+
+      expect(output.length, input.length);
+      expect(output.every((i) => i == 123), isTrue);
+    });
+
+    test('Test complex struct', () async {
+      // {1: {10: 20}, 2: {30: 40}}
+      protocol.writeStructBegin(new TStruct());
+      protocol.writeFieldBegin(new TField('success', TType.MAP, 0));
+      protocol.writeMapBegin(new TMap(TType.I32, TType.MAP, 2));
+
+      protocol.writeI32(1); // key
+      protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1));
+      protocol.writeI32(10); // key
+      protocol.writeI32(20); // value
+      protocol.writeMapEnd();
+
+      protocol.writeI32(2); // key
+      protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1));
+      protocol.writeI32(30); // key
+      protocol.writeI32(40); // value
+      protocol.writeMapEnd();
+
+      protocol.writeMapEnd();
+      protocol.writeFieldEnd();
+      protocol.writeFieldStop();
+      protocol.writeStructEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      protocol.readStructBegin();
+      expect(protocol.readFieldBegin().type, TType.MAP);
+      expect(protocol.readMapBegin().length, 2);
+
+      expect(protocol.readI32(), 1); // key
+      expect(protocol.readMapBegin().length, 1);
+      expect(protocol.readI32(), 10); // key
+      expect(protocol.readI32(), 20); // value
+      protocol.readMapEnd();
+
+      expect(protocol.readI32(), 2); // key
+      expect(protocol.readMapBegin().length, 1);
+      expect(protocol.readI32(), 30); // key
+      expect(protocol.readI32(), 40); // value
+      protocol.readMapEnd();
+
+      protocol.readMapEnd();
+      protocol.readFieldEnd();
+      protocol.readStructEnd();
+      protocol.readMessageEnd();
+    });
+
+    test('Test nested maps and lists', () async {
+      // {1: [{10: 20}], 2: [{30: 40}]}
+      protocol.writeMapBegin(new TMap(TType.I32, TType.LIST, 2));
+
+      protocol.writeI32(1); // key
+      protocol.writeListBegin(new TList(TType.MAP, 1));
+      protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1));
+      protocol.writeI32(10); // key
+      protocol.writeI32(20); // value
+      protocol.writeMapEnd();
+      protocol.writeListEnd();
+
+      protocol.writeI32(2); // key
+      protocol.writeListBegin(new TList(TType.MAP, 1));
+      protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1));
+      protocol.writeI32(30); // key
+      protocol.writeI32(40); // value
+      protocol.writeMapEnd();
+      protocol.writeListEnd();
+
+      protocol.writeMapEnd();
+      protocol.writeMessageEnd();
+
+      await protocol.transport.flush();
+
+      protocol.readMessageBegin();
+      expect(protocol.readMapBegin().length, 2);
+
+      expect(protocol.readI32(), 1); // key
+      expect(protocol.readListBegin().length, 1);
+      expect(protocol.readMapBegin().length, 1);
+      expect(protocol.readI32(), 10); // key
+      expect(protocol.readI32(), 20); // value
+      protocol.readMapEnd();
+      protocol.readListEnd();
+
+      expect(protocol.readI32(), 2); // key
+      expect(protocol.readListBegin().length, 1);
+      expect(protocol.readMapBegin().length, 1);
+      expect(protocol.readI32(), 30); // key
+      expect(protocol.readI32(), 40); // value
+      protocol.readMapEnd();
+      protocol.readListEnd();
+
+      protocol.readMapEnd();
+      protocol.readMessageEnd();
+    });
+  };
+
+  group('JSON', () {
+    setUp(() {
+      protocol = new TJsonProtocol(new TBufferedTransport());
+      protocol.writeMessageBegin(message);
+    });
+
+    group('shared tests', sharedTests);
+  });
+
+  group('binary', () {
+    setUp(() {
+      protocol = new TBinaryProtocol(new TBufferedTransport());
+      protocol.writeMessageBegin(message);
+    });
+
+    group('shared tests', sharedTests);
+  });
+}
+
+class Primitive {
+  final Function read;
+  final Function write;
+  final defaultValue;
+
+  Primitive(this.read, this.write, this.defaultValue);
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/test/t_application_error_test.dart
----------------------------------------------------------------------
diff --git a/lib/dart/test/t_application_error_test.dart b/lib/dart/test/t_application_error_test.dart
new file mode 100644
index 0000000..511d8d6
--- /dev/null
+++ b/lib/dart/test/t_application_error_test.dart
@@ -0,0 +1,46 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+library thrift.test.t_application_error_test;
+
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+void main() {
+  TProtocol protocol;
+
+  setUp(() {
+    protocol = new TBinaryProtocol(new TBufferedTransport());
+  });
+
+  test('Write and read an application error', () {
+    var expectedType = TApplicationErrorType.INTERNAL_ERROR;
+    var expectedMessage = 'test error message';
+
+    TApplicationError error =
+        new TApplicationError(expectedType, expectedMessage);
+    error.write(protocol);
+
+    protocol.transport.flush();
+
+    TApplicationError subject = TApplicationError.read(protocol);
+
+    expect(subject, isNotNull);
+    expect(subject.type, expectedType);
+    expect(subject.message, expectedMessage);
+  });
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/test/transport/t_http_transport_test.dart
----------------------------------------------------------------------
diff --git a/lib/dart/test/transport/t_http_transport_test.dart b/lib/dart/test/transport/t_http_transport_test.dart
new file mode 100644
index 0000000..7fcab3e
--- /dev/null
+++ b/lib/dart/test/transport/t_http_transport_test.dart
@@ -0,0 +1,165 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+library thrift.test.transport.t_socket_transport_test;
+
+import 'dart:async';
+import 'dart:convert' show Encoding;
+import 'dart:convert' show Utf8Codec;
+import 'dart:typed_data' show Uint8List;
+
+import 'package:crypto/crypto.dart' show CryptoUtils;
+import 'package:http/http.dart' show BaseRequest;
+import 'package:http/http.dart' show Client;
+import 'package:http/http.dart' show Response;
+import 'package:http/http.dart' show StreamedResponse;
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+void main() {
+  const utf8Codec = const Utf8Codec();
+
+  group('THttpClientTransport', () {
+    FakeHttpClient client;
+    THttpClientTransport transport;
+
+    setUp(() {
+      client = new FakeHttpClient(sync: false);
+      var config = new THttpConfig(Uri.parse('http://localhost'), {});
+      transport = new THttpClientTransport(client, config);
+    });
+
+    test('Test transport sends body', () async {
+      var expectedText = 'my request';
+      transport.writeAll(utf8Codec.encode(expectedText));
+
+      expect(client.postRequest, isEmpty);
+
+      await transport.flush();
+
+      expect(client.postRequest, isNotEmpty);
+
+      var requestText =
+          utf8Codec.decode(CryptoUtils.base64StringToBytes(client.postRequest));
+      expect(requestText, expectedText);
+    });
+
+    test('Test transport receives response', () async {
+      var expectedText = 'my response';
+      var expectedBytes = utf8Codec.encode(expectedText);
+      client.postResponse = CryptoUtils.bytesToBase64(expectedBytes);
+
+      transport.writeAll(utf8Codec.encode('my request'));
+      expect(transport.hasReadData, isFalse);
+
+      await transport.flush();
+
+      expect(transport.hasReadData, isTrue);
+
+      var buffer = new Uint8List(expectedBytes.length);
+      transport.readAll(buffer, 0, expectedBytes.length);
+
+      var bufferText = utf8Codec.decode(buffer);
+      expect(bufferText, expectedText);
+    });
+  });
+
+  group('THttpClientTransport with multiple messages', () {
+    FakeHttpClient client;
+    THttpClientTransport transport;
+
+    setUp(() {
+      client = new FakeHttpClient(sync: true);
+      var config = new THttpConfig(Uri.parse('http://localhost'), {});
+      transport = new THttpClientTransport(client, config);
+    });
+
+    test('Test read correct buffer after flush', () async {
+      String bufferText;
+      var expectedText = 'response 1';
+      var expectedBytes = utf8Codec.encode(expectedText);
+
+      // prepare a response
+      transport.writeAll(utf8Codec.encode('request 1'));
+      client.postResponse = CryptoUtils.bytesToBase64(expectedBytes);
+
+      Future responseReady = transport.flush().then((_) {
+        var buffer = new Uint8List(expectedBytes.length);
+        transport.readAll(buffer, 0, expectedBytes.length);
+        bufferText = utf8Codec.decode(buffer);
+      });
+
+      // prepare a second response
+      transport.writeAll(utf8Codec.encode('request 2'));
+      var response2Bytes = utf8Codec.encode('response 2');
+      client.postResponse = CryptoUtils.bytesToBase64(response2Bytes);
+      await transport.flush();
+
+      await responseReady;
+      expect(bufferText, expectedText);
+    });
+  });
+}
+
+class FakeHttpClient implements Client {
+  String postResponse = '';
+  String postRequest = '';
+
+  final bool sync;
+
+  FakeHttpClient({this.sync: false});
+
+  Future<Response> post(url,
+      {Map<String, String> headers, body, Encoding encoding}) {
+    postRequest = body;
+    var response = new Response(postResponse, 200);
+
+    if (sync) {
+      return new Future.sync(() => response);
+    } else {
+      return new Future.value(response);
+    }
+  }
+
+  Future<Response> head(url, {Map<String, String> headers}) =>
+      throw new UnimplementedError();
+
+  Future<Response> get(url, {Map<String, String> headers}) =>
+      throw new UnimplementedError();
+
+  Future<Response> put(url,
+          {Map<String, String> headers, body, Encoding encoding}) =>
+      throw new UnimplementedError();
+
+  Future<Response> patch(url,
+          {Map<String, String> headers, body, Encoding encoding}) =>
+      throw new UnimplementedError();
+
+  Future<Response> delete(url, {Map<String, String> headers}) =>
+      throw new UnimplementedError();
+
+  Future<String> read(url, {Map<String, String> headers}) =>
+      throw new UnimplementedError();
+
+  Future<Uint8List> readBytes(url, {Map<String, String> headers}) =>
+      throw new UnimplementedError();
+
+  Future<StreamedResponse> send(BaseRequest request) =>
+      throw new UnimplementedError();
+
+  void close() => throw new UnimplementedError();
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/test/transport/t_socket_transport_test.dart
----------------------------------------------------------------------
diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart
new file mode 100644
index 0000000..997df0d
--- /dev/null
+++ b/lib/dart/test/transport/t_socket_transport_test.dart
@@ -0,0 +1,312 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+library thrift.test.transport.t_socket_transport_test;
+
+import 'dart:async';
+import 'dart:convert' show Utf8Codec;
+import 'dart:typed_data' show Uint8List;
+
+import 'package:crypto/crypto.dart' show CryptoUtils;
+import 'package:mockito/mockito.dart';
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+void main() {
+  const utf8Codec = const Utf8Codec();
+
+  final requestText = 'my test request';
+  final requestBytes = new Uint8List.fromList(utf8Codec.encode(requestText));
+  final requestBase64 = CryptoUtils.bytesToBase64(requestBytes);
+
+  final responseText = 'response 1';
+  final responseBytes = new Uint8List.fromList(utf8Codec.encode(responseText));
+  final responseBase64 = CryptoUtils.bytesToBase64(responseBytes);
+
+  final framedResponseBase64 =
+      CryptoUtils.bytesToBase64(_getFramedResponse(responseBytes));
+
+  group('TClientSocketTransport', () {
+    FakeSocket socket;
+    TTransport transport;
+
+    setUp(() async {
+      socket = new FakeSocket(sync: false);
+      await socket.open();
+      transport = new TClientSocketTransport(socket);
+      await transport.open();
+      transport.writeAll(requestBytes);
+    });
+
+    test('Test client sending data over transport', () async {
+      expect(socket.sendPayload, isNull);
+
+      Future responseReady = transport.flush();
+
+      // allow microtask events to finish
+      await new Future.value();
+
+      expect(socket.sendPayload, isNotNull);
+      expect(socket.sendPayload, requestBytes);
+
+      // simulate a response
+      socket.receiveFakeMessage(responseBase64);
+
+      await responseReady;
+      var buffer = new Uint8List(responseBytes.length);
+      transport.readAll(buffer, 0, responseBytes.length);
+      var bufferText = utf8Codec.decode(buffer);
+
+      expect(bufferText, responseText);
+    });
+  }, timeout: new Timeout(new Duration(seconds: 1)));
+
+  group('TClientSocketTransport with FramedTransport', () {
+    FakeSocket socket;
+    TTransport transport;
+
+    setUp(() async {
+      socket = new FakeSocket(sync: true);
+      await socket.open();
+
+      transport = new TFramedTransport(new TClientSocketTransport(socket));
+      await transport.open();
+      transport.writeAll(requestBytes);
+    });
+
+    test('Test client sending data over framed transport', () async {
+      String bufferText;
+
+      Future responseReady = transport.flush().then((_) {
+        var buffer = new Uint8List(responseBytes.length);
+        transport.readAll(buffer, 0, responseBytes.length);
+        bufferText = utf8Codec.decode(buffer);
+      });
+
+      // simulate a response
+      socket.receiveFakeMessage(framedResponseBase64);
+
+      await responseReady;
+      expect(bufferText, responseText);
+    });
+  }, timeout: new Timeout(new Duration(seconds: 1)));
+
+  group('TAsyncClientSocketTransport', () {
+    FakeSocket socket;
+    FakeProtocolFactory protocolFactory;
+    TTransport transport;
+
+    setUp(() async {
+      socket = new FakeSocket(sync: true);
+      await socket.open();
+
+      protocolFactory = new FakeProtocolFactory();
+      protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123);
+      transport = new TAsyncClientSocketTransport(
+          socket, new TMessageReader(protocolFactory),
+          responseTimeout: Duration.ZERO);
+      await transport.open();
+      transport.writeAll(requestBytes);
+    });
+
+    test('Test response correlates to correct request', () async {
+      String bufferText;
+
+      Future responseReady = transport.flush().then((_) {
+        var buffer = new Uint8List(responseBytes.length);
+        transport.readAll(buffer, 0, responseBytes.length);
+        bufferText = utf8Codec.decode(buffer);
+      });
+
+      // simulate a response
+      protocolFactory.message = new TMessage('foo', TMessageType.REPLY, 123);
+      socket.receiveFakeMessage(responseBase64);
+
+      // simulate a second response
+      var response2Text = 'response 2';
+      var response2Bytes =
+          new Uint8List.fromList(utf8Codec.encode(response2Text));
+      var response2Base64 = CryptoUtils.bytesToBase64(response2Bytes);
+      protocolFactory.message = new TMessage('foo2', TMessageType.REPLY, 124);
+      socket.receiveFakeMessage(response2Base64);
+
+      await responseReady;
+      expect(bufferText, responseText);
+    });
+
+    test('Test response timeout', () async {
+      Future responseReady = transport.flush();
+      expect(responseReady, throwsA(new isInstanceOf<TimeoutException>()));
+    });
+  }, timeout: new Timeout(new Duration(seconds: 1)));
+
+  group('TAsyncClientSocketTransport with TFramedTransport', () {
+    FakeSocket socket;
+    FakeProtocolFactory protocolFactory;
+    TTransport transport;
+
+    setUp(() async {
+      socket = new FakeSocket(sync: true);
+      await socket.open();
+
+      protocolFactory = new FakeProtocolFactory();
+      protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123);
+      var messageReader = new TMessageReader(protocolFactory,
+          byteOffset: TFramedTransport.headerByteCount);
+
+      transport = new TFramedTransport(new TAsyncClientSocketTransport(
+          socket, messageReader,
+          responseTimeout: Duration.ZERO));
+      await transport.open();
+      transport.writeAll(requestBytes);
+    });
+
+    test('Test async client sending data over framed transport', () async {
+      String bufferText;
+
+      Future responseReady = transport.flush().then((_) {
+        var buffer = new Uint8List(responseBytes.length);
+        transport.readAll(buffer, 0, responseBytes.length);
+        bufferText = utf8Codec.decode(buffer);
+      });
+
+      // simulate a response
+      protocolFactory.message = new TMessage('foo', TMessageType.REPLY, 123);
+      socket.receiveFakeMessage(framedResponseBase64);
+
+      await responseReady;
+      expect(bufferText, responseText);
+    });
+  }, timeout: new Timeout(new Duration(seconds: 1)));
+
+  group('TServerTransport', () {
+    test('Test server transport listens to socket', () async {
+      var socket = new FakeSocket();
+      await socket.open();
+      expect(socket.isOpen, isTrue);
+
+      var transport = new TServerSocketTransport(socket);
+      expect(transport.hasReadData, isFalse);
+
+      socket.receiveFakeMessage(requestBase64);
+
+      // allow microtask events to finish
+      await new Future.value();
+
+      expect(transport.hasReadData, isTrue);
+
+      var buffer = new Uint8List(requestBytes.length);
+      transport.readAll(buffer, 0, requestBytes.length);
+
+      var bufferText = utf8Codec.decode(buffer);
+      expect(bufferText, requestText);
+    });
+
+    test('Test server sending data over transport', () async {
+      var socket = new FakeSocket();
+      await socket.open();
+
+      var transport = new TServerSocketTransport(socket);
+
+      transport.writeAll(responseBytes);
+      expect(socket.sendPayload, isNull);
+
+      transport.flush();
+
+      // allow microtask events to finish
+      await new Future.value();
+
+      expect(socket.sendPayload, isNotNull);
+      expect(socket.sendPayload, responseBytes);
+    });
+  }, timeout: new Timeout(new Duration(seconds: 1)));
+}
+
+class FakeSocket extends TSocket {
+  final StreamController<TSocketState> _onStateController;
+  Stream<TSocketState> get onState => _onStateController.stream;
+
+  final StreamController<Object> _onErrorController;
+  Stream<Object> get onError => _onErrorController.stream;
+
+  final StreamController<Uint8List> _onMessageController;
+  Stream<Uint8List> get onMessage => _onMessageController.stream;
+
+  FakeSocket({bool sync: false})
+      : _onStateController = new StreamController.broadcast(sync: sync),
+        _onErrorController = new StreamController.broadcast(sync: sync),
+        _onMessageController = new StreamController.broadcast(sync: sync);
+
+  bool _isOpen;
+
+  bool get isOpen => _isOpen;
+
+  bool get isClosed => !isOpen;
+
+  Future open() async {
+    _isOpen = true;
+    _onStateController.add(TSocketState.OPEN);
+  }
+
+  Future close() async {
+    _isOpen = false;
+    _onStateController.add(TSocketState.CLOSED);
+  }
+
+  Uint8List _sendPayload;
+  Uint8List get sendPayload => _sendPayload;
+
+  void send(Uint8List data) {
+    if (!isOpen) throw new StateError('The socket is not open');
+
+    _sendPayload = data;
+  }
+
+  void receiveFakeMessage(String base64) {
+    if (!isOpen) throw new StateError('The socket is not open');
+
+    var message =
+        new Uint8List.fromList(CryptoUtils.base64StringToBytes(base64));
+    _onMessageController.add(message);
+  }
+}
+
+class FakeProtocolFactory implements TProtocolFactory {
+  FakeProtocolFactory();
+
+  TMessage message;
+
+  getProtocol(TTransport transport) => new FakeProtocol(message);
+}
+
+class FakeProtocol extends Mock implements TProtocol {
+  FakeProtocol(this._message);
+
+  TMessage _message;
+
+  readMessageBegin() => _message;
+}
+
+Uint8List _getFramedResponse(Uint8List responseBytes) {
+  var byteOffset = TFramedTransport.headerByteCount;
+  var response = new Uint8List(byteOffset + responseBytes.length);
+
+  response.buffer.asByteData().setInt32(0, responseBytes.length);
+  response.setAll(byteOffset, responseBytes);
+
+  return response;
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/test/transport/t_transport_test.dart
----------------------------------------------------------------------
diff --git a/lib/dart/test/transport/t_transport_test.dart b/lib/dart/test/transport/t_transport_test.dart
new file mode 100644
index 0000000..0bb381a
--- /dev/null
+++ b/lib/dart/test/transport/t_transport_test.dart
@@ -0,0 +1,41 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+library thrift.test.transport.t_socket_transport_test;
+
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+/// Common transport tests
+void main() {
+  group('TTransportFactory', () {
+    test('transport is returned from base factory', () async {
+      TTransport result;
+      TTransport transport = null;
+
+      var factory = new TTransportFactory();
+
+      result = await factory.getTransport(transport);
+      expect(result, isNull);
+
+      transport = new TBufferedTransport();
+      result = await factory.getTransport(transport);
+
+      expect(result, transport);
+    });
+  });
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/lib/dart/tool/dev.dart
----------------------------------------------------------------------
diff --git a/lib/dart/tool/dev.dart b/lib/dart/tool/dev.dart
new file mode 100644
index 0000000..27f8b8f
--- /dev/null
+++ b/lib/dart/tool/dev.dart
@@ -0,0 +1,33 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+library tool.dev;
+
+import 'package:dart_dev/dart_dev.dart' show dev, config;
+
+main(List<String> args) async {
+  // https://github.com/Workiva/dart_dev
+
+  var directories = ['lib/', 'test/', 'tool/'];
+  config.analyze.entryPoints = directories;
+  config.format.directories = directories;
+  config.copyLicense
+    ..licensePath = 'LICENSE_HEADER'
+    ..directories = directories;
+
+  await dev(args);
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/test/Makefile.am
----------------------------------------------------------------------
diff --git a/test/Makefile.am b/test/Makefile.am
index 6ebcd27..7590921 100755
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -40,6 +40,11 @@ SUBDIRS += php
 PRECROSS_TARGET += precross-php
 endif
 
+if WITH_DART
+SUBDIRS += dart
+PRECROSS_TARGET += precross-dart
+endif
+
 if WITH_PYTHON
 SUBDIRS += py
 PRECROSS_TARGET += precross-py

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/test/dart/Makefile.am
----------------------------------------------------------------------
diff --git a/test/dart/Makefile.am b/test/dart/Makefile.am
new file mode 100644
index 0000000..59b3b7d
--- /dev/null
+++ b/test/dart/Makefile.am
@@ -0,0 +1,41 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+THRIFT = $(top_builddir)/compiler/cpp/thrift
+
+gen-dart/thrift_test/lib/thrift_test.dart: ../ThriftTest.thrift
+	$(THRIFT) --gen dart ../ThriftTest.thrift
+
+pub-get-gen: gen-dart/thrift_test/lib/thrift_test.dart
+	cd gen-dart/thrift_test; ${DARTPUB} get
+
+pub-get: pub-get-gen
+	cd test_client; ${DARTPUB} get
+
+stubs: gen-dart/thrift_test/lib/thrift_test.dart pub-get
+
+precross: stubs
+
+check: stubs
+
+clean-local:
+	$(RM) -r gen-dart test_client/.pub
+
+client: stubs
+	${DART} test_client/bin/main.dart

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/test/dart/test_client/bin/main.dart
----------------------------------------------------------------------
diff --git a/test/dart/test_client/bin/main.dart b/test/dart/test_client/bin/main.dart
new file mode 100644
index 0000000..3733a08
--- /dev/null
+++ b/test/dart/test_client/bin/main.dart
@@ -0,0 +1,288 @@
+/// Licensed to the Apache Software Foundation (ASF) under one
+/// or more contributor license agreements. See the NOTICE file
+/// distributed with this work for additional information
+/// regarding copyright ownership. The ASF licenses this file
+/// to you under the Apache License, Version 2.0 (the
+/// 'License'); you may not use this file except in compliance
+/// with the License. You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing,
+/// software distributed under the License is distributed on an
+/// 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+/// KIND, either express or implied. See the License for the
+/// specific language governing permissions and limitations
+/// under the License.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:collection/equality.dart';
+import 'package:thrift/thrift.dart';
+import 'package:thrift/thrift_console.dart';
+import 'package:thrift_test/thrift_test.dart';
+
+ThriftTestClient client;
+bool verbose;
+
+/// Adapted from TestClient.php
+main(List<String> args) async {
+  var parser = new ArgParser();
+  parser.addOption('host', defaultsTo: 'localhost', help: 'The server host');
+  parser.addOption('port', defaultsTo: '9090', help: 'The port to connect to');
+  parser.addOption('transport',
+      defaultsTo: 'buffered',
+      allowed: ['buffered', 'framed'],
+      help: 'The transport name',
+      allowedHelp: {
+        'buffered': 'TBufferedTransport',
+        'framed': 'TFramedTransport'
+      });
+  parser.addOption('protocol',
+      defaultsTo: 'binary',
+      allowed: ['binary', 'json'],
+      help: 'The protocol name',
+      allowedHelp: {'binary': 'TBinaryProtocol', 'json': 'TJsonProtocol'});
+  parser.addFlag('verbose', defaultsTo: false);
+
+  ArgResults results;
+  try {
+    results = parser.parse(args);
+  } catch (e) {
+    stdout.writeln('$e\n');
+    results = null;
+  }
+
+  if (results == null) {
+    print(parser.usage);
+    exit(0);
+  }
+
+  verbose = results['verbose'] == true;
+
+  await init(
+      host: results['host'],
+      port: int.parse(results['port']),
+      transportType: results['transport'],
+      protocolType: results['protocol']).then((_) {
+    exit(0);
+  }).catchError((e) {
+    stdout.writeln('Error:');
+    stdout.writeln('$e');
+    if (e is Error) {
+      stdout.writeln('${e.stackTrace}');
+    }
+    exit(1);
+  });
+  exit(0);
+}
+
+TProtocolFactory getProtocolFactory(String protocolType) {
+  if (protocolType == 'binary') {
+    return new TBinaryProtocolFactory();
+  } else if (protocolType == 'json') {
+    return new TJsonProtocolFactory();
+  }
+
+  throw new ArgumentError.value(protocolType);
+}
+
+Future init(
+    {String host, int port, String transportType, String protocolType}) async {
+  TTransport transport;
+  var protocolFactory = getProtocolFactory(protocolType);
+
+  if (transportType == 'http') {
+    var httpClient = new HttpClient();
+    var config = new THttpConfig(Uri.parse('http://localhost'), {});
+    transport = new THttpClientTransport(httpClient, config);
+  } else {
+    var socket = await Socket.connect(host, port);
+    transport = new TClientSocketTransport(new TTcpSocket(socket));
+    if (transportType == 'framed') {
+      transport = new TFramedTransport(transport);
+    }
+  }
+
+  var protocol = protocolFactory.getProtocol(transport);
+  client = new ThriftTestClient(protocol);
+
+  await transport.open();
+
+  await runTests();
+}
+
+Future _test(String name, Function testFunc) async {
+  if (verbose) stdout.write('$name... ');
+  await testFunc();
+  if (verbose) stdout.writeln('success!');
+}
+
+Future runTests() async {
+  var xtruct = new Xtruct()
+    ..string_thing = 'Zero'
+    ..byte_thing = 1
+    ..i32_thing = -3
+    ..i64_thing = -5;
+
+  await _test('testVoid', () async {
+    await client.testVoid();
+  });
+
+  await _test('testString', () async {
+    var input = 'Test';
+    var result = await client.testString(input);
+    if (result != input) throw new StateError('$result != $input');
+  });
+
+  await _test('testBool', () async {
+    var input = true;
+    var result = await client.testBool(input);
+    if (result != input) throw new StateError('$result != $input');
+  });
+
+  await _test('testByte', () async {
+    var input = 64;
+    var result = await client.testByte(input);
+    if (result != input) throw new StateError('$result != $input');
+  });
+
+  await _test('testI32', () async {
+    var input = 2147483647;
+    var result = await client.testI32(input);
+    if (result != input) throw new StateError('$result != $input');
+  });
+
+  await _test('testI64', () async {
+    var input = 9223372036854775807;
+    var result = await client.testI64(input);
+    if (result != input) throw new StateError('$result != $input');
+  });
+
+  await _test('testDouble', () async {
+    var input = 3.1415926;
+    var result = await client.testDouble(input);
+    if (result != input) throw new StateError('$result != $input');
+  });
+
+  await _test('testBinary', () async {
+    var utf8Codec = const Utf8Codec();
+    var input = utf8Codec.encode('foo');
+    var result = await client.testBinary(input);
+    var equality = const ListEquality();
+    if (!equality.equals(
+        result, input)) throw new StateError('$result != $input');
+  });
+
+  await _test('testStruct', () async {
+    var result = await client.testStruct(xtruct);
+    if ('$result' != '$xtruct') throw new StateError('$result != $xtruct');
+  });
+
+  await _test('testNest', () async {
+    var input = new Xtruct2()
+      ..byte_thing = 1
+      ..struct_thing = xtruct
+      ..i32_thing = -3;
+
+    var result = await client.testNest(input);
+    if ('$result' != '$input') throw new StateError('$result != $input');
+  });
+
+  await _test('testMap', () async {
+    Map<int, int> input = {1: -10, 2: -9, 3: -8, 4: -7, 5: -6};
+
+    var result = await client.testMap(input);
+    var equality = const MapEquality();
+    if (!equality.equals(
+        result, input)) throw new StateError('$result != $input');
+  });
+
+  await _test('testSet', () async {
+    var input = new Set.from([-2, -1, 0, 1, 2]);
+    var result = await client.testSet(input);
+    var equality = const SetEquality();
+    if (!equality.equals(
+        result, input)) throw new StateError('$result != $input');
+  });
+
+  await _test('testList', () async {
+    var input = [-2, -1, 0, 1, 2];
+    var result = await client.testList(input);
+    var equality = const ListEquality();
+    if (!equality.equals(
+        result, input)) throw new StateError('$result != $input');
+  });
+
+  await _test('testEnum', () async {
+    await _testEnum(Numberz.ONE);
+    await _testEnum(Numberz.TWO);
+    await _testEnum(Numberz.THREE);
+    await _testEnum(Numberz.FIVE);
+    await _testEnum(Numberz.EIGHT);
+  });
+
+  await _test('testTypedef', () async {
+    var input = 309858235082523;
+    var result = await client.testTypedef(input);
+    if (result != input) throw new StateError('$result != $input');
+  });
+
+  await _test('testMapMap', () async {
+    Map<int, Map<int, int>> result = await client.testMapMap(1);
+    if (result.isEmpty ||
+        result[result.keys.first].isEmpty) throw new StateError(
+        'expected Map<int, Map<int, int>> got $result');
+  });
+
+  await _test('testInsanity', () async {
+    var input = new Insanity();
+    input.userMap = {Numberz.FIVE: 5000};
+    input.xtructs = [xtruct];
+
+    Map<int, Map<int, Insanity>> result = await client.testInsanity(input);
+    if (result.isEmpty ||
+        result[result.keys.first].isEmpty) throw new StateError(
+        'expected Map<int, Map<int, Insanity>> got $result');
+  });
+
+  await _test('testMulti', () async {
+    var input = new Xtruct()
+      ..string_thing = 'Hello2'
+      ..byte_thing = 123
+      ..i32_thing = 456
+      ..i64_thing = 789;
+
+    var result = await client.testMulti(input.byte_thing, input.i32_thing,
+        input.i64_thing, {1: 'one'}, Numberz.EIGHT, 5678);
+    if ('$result' != '$input') throw new StateError('$result != $input');
+  });
+
+  await _test('testException', () async {
+    try {
+      await client.testException('Xception');
+    } on Xception catch (x) {
+      return;
+    }
+
+    throw new StateError('expected an Xception');
+  });
+
+  await _test('testMultiException', () async {
+    try {
+      await client.testMultiException('Xception2', 'foo');
+    } on Xception2 catch (x) {
+      return;
+    }
+
+    throw new StateError('expected an Xception2');
+  });
+}
+
+Future _testEnum(int input) async {
+  var result = await client.testEnum(input);
+  if (result != input) throw new StateError('$result != $input');
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/test/dart/test_client/pubspec.yaml
----------------------------------------------------------------------
diff --git a/test/dart/test_client/pubspec.yaml b/test/dart/test_client/pubspec.yaml
new file mode 100644
index 0000000..54d8b0f
--- /dev/null
+++ b/test/dart/test_client/pubspec.yaml
@@ -0,0 +1,32 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# 'License'); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: thrift_test_client
+version: 1.0.0-dev
+description: A client integration test for the Dart Thrift library
+author: Mark Erickson <ma...@workiva.com>
+homepage: https://github.com/apache/thrift
+environment:
+  sdk: ^1.12.0
+dependencies:
+  args: ^0.13.0
+  thrift:
+    path: ../../../lib/dart
+  thrift_test:
+    path: ../gen-dart/thrift_test
+dev_dependencies:
+  test: "^0.12.0"

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/test/tests.json
----------------------------------------------------------------------
diff --git a/test/tests.json b/test/tests.json
index c902cd4..8c152fc 100644
--- a/test/tests.json
+++ b/test/tests.json
@@ -354,5 +354,27 @@
       ]
     },
     "workdir": "php"
+  },
+  {
+    "name": "dart",
+    "client": {
+      "transports": [
+        "buffered",
+        "framed",
+        "http"
+      ],
+      "sockets": [
+        "ip"
+      ],
+      "protocols": [
+        "binary",
+        "json"
+      ],
+      "command": [
+        "dart",
+        "test_client/bin/main.dart"
+      ]
+    },
+    "workdir": "dart"
   }
 ]

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/tutorial/Makefile.am
----------------------------------------------------------------------
diff --git a/tutorial/Makefile.am b/tutorial/Makefile.am
index 47711a9..37addda 100755
--- a/tutorial/Makefile.am
+++ b/tutorial/Makefile.am
@@ -62,6 +62,10 @@ if WITH_NODEJS
 SUBDIRS += nodejs
 endif
 
+if WITH_DART
+SUBDIRS += dart
+endif
+
 #
 # generate html for ThriftTest.thrift
 #

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/tutorial/dart/Makefile.am
----------------------------------------------------------------------
diff --git a/tutorial/dart/Makefile.am b/tutorial/dart/Makefile.am
new file mode 100644
index 0000000..2bb6bf2
--- /dev/null
+++ b/tutorial/dart/Makefile.am
@@ -0,0 +1,56 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+THRIFT = $(top_builddir)/compiler/cpp/thrift
+
+gen-dart/tutorial/lib/tutorial.dart gen-dart/shared/lib/shared.dart: $(top_srcdir)/tutorial/tutorial.thrift
+	$(THRIFT) --gen dart -r $<
+
+all-local: gen-dart/tutorial/lib/tutorial.dart pub-get
+
+clean-local:
+	$(RM) -r gen-*
+
+pub-get: pub-get-gen pub-get-client pub-get-console-client pub-get-server
+
+pub-get-gen: pub-get-tutorial pub-get-shared
+
+pub-get-tutorial: gen-dart/tutorial/lib/tutorial.dart
+	cd gen-dart/tutorial; ${DARTPUB} get
+
+pub-get-shared: gen-dart/shared/lib/shared.dart
+	cd gen-dart/shared; ${DARTPUB} get
+
+pub-get-client:
+	cd client; ${DARTPUB} get
+
+pub-get-console-client:
+	cd console_client; ${DARTPUB} get
+
+pub-get-server:
+	cd client; ${DARTPUB} get
+
+tutorialserver: pub-get-gen pub-get-server
+	${DART} server/bin/main.dart
+
+tutorialclient: pub-get-gen pub-get-client
+	cd client; ${DARTPUB} serve
+
+tutorialconsoleclient: pub-get-console-client
+	${DART} console-client/bin/main.dart

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/tutorial/dart/build.sh
----------------------------------------------------------------------
diff --git a/tutorial/dart/build.sh b/tutorial/dart/build.sh
new file mode 100644
index 0000000..eabe04a
--- /dev/null
+++ b/tutorial/dart/build.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# 'License'); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -e;
+rm -r gen-dart || true;
+
+thrift --gen dart ../shared.thrift;
+cd gen-dart/shared;
+pub get;
+cd ../..;
+
+thrift --gen dart ../tutorial.thrift;
+cd gen-dart/tutorial;
+pub get;
+cd ../..;
+
+cd client;
+pub get;
+cd ..;
+
+cd console_client;
+pub get;
+cd ..;
+
+cd server;
+pub get;
+cd ..;
+
+dartfmt -w gen-dart;
+
+echo "\nEnjoy the Dart tutorial!";
+echo "\nTo run the server:";
+echo "> dart server/bin/main.dart";
+echo "\nTo run the client:";
+echo "# Serve the app from the client directory and view in a browser";
+echo "> cd client;";
+echo "> pub serve;";
+echo "\nTo run the console client:";
+echo "> dart console_client/bin/main.dart";
+echo "";

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/tutorial/dart/client/pubspec.yaml
----------------------------------------------------------------------
diff --git a/tutorial/dart/client/pubspec.yaml b/tutorial/dart/client/pubspec.yaml
new file mode 100644
index 0000000..97c625b
--- /dev/null
+++ b/tutorial/dart/client/pubspec.yaml
@@ -0,0 +1,34 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# 'License'); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: tutorial_client
+version: 1.0.0-dev
+description: A Dart client implementation of the Apache Thrift tutorial
+author: Mark Erickson <ma...@workiva.com>
+homepage: https://github.com/apache/thrift
+
+environment:
+  sdk: ^1.12.0
+
+dependencies:
+  browser: ^0.10.0
+  shared:
+    path: ../gen-dart/shared
+  thrift:
+    path: ../../../lib/dart
+  tutorial:
+    path: ../gen-dart/tutorial

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/tutorial/dart/client/web/client.dart
----------------------------------------------------------------------
diff --git a/tutorial/dart/client/web/client.dart b/tutorial/dart/client/web/client.dart
new file mode 100644
index 0000000..4f02d0d
--- /dev/null
+++ b/tutorial/dart/client/web/client.dart
@@ -0,0 +1,278 @@
+/// Licensed to the Apache Software Foundation (ASF) under one
+/// or more contributor license agreements. See the NOTICE file
+/// distributed with this work for additional information
+/// regarding copyright ownership. The ASF licenses this file
+/// to you under the Apache License, Version 2.0 (the
+/// "License"); you may not use this file except in compliance
+/// with the License. You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing,
+/// software distributed under the License is distributed on an
+/// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+/// KIND, either express or implied. See the License for the
+/// specific language governing permissions and limitations
+/// under the License.
+
+import 'dart:html';
+
+import 'package:thrift/thrift.dart';
+import 'package:thrift/thrift_browser.dart';
+import 'package:shared/shared.dart';
+import 'package:tutorial/tutorial.dart';
+
+/// Adapted from the AS3 tutorial
+void main() {
+  new CalculatorUI(querySelector('#output')).start();
+}
+
+class CalculatorUI {
+  final DivElement output;
+
+  CalculatorUI(this.output);
+
+  TTransport _transport;
+  Calculator _calculatorClient;
+
+  void start() {
+    _buildInterface();
+    _initConnection();
+  }
+
+  void _validate() {
+    if (!_transport.isOpen) {
+      window.alert("The transport is not open!");
+    }
+  }
+
+  void _initConnection() {
+    _transport = new TAsyncClientSocketTransport(
+        new TWebSocket(Uri.parse('ws://127.0.0.1:9090/ws')),
+        new TMessageReader(new TBinaryProtocolFactory()));
+    TProtocol protocol = new TBinaryProtocol(_transport);
+    _transport.open();
+
+    _calculatorClient = new CalculatorClient(protocol);
+  }
+
+  void _buildInterface() {
+    output.children.forEach((e) {
+      e.remove();
+    });
+
+    _buildPingComponent();
+
+    _buildAddComponent();
+
+    _buildCalculatorComponent();
+
+    _buildGetStructComponent();
+  }
+
+  void _buildPingComponent() {
+    output.append(new HeadingElement.h3()..text = "Ping");
+    ButtonElement pingButton = new ButtonElement()
+      ..text = "PING"
+      ..onClick.listen(_onPingClick);
+    output.append(pingButton);
+  }
+
+  void _onPingClick(MouseEvent e) {
+    _validate();
+
+    _calculatorClient.ping();
+  }
+
+  void _buildAddComponent() {
+    output.append(new HeadingElement.h3()..text = "Add");
+    InputElement num1 = new InputElement()
+      ..id = "add1"
+      ..type = "number"
+      ..style.fontSize = "14px"
+      ..style.width = "50px";
+    output.append(num1);
+    SpanElement op = new SpanElement()
+      ..text = "+"
+      ..style.fontSize = "14px"
+      ..style.marginLeft = "10px";
+    output.append(op);
+    InputElement num2 = new InputElement()
+      ..id = "add2"
+      ..type = "number"
+      ..style.fontSize = "14px"
+      ..style.width = "50px"
+      ..style.marginLeft = "10px";
+    output.append(num2);
+    ButtonElement addButton = new ButtonElement()
+      ..text = "="
+      ..style.fontSize = "14px"
+      ..style.marginLeft = "10px"
+      ..onClick.listen(_onAddClick);
+    output.append(addButton);
+    SpanElement result = new SpanElement()
+      ..id = "addResult"
+      ..style.fontSize = "14px"
+      ..style.marginLeft = "10px";
+    output.append(result);
+  }
+
+  void _onAddClick(MouseEvent e) {
+    _validate();
+
+    InputElement num1 = querySelector("#add1");
+    InputElement num2 = querySelector("#add2");
+    SpanElement result = querySelector("#addResult");
+
+    _calculatorClient
+        .add(int.parse(num1.value), int.parse(num2.value))
+        .then((int n) {
+      result.text = "$n";
+    });
+  }
+
+  void _buildCalculatorComponent() {
+    output.append(new HeadingElement.h3()..text = "Calculator");
+    InputElement num1 = new InputElement()
+      ..id = "calc1"
+      ..type = "number"
+      ..style.fontSize = "14px"
+      ..style.width = "50px";
+    output.append(num1);
+    SelectElement op = new SelectElement()
+      ..id = "calcOp"
+      ..multiple = false
+      ..selectedIndex = 0
+      ..style.fontSize = "16px"
+      ..style.marginLeft = "10px"
+      ..style.width = "50px";
+    OptionElement addOp = new OptionElement()
+      ..text = "+"
+      ..value = Operation.ADD.toString();
+    op.add(addOp, 0);
+    OptionElement subtractOp = new OptionElement()
+      ..text = "-"
+      ..value = Operation.SUBTRACT.toString();
+    op.add(subtractOp, 1);
+    OptionElement multiplyOp = new OptionElement()
+      ..text = "*"
+      ..value = Operation.MULTIPLY.toString();
+    op.add(multiplyOp, 2);
+    OptionElement divideOp = new OptionElement()
+      ..text = "/"
+      ..value = Operation.DIVIDE.toString();
+    op.add(divideOp, 3);
+    output.append(op);
+    InputElement num2 = new InputElement()
+      ..id = "calc2"
+      ..type = "number"
+      ..style.fontSize = "14px"
+      ..style.width = "50px"
+      ..style.marginLeft = "10px";
+    output.append(num2);
+    ButtonElement calcButton = new ButtonElement()
+      ..text = "="
+      ..style.fontSize = "14px"
+      ..style.marginLeft = "10px"
+      ..onClick.listen(_onCalcClick);
+    output.append(calcButton);
+    SpanElement result = new SpanElement()
+      ..id = "calcResult"
+      ..style.fontSize = "14px"
+      ..style.marginLeft = "10px";
+    output.append(result);
+    output.append(new BRElement());
+    output.append(new BRElement());
+    LabelElement logIdLabel = new LabelElement()
+      ..text = "Log ID:"
+      ..style.fontSize = "14px";
+    output.append(logIdLabel);
+    InputElement logId = new InputElement()
+      ..id = "logId"
+      ..type = "number"
+      ..value = "1"
+      ..style.fontSize = "14px"
+      ..style.width = "50px"
+      ..style.marginLeft = "10px";
+    output.append(logId);
+    LabelElement commentLabel = new LabelElement()
+      ..text = "Comment:"
+      ..style.fontSize = "14px"
+      ..style.marginLeft = "10px";
+    output.append(commentLabel);
+    InputElement comment = new InputElement()
+      ..id = "comment"
+      ..style.fontSize = "14px"
+      ..style.width = "100px"
+      ..style.marginLeft = "10px";
+    output.append(comment);
+  }
+
+  void _onCalcClick(MouseEvent e) {
+    _validate();
+
+    InputElement num1 = querySelector("#calc1");
+    InputElement num2 = querySelector("#calc2");
+    SelectElement op = querySelector("#calcOp");
+    SpanElement result = querySelector("#calcResult");
+    InputElement logId = querySelector("#logId");
+    InputElement comment = querySelector("#comment");
+
+    int logIdValue = int.parse(logId.value);
+    logId.value = (logIdValue + 1).toString();
+
+    Work work = new Work();
+    work.num1 = int.parse(num1.value);
+    work.num2 = int.parse(num2.value);
+    work.op = int.parse(op.options[op.selectedIndex].value);
+    work.comment = comment.value;
+
+    _calculatorClient.calculate(logIdValue, work).then((int n) {
+      result.text = "$n";
+    });
+  }
+
+  void _buildGetStructComponent() {
+    output.append(new HeadingElement.h3()..text = "Get Struct");
+    LabelElement logIdLabel = new LabelElement()
+      ..text = "Struct Key:"
+      ..style.fontSize = "14px";
+    output.append(logIdLabel);
+    InputElement logId = new InputElement()
+      ..id = "structKey"
+      ..type = "number"
+      ..value = "1"
+      ..style.fontSize = "14px"
+      ..style.width = "50px"
+      ..style.marginLeft = "10px";
+    output.append(logId);
+    ButtonElement getStructButton = new ButtonElement()
+      ..text = "GET"
+      ..style.fontSize = "14px"
+      ..style.marginLeft = "10px"
+      ..onClick.listen(_onGetStructClick);
+    output.append(getStructButton);
+    output.append(new BRElement());
+    output.append(new BRElement());
+    TextAreaElement result = new TextAreaElement()
+      ..id = "getStructResult"
+      ..style.fontSize = "14px"
+      ..style.width = "300px"
+      ..style.height = "50px"
+      ..style.marginLeft = "10px";
+    output.append(result);
+  }
+
+  void _onGetStructClick(MouseEvent e) {
+    _validate();
+
+    InputElement structKey = querySelector("#structKey");
+    TextAreaElement result = querySelector("#getStructResult");
+
+    _calculatorClient
+        .getStruct(int.parse(structKey.value))
+        .then((SharedStruct s) {
+      result.text = "${s.toString()}";
+    });
+  }
+}

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/tutorial/dart/client/web/index.html
----------------------------------------------------------------------
diff --git a/tutorial/dart/client/web/index.html b/tutorial/dart/client/web/index.html
new file mode 100644
index 0000000..64b184e
--- /dev/null
+++ b/tutorial/dart/client/web/index.html
@@ -0,0 +1,36 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Thrift Tutorial</title>
+    <link rel="stylesheet" href="styles.css">
+    <script async src="client.dart" type="application/dart"></script>
+    <script async src="packages/browser/dart.js"></script>
+</head>
+
+<body>
+
+  <div id="output"></div>
+
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/thrift/blob/932c4700/tutorial/dart/client/web/styles.css
----------------------------------------------------------------------
diff --git a/tutorial/dart/client/web/styles.css b/tutorial/dart/client/web/styles.css
new file mode 100644
index 0000000..c031502
--- /dev/null
+++ b/tutorial/dart/client/web/styles.css
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+@import url(https://fonts.googleapis.com/css?family=Roboto);
+
+html, body {
+    width: 100%;
+    height: 100%;
+    margin: 0;
+    padding: 10px;
+    font-family: 'Roboto', sans-serif;
+}
+
+h3 {
+    border-bottom: solid;
+    border-width: thin;
+    padding-top: 20px;
+}