Dart Tutorial

Introduction

All Apache Thrift tutorials require that you have:

  1. The Apache Thrift Compiler and Libraries, see Download and Building from Source for more details.
  2. Generated the tutorial.thrift and shared.thrift files:
    thrift -r --gen dart tutorial.thrift
  3. Followed all prerequisites listed below.

Prerequisites

TBD

Client

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()}";
    });
  }
}

Client web page

<!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.js"></script>
</head>

<body>

  <div id="output"></div>

</body>
</html>

Server

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];
  }
}

Additional Information

TBD