blob: 92c531ab03fd77f6900293230aed7a6afe6a8777 [file] [log] [blame]
David Reissea2cba82009-03-30 21:35:00 +00001%%
2%% Licensed to the Apache Software Foundation (ASF) under one
3%% or more contributor license agreements. See the NOTICE file
4%% distributed with this work for additional information
5%% regarding copyright ownership. The ASF licenses this file
6%% to you under the Apache License, Version 2.0 (the
7%% "License"); you may not use this file except in compliance
8%% with the License. You may obtain a copy of the License at
9%%
10%% http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing,
13%% software distributed under the License is distributed on an
14%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15%% KIND, either express or implied. See the License for the
16%% specific language governing permissions and limitations
17%% under the License.
18%%
19
David Reiss2c534032008-06-11 00:58:00 +000020-module(thrift_client).
21
22-behaviour(gen_server).
23
24%% API
David Reiss5e530af2009-06-04 02:01:24 +000025-export([start_link/2, start_link/3, start_link/4,
26 start/3, start/4,
27 call/3, send_call/3, close/1]).
David Reiss2c534032008-06-11 00:58:00 +000028
29%% gen_server callbacks
30-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
31 terminate/2, code_change/3]).
32
33
34-include("thrift_constants.hrl").
35-include("thrift_protocol.hrl").
36
David Reissad74b322008-06-11 01:03:29 +000037-record(state, {service, protocol, seqid}).
David Reiss2c534032008-06-11 00:58:00 +000038
39%%====================================================================
40%% API
41%%====================================================================
42%%--------------------------------------------------------------------
43%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
David Reiss5e530af2009-06-04 02:01:24 +000044%% Description: Starts the server as a linked process.
David Reiss2c534032008-06-11 00:58:00 +000045%%--------------------------------------------------------------------
David Reissad74b322008-06-11 01:03:29 +000046start_link(Host, Port, Service) when is_integer(Port), is_atom(Service) ->
David Reiss914ebb42008-06-11 01:01:48 +000047 start_link(Host, Port, Service, []).
David Reiss4fd78182008-06-11 01:01:13 +000048
David Reiss5e530af2009-06-04 02:01:24 +000049start_link(Host, Port, Service, Options) ->
50 start(Host, Port, Service, [{monitor, link} | Options]).
51
52start_link(ProtocolFactory, Service) ->
53 start(ProtocolFactory, Service, [{monitor, link}]).
David Reissfc427af2008-06-11 01:11:57 +000054
55%%
56%% Splits client options into protocol options and transport options
57%%
58%% split_options([Options...]) -> {ProtocolOptions, TransportOptions}
59%%
60split_options(Options) ->
David Reiss5e530af2009-06-04 02:01:24 +000061 split_options(Options, [], [], []).
David Reissfc427af2008-06-11 01:11:57 +000062
David Reiss5e530af2009-06-04 02:01:24 +000063split_options([], ClientIn, ProtoIn, TransIn) ->
64 {ClientIn, ProtoIn, TransIn};
David Reissfc427af2008-06-11 01:11:57 +000065
David Reiss5e530af2009-06-04 02:01:24 +000066split_options([Opt = {OptKey, _} | Rest], ClientIn, ProtoIn, TransIn)
67 when OptKey =:= monitor ->
68 split_options(Rest, [Opt | ClientIn], ProtoIn, TransIn);
69
70split_options([Opt = {OptKey, _} | Rest], ClientIn, ProtoIn, TransIn)
David Reissfc427af2008-06-11 01:11:57 +000071 when OptKey =:= strict_read;
72 OptKey =:= strict_write ->
David Reiss5e530af2009-06-04 02:01:24 +000073 split_options(Rest, ClientIn, [Opt | ProtoIn], TransIn);
David Reissfc427af2008-06-11 01:11:57 +000074
David Reiss5e530af2009-06-04 02:01:24 +000075split_options([Opt = {OptKey, _} | Rest], ClientIn, ProtoIn, TransIn)
David Reissfc427af2008-06-11 01:11:57 +000076 when OptKey =:= framed;
77 OptKey =:= connect_timeout;
78 OptKey =:= sockopts ->
David Reiss5e530af2009-06-04 02:01:24 +000079 split_options(Rest, ClientIn, ProtoIn, [Opt | TransIn]).
David Reissfc427af2008-06-11 01:11:57 +000080
81
David Reiss5e530af2009-06-04 02:01:24 +000082%%--------------------------------------------------------------------
83%% Function: start() -> {ok,Pid} | ignore | {error,Error}
84%% Description: Starts the server as an unlinked process.
85%%--------------------------------------------------------------------
86
David Reissfc427af2008-06-11 01:11:57 +000087%% Backwards-compatible starter for the common-case of socket transports
David Reiss5e530af2009-06-04 02:01:24 +000088start(Host, Port, Service, Options)
David Reiss2fe905e2008-06-11 01:02:23 +000089 when is_integer(Port), is_atom(Service), is_list(Options) ->
David Reiss5e530af2009-06-04 02:01:24 +000090 {ClientOpts, ProtoOpts, TransOpts} = split_options(Options),
David Reissfc427af2008-06-11 01:11:57 +000091
92 {ok, TransportFactory} =
93 thrift_socket_transport:new_transport_factory(Host, Port, TransOpts),
94
95 {ok, ProtocolFactory} = thrift_binary_protocol:new_protocol_factory(
96 TransportFactory, ProtoOpts),
97
David Reiss5e530af2009-06-04 02:01:24 +000098 start(ProtocolFactory, Service, ClientOpts).
David Reissad74b322008-06-11 01:03:29 +000099
David Reissfc427af2008-06-11 01:11:57 +0000100
David Reiss44f785e2008-06-11 01:03:37 +0000101%% ProtocolFactory :: fun() -> thrift_protocol()
David Reiss5e530af2009-06-04 02:01:24 +0000102start(ProtocolFactory, Service, ClientOpts)
David Reiss44f785e2008-06-11 01:03:37 +0000103 when is_function(ProtocolFactory), is_atom(Service) ->
David Reiss5e530af2009-06-04 02:01:24 +0000104 Starter =
105 case lists:keysearch(monitor, 1, ClientOpts) of
106 {value, {monitor, link}} ->
107 start_link;
108 _ ->
109 start
110 end,
111
David Reissbb97fd92009-06-04 02:01:28 +0000112 Connect =
113 case lists:keysearch(connect, 1, ClientOpts) of
114 {value, {connect, Choice}} ->
115 Choice;
116 _ ->
117 %% By default, connect at creation-time.
118 true
119 end,
120
121
122 Started = gen_server:Starter(?MODULE, [Service], []),
123
124 if
125 Connect ->
126 case Started of
127 {ok, Pid} ->
128 case gen_server:call(Pid, {connect, ProtocolFactory}) of
129 ok ->
130 {ok, Pid};
131 Error ->
132 Error
133 end;
134 Else ->
135 Else
David Reisse5a4d0c2008-06-11 01:02:10 +0000136 end;
David Reissbb97fd92009-06-04 02:01:28 +0000137 true ->
138 Started
David Reisse5a4d0c2008-06-11 01:02:10 +0000139 end.
David Reiss2c534032008-06-11 00:58:00 +0000140
David Reiss2c534032008-06-11 00:58:00 +0000141call(Client, Function, Args)
142 when is_pid(Client), is_atom(Function), is_list(Args) ->
143 case gen_server:call(Client, {call, Function, Args}) of
144 R = {ok, _} -> R;
145 R = {error, _} -> R;
146 {exception, Exception} -> throw(Exception)
147 end.
David Reiss2c534032008-06-11 00:58:00 +0000148
David Reissa2f45972008-06-11 01:13:33 +0000149cast(Client, Function, Args)
150 when is_pid(Client), is_atom(Function), is_list(Args) ->
151 gen_server:cast(Client, {call, Function, Args}).
152
David Reiss65cf7202008-06-11 01:12:20 +0000153%% Sends a function call but does not read the result. This is useful
David Reissc51986f2009-03-24 20:01:25 +0000154%% if you're trying to log non-oneway function calls to write-only
David Reiss65cf7202008-06-11 01:12:20 +0000155%% transports like thrift_disk_log_transport.
156send_call(Client, Function, Args)
157 when is_pid(Client), is_atom(Function), is_list(Args) ->
158 gen_server:call(Client, {send_call, Function, Args}).
159
David Reiss464e3002008-06-11 01:00:45 +0000160close(Client) when is_pid(Client) ->
David Reiss6f1cd532008-06-11 01:01:21 +0000161 gen_server:cast(Client, close).
David Reiss464e3002008-06-11 01:00:45 +0000162
David Reiss2c534032008-06-11 00:58:00 +0000163%%====================================================================
164%% gen_server callbacks
165%%====================================================================
166
167%%--------------------------------------------------------------------
168%% Function: init(Args) -> {ok, State} |
169%% {ok, State, Timeout} |
170%% ignore |
171%% {stop, Reason}
172%% Description: Initiates the server
173%%--------------------------------------------------------------------
David Reissad74b322008-06-11 01:03:29 +0000174init([Service]) ->
175 {ok, #state{service = Service}}.
David Reiss2c534032008-06-11 00:58:00 +0000176
177%%--------------------------------------------------------------------
178%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
179%% {reply, Reply, State, Timeout} |
180%% {noreply, State} |
181%% {noreply, State, Timeout} |
182%% {stop, Reason, Reply, State} |
183%% {stop, Reason, State}
184%% Description: Handling call messages
185%%--------------------------------------------------------------------
David Reiss44f785e2008-06-11 01:03:37 +0000186handle_call({connect, ProtocolFactory}, _From,
David Reissad74b322008-06-11 01:03:29 +0000187 State = #state{service = Service}) ->
David Reiss44f785e2008-06-11 01:03:37 +0000188 case ProtocolFactory() of
David Reissad74b322008-06-11 01:03:29 +0000189 {ok, Protocol} ->
190 {reply, ok, State#state{protocol = Protocol,
191 seqid = 0}};
David Reisse5a4d0c2008-06-11 01:02:10 +0000192 Error ->
193 {stop, normal, Error, State}
194 end;
195
David Reiss65cf7202008-06-11 01:12:20 +0000196handle_call({call, Function, Args}, _From, State = #state{service = Service}) ->
197 Result = catch_function_exceptions(
198 fun() ->
199 ok = send_function_call(State, Function, Args),
200 receive_function_result(State, Function)
201 end,
202 Service),
203 {reply, Result, State};
David Reiss6b3e40f2008-06-11 00:59:03 +0000204
David Reiss65cf7202008-06-11 01:12:20 +0000205
206handle_call({send_call, Function, Args}, _From, State = #state{service = Service}) ->
207 Result = catch_function_exceptions(
208 fun() ->
209 send_function_call(State, Function, Args)
210 end,
211 Service),
David Reiss6f1cd532008-06-11 01:01:21 +0000212 {reply, Result, State}.
David Reiss2c534032008-06-11 00:58:00 +0000213
David Reiss65cf7202008-06-11 01:12:20 +0000214
215%% Helper function that catches exceptions thrown by sending or receiving
216%% a function and returns the correct response for call or send_only above.
217catch_function_exceptions(Fun, Service) ->
218 try
219 Fun()
220 catch
221 throw:{return, Return} ->
222 Return;
223 error:function_clause ->
224 ST = erlang:get_stacktrace(),
225 case hd(ST) of
226 {Service, function_info, [Function, _]} ->
227 {error, {no_function, Function}};
228 _ -> throw({error, {function_clause, ST}})
229 end
230 end.
231
232
David Reiss2c534032008-06-11 00:58:00 +0000233%%--------------------------------------------------------------------
234%% Function: handle_cast(Msg, State) -> {noreply, State} |
235%% {noreply, State, Timeout} |
236%% {stop, Reason, State}
237%% Description: Handling cast messages
238%%--------------------------------------------------------------------
David Reissa2f45972008-06-11 01:13:33 +0000239handle_cast({call, Function, Args}, State = #state{service = Service,
240 protocol = Protocol,
241 seqid = SeqId}) ->
242 _Result =
243 try
244 ok = send_function_call(State, Function, Args),
245 receive_function_result(State, Function)
246 catch
247 Class:Reason ->
248 error_logger:error_msg("error ignored in handle_cast({cast,...},...): ~p:~p~n", [Class, Reason])
249 end,
250
251 {noreply, State};
252
David Reiss6f1cd532008-06-11 01:01:21 +0000253handle_cast(close, State=#state{protocol = Protocol}) ->
254%% error_logger:info_msg("thrift_client ~p received close", [self()]),
255 {stop,normal,State};
David Reiss2c534032008-06-11 00:58:00 +0000256handle_cast(_Msg, State) ->
257 {noreply, State}.
258
259%%--------------------------------------------------------------------
260%% Function: handle_info(Info, State) -> {noreply, State} |
261%% {noreply, State, Timeout} |
262%% {stop, Reason, State}
263%% Description: Handling all non call/cast messages
264%%--------------------------------------------------------------------
265handle_info(_Info, State) ->
266 {noreply, State}.
267
268%%--------------------------------------------------------------------
269%% Function: terminate(Reason, State) -> void()
270%% Description: This function is called by a gen_server when it is about to
271%% terminate. It should be the opposite of Module:init/1 and do any necessary
272%% cleaning up. When it returns, the gen_server terminates with Reason.
273%% The return value is ignored.
274%%--------------------------------------------------------------------
David Reisse5a4d0c2008-06-11 01:02:10 +0000275terminate(Reason, State = #state{protocol=undefined}) ->
276 ok;
277terminate(Reason, State = #state{protocol=Protocol}) ->
David Reiss464e3002008-06-11 01:00:45 +0000278 thrift_protocol:close_transport(Protocol),
David Reiss2c534032008-06-11 00:58:00 +0000279 ok.
280
281%%--------------------------------------------------------------------
282%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
283%% Description: Convert process state when code is changed
284%%--------------------------------------------------------------------
285code_change(_OldVsn, State, _Extra) ->
286 {ok, State}.
287
288%%--------------------------------------------------------------------
289%%% Internal functions
290%%--------------------------------------------------------------------
291send_function_call(#state{protocol = Proto,
292 service = Service,
293 seqid = SeqId},
294 Function,
295 Args) ->
296 Params = Service:function_info(Function, params_type),
297 {struct, PList} = Params,
David Reissc5257452008-06-11 00:59:27 +0000298 if
299 length(PList) =/= length(Args) ->
David Reiss2c534032008-06-11 00:58:00 +0000300 throw({return, {error, {bad_args, Function, Args}}});
David Reissc5257452008-06-11 00:59:27 +0000301 true -> ok
David Reiss2c534032008-06-11 00:58:00 +0000302 end,
303
304 Begin = #protocol_message_begin{name = atom_to_list(Function),
305 type = ?tMessageType_CALL,
306 seqid = SeqId},
307 ok = thrift_protocol:write(Proto, Begin),
308 ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
309 ok = thrift_protocol:write(Proto, message_end),
310 thrift_protocol:flush_transport(Proto),
311 ok.
312
David Reiss2c534032008-06-11 00:58:00 +0000313receive_function_result(State = #state{protocol = Proto,
314 service = Service},
315 Function) ->
316 ResultType = Service:function_info(Function, reply_type),
317 read_result(State, Function, ResultType).
318
319read_result(_State,
320 _Function,
David Reissfe931d12009-03-24 20:02:08 +0000321 oneway_void) ->
David Reiss2c534032008-06-11 00:58:00 +0000322 {ok, ok};
323
324read_result(State = #state{protocol = Proto,
325 seqid = SeqId},
326 Function,
327 ReplyType) ->
328 case thrift_protocol:read(Proto, message_begin) of
329 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
330 {error, {bad_seq_id, SeqId}};
David Reiss6b3e40f2008-06-11 00:59:03 +0000331
David Reiss2c534032008-06-11 00:58:00 +0000332 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
333 handle_application_exception(State);
David Reiss6b3e40f2008-06-11 00:59:03 +0000334
David Reiss2c534032008-06-11 00:58:00 +0000335 #protocol_message_begin{type = ?tMessageType_REPLY} ->
336 handle_reply(State, Function, ReplyType)
337 end.
338
David Reiss2c534032008-06-11 00:58:00 +0000339handle_reply(State = #state{protocol = Proto,
340 service = Service},
341 Function,
342 ReplyType) ->
343 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
344 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
David Reiss2c534032008-06-11 00:58:00 +0000345 {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
346 ReplyList = tuple_to_list(Reply),
347 true = length(ReplyList) == length(ExceptionFields) + 1,
348 ExceptionVals = tl(ReplyList),
349 Thrown = [X || X <- ExceptionVals,
350 X =/= undefined],
David Reiss2c534032008-06-11 00:58:00 +0000351 Result =
352 case Thrown of
353 [] when ReplyType == {struct, []} ->
354 {ok, ok};
355 [] ->
356 {ok, hd(ReplyList)};
357 [Exception] ->
358 {exception, Exception}
359 end,
360 ok = thrift_protocol:read(Proto, message_end),
361 Result.
David Reiss6b3e40f2008-06-11 00:59:03 +0000362
David Reiss55ff70f2008-06-11 00:58:25 +0000363handle_application_exception(State = #state{protocol = Proto}) ->
364 {ok, Exception} = thrift_protocol:read(Proto,
365 ?TApplicationException_Structure),
366 ok = thrift_protocol:read(Proto, message_end),
367 XRecord = list_to_tuple(
368 ['TApplicationException' | tuple_to_list(Exception)]),
David Reiss1af18682008-06-11 01:01:36 +0000369 error_logger:error_msg("X: ~p~n", [XRecord]),
David Reiss55ff70f2008-06-11 00:58:25 +0000370 true = is_record(XRecord, 'TApplicationException'),
371 {exception, XRecord}.