blob: 9553e07099b15cbafaf3b93d5f4164ee12dd5347 [file] [log] [blame]
David Reiss2c534032008-06-11 00:58:00 +00001%%%-------------------------------------------------------------------
2%%% File : thrift_client.erl
3%%% Author : Todd Lipcon <todd@lipcon.org>
4%%% Description : A client which connects to a thrift service
5%%%
6%%% Created : 24 Feb 2008 by Todd Lipcon <todd@lipcon.org>
7%%%-------------------------------------------------------------------
8-module(thrift_client).
9
10-behaviour(gen_server).
11
12%% API
13-export([start_link/3, call/3]).
14
15%% gen_server callbacks
16-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
17 terminate/2, code_change/3]).
18
19
20-include("thrift_constants.hrl").
21-include("thrift_protocol.hrl").
22
23-record(state, {service, protocol, seqid}).
24
25%%====================================================================
26%% API
27%%====================================================================
28%%--------------------------------------------------------------------
29%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
30%% Description: Starts the server
31%%--------------------------------------------------------------------
32start_link(Host, Port, Service) when is_integer(Port), is_atom(Service) ->
33 gen_server:start_link(?MODULE, [Host, Port, Service], []).
34
35
36call(Client, Function, Args)
37 when is_pid(Client), is_atom(Function), is_list(Args) ->
38 case gen_server:call(Client, {call, Function, Args}) of
39 R = {ok, _} -> R;
40 R = {error, _} -> R;
41 {exception, Exception} -> throw(Exception)
42 end.
43
44
45
46
47%%====================================================================
48%% gen_server callbacks
49%%====================================================================
50
51%%--------------------------------------------------------------------
52%% Function: init(Args) -> {ok, State} |
53%% {ok, State, Timeout} |
54%% ignore |
55%% {stop, Reason}
56%% Description: Initiates the server
57%%--------------------------------------------------------------------
58init([Host, Port, Service]) ->
59 {ok, Sock} = gen_tcp:connect(Host, Port,
60 [binary,
61 {packet, 0},
62 {active, false},
63 {nodelay, true}]),
64 {ok, Transport} = thrift_socket_transport:new(Sock),
65 {ok, BufTransport} = thrift_buffered_transport:new(Transport),
66 {ok, Protocol} = thrift_binary_protocol:new(BufTransport),
67 {ok, #state{service = Service,
68 protocol = Protocol,
69 seqid = 0}}.
70
71%%--------------------------------------------------------------------
72%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
73%% {reply, Reply, State, Timeout} |
74%% {noreply, State} |
75%% {noreply, State, Timeout} |
76%% {stop, Reason, Reply, State} |
77%% {stop, Reason, State}
78%% Description: Handling call messages
79%%--------------------------------------------------------------------
80handle_call({call, Function, Args}, _From, State = #state{service = Service,
81 protocol = Protocol,
82 seqid = SeqId}) ->
83 Result =
84 try
85 ok = send_function_call(State, Function, Args),
86 receive_function_result(State, Function)
87 catch
88 throw:{return, Return} ->
89 Return;
90 error:function_clause ->
91 ST = erlang:get_stacktrace(),
92 case hd(ST) of
93 {Service, function_info, [Function, _]} ->
94 {error, {no_function, Function}};
95 _ -> throw({error, {function_clause, ST}})
96 end
97 end,
98
99 {reply, Result, State}.
100
101
102%%--------------------------------------------------------------------
103%% Function: handle_cast(Msg, State) -> {noreply, State} |
104%% {noreply, State, Timeout} |
105%% {stop, Reason, State}
106%% Description: Handling cast messages
107%%--------------------------------------------------------------------
108handle_cast(_Msg, State) ->
109 {noreply, State}.
110
111%%--------------------------------------------------------------------
112%% Function: handle_info(Info, State) -> {noreply, State} |
113%% {noreply, State, Timeout} |
114%% {stop, Reason, State}
115%% Description: Handling all non call/cast messages
116%%--------------------------------------------------------------------
117handle_info(_Info, State) ->
118 {noreply, State}.
119
120%%--------------------------------------------------------------------
121%% Function: terminate(Reason, State) -> void()
122%% Description: This function is called by a gen_server when it is about to
123%% terminate. It should be the opposite of Module:init/1 and do any necessary
124%% cleaning up. When it returns, the gen_server terminates with Reason.
125%% The return value is ignored.
126%%--------------------------------------------------------------------
127terminate(_Reason, _State) ->
128 ok.
129
130%%--------------------------------------------------------------------
131%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
132%% Description: Convert process state when code is changed
133%%--------------------------------------------------------------------
134code_change(_OldVsn, State, _Extra) ->
135 {ok, State}.
136
137%%--------------------------------------------------------------------
138%%% Internal functions
139%%--------------------------------------------------------------------
140send_function_call(#state{protocol = Proto,
141 service = Service,
142 seqid = SeqId},
143 Function,
144 Args) ->
145 Params = Service:function_info(Function, params_type),
146 {struct, PList} = Params,
147 case length(PList) of
148 N when N =/= length(Args) ->
149 throw({return, {error, {bad_args, Function, Args}}});
150 _ -> ok
151 end,
152
153 Begin = #protocol_message_begin{name = atom_to_list(Function),
154 type = ?tMessageType_CALL,
155 seqid = SeqId},
156 ok = thrift_protocol:write(Proto, Begin),
157 ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
158 ok = thrift_protocol:write(Proto, message_end),
159 thrift_protocol:flush_transport(Proto),
160 ok.
161
162
163receive_function_result(State = #state{protocol = Proto,
164 service = Service},
165 Function) ->
166 ResultType = Service:function_info(Function, reply_type),
167 read_result(State, Function, ResultType).
168
169read_result(_State,
170 _Function,
171 async_void) ->
172 {ok, ok};
173
174read_result(State = #state{protocol = Proto,
175 seqid = SeqId},
176 Function,
177 ReplyType) ->
178 case thrift_protocol:read(Proto, message_begin) of
179 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
180 {error, {bad_seq_id, SeqId}};
181
182 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
183 handle_application_exception(State);
184
185 #protocol_message_begin{type = ?tMessageType_REPLY} ->
186 handle_reply(State, Function, ReplyType)
187 end.
188
189
190handle_reply(State = #state{protocol = Proto,
191 service = Service},
192 Function,
193 ReplyType) ->
194 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
195 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
196 io:format("RSD: ~p~n", [ReplyStructDef]),
197 {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
198 ReplyList = tuple_to_list(Reply),
199 true = length(ReplyList) == length(ExceptionFields) + 1,
200 ExceptionVals = tl(ReplyList),
201 Thrown = [X || X <- ExceptionVals,
202 X =/= undefined],
203 io:format("RL: ~p~nEV:~p~n", [ReplyList, ExceptionVals]),
204 Result =
205 case Thrown of
206 [] when ReplyType == {struct, []} ->
207 {ok, ok};
208 [] ->
209 {ok, hd(ReplyList)};
210 [Exception] ->
211 {exception, Exception}
212 end,
213 ok = thrift_protocol:read(Proto, message_end),
214 Result.
215
216
217handle_application_exception(_State) ->
218 not_yet_impl.