blob: 5ba8aee6d5d405e4b03e4473abdcf05b490ac5fa [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 Reiss65cf7202008-06-11 01:12:20 +000025-export([start_link/2, start_link/3, start_link/4, call/3, send_call/3, close/1]).
David Reiss2c534032008-06-11 00:58:00 +000026
27%% gen_server callbacks
28-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
29 terminate/2, code_change/3]).
30
31
32-include("thrift_constants.hrl").
33-include("thrift_protocol.hrl").
34
David Reissad74b322008-06-11 01:03:29 +000035-record(state, {service, protocol, seqid}).
David Reiss2c534032008-06-11 00:58:00 +000036
37%%====================================================================
38%% API
39%%====================================================================
40%%--------------------------------------------------------------------
41%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
42%% Description: Starts the server
43%%--------------------------------------------------------------------
David Reissad74b322008-06-11 01:03:29 +000044start_link(Host, Port, Service) when is_integer(Port), is_atom(Service) ->
David Reiss914ebb42008-06-11 01:01:48 +000045 start_link(Host, Port, Service, []).
David Reiss4fd78182008-06-11 01:01:13 +000046
David Reissfc427af2008-06-11 01:11:57 +000047
48%%
49%% Splits client options into protocol options and transport options
50%%
51%% split_options([Options...]) -> {ProtocolOptions, TransportOptions}
52%%
53split_options(Options) ->
54 split_options(Options, [], []).
55
56split_options([], ProtoIn, TransIn) ->
57 {ProtoIn, TransIn};
58
59split_options([Opt = {OptKey, _} | Rest], ProtoIn, TransIn)
60 when OptKey =:= strict_read;
61 OptKey =:= strict_write ->
62 split_options(Rest, [Opt | ProtoIn], TransIn);
63
64split_options([Opt = {OptKey, _} | Rest], ProtoIn, TransIn)
65 when OptKey =:= framed;
66 OptKey =:= connect_timeout;
67 OptKey =:= sockopts ->
68 split_options(Rest, ProtoIn, [Opt | TransIn]).
69
70
71%% Backwards-compatible starter for the common-case of socket transports
David Reiss2fe905e2008-06-11 01:02:23 +000072start_link(Host, Port, Service, Options)
73 when is_integer(Port), is_atom(Service), is_list(Options) ->
David Reissfc427af2008-06-11 01:11:57 +000074 {ProtoOpts, TransOpts} = split_options(Options),
75
76 {ok, TransportFactory} =
77 thrift_socket_transport:new_transport_factory(Host, Port, TransOpts),
78
79 {ok, ProtocolFactory} = thrift_binary_protocol:new_protocol_factory(
80 TransportFactory, ProtoOpts),
81
David Reiss44f785e2008-06-11 01:03:37 +000082 start_link(ProtocolFactory, Service).
David Reissad74b322008-06-11 01:03:29 +000083
David Reissfc427af2008-06-11 01:11:57 +000084
David Reiss44f785e2008-06-11 01:03:37 +000085%% ProtocolFactory :: fun() -> thrift_protocol()
86start_link(ProtocolFactory, Service)
87 when is_function(ProtocolFactory), is_atom(Service) ->
David Reissad74b322008-06-11 01:03:29 +000088 case gen_server:start_link(?MODULE, [Service], []) of
David Reisse5a4d0c2008-06-11 01:02:10 +000089 {ok, Pid} ->
David Reiss44f785e2008-06-11 01:03:37 +000090 case gen_server:call(Pid, {connect, ProtocolFactory}) of
David Reisse5a4d0c2008-06-11 01:02:10 +000091 ok ->
92 {ok, Pid};
93 Error ->
94 Error
95 end;
96 Else ->
97 Else
98 end.
David Reiss2c534032008-06-11 00:58:00 +000099
David Reiss2c534032008-06-11 00:58:00 +0000100call(Client, Function, Args)
101 when is_pid(Client), is_atom(Function), is_list(Args) ->
102 case gen_server:call(Client, {call, Function, Args}) of
103 R = {ok, _} -> R;
104 R = {error, _} -> R;
105 {exception, Exception} -> throw(Exception)
106 end.
David Reiss2c534032008-06-11 00:58:00 +0000107
David Reissa2f45972008-06-11 01:13:33 +0000108cast(Client, Function, Args)
109 when is_pid(Client), is_atom(Function), is_list(Args) ->
110 gen_server:cast(Client, {call, Function, Args}).
111
David Reiss65cf7202008-06-11 01:12:20 +0000112%% Sends a function call but does not read the result. This is useful
David Reissc51986f2009-03-24 20:01:25 +0000113%% if you're trying to log non-oneway function calls to write-only
David Reiss65cf7202008-06-11 01:12:20 +0000114%% transports like thrift_disk_log_transport.
115send_call(Client, Function, Args)
116 when is_pid(Client), is_atom(Function), is_list(Args) ->
117 gen_server:call(Client, {send_call, Function, Args}).
118
David Reiss464e3002008-06-11 01:00:45 +0000119close(Client) when is_pid(Client) ->
David Reiss6f1cd532008-06-11 01:01:21 +0000120 gen_server:cast(Client, close).
David Reiss464e3002008-06-11 01:00:45 +0000121
David Reiss2c534032008-06-11 00:58:00 +0000122%%====================================================================
123%% gen_server callbacks
124%%====================================================================
125
126%%--------------------------------------------------------------------
127%% Function: init(Args) -> {ok, State} |
128%% {ok, State, Timeout} |
129%% ignore |
130%% {stop, Reason}
131%% Description: Initiates the server
132%%--------------------------------------------------------------------
David Reissad74b322008-06-11 01:03:29 +0000133init([Service]) ->
134 {ok, #state{service = Service}}.
David Reiss2c534032008-06-11 00:58:00 +0000135
136%%--------------------------------------------------------------------
137%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
138%% {reply, Reply, State, Timeout} |
139%% {noreply, State} |
140%% {noreply, State, Timeout} |
141%% {stop, Reason, Reply, State} |
142%% {stop, Reason, State}
143%% Description: Handling call messages
144%%--------------------------------------------------------------------
David Reiss44f785e2008-06-11 01:03:37 +0000145handle_call({connect, ProtocolFactory}, _From,
David Reissad74b322008-06-11 01:03:29 +0000146 State = #state{service = Service}) ->
David Reiss44f785e2008-06-11 01:03:37 +0000147 case ProtocolFactory() of
David Reissad74b322008-06-11 01:03:29 +0000148 {ok, Protocol} ->
149 {reply, ok, State#state{protocol = Protocol,
150 seqid = 0}};
David Reisse5a4d0c2008-06-11 01:02:10 +0000151 Error ->
152 {stop, normal, Error, State}
153 end;
154
David Reiss65cf7202008-06-11 01:12:20 +0000155handle_call({call, Function, Args}, _From, State = #state{service = Service}) ->
156 Result = catch_function_exceptions(
157 fun() ->
158 ok = send_function_call(State, Function, Args),
159 receive_function_result(State, Function)
160 end,
161 Service),
162 {reply, Result, State};
David Reiss6b3e40f2008-06-11 00:59:03 +0000163
David Reiss65cf7202008-06-11 01:12:20 +0000164
165handle_call({send_call, Function, Args}, _From, State = #state{service = Service}) ->
166 Result = catch_function_exceptions(
167 fun() ->
168 send_function_call(State, Function, Args)
169 end,
170 Service),
David Reiss6f1cd532008-06-11 01:01:21 +0000171 {reply, Result, State}.
David Reiss2c534032008-06-11 00:58:00 +0000172
David Reiss65cf7202008-06-11 01:12:20 +0000173
174%% Helper function that catches exceptions thrown by sending or receiving
175%% a function and returns the correct response for call or send_only above.
176catch_function_exceptions(Fun, Service) ->
177 try
178 Fun()
179 catch
180 throw:{return, Return} ->
181 Return;
182 error:function_clause ->
183 ST = erlang:get_stacktrace(),
184 case hd(ST) of
185 {Service, function_info, [Function, _]} ->
186 {error, {no_function, Function}};
187 _ -> throw({error, {function_clause, ST}})
188 end
189 end.
190
191
David Reiss2c534032008-06-11 00:58:00 +0000192%%--------------------------------------------------------------------
193%% Function: handle_cast(Msg, State) -> {noreply, State} |
194%% {noreply, State, Timeout} |
195%% {stop, Reason, State}
196%% Description: Handling cast messages
197%%--------------------------------------------------------------------
David Reissa2f45972008-06-11 01:13:33 +0000198handle_cast({call, Function, Args}, State = #state{service = Service,
199 protocol = Protocol,
200 seqid = SeqId}) ->
201 _Result =
202 try
203 ok = send_function_call(State, Function, Args),
204 receive_function_result(State, Function)
205 catch
206 Class:Reason ->
207 error_logger:error_msg("error ignored in handle_cast({cast,...},...): ~p:~p~n", [Class, Reason])
208 end,
209
210 {noreply, State};
211
David Reiss6f1cd532008-06-11 01:01:21 +0000212handle_cast(close, State=#state{protocol = Protocol}) ->
213%% error_logger:info_msg("thrift_client ~p received close", [self()]),
214 {stop,normal,State};
David Reiss2c534032008-06-11 00:58:00 +0000215handle_cast(_Msg, State) ->
216 {noreply, State}.
217
218%%--------------------------------------------------------------------
219%% Function: handle_info(Info, State) -> {noreply, State} |
220%% {noreply, State, Timeout} |
221%% {stop, Reason, State}
222%% Description: Handling all non call/cast messages
223%%--------------------------------------------------------------------
224handle_info(_Info, State) ->
225 {noreply, State}.
226
227%%--------------------------------------------------------------------
228%% Function: terminate(Reason, State) -> void()
229%% Description: This function is called by a gen_server when it is about to
230%% terminate. It should be the opposite of Module:init/1 and do any necessary
231%% cleaning up. When it returns, the gen_server terminates with Reason.
232%% The return value is ignored.
233%%--------------------------------------------------------------------
David Reisse5a4d0c2008-06-11 01:02:10 +0000234terminate(Reason, State = #state{protocol=undefined}) ->
235 ok;
236terminate(Reason, State = #state{protocol=Protocol}) ->
David Reiss464e3002008-06-11 01:00:45 +0000237 thrift_protocol:close_transport(Protocol),
David Reiss2c534032008-06-11 00:58:00 +0000238 ok.
239
240%%--------------------------------------------------------------------
241%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
242%% Description: Convert process state when code is changed
243%%--------------------------------------------------------------------
244code_change(_OldVsn, State, _Extra) ->
245 {ok, State}.
246
247%%--------------------------------------------------------------------
248%%% Internal functions
249%%--------------------------------------------------------------------
250send_function_call(#state{protocol = Proto,
251 service = Service,
252 seqid = SeqId},
253 Function,
254 Args) ->
255 Params = Service:function_info(Function, params_type),
256 {struct, PList} = Params,
David Reissc5257452008-06-11 00:59:27 +0000257 if
258 length(PList) =/= length(Args) ->
David Reiss2c534032008-06-11 00:58:00 +0000259 throw({return, {error, {bad_args, Function, Args}}});
David Reissc5257452008-06-11 00:59:27 +0000260 true -> ok
David Reiss2c534032008-06-11 00:58:00 +0000261 end,
262
263 Begin = #protocol_message_begin{name = atom_to_list(Function),
264 type = ?tMessageType_CALL,
265 seqid = SeqId},
266 ok = thrift_protocol:write(Proto, Begin),
267 ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
268 ok = thrift_protocol:write(Proto, message_end),
269 thrift_protocol:flush_transport(Proto),
270 ok.
271
David Reiss2c534032008-06-11 00:58:00 +0000272receive_function_result(State = #state{protocol = Proto,
273 service = Service},
274 Function) ->
275 ResultType = Service:function_info(Function, reply_type),
276 read_result(State, Function, ResultType).
277
278read_result(_State,
279 _Function,
David Reissfe931d12009-03-24 20:02:08 +0000280 oneway_void) ->
David Reiss2c534032008-06-11 00:58:00 +0000281 {ok, ok};
282
283read_result(State = #state{protocol = Proto,
284 seqid = SeqId},
285 Function,
286 ReplyType) ->
287 case thrift_protocol:read(Proto, message_begin) of
288 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
289 {error, {bad_seq_id, SeqId}};
David Reiss6b3e40f2008-06-11 00:59:03 +0000290
David Reiss2c534032008-06-11 00:58:00 +0000291 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
292 handle_application_exception(State);
David Reiss6b3e40f2008-06-11 00:59:03 +0000293
David Reiss2c534032008-06-11 00:58:00 +0000294 #protocol_message_begin{type = ?tMessageType_REPLY} ->
295 handle_reply(State, Function, ReplyType)
296 end.
297
David Reiss2c534032008-06-11 00:58:00 +0000298handle_reply(State = #state{protocol = Proto,
299 service = Service},
300 Function,
301 ReplyType) ->
302 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
303 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
David Reiss2c534032008-06-11 00:58:00 +0000304 {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
305 ReplyList = tuple_to_list(Reply),
306 true = length(ReplyList) == length(ExceptionFields) + 1,
307 ExceptionVals = tl(ReplyList),
308 Thrown = [X || X <- ExceptionVals,
309 X =/= undefined],
David Reiss2c534032008-06-11 00:58:00 +0000310 Result =
311 case Thrown of
312 [] when ReplyType == {struct, []} ->
313 {ok, ok};
314 [] ->
315 {ok, hd(ReplyList)};
316 [Exception] ->
317 {exception, Exception}
318 end,
319 ok = thrift_protocol:read(Proto, message_end),
320 Result.
David Reiss6b3e40f2008-06-11 00:59:03 +0000321
David Reiss55ff70f2008-06-11 00:58:25 +0000322handle_application_exception(State = #state{protocol = Proto}) ->
323 {ok, Exception} = thrift_protocol:read(Proto,
324 ?TApplicationException_Structure),
325 ok = thrift_protocol:read(Proto, message_end),
326 XRecord = list_to_tuple(
327 ['TApplicationException' | tuple_to_list(Exception)]),
David Reiss1af18682008-06-11 01:01:36 +0000328 error_logger:error_msg("X: ~p~n", [XRecord]),
David Reiss55ff70f2008-06-11 00:58:25 +0000329 true = is_record(XRecord, 'TApplicationException'),
330 {exception, XRecord}.