blob: d70df3443bfca2eb7e7472e1ecf5eeb1eae42903 [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
112 case gen_server:Starter(?MODULE, [Service], []) of
David Reisse5a4d0c2008-06-11 01:02:10 +0000113 {ok, Pid} ->
David Reiss44f785e2008-06-11 01:03:37 +0000114 case gen_server:call(Pid, {connect, ProtocolFactory}) of
David Reisse5a4d0c2008-06-11 01:02:10 +0000115 ok ->
116 {ok, Pid};
117 Error ->
118 Error
119 end;
120 Else ->
121 Else
122 end.
David Reiss2c534032008-06-11 00:58:00 +0000123
David Reiss2c534032008-06-11 00:58:00 +0000124call(Client, Function, Args)
125 when is_pid(Client), is_atom(Function), is_list(Args) ->
126 case gen_server:call(Client, {call, Function, Args}) of
127 R = {ok, _} -> R;
128 R = {error, _} -> R;
129 {exception, Exception} -> throw(Exception)
130 end.
David Reiss2c534032008-06-11 00:58:00 +0000131
David Reissa2f45972008-06-11 01:13:33 +0000132cast(Client, Function, Args)
133 when is_pid(Client), is_atom(Function), is_list(Args) ->
134 gen_server:cast(Client, {call, Function, Args}).
135
David Reiss65cf7202008-06-11 01:12:20 +0000136%% Sends a function call but does not read the result. This is useful
David Reissc51986f2009-03-24 20:01:25 +0000137%% if you're trying to log non-oneway function calls to write-only
David Reiss65cf7202008-06-11 01:12:20 +0000138%% transports like thrift_disk_log_transport.
139send_call(Client, Function, Args)
140 when is_pid(Client), is_atom(Function), is_list(Args) ->
141 gen_server:call(Client, {send_call, Function, Args}).
142
David Reiss464e3002008-06-11 01:00:45 +0000143close(Client) when is_pid(Client) ->
David Reiss6f1cd532008-06-11 01:01:21 +0000144 gen_server:cast(Client, close).
David Reiss464e3002008-06-11 01:00:45 +0000145
David Reiss2c534032008-06-11 00:58:00 +0000146%%====================================================================
147%% gen_server callbacks
148%%====================================================================
149
150%%--------------------------------------------------------------------
151%% Function: init(Args) -> {ok, State} |
152%% {ok, State, Timeout} |
153%% ignore |
154%% {stop, Reason}
155%% Description: Initiates the server
156%%--------------------------------------------------------------------
David Reissad74b322008-06-11 01:03:29 +0000157init([Service]) ->
158 {ok, #state{service = Service}}.
David Reiss2c534032008-06-11 00:58:00 +0000159
160%%--------------------------------------------------------------------
161%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
162%% {reply, Reply, State, Timeout} |
163%% {noreply, State} |
164%% {noreply, State, Timeout} |
165%% {stop, Reason, Reply, State} |
166%% {stop, Reason, State}
167%% Description: Handling call messages
168%%--------------------------------------------------------------------
David Reiss44f785e2008-06-11 01:03:37 +0000169handle_call({connect, ProtocolFactory}, _From,
David Reissad74b322008-06-11 01:03:29 +0000170 State = #state{service = Service}) ->
David Reiss44f785e2008-06-11 01:03:37 +0000171 case ProtocolFactory() of
David Reissad74b322008-06-11 01:03:29 +0000172 {ok, Protocol} ->
173 {reply, ok, State#state{protocol = Protocol,
174 seqid = 0}};
David Reisse5a4d0c2008-06-11 01:02:10 +0000175 Error ->
176 {stop, normal, Error, State}
177 end;
178
David Reiss65cf7202008-06-11 01:12:20 +0000179handle_call({call, Function, Args}, _From, State = #state{service = Service}) ->
180 Result = catch_function_exceptions(
181 fun() ->
182 ok = send_function_call(State, Function, Args),
183 receive_function_result(State, Function)
184 end,
185 Service),
186 {reply, Result, State};
David Reiss6b3e40f2008-06-11 00:59:03 +0000187
David Reiss65cf7202008-06-11 01:12:20 +0000188
189handle_call({send_call, Function, Args}, _From, State = #state{service = Service}) ->
190 Result = catch_function_exceptions(
191 fun() ->
192 send_function_call(State, Function, Args)
193 end,
194 Service),
David Reiss6f1cd532008-06-11 01:01:21 +0000195 {reply, Result, State}.
David Reiss2c534032008-06-11 00:58:00 +0000196
David Reiss65cf7202008-06-11 01:12:20 +0000197
198%% Helper function that catches exceptions thrown by sending or receiving
199%% a function and returns the correct response for call or send_only above.
200catch_function_exceptions(Fun, Service) ->
201 try
202 Fun()
203 catch
204 throw:{return, Return} ->
205 Return;
206 error:function_clause ->
207 ST = erlang:get_stacktrace(),
208 case hd(ST) of
209 {Service, function_info, [Function, _]} ->
210 {error, {no_function, Function}};
211 _ -> throw({error, {function_clause, ST}})
212 end
213 end.
214
215
David Reiss2c534032008-06-11 00:58:00 +0000216%%--------------------------------------------------------------------
217%% Function: handle_cast(Msg, State) -> {noreply, State} |
218%% {noreply, State, Timeout} |
219%% {stop, Reason, State}
220%% Description: Handling cast messages
221%%--------------------------------------------------------------------
David Reissa2f45972008-06-11 01:13:33 +0000222handle_cast({call, Function, Args}, State = #state{service = Service,
223 protocol = Protocol,
224 seqid = SeqId}) ->
225 _Result =
226 try
227 ok = send_function_call(State, Function, Args),
228 receive_function_result(State, Function)
229 catch
230 Class:Reason ->
231 error_logger:error_msg("error ignored in handle_cast({cast,...},...): ~p:~p~n", [Class, Reason])
232 end,
233
234 {noreply, State};
235
David Reiss6f1cd532008-06-11 01:01:21 +0000236handle_cast(close, State=#state{protocol = Protocol}) ->
237%% error_logger:info_msg("thrift_client ~p received close", [self()]),
238 {stop,normal,State};
David Reiss2c534032008-06-11 00:58:00 +0000239handle_cast(_Msg, State) ->
240 {noreply, State}.
241
242%%--------------------------------------------------------------------
243%% Function: handle_info(Info, State) -> {noreply, State} |
244%% {noreply, State, Timeout} |
245%% {stop, Reason, State}
246%% Description: Handling all non call/cast messages
247%%--------------------------------------------------------------------
248handle_info(_Info, State) ->
249 {noreply, State}.
250
251%%--------------------------------------------------------------------
252%% Function: terminate(Reason, State) -> void()
253%% Description: This function is called by a gen_server when it is about to
254%% terminate. It should be the opposite of Module:init/1 and do any necessary
255%% cleaning up. When it returns, the gen_server terminates with Reason.
256%% The return value is ignored.
257%%--------------------------------------------------------------------
David Reisse5a4d0c2008-06-11 01:02:10 +0000258terminate(Reason, State = #state{protocol=undefined}) ->
259 ok;
260terminate(Reason, State = #state{protocol=Protocol}) ->
David Reiss464e3002008-06-11 01:00:45 +0000261 thrift_protocol:close_transport(Protocol),
David Reiss2c534032008-06-11 00:58:00 +0000262 ok.
263
264%%--------------------------------------------------------------------
265%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
266%% Description: Convert process state when code is changed
267%%--------------------------------------------------------------------
268code_change(_OldVsn, State, _Extra) ->
269 {ok, State}.
270
271%%--------------------------------------------------------------------
272%%% Internal functions
273%%--------------------------------------------------------------------
274send_function_call(#state{protocol = Proto,
275 service = Service,
276 seqid = SeqId},
277 Function,
278 Args) ->
279 Params = Service:function_info(Function, params_type),
280 {struct, PList} = Params,
David Reissc5257452008-06-11 00:59:27 +0000281 if
282 length(PList) =/= length(Args) ->
David Reiss2c534032008-06-11 00:58:00 +0000283 throw({return, {error, {bad_args, Function, Args}}});
David Reissc5257452008-06-11 00:59:27 +0000284 true -> ok
David Reiss2c534032008-06-11 00:58:00 +0000285 end,
286
287 Begin = #protocol_message_begin{name = atom_to_list(Function),
288 type = ?tMessageType_CALL,
289 seqid = SeqId},
290 ok = thrift_protocol:write(Proto, Begin),
291 ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
292 ok = thrift_protocol:write(Proto, message_end),
293 thrift_protocol:flush_transport(Proto),
294 ok.
295
David Reiss2c534032008-06-11 00:58:00 +0000296receive_function_result(State = #state{protocol = Proto,
297 service = Service},
298 Function) ->
299 ResultType = Service:function_info(Function, reply_type),
300 read_result(State, Function, ResultType).
301
302read_result(_State,
303 _Function,
David Reissfe931d12009-03-24 20:02:08 +0000304 oneway_void) ->
David Reiss2c534032008-06-11 00:58:00 +0000305 {ok, ok};
306
307read_result(State = #state{protocol = Proto,
308 seqid = SeqId},
309 Function,
310 ReplyType) ->
311 case thrift_protocol:read(Proto, message_begin) of
312 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
313 {error, {bad_seq_id, SeqId}};
David Reiss6b3e40f2008-06-11 00:59:03 +0000314
David Reiss2c534032008-06-11 00:58:00 +0000315 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
316 handle_application_exception(State);
David Reiss6b3e40f2008-06-11 00:59:03 +0000317
David Reiss2c534032008-06-11 00:58:00 +0000318 #protocol_message_begin{type = ?tMessageType_REPLY} ->
319 handle_reply(State, Function, ReplyType)
320 end.
321
David Reiss2c534032008-06-11 00:58:00 +0000322handle_reply(State = #state{protocol = Proto,
323 service = Service},
324 Function,
325 ReplyType) ->
326 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
327 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
David Reiss2c534032008-06-11 00:58:00 +0000328 {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
329 ReplyList = tuple_to_list(Reply),
330 true = length(ReplyList) == length(ExceptionFields) + 1,
331 ExceptionVals = tl(ReplyList),
332 Thrown = [X || X <- ExceptionVals,
333 X =/= undefined],
David Reiss2c534032008-06-11 00:58:00 +0000334 Result =
335 case Thrown of
336 [] when ReplyType == {struct, []} ->
337 {ok, ok};
338 [] ->
339 {ok, hd(ReplyList)};
340 [Exception] ->
341 {exception, Exception}
342 end,
343 ok = thrift_protocol:read(Proto, message_end),
344 Result.
David Reiss6b3e40f2008-06-11 00:59:03 +0000345
David Reiss55ff70f2008-06-11 00:58:25 +0000346handle_application_exception(State = #state{protocol = Proto}) ->
347 {ok, Exception} = thrift_protocol:read(Proto,
348 ?TApplicationException_Structure),
349 ok = thrift_protocol:read(Proto, message_end),
350 XRecord = list_to_tuple(
351 ['TApplicationException' | tuple_to_list(Exception)]),
David Reiss1af18682008-06-11 01:01:36 +0000352 error_logger:error_msg("X: ~p~n", [XRecord]),
David Reiss55ff70f2008-06-11 00:58:25 +0000353 true = is_record(XRecord, 'TApplicationException'),
354 {exception, XRecord}.