こんにちは。伊藤です。
今回はFacebookが開発したRPCフレームワークであるThriftについて書きます。
Thriftとは?
http://ja.wikipedia.org/wiki/Apache_Thrift
つまりこれを使うと、Server側はJavaで開発、クライアント側はPHPで開発といった事が簡単にできるワケです。
今回はServer側をJava、クライアント側をPHPとして動かす方法について紹介します。
1. Thriftのインストール
弊社のt_ishidaによるこの記事を参考にどうぞ http://qiita.com/items/ca2eeb813249a9a09a58
2. .thriftファイルの作成
この辺を参考にして、.thriftファイルを作成します。
使用できる型は、bool/byte/short/int/long/double/stringの他に、Map/List/Enumも可能です。
test.thrift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
namespace java jp.co.aainc.thrift // Namespaceの定義 // Listの定義 typedef list<map<string, bool>> ListMap1 // Enumの定義 enum Enum1 { YES, NO } // 構造の定義 struct StructTest { 1: required bool boolTest; 2: required byte byteTest; 3: required i16 i16Test; // Javaではshort 4: optional i32 i32Test; // Javaではint 5: optional i64 i64Test; // Javaではlong 6: required double doubleTest; 7: required string stringTest; 8: optional ListMap1 listMapTest; // 上で定義したList 9: optional Enum1 enumTest; // 上で定義したEnum } struct ResultTest { 1: bool result; } // サービスを定義 service ServiceTest { ResultTest executeTest1(1:StructTest parameter) // 引数はStructTestで結果はResultTestで返す } |
3. .thriftファイルを基にソースコードを生成
まずJavaのソースコードを生成します。
1 |
thrift --gen java test.thrift |
このコマンドを叩く事で、gen-java配下の指定したNamespace(パッケージ)に次のファイル群が生成されます。
Enum(Enum1.java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/** * Autogenerated by Thrift Compiler (0.8.0) * * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING * @generated */ package jp.co.aainc.thrift; import java.util.Map; import java.util.HashMap; import org.apache.thrift.TEnum; public enum Enum1 implements org.apache.thrift.TEnum { YES(0), NO(1); private final int value; private Enum1(int value) { this.value = value; } /** * Get the integer value of this enum value, as defined in the Thrift IDL. */ public int getValue() { return value; } /** * Find a the enum type by its integer value, as defined in the Thrift IDL. * @return null if the value is not found. */ public static Enum1 findByValue(int value) { switch (value) { case 0: return YES; case 1: return NO; default: return null; } } } |
構造(StructTest.java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public class StructTest implements org.apache.thrift.TBase<StructTest, StructTest._Fields>, java.io.Serializable, Cloneable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("StructTest"); private static final org.apache.thrift.protocol.TField BOOL_TEST_FIELD_DESC = new org.apache.thrift.protocol.TField("boolTest", org.apache.thrift.protocol.TType.BOOL, (short)1); private static final org.apache.thrift.protocol.TField BYTE_TEST_FIELD_DESC = new org.apache.thrift.protocol.TField("byteTest", org.apache.thrift.protocol.TType.BYTE, (short)2); private static final org.apache.thrift.protocol.TField I16_TEST_FIELD_DESC = new org.apache.thrift.protocol.TField("i16Test", org.apache.thrift.protocol.TType.I16, (short)3); private static final org.apache.thrift.protocol.TField I32_TEST_FIELD_DESC = new org.apache.thrift.protocol.TField("i32Test", org.apache.thrift.protocol.TType.I32, (short)4); private static final org.apache.thrift.protocol.TField I64_TEST_FIELD_DESC = new org.apache.thrift.protocol.TField("i64Test", org.apache.thrift.protocol.TType.I64, (short)5); private static final org.apache.thrift.protocol.TField DOUBLE_TEST_FIELD_DESC = new org.apache.thrift.protocol.TField("doubleTest", org.apache.thrift.protocol.TType.DOUBLE, (short)6); private static final org.apache.thrift.protocol.TField STRING_TEST_FIELD_DESC = new org.apache.thrift.protocol.TField("stringTest", org.apache.thrift.protocol.TType.STRING, (short)7); private static final org.apache.thrift.protocol.TField LIST_MAP_TEST_FIELD_DESC = new org.apache.thrift.protocol.TField("listMapTest", org.apache.thrift.protocol.TType.LIST, (short)8); private static final org.apache.thrift.protocol.TField ENUM_TEST_FIELD_DESC = new org.apache.thrift.protocol.TField("enumTest", org.apache.thrift.protocol.TType.I32, (short)9); private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>(); static { schemes.put(StandardScheme.class, new StructTestStandardSchemeFactory()); schemes.put(TupleScheme.class, new StructTestTupleSchemeFactory()); } public boolean boolTest; // required public byte byteTest; // required public short i16Test; // required public int i32Test; // optional public long i64Test; // optional public double doubleTest; // required public String stringTest; // required public List<Map<String,Boolean>> listMapTest; // optional /** * * @see Enum1 */ public Enum1 enumTest; // optional 以下略。。。 } |
構造(ResultTest.java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class ResultTest implements org.apache.thrift.TBase<ResultTest, ResultTest._Fields>, java.io.Serializable, Cloneable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("ResultTest"); private static final org.apache.thrift.protocol.TField RESULT_FIELD_DESC = new org.apache.thrift.protocol.TField("result", org.apache.thrift.protocol.TType.BOOL, (short)1); private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>(); static { schemes.put(StandardScheme.class, new ResultTestStandardSchemeFactory()); schemes.put(TupleScheme.class, new ResultTestTupleSchemeFactory()); } public boolean result; // required 以下略。。。 } |
サービスクラス(ServiceTest.java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class ServiceTest { public interface Iface { public ResultTest executeTest1(StructTest parameter) throws org.apache.thrift.TException; } public interface AsyncIface { public void executeTest1(StructTest parameter, org.apache.thrift.async.AsyncMethodCallback<AsyncClient.executeTest1_call> resultHandler) throws org.apache.thrift.TException; } public static class Client extends org.apache.thrift.TServiceClient implements Iface { public static class Factory implements org.apache.thrift.TServiceClientFactory<Client> { public Factory() {} public Client getClient(org.apache.thrift.protocol.TProtocol prot) { return new Client(prot); } public Client getClient(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) { return new Client(iprot, oprot); } } 以下略。。。 } |
次にPHPのソースコードを生成します。
1 |
thrift --gen java test.thrift |
このコマンドを叩く事で、gen-php配下に次のファイル群が生成されます。
定義などをarrayにしたもの(test_types.php)
※ソースは割愛
サービスクラス(ServiceTest.php)
※ソースは割愛
また、PHPについてはThriftのソースコードをコピーしてきて、このような階層にしておく必要があります。
http://wiki.apache.org/thrift/ThriftUsagePhp
packagesの中に今回生成した2ファイルを配置して下さい。
さて、ここまででやっと準備が整いました。
4. サーバ側実装(Java)
Server.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/** * PHP側からパラメータ「StructTest」を受け取って、結果「ResultTest」を返す */ public class Server { public static void main(String[] args) { try { TServerTransport serverTransport = new TServerSocket(9090); final TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor( new ServiceTest.Processor(new ServiceTest.Iface() { // 自動生成されたServiceTestを使用する @Override public ResultTest executeTest1(StructTest parameter) throws TException { // test.thriftで定義したパラメータたちが渡ってくる boolean boolTest = parameter.boolTest; byte byteTest = parameter.byteTest; short i16Test = parameter.i16Test; int i32Test = parameter.i32Test; long i64Test = parameter.i64Test; double doubleTest = parameter.doubleTest; String stringTest = parameter.stringTest; List<Map<String,Boolean>> listMapTest = parameter.listMapTest; Enum1 enumTest = parameter.enumTest; // ここで何らかの処理実行 // doSomething(); // 結果返す。test.thriftファイルで定義した結果用オブジェクトを作る ResultTest resultTest = new ResultTest(); resultTest.result = true; // booleanのresultにTrueをセット return resultTest; } })) ); new Thread(new Runnable() { public void run() { System.out.println("Starting the server..."); server.serve(); System.out.println("Stopped the server..."); } }).start(); } catch (Exception e) { e.printStackTrace(); } } } |
このクラスを実行して、localhostの9090ポートで待ち受け状態にしておきます。
5. クライアント側実装(PHP)
ThriftTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<?php require_once 'thrift/Thrift.php'; require_once 'thrift/packages/ServiceTest.php'; $socket = new TSocket ( 'localhost', '9090' ) // Serverが起動しているアドレスとポートを指定; $socket->setSendTimeout(600 * 1000); $socket->setRecvTimeout(600 * 1000); $transport = new TBufferedTransport ( $socket ); $protocol = new TBinaryProtocol($transport); $client = new ServiceTestClient($protocol); $transport->open(); // Serverへ渡すパラメータを作成 // ※test.thriftでrequiredにしているパラメータがNULLだと怒られます $struct = new StructTest(); $struct->boolTest = true; $struct->byteTest = 1; $struct->i16Test = 123; $struct->i32Test = 1234; $struct->i64Test = 12345; $struct->doubleTest = 123.45; $struct->stringTest = 'hoge'; $struct->listMapTest = array(array('fuga' => false)); $struct->enumTest = 'YES'; $result = $client->executeTest1($struct); // 処理を実行 echo $result->result; // 結果取得 trueが返ってきます。 |
ThriftTest.phpを実行すると、PHP側のexecuteTest1()を経由して、Server側のexecuteTest1()が呼ばれ、結果が返ってきます。この辺の呼びだしをThriftがよしなにやってくれるのです。
何かダラダラと長くなってしまいましが、このようにThriftは簡単に言語間の差を吸収してくれます。
色々と使い道がありそうですね。
元Javaプログラマ。現在はScala/PlayでWeb開発と、SwiftでiOSアプリ開発をしています。 Unitテストとか書いてる時が一番楽しかったりします。