Thrift で client: TypeScript, server: Java

「Thrift いいよー」みたいな話が聞こえてきて、調べてました。 なんか聞いたことあるなと思ったら、Hive の stack trace でよく見かけたわーと思い Hive でも利用されていることを知りました。 Thrift って何かというと wikipedia からの引用だと

Apache Thrift(アパッチ スリフト)は、「スケーラブルな言語間サービス開発」のためにFacebookにて開発されたRPCフレームワークである。これはソフトウェアスタックとコード生成エンジンを組み合わせることで、C++C#JavaPerlPythonPHPErlangRubyなどの言語間にて効率的かつシームレスに動作するサービスを開発することを可能とする。

ということで、SOAP みたいなやつで Web API の作成に利用できそう。 で、Thrift の公式サイトを眺めてたら、「こいつ TypeScript の型定義ファイルを吐き出せそう!」と分かり、tutorial の TypeScript 化をやってみました。ソースは、github に上げています。例によって四則演算とかするサンプルです。

Server

今回は、TypeScriptのクライアントがメインなのですが、一応書いておきます。基本は、ここのチュートリアルと一緒です。 Thrift は、まず XXX.thrift みたいなサービスのインターフェースを定義するファイルを作成し、それをコンパイルすると サーバ用のコード、クライアント用のコードが吐き出せれ、サーバ用のインターフェースの実装を書く、という流れになるかと思います。 公式チュートリアルは、thrift自身のもっているサーバの機能で書かれているのですが、クライアント用のファイルを同じドメインでレスポンスできるように、Servletベースで起動するようにしました。 Servlet クラスは、以下です。

package server;

import javax.servlet.annotation.WebServlet;

import org.apache.thrift.protocol.TJSONProtocol;
import org.apache.thrift.server.TServlet;

import tutorial.Calculator;

@WebServlet(name = "WebServlet", urlPatterns = { "/call" })
public class Servlet extends TServlet {

    private static final long serialVersionUID = 1L;

    public Servlet() {
        super(new Calculator.Processor<>(new CalculatorHandler()), new TJSONProtocol.Factory());
    }
}

Client

今回メインの client 側です。作成した *.thrift ファイルに対し、 thrift コマンドを実行します。

thrift-0.9.2 -r --gen js --gen js:ts -out src/main/webapp/gen-js src/main/thrift/tutorial.thrift

今回、言いたかったのは、'js:ts'ってオプションがある!っていうのが全てです。 以下のようにJSファイルと型定義ファイルが生成されます。

  • src/main/webapp/gen-js/Calculator.js
  • src/main/webapp/gen-js/Calculator.d.ts
  • src/main/webapp/gen-js/shared_types.js
  • src/main/webapp/gen-js/shared_types.d.ts
  • src/main/webapp/gen-js/SharedService.js
  • src/main/webapp/gen-js/SharedService.d.ts
  • src/main/webapp/gen-js/tutorial_types.js
  • src/main/webapp/gen-js/tutorial_types.d.ts

型定義ファイルがJSと同じ場所にあるは気持ち悪いので、 src/main/typescript/typings 配下に移しました。

しかしです。生成された型定義ファイルが、解析エラーになりました。しょうがないんで生成されたファイルを直しました。なんかバグってる感じがします。。 thrift.d.ts の型定義も甘い感じで、、TypeScriptのコンパイルオプションに --noImplicitAny を付けてるとエラーになります。しょうがないんで提供されているファイルを直接直しました。

tutorial.thml

client の html ファイルを作成

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>Thrift Javascript Bindings - Tutorial Example</title>

  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript" src="js/thrift.js"></script>
  <script type="text/javascript" src="gen-js/tutorial_types.js"></script>
  <script type="text/javascript" src="gen-js/shared_types.js"></script>
  <script type="text/javascript" src="gen-js/SharedService.js"></script>
  <script type="text/javascript" src="gen-js/Calculator.js"></script>
  <script type="text/javascript" src="ts/tutorial.js"></script>

</head>
<body>
  <h2>Thrift Javascript Bindings</h2>
  <form action="">
  <table class="calculator">
    <tr>
      <td>num1</td>
      <td><input type="text" id="num1" value="20" onkeyup="javascript:auto_calc();"/></td>
    </tr>
    <tr>
      <td>Operation</td>
      <td><select id="op" size="1" onchange="javascript:auto_calc();"><option></option></select></td>
    </tr>
    <tr>
      <td>num2</td>
      <td><input type="text" id="num2" value="5" onkeyup="javascript:auto_calc();"/></td></tr>
    <tr>
      <td>result</td>
      <td><input type="text" id="result" value=""/></td></tr>
    <tr>
      <td><input type="checkbox" id="autoupdate" checked="checked"/>autoupdate</td>
      <td><input type="button" id="calculate" value="calculate" onclick="javascript:calc();"/></td>
    </tr>
  </table>
  </form>
</body>
</html>

tutorial.ts

client の typescript ファイルを作成

/// <reference path="typings/thrift.d.ts" />
/// <reference path="typings/Calculator.d.ts" />
/// <reference path="typings/jquery/jquery.d.ts" />

$(document).ready(function(){
  // remove pseudo child required for valid xhtml strict
  $("#op").children().remove();
  // add operations to it's dropdown menu
  $.each(Operation, function(key, value) {
    $('#op').append($("<option></option>").attr("value",value).text(key)); 
  });

   $('table.calculator').attr('width', 500);
});

function calc() {
  var transport = new Thrift.Transport("/thrift_typescript_servlet-example/call");
  var protocol  = new Thrift.Protocol(transport);
  var client    = new CalculatorClient(protocol);

  var work = new Work();
  work.num1 = $("#num1").val();
  work.num2 = $("#num2").val();
  work.op = $("#op").val();

  try {
    var result =  String(client.calculate(1, work));
    $('#result').val(result);
    $('#result').css('color', 'black');
  } catch(ouch){
    $('#result').val(ouch.why);
    $('#result').css('color', 'red');
  }
}

function auto_calc() {
  if ($('#autoupdate:checked').val() !== undefined) {
    calc();
  }
}

で、サーブレットを起動し、

http://localhost:8080/thrift_typescript_servlet-example/tutorial.html

にアクセスすると四則演算の画面が確認できます。

まとめ

Thrift で TypeScript の型定義ファイルが生成できるので、使いこなせるようになればタイプセーフになり色々捗るかも!