Apache Thrift 提供了一個產生不同語言程式碼的 compiler engine,可在不同程式語言(C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi)環境之間,透過 Thrift 以 RPC 方式傳輸資料。
安裝 thrift compiler tool
thrift compiler tool 是產生 thrift 程式碼的工具。
在 macos,可透過 macport 安裝,在 thrift Portfile 中查看目前支援的語言是 java, c#, C, haskell, php, erlang,還不是很清楚為什麼不支援 go, python, ruby 等語言。
sudo port install thrift +java +erlang
$ thrift --version
Thrift version 0.10.0
在 CentOS 可直接編譯
cd thrift-0.10.0
./configure
make
sudo make install
.thrift 文件
在安裝 thrift compiler 後,要撰寫一份 .thrift 定義文件,該檔案是一份以 thrift types 及 Services 定義的interface definition,其中 Services 就是 server 要實作的功能,並提供給 client 呼叫,然後才能用 compiler 產生某個對應程式語言的程式碼,使用方式如下。
thrift --gen <language> <Thrift filename>
Thrift Types
Base Types
一般程式語言都提供的最基本的資料類型
- bool: A boolean value (true or false)
- byte: An 8-bit signed integer
- i16: A 16-bit signed integer
- i32: A 32-bit signed integer
- i64: A 64-bit signed integer
- double: A 64-bit floating point number
- string: A text string encoded using UTF-8 encoding
Special Types
binary: a sequence of unencoded bytes
這是上述 string 的特殊格式,可用在跟 java 系統之間有更好的資料收送,未來可能會提升為 base type
Structs
定義 common object,這等同於 OOP 中的 class,但沒有繼承的機制,前面的數字 1: 是要讓 compiler 可針對不同版本 IDL 識別,可填上 default value,也可以設定為 optional。
ex:
struct User {
1: i16 gender = 1,
2: string username,
3: string password,
4: i32 id
}
Containers
有三種 container types
list: 有順序的元素集合 An ordered list of elements,會對應到 STL vector, Java ArrayList, native arrays in scripting languages
set: 沒有順序的元素集合 An unordered set of unique elements,會對應到 an STL set, Java HashSet, set in Python, etc.
Note: PHP 不支援 sets,會以 List 方式處理。
map: 唯一的 key 對應 value 的集合 A map of strictly unique keys to values,會對應到 an STL map, Java HashMap, PHP associative array, Python/Ruby dictionary, etc.
如果有初始值時,可以在不同的程式語言中,以自訂的 code generator 替代為 custom data type
為了提供最佳的相容性,key 的 data type 最好使用 Basic Type
Exceptions
Exception 等同於 structs,是繼承自不同程式語言的 native exception base class
exception NotFoundException{
1:i16 errorType,
2:string message
}
Services
以 Thrift type 定義 services,其作用就像是 OOP 裡面的 interface,Thrift compiler 可以自動產生實作 client, server 的 stubs codes,service 之間有提供繼承的機制。
service 包含了一組 functions,每一個都有一個參數 list 及 return type。
return type 可以使用 void,也就是不回傳資料,但實際上,server 還是會回傳一個 response 給 client,用來告訴 client 已經把所有工作都做完了。
可以在 function 上增加一個 oneway modifier,這代表 clietn 不會等待 server 的 response,這表示只能保證 client 會呼叫 server 的 function,但不能保證 server 會依照呼叫的順序執行該 function。
service <name> {
<returntype> <name> (<arguments>)[throws (<exceptions>)]
}
service UserService{
void saveUser(1:User user),
User get(1:i32 id) throws (1:NotFoundException nfe),
oneway void zip()
}
service Calculator extends shared.SharedService
其他
include
通過include引用其他的thrift文件,默認在當前路徑下尋找,也可以在相對路徑下尋找,需要通過編譯參數 -I 來設置
namespace 與 java 的 package 作用一樣
namespace java thrift.sa
namespace python thrift.sa
- 常數
const i32 INT32CONSTANT = 9853
const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
- enum
enum Operation {
ADD = 1,
SUBTRACT = 2,
MULTIPLY = 3,
DIVIDE = 4
}
Java 支援的傳輸格式, 方式
支援的傳輸格式
- TBinaryProtocol 二進制格式.
- TCompactProtocol 壓縮格式
- TJSONProtocol JSON格式
- TSimpleJSONProtocol 提供 JSON 只寫協議, 生成的文件很容易通過腳本語言解析
- TDebugProtocol 使用易懂的可讀的文本格式,以便於debug
支援的數據傳輸方式
- TSocket 阻塞式 socket server
- TFramedTransport 以frame為單位進行傳輸,非阻塞式服務中使用
- TFileTransport 以文件形式進行傳輸。
- TMemoryTransport 將 memory 用於I/O,java 實作時內部實際使用了簡單的ByteArrayOutputStream
- TZlibTransport – 使用zlib進行壓縮, 與其他傳輸方式聯合使用,目前無java實現
支援的服務模型
- TSimpleServer 簡單的單線程服務模型,常用於測試
- TThreadPoolServer 多線程服務模型,使用標準的阻塞式IO。
- TNonblockingServer 多線程服務模型,使用非阻塞式IO(需使用TFramedTransport數據傳輸方式)
Example - bookservice.thrift
先寫 bookservice.thrift,然後以 thrift compiler 產生 java 及 erlang 的 code
namespace java tw.com.maxkit.test
struct Book_info{
1: i32 book_id;
2: string book_name;
3: string book_author;
4: double book_price;
5: string book_publisher
}
service BookSender{
void ping(),
i32 add(
1:i32 num1, 2:i32 num2
),
bool sender(
1: list<Book_info> books
);
oneway void sender2(
1: list<Book_info> books
);
}
產生 java 及 erlang 的 code
thrift --gen erl bookservice.thrift
thrift --gen java bookservice.thrift
gen-erl/
book_sender_thrift.erl
book_sender_thrift.hrl
bookservice_constants.hrl
bookservice_types.erl
bookservice_types.hrl
gen-java/tw/com/maxkit/test/
Book_info.java
BookSender.java
Java
BookServiceHandler 實作 Server Side 的四個 function
import tw.com.maxkit.test.*;
public class BookServiceHandler implements BookSender.Iface {
public BookServiceHandler() {
}
public void ping() {
System.out.println("ping()");
}
public int add(int n1, int n2) {
System.out.println("add(" + n1 + "," + n2 + ")");
return n1 + n2;
}
public boolean sender(java.util.List<Book_info> books) throws org.apache.thrift.TException {
System.out.println("Sender get books");
for(Book_info b: books) {
System.out.println("get book "+b.book_id);
}
return true;
}
public void sender2(java.util.List<Book_info> books) throws org.apache.thrift.TException {
System.out.println("Sender2 get books");
for(Book_info b: books) {
System.out.println("get book "+b.book_id);
}
}
}
BookServer
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TServer.Args;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TSSLTransportFactory;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import org.apache.thrift.transport.TSSLTransportFactory.TSSLTransportParameters;
import tw.com.maxkit.test.*;
public class BookServer {
public static BookServiceHandler handler;
public static BookSender.Processor processor;
public static void main(String [] args) {
try {
handler = new BookServiceHandler();
processor = new BookSender.Processor(handler);
Runnable simple = new Runnable() {
public void run() {
simple(processor);
}
};
Runnable secure = new Runnable() {
public void run() {
secure(processor);
}
};
new Thread(simple).start();
new Thread(secure).start();
} catch (Exception x) {
x.printStackTrace();
}
}
public static void simple(BookSender.Processor processor) {
try {
TServerTransport serverTransport = new TServerSocket(9090);
TServer server = new TSimpleServer(new Args(serverTransport).processor(processor));
// Use this for a multithreaded server
// TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));
System.out.println("Starting the simple server...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void secure(BookSender.Processor processor) {
try {
TSSLTransportParameters params = new TSSLTransportParameters();
// The Keystore contains the private key
params.setKeyStore("keystore", "max168kit", null, "PKCS12");
TServerTransport serverTransport = TSSLTransportFactory.getServerSocket(9091, 0, null, params);
TServer server = new TSimpleServer(new Args(serverTransport).processor(processor));
// Use this for a multi threaded server
// TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));
System.out.println("Starting the secure server...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
}
BookClient
import org.apache.thrift.TException;
import org.apache.thrift.transport.TSSLTransportFactory;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TSSLTransportFactory.TSSLTransportParameters;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import tw.com.maxkit.test.BookSender;
import tw.com.maxkit.test.Book_info;
import java.util.ArrayList;
public class BookClient {
public static void main(String[] args) {
String servertype="simple";
if (args.length == 1) {
//System.out.println("Please enter 'simple' or 'secure'");
//System.exit(0);
servertype=args[0];
}
try {
TTransport transport;
if (servertype.contains("simple")) {
transport = new TSocket("localhost", 9090);
transport.open();
} else {
TSSLTransportParameters params = new TSSLTransportParameters();
params.setTrustStore("keystore", "max168kit", null, "PKCS12");
transport = TSSLTransportFactory.getClientSocket("localhost", 9091, 0, params);
}
TProtocol protocol = new TBinaryProtocol(transport);
BookSender.Client client = new BookSender.Client(protocol);
perform(client);
transport.close();
} catch (TException x) {
x.printStackTrace();
}
}
private static void perform(BookSender.Client client) throws TException {
client.ping();
System.out.println("ping()");
int sum = client.add(1, 1);
System.out.println("1+1=" + sum);
Book_info b1 = new Book_info(1, "name1", "author1", 11.1, "publisher1");
Book_info b2 = new Book_info(2, "name2", "author2", 22.2, "publisher2");
ArrayList list = new ArrayList();
list.add(b1);
list.add(b2);
boolean result = client.sender(list);
System.out.println("sender1 result="+result);
client.sender2(list);
System.out.println("sender2 done");
}
}
Erlang
book_server.erl
-module(book_server).
-include("book_sender_thrift.hrl").
%% API
-export([start/0, handle_function/2, ping/0, add/2, sender/1, sender2/1, stop/1]).
debug(Data)->
io:format("Debug info:~s~n",[Data]).
debug(Format, Data) ->
error_logger:info_msg(Format, Data).
ping() ->
debug("ping()",[]),
ok.
add(N1, N2) ->
debug("add(~p,~p)",[N1,N2]),
N1+N2.
sender(_L1) ->
true.
sender2(_L1) ->
ok.
start()->
start(9090).
start(Port)->
Handler = ?MODULE,
debug("1",[]),
Res=thrift_socket_server:start([{handler, Handler},
{service, book_sender_thrift},
{port, Port},
{name, book_server}]),
debug("2 ~p ~n",[Res]),
Res.
stop(Server)->
thrift_socket_server:stop(Server).
%%handle_function(Function, Args) when is_atom(Function), is_tuple(Args) ->
%% case Function of
%% ping ->
%% {reply, ping()};
%% add ->
%% {reply, add(tuple_to_list(Args))};
%% % add function here
%% _ ->
%% error
%% end.
handle_function(Function, Args) when is_atom(Function), is_tuple(Args) ->
debug("handle_function ~n",[]),
case apply(?MODULE, Function, tuple_to_list(Args)) of
ok -> ok;
Reply -> {reply, Reply}
end.
book_client.erl
-module(book_client).
%% API
-export([]).
%% API
-export([ping/0]).
-spec ping() -> ok.
ping() ->
io:format("call thrift server\n"),
%% {ok, Port} = application:get_env(larzio, agent_port),
%% {ok, Ip} = application:get_env(larzio, agent_server),
Port = 9090,
Ip = "localhost",
{ok, Client0} = thrift_client_util:new(Ip, Port, book_sender_thrift, []),
{Client1, {ok, ok}} = thrift_client:call(Client0, ping, []),
{Client1, {ok, AddResult}} = thrift_client:call(Client0, add, [1, 2]),
io:format("add result ~p ~n", [AddResult]),
{_Client8, ok} = thrift_client:close(Client1),
io:format("call thrift done~n"),
ok.
沒有留言:
張貼留言