blob: 0b6c501cdf7d0b525ba5415fd0648f84ebe1d982 [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) ->
41 gen_server:start_link(?MODULE, [Host, Port, Service, Options], []).
David Reiss2c534032008-06-11 00:58:00 +000042
David Reiss2c534032008-06-11 00:58:00 +000043call(Client, Function, Args)
44 when is_pid(Client), is_atom(Function), is_list(Args) ->
45 case gen_server:call(Client, {call, Function, Args}) of
46 R = {ok, _} -> R;
47 R = {error, _} -> R;
48 {exception, Exception} -> throw(Exception)
49 end.
David Reiss2c534032008-06-11 00:58:00 +000050
David Reiss464e3002008-06-11 01:00:45 +000051close(Client) when is_pid(Client) ->
David Reiss6f1cd532008-06-11 01:01:21 +000052 gen_server:cast(Client, close).
David Reiss464e3002008-06-11 01:00:45 +000053
David Reiss2c534032008-06-11 00:58:00 +000054%%====================================================================
55%% gen_server callbacks
56%%====================================================================
57
58%%--------------------------------------------------------------------
59%% Function: init(Args) -> {ok, State} |
60%% {ok, State, Timeout} |
61%% ignore |
62%% {stop, Reason}
63%% Description: Initiates the server
64%%--------------------------------------------------------------------
David Reiss914ebb42008-06-11 01:01:48 +000065init([Host, Port, Service, Options]) ->
66 State = parse_options(Options, #state{}),
David Reiss4fd78182008-06-11 01:01:13 +000067
David Reissc920faf2008-06-11 01:01:56 +000068 TcpOptions = [binary,
69 {packet, 0},
70 {active, false},
71 {nodelay, true}],
72 TcpTimeout = State#state.connect_timeout,
David Reiss4fd78182008-06-11 01:01:13 +000073
David Reiss672968d2008-06-11 01:02:03 +000074 case catch gen_tcp:connect(Host, Port, TcpOptions, TcpTimeout) of
David Reissc920faf2008-06-11 01:01:56 +000075 {ok, Sock} ->
76 {ok, Transport} = thrift_socket_transport:new(Sock),
77 {ok, BufTransport} =
78 case State#state.framed of
79 true -> thrift_framed_transport:new(Transport);
80 false -> thrift_buffered_transport:new(Transport)
81 end,
82 {ok, Protocol} = thrift_binary_protocol:new(BufTransport,
David Reiss672968d2008-06-11 01:02:03 +000083 [{strict_read, State#state.strict_read},
84 {strict_write, State#state.strict_write}]),
David Reiss914ebb42008-06-11 01:01:48 +000085
David Reissc920faf2008-06-11 01:01:56 +000086 {ok, State#state{service = Service,
87 protocol = Protocol,
88 seqid = 0}};
89 Error ->
90 {stop, Error}
91 end.
David Reiss914ebb42008-06-11 01:01:48 +000092
93parse_options([], State) ->
94 State;
95parse_options([{strict_read, Bool} | Rest], State) when is_boolean(Bool) ->
96 parse_options(Rest, State#state{strict_read=Bool});
97parse_options([{strict_write, Bool} | Rest], State) when is_boolean(Bool) ->
98 parse_options(Rest, State#state{strict_write=Bool});
99parse_options([{framed, Bool} | Rest], State) when is_boolean(Bool) ->
100 parse_options(Rest, State#state{framed=Bool});
101parse_options([{connect_timeout, TO} | Rest], State) when TO =:= infinity; is_integer(TO) ->
102 parse_options(Rest, State#state{connect_timeout=TO}).
David Reiss2c534032008-06-11 00:58:00 +0000103
104%%--------------------------------------------------------------------
105%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
106%% {reply, Reply, State, Timeout} |
107%% {noreply, State} |
108%% {noreply, State, Timeout} |
109%% {stop, Reason, Reply, State} |
110%% {stop, Reason, State}
111%% Description: Handling call messages
112%%--------------------------------------------------------------------
113handle_call({call, Function, Args}, _From, State = #state{service = Service,
114 protocol = Protocol,
115 seqid = SeqId}) ->
116 Result =
117 try
118 ok = send_function_call(State, Function, Args),
119 receive_function_result(State, Function)
120 catch
121 throw:{return, Return} ->
122 Return;
123 error:function_clause ->
124 ST = erlang:get_stacktrace(),
125 case hd(ST) of
126 {Service, function_info, [Function, _]} ->
127 {error, {no_function, Function}};
128 _ -> throw({error, {function_clause, ST}})
129 end
130 end,
David Reiss6b3e40f2008-06-11 00:59:03 +0000131
David Reiss6f1cd532008-06-11 01:01:21 +0000132 {reply, Result, State}.
David Reiss2c534032008-06-11 00:58:00 +0000133
134%%--------------------------------------------------------------------
135%% Function: handle_cast(Msg, State) -> {noreply, State} |
136%% {noreply, State, Timeout} |
137%% {stop, Reason, State}
138%% Description: Handling cast messages
139%%--------------------------------------------------------------------
David Reiss6f1cd532008-06-11 01:01:21 +0000140handle_cast(close, State=#state{protocol = Protocol}) ->
141%% error_logger:info_msg("thrift_client ~p received close", [self()]),
142 {stop,normal,State};
David Reiss2c534032008-06-11 00:58:00 +0000143handle_cast(_Msg, State) ->
144 {noreply, State}.
145
146%%--------------------------------------------------------------------
147%% Function: handle_info(Info, State) -> {noreply, State} |
148%% {noreply, State, Timeout} |
149%% {stop, Reason, State}
150%% Description: Handling all non call/cast messages
151%%--------------------------------------------------------------------
152handle_info(_Info, State) ->
153 {noreply, State}.
154
155%%--------------------------------------------------------------------
156%% Function: terminate(Reason, State) -> void()
157%% Description: This function is called by a gen_server when it is about to
158%% terminate. It should be the opposite of Module:init/1 and do any necessary
159%% cleaning up. When it returns, the gen_server terminates with Reason.
160%% The return value is ignored.
161%%--------------------------------------------------------------------
David Reiss6f1cd532008-06-11 01:01:21 +0000162terminate(Reason, State = #state{protocol = Protocol}) ->
163%% error_logger:info_msg("thrift_client ~p terminating due to ~p", [self(), Reason]),
David Reiss464e3002008-06-11 01:00:45 +0000164 thrift_protocol:close_transport(Protocol),
David Reiss2c534032008-06-11 00:58:00 +0000165 ok.
166
167%%--------------------------------------------------------------------
168%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
169%% Description: Convert process state when code is changed
170%%--------------------------------------------------------------------
171code_change(_OldVsn, State, _Extra) ->
172 {ok, State}.
173
174%%--------------------------------------------------------------------
175%%% Internal functions
176%%--------------------------------------------------------------------
177send_function_call(#state{protocol = Proto,
178 service = Service,
179 seqid = SeqId},
180 Function,
181 Args) ->
182 Params = Service:function_info(Function, params_type),
183 {struct, PList} = Params,
David Reissc5257452008-06-11 00:59:27 +0000184 if
185 length(PList) =/= length(Args) ->
David Reiss2c534032008-06-11 00:58:00 +0000186 throw({return, {error, {bad_args, Function, Args}}});
David Reissc5257452008-06-11 00:59:27 +0000187 true -> ok
David Reiss2c534032008-06-11 00:58:00 +0000188 end,
189
190 Begin = #protocol_message_begin{name = atom_to_list(Function),
191 type = ?tMessageType_CALL,
192 seqid = SeqId},
193 ok = thrift_protocol:write(Proto, Begin),
194 ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
195 ok = thrift_protocol:write(Proto, message_end),
196 thrift_protocol:flush_transport(Proto),
197 ok.
198
David Reiss2c534032008-06-11 00:58:00 +0000199receive_function_result(State = #state{protocol = Proto,
200 service = Service},
201 Function) ->
202 ResultType = Service:function_info(Function, reply_type),
203 read_result(State, Function, ResultType).
204
205read_result(_State,
206 _Function,
207 async_void) ->
208 {ok, ok};
209
210read_result(State = #state{protocol = Proto,
211 seqid = SeqId},
212 Function,
213 ReplyType) ->
214 case thrift_protocol:read(Proto, message_begin) of
215 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
216 {error, {bad_seq_id, SeqId}};
David Reiss6b3e40f2008-06-11 00:59:03 +0000217
David Reiss2c534032008-06-11 00:58:00 +0000218 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
219 handle_application_exception(State);
David Reiss6b3e40f2008-06-11 00:59:03 +0000220
David Reiss2c534032008-06-11 00:58:00 +0000221 #protocol_message_begin{type = ?tMessageType_REPLY} ->
222 handle_reply(State, Function, ReplyType)
223 end.
224
David Reiss2c534032008-06-11 00:58:00 +0000225handle_reply(State = #state{protocol = Proto,
226 service = Service},
227 Function,
228 ReplyType) ->
229 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
230 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
David Reiss2c534032008-06-11 00:58:00 +0000231 {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
232 ReplyList = tuple_to_list(Reply),
233 true = length(ReplyList) == length(ExceptionFields) + 1,
234 ExceptionVals = tl(ReplyList),
235 Thrown = [X || X <- ExceptionVals,
236 X =/= undefined],
David Reiss2c534032008-06-11 00:58:00 +0000237 Result =
238 case Thrown of
239 [] when ReplyType == {struct, []} ->
240 {ok, ok};
241 [] ->
242 {ok, hd(ReplyList)};
243 [Exception] ->
244 {exception, Exception}
245 end,
246 ok = thrift_protocol:read(Proto, message_end),
247 Result.
David Reiss6b3e40f2008-06-11 00:59:03 +0000248
David Reiss55ff70f2008-06-11 00:58:25 +0000249handle_application_exception(State = #state{protocol = Proto}) ->
250 {ok, Exception} = thrift_protocol:read(Proto,
251 ?TApplicationException_Structure),
252 ok = thrift_protocol:read(Proto, message_end),
253 XRecord = list_to_tuple(
254 ['TApplicationException' | tuple_to_list(Exception)]),
David Reiss1af18682008-06-11 01:01:36 +0000255 error_logger:error_msg("X: ~p~n", [XRecord]),
David Reiss55ff70f2008-06-11 00:58:25 +0000256 true = is_record(XRecord, 'TApplicationException'),
257 {exception, XRecord}.