THRIFT-3299 Create an Apache Thrift language binding for Dart (dartlang.org).
Client: Dart
Patch: Mark Erickson <mark.erickson@workiva.com>

This closes #608
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 @@
 SUBDIRS += nodejs
 endif
 
+if WITH_DART
+SUBDIRS += dart
+endif
+
 #
 # generate html for ThriftTest.thrift
 #
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
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 "";
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 <mark.erickson@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
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()}";
+    });
+  }
+}
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>
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;
+}
diff --git a/tutorial/dart/console_client/bin/main.dart b/tutorial/dart/console_client/bin/main.dart
new file mode 100644
index 0000000..fda206a
--- /dev/null
+++ b/tutorial/dart/console_client/bin/main.dart
@@ -0,0 +1,149 @@
+/// 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:io';
+
+import 'package:args/args.dart';
+import 'package:logging/logging.dart';
+import 'package:thrift/thrift.dart';
+import 'package:thrift/thrift_console.dart';
+import 'package:tutorial/tutorial.dart';
+
+TTransport _transport;
+Calculator _calculator;
+int logid = 0;
+
+const Map<String, int> operationLookup = const {
+  '+': Operation.ADD,
+  '-': Operation.SUBTRACT,
+  '*': Operation.MULTIPLY,
+  '/': Operation.DIVIDE
+};
+
+main(List<String> args) {
+  Logger.root.level = Level.ALL;
+  Logger.root.onRecord.listen((LogRecord rec) {
+    print('${rec.level.name}: ${rec.time}: ${rec.message}');
+  });
+
+  var parser = new ArgParser();
+  parser.addOption('port', defaultsTo: '9090', help: 'The port to connect to');
+
+  ArgResults results;
+  try {
+    results = parser.parse(args);
+  } catch (e) {
+    results = null;
+  }
+
+  if (results == null) {
+    print(parser.usage);
+    exit(0);
+  }
+
+  int port = int.parse(results['port']);
+
+  _initConnection(port).then((_) => _run());
+}
+
+Future _initConnection(int port) async {
+  var socket = await Socket.connect('127.0.0.1', port);
+  _transport = new TAsyncClientSocketTransport(
+      new TTcpSocket(socket), new TMessageReader(new TBinaryProtocolFactory()));
+  TProtocol protocol = new TBinaryProtocol(_transport);
+  await _transport.open();
+
+  _calculator = new CalculatorClient(protocol);
+}
+
+Future _run() async {
+  _help();
+
+  while (true) {
+    stdout.write("> ");
+    var input = stdin.readLineSync();
+    var parts = input.split(' ');
+    var command = parts[0];
+    var args = parts.length > 1 ? parts.sublist(1) : [];
+
+    switch (command) {
+      case 'ping':
+        await _ping();
+        break;
+
+      case 'add':
+        await _add(int.parse(args[0]), int.parse(args[1]));
+        break;
+
+      case 'calc':
+        int op = operationLookup[args[1]];
+        if (!Operation.VALID_VALUES.contains(op)) {
+          stdout.writeln('Unknown operator ${args[1]}');
+          break;
+        }
+
+        var work = new Work()
+          ..num1 = int.parse(args[0])
+          ..op = op
+          ..num2 = int.parse(args[2])
+          ..comment = args.length > 3 ? args[3] : '';
+
+        await _calc(work);
+        break;
+
+      case 'struct':
+        await _struct(int.parse(args[0]));
+        break;
+
+      case 'help':
+      default:
+        _help();
+        break;
+    }
+  }
+}
+
+void _help() {
+  stdout.writeln('Commands:');
+  stdout.writeln('  help');
+  stdout.writeln('  ping');
+  stdout.writeln('  add x y');
+  stdout.writeln('  calc x op y [comment]');
+  stdout.writeln('  struct id');
+  stdout.writeln('');
+}
+
+Future _ping() async {
+  await _calculator.ping();
+  stdout.writeln('ping succeeded');
+}
+
+Future _add(int x, int y) async {
+  int result = await _calculator.add(x, y);
+  stdout.writeln('= $result');
+}
+
+Future _calc(Work work) async {
+  int result = await _calculator.calculate(logid++, work);
+  stdout.writeln('= $result');
+}
+
+Future _struct(int key) async {
+  var struct = await _calculator.getStruct(key);
+  stdout.writeln(struct.toString());
+}
diff --git a/tutorial/dart/console_client/pubspec.yaml b/tutorial/dart/console_client/pubspec.yaml
new file mode 100644
index 0000000..3db9fb9
--- /dev/null
+++ b/tutorial/dart/console_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_console_client
+version: 1.0.0-dev
+description: >
+  A Dart console client to implementation of the Apache Thrift tutorial
+author: Mark Erickson <mark.erickson@workiva.com>
+homepage: https://github.com/apache/thrift
+environment:
+  sdk: ^1.12.0
+dependencies:
+  args: ^0.13.0
+  collection: ^1.1.0
+  shared:
+    path: ../gen-dart/shared
+  thrift:
+    path: ../../../lib/dart
+  tutorial:
+    path: ../gen-dart/tutorial
diff --git a/tutorial/dart/server/bin/main.dart b/tutorial/dart/server/bin/main.dart
new file mode 100644
index 0000000..b8ac30d
--- /dev/null
+++ b/tutorial/dart/server/bin/main.dart
@@ -0,0 +1,163 @@
+/// 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:io';
+
+import 'package:args/args.dart';
+import 'package:logging/logging.dart';
+import 'package:thrift/thrift.dart';
+import 'package:thrift/thrift_console.dart';
+import 'package:tutorial/tutorial.dart';
+import 'package:shared/shared.dart';
+
+TProtocol _protocol;
+TProcessor _processor;
+WebSocket _webSocket;
+
+main(List<String> args) {
+  Logger.root.level = Level.ALL;
+  Logger.root.onRecord.listen((LogRecord rec) {
+    print('${rec.level.name}: ${rec.time}: ${rec.message}');
+  });
+
+  var parser = new ArgParser();
+  parser.addOption('port', defaultsTo: '9090', help: 'The port to listen on');
+  parser.addOption('type',
+      defaultsTo: 'ws',
+      allowed: ['ws', 'tcp'],
+      help: 'The type of socket',
+      allowedHelp: {'ws': 'WebSocket', 'tcp': 'TCP Socket'});
+
+  ArgResults results;
+  try {
+    results = parser.parse(args);
+  } catch (e) {
+    results = null;
+  }
+
+  if (results == null) {
+    print(parser.usage);
+    exit(0);
+  }
+
+  int port = int.parse(results['port']);
+  String socketType = results['type'];
+
+  if (socketType == 'tcp') {
+    _runTcpServer(port);
+  } else if (socketType == 'ws') {
+    _runWebSocketServer(port);
+  }
+}
+
+Future _runWebSocketServer(int port) async {
+  var httpServer = await HttpServer.bind('127.0.0.1', port);
+  print('listening for WebSocket connections on $port');
+
+  httpServer.listen((HttpRequest request) async {
+    if (request.uri.path == '/ws') {
+      _webSocket = await WebSocketTransformer.upgrade(request);
+      await _initProcessor(new TWebSocket(_webSocket));
+    } else {
+      print('Invalid path: ${request.uri.path}');
+    }
+  });
+}
+
+Future _runTcpServer(int port) async {
+  var serverSocket = await ServerSocket.bind('127.0.0.1', port);
+  print('listening for TCP connections on $port');
+
+  Socket socket = await serverSocket.first;
+  await _initProcessor(new TTcpSocket(socket));
+}
+
+Future _initProcessor(TSocket socket) async {
+  TServerSocketTransport transport = new TServerSocketTransport(socket);
+  transport.onIncomingMessage.listen(_processMessage);
+  _processor = new CalculatorProcessor(new CalculatorServer());
+  _protocol = new TBinaryProtocol(transport);
+  await _protocol.transport.open();
+
+  print('connected');
+}
+
+Future _processMessage(_) async {
+  _processor.process(_protocol, _protocol);
+}
+
+class CalculatorServer implements Calculator {
+  final Map<int, SharedStruct> _log = {};
+
+  Future ping() async {
+    print('ping()');
+  }
+
+  Future<int> add(int num1, int num2) async {
+    print('add($num1, $num2)');
+
+    return num1 + num2;
+  }
+
+  Future<int> calculate(int logid, Work work) async {
+    print('calulate($logid, ${work.toString()})');
+
+    int val;
+
+    switch (work.op) {
+      case Operation.ADD:
+        val = work.num1 + work.num2;
+        break;
+
+      case Operation.SUBTRACT:
+        val = work.num1 - work.num2;
+        break;
+
+      case Operation.MULTIPLY:
+        val = work.num1 * work.num2;
+        break;
+
+      case Operation.DIVIDE:
+        if (work.num2 == 0) {
+          var x = new InvalidOperation();
+          x.whatOp = work.op;
+          x.why = 'Cannot divide by 0';
+          throw x;
+        }
+        val = (work.num1 / work.num2).floor();
+        break;
+    }
+
+    var log = new SharedStruct();
+    log.key = logid;
+    log.value = '$val "${work.comment}"';
+    this._log[logid] = log;
+
+    return val;
+  }
+
+  Future zip() async {
+    print('zip()');
+  }
+
+  Future<SharedStruct> getStruct(int key) async {
+    print('getStruct($key)');
+
+    return _log[key];
+  }
+}
diff --git a/tutorial/dart/server/pubspec.yaml b/tutorial/dart/server/pubspec.yaml
new file mode 100644
index 0000000..f502974
--- /dev/null
+++ b/tutorial/dart/server/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: tutorial_server
+version: 1.0.0-dev
+description: A Dart server to support the Apache Thrift tutorial
+author: Mark Erickson <mark.erickson@workiva.com>
+homepage: https://github.com/apache/thrift
+environment:
+  sdk: ^1.12.0
+dependencies:
+  args: ^0.13.0
+  shared:
+    path: ../gen-dart/shared
+  thrift:
+    path: ../../../lib/dart
+  tutorial:
+    path: ../gen-dart/tutorial
diff --git a/tutorial/shared.thrift b/tutorial/shared.thrift
index 60e8e7a..386000b 100644
--- a/tutorial/shared.thrift
+++ b/tutorial/shared.thrift
@@ -24,6 +24,7 @@
 
 namespace cpp shared
 namespace d share // "shared" would collide with the eponymous D keyword.
+namespace dart shared
 namespace java shared
 namespace perl shared
 namespace php shared
diff --git a/tutorial/tutorial.thrift b/tutorial/tutorial.thrift
index 571ab8e..1149edf 100644
--- a/tutorial/tutorial.thrift
+++ b/tutorial/tutorial.thrift
@@ -64,6 +64,7 @@
  */
 namespace cpp tutorial
 namespace d tutorial
+namespace dart tutorial
 namespace java tutorial
 namespace php tutorial
 namespace perl tutorial