blob: dee91fb2f64a7ede373ccfb83d9609734f88d4b7 [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,
David Reiss2fe905e2008-06-11 01:02:23 +000027 connect_timeout = infinity,
28 sockopts = []
David Reiss914ebb42008-06-11 01:01:48 +000029 }).
David Reiss2c534032008-06-11 00:58:00 +000030
31%%====================================================================
32%% API
33%%====================================================================
34%%--------------------------------------------------------------------
35%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
36%% Description: Starts the server
37%%--------------------------------------------------------------------
David Reiss4fd78182008-06-11 01:01:13 +000038start_link(Host, Port, Service) ->
David Reiss914ebb42008-06-11 01:01:48 +000039 start_link(Host, Port, Service, []).
David Reiss4fd78182008-06-11 01:01:13 +000040
David Reiss2fe905e2008-06-11 01:02:23 +000041start_link(Host, Port, Service, Options)
42 when is_integer(Port), is_atom(Service), is_list(Options) ->
43 case gen_server:start_link(?MODULE, [Options], []) of
David Reisse5a4d0c2008-06-11 01:02:10 +000044 {ok, Pid} ->
45 case gen_server:call(Pid, {connect, Host, Port, Service}) of
46 ok ->
47 {ok, Pid};
48 Error ->
49 Error
50 end;
51 Else ->
52 Else
53 end.
David Reiss2c534032008-06-11 00:58:00 +000054
David Reiss2c534032008-06-11 00:58:00 +000055call(Client, Function, Args)
56 when is_pid(Client), is_atom(Function), is_list(Args) ->
57 case gen_server:call(Client, {call, Function, Args}) of
58 R = {ok, _} -> R;
59 R = {error, _} -> R;
60 {exception, Exception} -> throw(Exception)
61 end.
David Reiss2c534032008-06-11 00:58:00 +000062
David Reiss464e3002008-06-11 01:00:45 +000063close(Client) when is_pid(Client) ->
David Reiss6f1cd532008-06-11 01:01:21 +000064 gen_server:cast(Client, close).
David Reiss464e3002008-06-11 01:00:45 +000065
David Reiss2c534032008-06-11 00:58:00 +000066%%====================================================================
67%% gen_server callbacks
68%%====================================================================
69
70%%--------------------------------------------------------------------
71%% Function: init(Args) -> {ok, State} |
72%% {ok, State, Timeout} |
73%% ignore |
74%% {stop, Reason}
75%% Description: Initiates the server
76%%--------------------------------------------------------------------
David Reiss2fe905e2008-06-11 01:02:23 +000077init([Options]) ->
David Reiss914ebb42008-06-11 01:01:48 +000078 State = parse_options(Options, #state{}),
David Reisse5a4d0c2008-06-11 01:02:10 +000079 {ok, State}.
David Reiss914ebb42008-06-11 01:01:48 +000080
81parse_options([], State) ->
82 State;
83parse_options([{strict_read, Bool} | Rest], State) when is_boolean(Bool) ->
84 parse_options(Rest, State#state{strict_read=Bool});
85parse_options([{strict_write, Bool} | Rest], State) when is_boolean(Bool) ->
86 parse_options(Rest, State#state{strict_write=Bool});
87parse_options([{framed, Bool} | Rest], State) when is_boolean(Bool) ->
88 parse_options(Rest, State#state{framed=Bool});
David Reiss2fe905e2008-06-11 01:02:23 +000089parse_options([{sockopts, OptList} | Rest], State) when is_list(OptList) ->
90 parse_options(Rest, State#state{sockopts=OptList});
David Reiss914ebb42008-06-11 01:01:48 +000091parse_options([{connect_timeout, TO} | Rest], State) when TO =:= infinity; is_integer(TO) ->
92 parse_options(Rest, State#state{connect_timeout=TO}).
David Reiss2c534032008-06-11 00:58:00 +000093
94%%--------------------------------------------------------------------
95%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
96%% {reply, Reply, State, Timeout} |
97%% {noreply, State} |
98%% {noreply, State, Timeout} |
99%% {stop, Reason, Reply, State} |
100%% {stop, Reason, State}
101%% Description: Handling call messages
102%%--------------------------------------------------------------------
David Reiss2fe905e2008-06-11 01:02:23 +0000103handle_call({connect, Host, Port, Service}, _From,
104 State = #state{connect_timeout=Timeout,
105 sockopts=SockOpts}) ->
David Reisse5a4d0c2008-06-11 01:02:10 +0000106 Options = [binary,
107 {packet, 0},
108 {active, false},
David Reiss2fe905e2008-06-11 01:02:23 +0000109 {nodelay, true}
110 | SockOpts
111 ],
David Reisse5a4d0c2008-06-11 01:02:10 +0000112 case catch gen_tcp:connect(Host, Port, Options, Timeout) of
113 {ok, Sock} ->
114 {ok, Transport} = thrift_socket_transport:new(Sock),
115 {ok, BufTransport} =
116 case State#state.framed of
117 true -> thrift_framed_transport:new(Transport);
118 false -> thrift_buffered_transport:new(Transport)
119 end,
120 {ok, Protocol} = thrift_binary_protocol:new(BufTransport,
121 [{strict_read, State#state.strict_read},
122 {strict_write, State#state.strict_write}]),
123
124 {reply, ok, State#state{service = Service,
125 protocol = Protocol,
126 seqid = 0}};
127 Error ->
128 {stop, normal, Error, State}
129 end;
130
David Reiss2c534032008-06-11 00:58:00 +0000131handle_call({call, Function, Args}, _From, State = #state{service = Service,
132 protocol = Protocol,
133 seqid = SeqId}) ->
134 Result =
135 try
136 ok = send_function_call(State, Function, Args),
137 receive_function_result(State, Function)
138 catch
139 throw:{return, Return} ->
140 Return;
141 error:function_clause ->
142 ST = erlang:get_stacktrace(),
143 case hd(ST) of
144 {Service, function_info, [Function, _]} ->
145 {error, {no_function, Function}};
146 _ -> throw({error, {function_clause, ST}})
147 end
148 end,
David Reiss6b3e40f2008-06-11 00:59:03 +0000149
David Reiss6f1cd532008-06-11 01:01:21 +0000150 {reply, Result, State}.
David Reiss2c534032008-06-11 00:58:00 +0000151
152%%--------------------------------------------------------------------
153%% Function: handle_cast(Msg, State) -> {noreply, State} |
154%% {noreply, State, Timeout} |
155%% {stop, Reason, State}
156%% Description: Handling cast messages
157%%--------------------------------------------------------------------
David Reiss6f1cd532008-06-11 01:01:21 +0000158handle_cast(close, State=#state{protocol = Protocol}) ->
159%% error_logger:info_msg("thrift_client ~p received close", [self()]),
160 {stop,normal,State};
David Reiss2c534032008-06-11 00:58:00 +0000161handle_cast(_Msg, State) ->
162 {noreply, State}.
163
164%%--------------------------------------------------------------------
165%% Function: handle_info(Info, State) -> {noreply, State} |
166%% {noreply, State, Timeout} |
167%% {stop, Reason, State}
168%% Description: Handling all non call/cast messages
169%%--------------------------------------------------------------------
170handle_info(_Info, State) ->
171 {noreply, State}.
172
173%%--------------------------------------------------------------------
174%% Function: terminate(Reason, State) -> void()
175%% Description: This function is called by a gen_server when it is about to
176%% terminate. It should be the opposite of Module:init/1 and do any necessary
177%% cleaning up. When it returns, the gen_server terminates with Reason.
178%% The return value is ignored.
179%%--------------------------------------------------------------------
David Reisse5a4d0c2008-06-11 01:02:10 +0000180terminate(Reason, State = #state{protocol=undefined}) ->
181 ok;
182terminate(Reason, State = #state{protocol=Protocol}) ->
David Reiss464e3002008-06-11 01:00:45 +0000183 thrift_protocol:close_transport(Protocol),
David Reiss2c534032008-06-11 00:58:00 +0000184 ok.
185
186%%--------------------------------------------------------------------
187%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
188%% Description: Convert process state when code is changed
189%%--------------------------------------------------------------------
190code_change(_OldVsn, State, _Extra) ->
191 {ok, State}.
192
193%%--------------------------------------------------------------------
194%%% Internal functions
195%%--------------------------------------------------------------------
196send_function_call(#state{protocol = Proto,
197 service = Service,
198 seqid = SeqId},
199 Function,
200 Args) ->
201 Params = Service:function_info(Function, params_type),
202 {struct, PList} = Params,
David Reissc5257452008-06-11 00:59:27 +0000203 if
204 length(PList) =/= length(Args) ->
David Reiss2c534032008-06-11 00:58:00 +0000205 throw({return, {error, {bad_args, Function, Args}}});
David Reissc5257452008-06-11 00:59:27 +0000206 true -> ok
David Reiss2c534032008-06-11 00:58:00 +0000207 end,
208
209 Begin = #protocol_message_begin{name = atom_to_list(Function),
210 type = ?tMessageType_CALL,
211 seqid = SeqId},
212 ok = thrift_protocol:write(Proto, Begin),
213 ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
214 ok = thrift_protocol:write(Proto, message_end),
215 thrift_protocol:flush_transport(Proto),
216 ok.
217
David Reiss2c534032008-06-11 00:58:00 +0000218receive_function_result(State = #state{protocol = Proto,
219 service = Service},
220 Function) ->
221 ResultType = Service:function_info(Function, reply_type),
222 read_result(State, Function, ResultType).
223
224read_result(_State,
225 _Function,
226 async_void) ->
227 {ok, ok};
228
229read_result(State = #state{protocol = Proto,
230 seqid = SeqId},
231 Function,
232 ReplyType) ->
233 case thrift_protocol:read(Proto, message_begin) of
234 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
235 {error, {bad_seq_id, SeqId}};
David Reiss6b3e40f2008-06-11 00:59:03 +0000236
David Reiss2c534032008-06-11 00:58:00 +0000237 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
238 handle_application_exception(State);
David Reiss6b3e40f2008-06-11 00:59:03 +0000239
David Reiss2c534032008-06-11 00:58:00 +0000240 #protocol_message_begin{type = ?tMessageType_REPLY} ->
241 handle_reply(State, Function, ReplyType)
242 end.
243
David Reiss2c534032008-06-11 00:58:00 +0000244handle_reply(State = #state{protocol = Proto,
245 service = Service},
246 Function,
247 ReplyType) ->
248 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
249 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
David Reiss2c534032008-06-11 00:58:00 +0000250 {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
251 ReplyList = tuple_to_list(Reply),
252 true = length(ReplyList) == length(ExceptionFields) + 1,
253 ExceptionVals = tl(ReplyList),
254 Thrown = [X || X <- ExceptionVals,
255 X =/= undefined],
David Reiss2c534032008-06-11 00:58:00 +0000256 Result =
257 case Thrown of
258 [] when ReplyType == {struct, []} ->
259 {ok, ok};
260 [] ->
261 {ok, hd(ReplyList)};
262 [Exception] ->
263 {exception, Exception}
264 end,
265 ok = thrift_protocol:read(Proto, message_end),
266 Result.
David Reiss6b3e40f2008-06-11 00:59:03 +0000267
David Reiss55ff70f2008-06-11 00:58:25 +0000268handle_application_exception(State = #state{protocol = Proto}) ->
269 {ok, Exception} = thrift_protocol:read(Proto,
270 ?TApplicationException_Structure),
271 ok = thrift_protocol:read(Proto, message_end),
272 XRecord = list_to_tuple(
273 ['TApplicationException' | tuple_to_list(Exception)]),
David Reiss1af18682008-06-11 01:01:36 +0000274 error_logger:error_msg("X: ~p~n", [XRecord]),
David Reiss55ff70f2008-06-11 00:58:25 +0000275 true = is_record(XRecord, 'TApplicationException'),
276 {exception, XRecord}.