blob: bd93e29b9bff208464565b4311a23b28210e36f2 [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
David Reissad74b322008-06-11 01:03:29 +000013-export([start_link/2, start_link/3, start_link/4, call/3, close/1]).
David Reiss2c534032008-06-11 00:58:00 +000014
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
David Reissad74b322008-06-11 01:03:29 +000023-record(state, {service, protocol, seqid}).
David Reiss2c534032008-06-11 00:58:00 +000024
25%%====================================================================
26%% API
27%%====================================================================
28%%--------------------------------------------------------------------
29%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
30%% Description: Starts the server
31%%--------------------------------------------------------------------
David Reissad74b322008-06-11 01:03:29 +000032start_link(Host, Port, Service) when is_integer(Port), is_atom(Service) ->
David Reiss914ebb42008-06-11 01:01:48 +000033 start_link(Host, Port, Service, []).
David Reiss4fd78182008-06-11 01:01:13 +000034
David Reissad74b322008-06-11 01:03:29 +000035%% Backwards-compatible starter for the usual case of socket transports
David Reiss2fe905e2008-06-11 01:02:23 +000036start_link(Host, Port, Service, Options)
37 when is_integer(Port), is_atom(Service), is_list(Options) ->
David Reiss44f785e2008-06-11 01:03:37 +000038 {ok, ProtocolFactory} = thrift_socket_transport:new_protocol_factory(Host, Port, Options),
39 start_link(ProtocolFactory, Service).
David Reissad74b322008-06-11 01:03:29 +000040
David Reiss44f785e2008-06-11 01:03:37 +000041%% ProtocolFactory :: fun() -> thrift_protocol()
42start_link(ProtocolFactory, Service)
43 when is_function(ProtocolFactory), is_atom(Service) ->
David Reissad74b322008-06-11 01:03:29 +000044 case gen_server:start_link(?MODULE, [Service], []) of
David Reisse5a4d0c2008-06-11 01:02:10 +000045 {ok, Pid} ->
David Reiss44f785e2008-06-11 01:03:37 +000046 case gen_server:call(Pid, {connect, ProtocolFactory}) of
David Reisse5a4d0c2008-06-11 01:02:10 +000047 ok ->
48 {ok, Pid};
49 Error ->
50 Error
51 end;
52 Else ->
53 Else
54 end.
David Reiss2c534032008-06-11 00:58:00 +000055
David Reiss2c534032008-06-11 00:58:00 +000056call(Client, Function, Args)
57 when is_pid(Client), is_atom(Function), is_list(Args) ->
58 case gen_server:call(Client, {call, Function, Args}) of
59 R = {ok, _} -> R;
60 R = {error, _} -> R;
61 {exception, Exception} -> throw(Exception)
62 end.
David Reiss2c534032008-06-11 00:58:00 +000063
David Reiss464e3002008-06-11 01:00:45 +000064close(Client) when is_pid(Client) ->
David Reiss6f1cd532008-06-11 01:01:21 +000065 gen_server:cast(Client, close).
David Reiss464e3002008-06-11 01:00:45 +000066
David Reiss2c534032008-06-11 00:58:00 +000067%%====================================================================
68%% gen_server callbacks
69%%====================================================================
70
71%%--------------------------------------------------------------------
72%% Function: init(Args) -> {ok, State} |
73%% {ok, State, Timeout} |
74%% ignore |
75%% {stop, Reason}
76%% Description: Initiates the server
77%%--------------------------------------------------------------------
David Reissad74b322008-06-11 01:03:29 +000078init([Service]) ->
79 {ok, #state{service = Service}}.
David Reiss2c534032008-06-11 00:58:00 +000080
81%%--------------------------------------------------------------------
82%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
83%% {reply, Reply, State, Timeout} |
84%% {noreply, State} |
85%% {noreply, State, Timeout} |
86%% {stop, Reason, Reply, State} |
87%% {stop, Reason, State}
88%% Description: Handling call messages
89%%--------------------------------------------------------------------
David Reiss44f785e2008-06-11 01:03:37 +000090handle_call({connect, ProtocolFactory}, _From,
David Reissad74b322008-06-11 01:03:29 +000091 State = #state{service = Service}) ->
David Reiss44f785e2008-06-11 01:03:37 +000092 case ProtocolFactory() of
David Reissad74b322008-06-11 01:03:29 +000093 {ok, Protocol} ->
94 {reply, ok, State#state{protocol = Protocol,
95 seqid = 0}};
David Reisse5a4d0c2008-06-11 01:02:10 +000096 Error ->
97 {stop, normal, Error, State}
98 end;
99
David Reiss2c534032008-06-11 00:58:00 +0000100handle_call({call, Function, Args}, _From, State = #state{service = Service,
101 protocol = Protocol,
102 seqid = SeqId}) ->
103 Result =
104 try
105 ok = send_function_call(State, Function, Args),
106 receive_function_result(State, Function)
107 catch
108 throw:{return, Return} ->
109 Return;
110 error:function_clause ->
111 ST = erlang:get_stacktrace(),
112 case hd(ST) of
113 {Service, function_info, [Function, _]} ->
114 {error, {no_function, Function}};
115 _ -> throw({error, {function_clause, ST}})
116 end
117 end,
David Reiss6b3e40f2008-06-11 00:59:03 +0000118
David Reiss6f1cd532008-06-11 01:01:21 +0000119 {reply, Result, State}.
David Reiss2c534032008-06-11 00:58:00 +0000120
121%%--------------------------------------------------------------------
122%% Function: handle_cast(Msg, State) -> {noreply, State} |
123%% {noreply, State, Timeout} |
124%% {stop, Reason, State}
125%% Description: Handling cast messages
126%%--------------------------------------------------------------------
David Reiss6f1cd532008-06-11 01:01:21 +0000127handle_cast(close, State=#state{protocol = Protocol}) ->
128%% error_logger:info_msg("thrift_client ~p received close", [self()]),
129 {stop,normal,State};
David Reiss2c534032008-06-11 00:58:00 +0000130handle_cast(_Msg, State) ->
131 {noreply, State}.
132
133%%--------------------------------------------------------------------
134%% Function: handle_info(Info, State) -> {noreply, State} |
135%% {noreply, State, Timeout} |
136%% {stop, Reason, State}
137%% Description: Handling all non call/cast messages
138%%--------------------------------------------------------------------
139handle_info(_Info, State) ->
140 {noreply, State}.
141
142%%--------------------------------------------------------------------
143%% Function: terminate(Reason, State) -> void()
144%% Description: This function is called by a gen_server when it is about to
145%% terminate. It should be the opposite of Module:init/1 and do any necessary
146%% cleaning up. When it returns, the gen_server terminates with Reason.
147%% The return value is ignored.
148%%--------------------------------------------------------------------
David Reisse5a4d0c2008-06-11 01:02:10 +0000149terminate(Reason, State = #state{protocol=undefined}) ->
150 ok;
151terminate(Reason, State = #state{protocol=Protocol}) ->
David Reiss464e3002008-06-11 01:00:45 +0000152 thrift_protocol:close_transport(Protocol),
David Reiss2c534032008-06-11 00:58:00 +0000153 ok.
154
155%%--------------------------------------------------------------------
156%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
157%% Description: Convert process state when code is changed
158%%--------------------------------------------------------------------
159code_change(_OldVsn, State, _Extra) ->
160 {ok, State}.
161
162%%--------------------------------------------------------------------
163%%% Internal functions
164%%--------------------------------------------------------------------
165send_function_call(#state{protocol = Proto,
166 service = Service,
167 seqid = SeqId},
168 Function,
169 Args) ->
170 Params = Service:function_info(Function, params_type),
171 {struct, PList} = Params,
David Reissc5257452008-06-11 00:59:27 +0000172 if
173 length(PList) =/= length(Args) ->
David Reiss2c534032008-06-11 00:58:00 +0000174 throw({return, {error, {bad_args, Function, Args}}});
David Reissc5257452008-06-11 00:59:27 +0000175 true -> ok
David Reiss2c534032008-06-11 00:58:00 +0000176 end,
177
178 Begin = #protocol_message_begin{name = atom_to_list(Function),
179 type = ?tMessageType_CALL,
180 seqid = SeqId},
181 ok = thrift_protocol:write(Proto, Begin),
182 ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
183 ok = thrift_protocol:write(Proto, message_end),
184 thrift_protocol:flush_transport(Proto),
185 ok.
186
David Reiss2c534032008-06-11 00:58:00 +0000187receive_function_result(State = #state{protocol = Proto,
188 service = Service},
189 Function) ->
190 ResultType = Service:function_info(Function, reply_type),
191 read_result(State, Function, ResultType).
192
193read_result(_State,
194 _Function,
195 async_void) ->
196 {ok, ok};
197
198read_result(State = #state{protocol = Proto,
199 seqid = SeqId},
200 Function,
201 ReplyType) ->
202 case thrift_protocol:read(Proto, message_begin) of
203 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
204 {error, {bad_seq_id, SeqId}};
David Reiss6b3e40f2008-06-11 00:59:03 +0000205
David Reiss2c534032008-06-11 00:58:00 +0000206 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
207 handle_application_exception(State);
David Reiss6b3e40f2008-06-11 00:59:03 +0000208
David Reiss2c534032008-06-11 00:58:00 +0000209 #protocol_message_begin{type = ?tMessageType_REPLY} ->
210 handle_reply(State, Function, ReplyType)
211 end.
212
David Reiss2c534032008-06-11 00:58:00 +0000213handle_reply(State = #state{protocol = Proto,
214 service = Service},
215 Function,
216 ReplyType) ->
217 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
218 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
David Reiss2c534032008-06-11 00:58:00 +0000219 {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
220 ReplyList = tuple_to_list(Reply),
221 true = length(ReplyList) == length(ExceptionFields) + 1,
222 ExceptionVals = tl(ReplyList),
223 Thrown = [X || X <- ExceptionVals,
224 X =/= undefined],
David Reiss2c534032008-06-11 00:58:00 +0000225 Result =
226 case Thrown of
227 [] when ReplyType == {struct, []} ->
228 {ok, ok};
229 [] ->
230 {ok, hd(ReplyList)};
231 [Exception] ->
232 {exception, Exception}
233 end,
234 ok = thrift_protocol:read(Proto, message_end),
235 Result.
David Reiss6b3e40f2008-06-11 00:59:03 +0000236
David Reiss55ff70f2008-06-11 00:58:25 +0000237handle_application_exception(State = #state{protocol = Proto}) ->
238 {ok, Exception} = thrift_protocol:read(Proto,
239 ?TApplicationException_Structure),
240 ok = thrift_protocol:read(Proto, message_end),
241 XRecord = list_to_tuple(
242 ['TApplicationException' | tuple_to_list(Exception)]),
David Reiss1af18682008-06-11 01:01:36 +0000243 error_logger:error_msg("X: ~p~n", [XRecord]),
David Reiss55ff70f2008-06-11 00:58:25 +0000244 true = is_record(XRecord, 'TApplicationException'),
245 {exception, XRecord}.