blob: ec1f87f56c3275a551fc595b0335aa6d2310193b [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 Reiss4fd78182008-06-11 01:01:13 +000013-export([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 Reiss914ebb42008-06-11 01:01:48 +000023-record(state, {service, protocol, seqid,
24 strict_read = true,
25 strict_write = true,
26 framed = false,
27 connect_timeout = infinity
28 }).
David Reiss2c534032008-06-11 00:58:00 +000029
30%%====================================================================
31%% API
32%%====================================================================
33%%--------------------------------------------------------------------
34%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
35%% Description: Starts the server
36%%--------------------------------------------------------------------
David Reiss4fd78182008-06-11 01:01:13 +000037start_link(Host, Port, Service) ->
David Reiss914ebb42008-06-11 01:01:48 +000038 start_link(Host, Port, Service, []).
David Reiss4fd78182008-06-11 01:01:13 +000039
David Reiss914ebb42008-06-11 01:01:48 +000040start_link(Host, Port, Service, Options) when is_integer(Port), is_atom(Service), is_list(Options) ->
David Reisse5a4d0c2008-06-11 01:02:10 +000041 case gen_server:start_link(?MODULE, [Host, Port, Service, Options], []) of
42 {ok, Pid} ->
43 case gen_server:call(Pid, {connect, Host, Port, Service}) of
44 ok ->
45 {ok, Pid};
46 Error ->
47 Error
48 end;
49 Else ->
50 Else
51 end.
David Reiss2c534032008-06-11 00:58:00 +000052
David Reiss2c534032008-06-11 00:58:00 +000053call(Client, Function, Args)
54 when is_pid(Client), is_atom(Function), is_list(Args) ->
55 case gen_server:call(Client, {call, Function, Args}) of
56 R = {ok, _} -> R;
57 R = {error, _} -> R;
58 {exception, Exception} -> throw(Exception)
59 end.
David Reiss2c534032008-06-11 00:58:00 +000060
David Reiss464e3002008-06-11 01:00:45 +000061close(Client) when is_pid(Client) ->
David Reiss6f1cd532008-06-11 01:01:21 +000062 gen_server:cast(Client, close).
David Reiss464e3002008-06-11 01:00:45 +000063
David Reiss2c534032008-06-11 00:58:00 +000064%%====================================================================
65%% gen_server callbacks
66%%====================================================================
67
68%%--------------------------------------------------------------------
69%% Function: init(Args) -> {ok, State} |
70%% {ok, State, Timeout} |
71%% ignore |
72%% {stop, Reason}
73%% Description: Initiates the server
74%%--------------------------------------------------------------------
David Reisse5a4d0c2008-06-11 01:02:10 +000075init([_Host, _Port, _Service, Options]) ->
David Reiss914ebb42008-06-11 01:01:48 +000076 State = parse_options(Options, #state{}),
David Reisse5a4d0c2008-06-11 01:02:10 +000077 {ok, State}.
David Reiss914ebb42008-06-11 01:01:48 +000078
79parse_options([], State) ->
80 State;
81parse_options([{strict_read, Bool} | Rest], State) when is_boolean(Bool) ->
82 parse_options(Rest, State#state{strict_read=Bool});
83parse_options([{strict_write, Bool} | Rest], State) when is_boolean(Bool) ->
84 parse_options(Rest, State#state{strict_write=Bool});
85parse_options([{framed, Bool} | Rest], State) when is_boolean(Bool) ->
86 parse_options(Rest, State#state{framed=Bool});
87parse_options([{connect_timeout, TO} | Rest], State) when TO =:= infinity; is_integer(TO) ->
88 parse_options(Rest, State#state{connect_timeout=TO}).
David Reiss2c534032008-06-11 00:58:00 +000089
90%%--------------------------------------------------------------------
91%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
92%% {reply, Reply, State, Timeout} |
93%% {noreply, State} |
94%% {noreply, State, Timeout} |
95%% {stop, Reason, Reply, State} |
96%% {stop, Reason, State}
97%% Description: Handling call messages
98%%--------------------------------------------------------------------
David Reisse5a4d0c2008-06-11 01:02:10 +000099handle_call({connect, Host, Port, Service}, _From, State = #state{connect_timeout=Timeout}) ->
100 Options = [binary,
101 {packet, 0},
102 {active, false},
103 {nodelay, true}],
104
105 case catch gen_tcp:connect(Host, Port, Options, Timeout) of
106 {ok, Sock} ->
107 {ok, Transport} = thrift_socket_transport:new(Sock),
108 {ok, BufTransport} =
109 case State#state.framed of
110 true -> thrift_framed_transport:new(Transport);
111 false -> thrift_buffered_transport:new(Transport)
112 end,
113 {ok, Protocol} = thrift_binary_protocol:new(BufTransport,
114 [{strict_read, State#state.strict_read},
115 {strict_write, State#state.strict_write}]),
116
117 {reply, ok, State#state{service = Service,
118 protocol = Protocol,
119 seqid = 0}};
120 Error ->
121 {stop, normal, Error, State}
122 end;
123
David Reiss2c534032008-06-11 00:58:00 +0000124handle_call({call, Function, Args}, _From, State = #state{service = Service,
125 protocol = Protocol,
126 seqid = SeqId}) ->
127 Result =
128 try
129 ok = send_function_call(State, Function, Args),
130 receive_function_result(State, Function)
131 catch
132 throw:{return, Return} ->
133 Return;
134 error:function_clause ->
135 ST = erlang:get_stacktrace(),
136 case hd(ST) of
137 {Service, function_info, [Function, _]} ->
138 {error, {no_function, Function}};
139 _ -> throw({error, {function_clause, ST}})
140 end
141 end,
David Reiss6b3e40f2008-06-11 00:59:03 +0000142
David Reiss6f1cd532008-06-11 01:01:21 +0000143 {reply, Result, State}.
David Reiss2c534032008-06-11 00:58:00 +0000144
145%%--------------------------------------------------------------------
146%% Function: handle_cast(Msg, State) -> {noreply, State} |
147%% {noreply, State, Timeout} |
148%% {stop, Reason, State}
149%% Description: Handling cast messages
150%%--------------------------------------------------------------------
David Reiss6f1cd532008-06-11 01:01:21 +0000151handle_cast(close, State=#state{protocol = Protocol}) ->
152%% error_logger:info_msg("thrift_client ~p received close", [self()]),
153 {stop,normal,State};
David Reiss2c534032008-06-11 00:58:00 +0000154handle_cast(_Msg, State) ->
155 {noreply, State}.
156
157%%--------------------------------------------------------------------
158%% Function: handle_info(Info, State) -> {noreply, State} |
159%% {noreply, State, Timeout} |
160%% {stop, Reason, State}
161%% Description: Handling all non call/cast messages
162%%--------------------------------------------------------------------
163handle_info(_Info, State) ->
164 {noreply, State}.
165
166%%--------------------------------------------------------------------
167%% Function: terminate(Reason, State) -> void()
168%% Description: This function is called by a gen_server when it is about to
169%% terminate. It should be the opposite of Module:init/1 and do any necessary
170%% cleaning up. When it returns, the gen_server terminates with Reason.
171%% The return value is ignored.
172%%--------------------------------------------------------------------
David Reisse5a4d0c2008-06-11 01:02:10 +0000173terminate(Reason, State = #state{protocol=undefined}) ->
174 ok;
175terminate(Reason, State = #state{protocol=Protocol}) ->
David Reiss464e3002008-06-11 01:00:45 +0000176 thrift_protocol:close_transport(Protocol),
David Reiss2c534032008-06-11 00:58:00 +0000177 ok.
178
179%%--------------------------------------------------------------------
180%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
181%% Description: Convert process state when code is changed
182%%--------------------------------------------------------------------
183code_change(_OldVsn, State, _Extra) ->
184 {ok, State}.
185
186%%--------------------------------------------------------------------
187%%% Internal functions
188%%--------------------------------------------------------------------
189send_function_call(#state{protocol = Proto,
190 service = Service,
191 seqid = SeqId},
192 Function,
193 Args) ->
194 Params = Service:function_info(Function, params_type),
195 {struct, PList} = Params,
David Reissc5257452008-06-11 00:59:27 +0000196 if
197 length(PList) =/= length(Args) ->
David Reiss2c534032008-06-11 00:58:00 +0000198 throw({return, {error, {bad_args, Function, Args}}});
David Reissc5257452008-06-11 00:59:27 +0000199 true -> ok
David Reiss2c534032008-06-11 00:58:00 +0000200 end,
201
202 Begin = #protocol_message_begin{name = atom_to_list(Function),
203 type = ?tMessageType_CALL,
204 seqid = SeqId},
205 ok = thrift_protocol:write(Proto, Begin),
206 ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
207 ok = thrift_protocol:write(Proto, message_end),
208 thrift_protocol:flush_transport(Proto),
209 ok.
210
David Reiss2c534032008-06-11 00:58:00 +0000211receive_function_result(State = #state{protocol = Proto,
212 service = Service},
213 Function) ->
214 ResultType = Service:function_info(Function, reply_type),
215 read_result(State, Function, ResultType).
216
217read_result(_State,
218 _Function,
219 async_void) ->
220 {ok, ok};
221
222read_result(State = #state{protocol = Proto,
223 seqid = SeqId},
224 Function,
225 ReplyType) ->
226 case thrift_protocol:read(Proto, message_begin) of
227 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
228 {error, {bad_seq_id, SeqId}};
David Reiss6b3e40f2008-06-11 00:59:03 +0000229
David Reiss2c534032008-06-11 00:58:00 +0000230 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
231 handle_application_exception(State);
David Reiss6b3e40f2008-06-11 00:59:03 +0000232
David Reiss2c534032008-06-11 00:58:00 +0000233 #protocol_message_begin{type = ?tMessageType_REPLY} ->
234 handle_reply(State, Function, ReplyType)
235 end.
236
David Reiss2c534032008-06-11 00:58:00 +0000237handle_reply(State = #state{protocol = Proto,
238 service = Service},
239 Function,
240 ReplyType) ->
241 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
242 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
David Reiss2c534032008-06-11 00:58:00 +0000243 {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
244 ReplyList = tuple_to_list(Reply),
245 true = length(ReplyList) == length(ExceptionFields) + 1,
246 ExceptionVals = tl(ReplyList),
247 Thrown = [X || X <- ExceptionVals,
248 X =/= undefined],
David Reiss2c534032008-06-11 00:58:00 +0000249 Result =
250 case Thrown of
251 [] when ReplyType == {struct, []} ->
252 {ok, ok};
253 [] ->
254 {ok, hd(ReplyList)};
255 [Exception] ->
256 {exception, Exception}
257 end,
258 ok = thrift_protocol:read(Proto, message_end),
259 Result.
David Reiss6b3e40f2008-06-11 00:59:03 +0000260
David Reiss55ff70f2008-06-11 00:58:25 +0000261handle_application_exception(State = #state{protocol = Proto}) ->
262 {ok, Exception} = thrift_protocol:read(Proto,
263 ?TApplicationException_Structure),
264 ok = thrift_protocol:read(Proto, message_end),
265 XRecord = list_to_tuple(
266 ['TApplicationException' | tuple_to_list(Exception)]),
David Reiss1af18682008-06-11 01:01:36 +0000267 error_logger:error_msg("X: ~p~n", [XRecord]),
David Reiss55ff70f2008-06-11 00:58:25 +0000268 true = is_record(XRecord, 'TApplicationException'),
269 {exception, XRecord}.