blob: 779595cb1013f6c1693d0eee8b20f94bee93a5fc [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 Reiss2c534032008-06-11 00:58:00 +000068 {ok, Sock} = gen_tcp:connect(Host, Port,
69 [binary,
70 {packet, 0},
71 {active, false},
David Reiss6b3e40f2008-06-11 00:59:03 +000072 {nodelay, true}
David Reiss4fd78182008-06-11 01:01:13 +000073 ],
David Reiss914ebb42008-06-11 01:01:48 +000074 State#state.connect_timeout),
David Reiss4fd78182008-06-11 01:01:13 +000075
David Reiss914ebb42008-06-11 01:01:48 +000076 {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,
83 [{strict_read, State#state.strict_read},
84 {strict_write, State#state.strict_write}]),
85
86 {ok, State#state{service = Service,
87 protocol = Protocol,
88 seqid = 0}}.
89
90parse_options([], State) ->
91 State;
92parse_options([{strict_read, Bool} | Rest], State) when is_boolean(Bool) ->
93 parse_options(Rest, State#state{strict_read=Bool});
94parse_options([{strict_write, Bool} | Rest], State) when is_boolean(Bool) ->
95 parse_options(Rest, State#state{strict_write=Bool});
96parse_options([{framed, Bool} | Rest], State) when is_boolean(Bool) ->
97 parse_options(Rest, State#state{framed=Bool});
98parse_options([{connect_timeout, TO} | Rest], State) when TO =:= infinity; is_integer(TO) ->
99 parse_options(Rest, State#state{connect_timeout=TO}).
David Reiss2c534032008-06-11 00:58:00 +0000100
101%%--------------------------------------------------------------------
102%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
103%% {reply, Reply, State, Timeout} |
104%% {noreply, State} |
105%% {noreply, State, Timeout} |
106%% {stop, Reason, Reply, State} |
107%% {stop, Reason, State}
108%% Description: Handling call messages
109%%--------------------------------------------------------------------
110handle_call({call, Function, Args}, _From, State = #state{service = Service,
111 protocol = Protocol,
112 seqid = SeqId}) ->
113 Result =
114 try
115 ok = send_function_call(State, Function, Args),
116 receive_function_result(State, Function)
117 catch
118 throw:{return, Return} ->
119 Return;
120 error:function_clause ->
121 ST = erlang:get_stacktrace(),
122 case hd(ST) of
123 {Service, function_info, [Function, _]} ->
124 {error, {no_function, Function}};
125 _ -> throw({error, {function_clause, ST}})
126 end
127 end,
David Reiss6b3e40f2008-06-11 00:59:03 +0000128
David Reiss6f1cd532008-06-11 01:01:21 +0000129 {reply, Result, State}.
David Reiss2c534032008-06-11 00:58:00 +0000130
131%%--------------------------------------------------------------------
132%% Function: handle_cast(Msg, State) -> {noreply, State} |
133%% {noreply, State, Timeout} |
134%% {stop, Reason, State}
135%% Description: Handling cast messages
136%%--------------------------------------------------------------------
David Reiss6f1cd532008-06-11 01:01:21 +0000137handle_cast(close, State=#state{protocol = Protocol}) ->
138%% error_logger:info_msg("thrift_client ~p received close", [self()]),
139 {stop,normal,State};
David Reiss2c534032008-06-11 00:58:00 +0000140handle_cast(_Msg, State) ->
141 {noreply, State}.
142
143%%--------------------------------------------------------------------
144%% Function: handle_info(Info, State) -> {noreply, State} |
145%% {noreply, State, Timeout} |
146%% {stop, Reason, State}
147%% Description: Handling all non call/cast messages
148%%--------------------------------------------------------------------
149handle_info(_Info, State) ->
150 {noreply, State}.
151
152%%--------------------------------------------------------------------
153%% Function: terminate(Reason, State) -> void()
154%% Description: This function is called by a gen_server when it is about to
155%% terminate. It should be the opposite of Module:init/1 and do any necessary
156%% cleaning up. When it returns, the gen_server terminates with Reason.
157%% The return value is ignored.
158%%--------------------------------------------------------------------
David Reiss6f1cd532008-06-11 01:01:21 +0000159terminate(Reason, State = #state{protocol = Protocol}) ->
160%% error_logger:info_msg("thrift_client ~p terminating due to ~p", [self(), Reason]),
David Reiss464e3002008-06-11 01:00:45 +0000161 thrift_protocol:close_transport(Protocol),
David Reiss2c534032008-06-11 00:58:00 +0000162 ok.
163
164%%--------------------------------------------------------------------
165%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
166%% Description: Convert process state when code is changed
167%%--------------------------------------------------------------------
168code_change(_OldVsn, State, _Extra) ->
169 {ok, State}.
170
171%%--------------------------------------------------------------------
172%%% Internal functions
173%%--------------------------------------------------------------------
174send_function_call(#state{protocol = Proto,
175 service = Service,
176 seqid = SeqId},
177 Function,
178 Args) ->
179 Params = Service:function_info(Function, params_type),
180 {struct, PList} = Params,
David Reissc5257452008-06-11 00:59:27 +0000181 if
182 length(PList) =/= length(Args) ->
David Reiss2c534032008-06-11 00:58:00 +0000183 throw({return, {error, {bad_args, Function, Args}}});
David Reissc5257452008-06-11 00:59:27 +0000184 true -> ok
David Reiss2c534032008-06-11 00:58:00 +0000185 end,
186
187 Begin = #protocol_message_begin{name = atom_to_list(Function),
188 type = ?tMessageType_CALL,
189 seqid = SeqId},
190 ok = thrift_protocol:write(Proto, Begin),
191 ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
192 ok = thrift_protocol:write(Proto, message_end),
193 thrift_protocol:flush_transport(Proto),
194 ok.
195
David Reiss2c534032008-06-11 00:58:00 +0000196receive_function_result(State = #state{protocol = Proto,
197 service = Service},
198 Function) ->
199 ResultType = Service:function_info(Function, reply_type),
200 read_result(State, Function, ResultType).
201
202read_result(_State,
203 _Function,
204 async_void) ->
205 {ok, ok};
206
207read_result(State = #state{protocol = Proto,
208 seqid = SeqId},
209 Function,
210 ReplyType) ->
211 case thrift_protocol:read(Proto, message_begin) of
212 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
213 {error, {bad_seq_id, SeqId}};
David Reiss6b3e40f2008-06-11 00:59:03 +0000214
David Reiss2c534032008-06-11 00:58:00 +0000215 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
216 handle_application_exception(State);
David Reiss6b3e40f2008-06-11 00:59:03 +0000217
David Reiss2c534032008-06-11 00:58:00 +0000218 #protocol_message_begin{type = ?tMessageType_REPLY} ->
219 handle_reply(State, Function, ReplyType)
220 end.
221
David Reiss2c534032008-06-11 00:58:00 +0000222handle_reply(State = #state{protocol = Proto,
223 service = Service},
224 Function,
225 ReplyType) ->
226 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
227 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
David Reiss2c534032008-06-11 00:58:00 +0000228 {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
229 ReplyList = tuple_to_list(Reply),
230 true = length(ReplyList) == length(ExceptionFields) + 1,
231 ExceptionVals = tl(ReplyList),
232 Thrown = [X || X <- ExceptionVals,
233 X =/= undefined],
David Reiss2c534032008-06-11 00:58:00 +0000234 Result =
235 case Thrown of
236 [] when ReplyType == {struct, []} ->
237 {ok, ok};
238 [] ->
239 {ok, hd(ReplyList)};
240 [Exception] ->
241 {exception, Exception}
242 end,
243 ok = thrift_protocol:read(Proto, message_end),
244 Result.
David Reiss6b3e40f2008-06-11 00:59:03 +0000245
David Reiss55ff70f2008-06-11 00:58:25 +0000246handle_application_exception(State = #state{protocol = Proto}) ->
247 {ok, Exception} = thrift_protocol:read(Proto,
248 ?TApplicationException_Structure),
249 ok = thrift_protocol:read(Proto, message_end),
250 XRecord = list_to_tuple(
251 ['TApplicationException' | tuple_to_list(Exception)]),
David Reiss1af18682008-06-11 01:01:36 +0000252 error_logger:error_msg("X: ~p~n", [XRecord]),
David Reiss55ff70f2008-06-11 00:58:25 +0000253 true = is_record(XRecord, 'TApplicationException'),
254 {exception, XRecord}.