blob: af7aea9f4c24dd84451faabf4eaf3701f30802c1 [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
23-record(state, {service, protocol, seqid}).
24
25%%====================================================================
26%% API
27%%====================================================================
28%%--------------------------------------------------------------------
29%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
30%% Description: Starts the server
31%%--------------------------------------------------------------------
David Reiss4fd78182008-06-11 01:01:13 +000032start_link(Host, Port, Service) ->
33 start_link(Host, Port, Service, _Timeout = infinity).
34
35start_link(Host, Port, Service, Timeout) when is_integer(Port), is_atom(Service) ->
36 gen_server:start_link(?MODULE, [Host, Port, Service, Timeout], []).
David Reiss2c534032008-06-11 00:58:00 +000037
David Reiss2c534032008-06-11 00:58:00 +000038call(Client, Function, Args)
39 when is_pid(Client), is_atom(Function), is_list(Args) ->
40 case gen_server:call(Client, {call, Function, Args}) of
41 R = {ok, _} -> R;
42 R = {error, _} -> R;
43 {exception, Exception} -> throw(Exception)
44 end.
David Reiss2c534032008-06-11 00:58:00 +000045
David Reiss464e3002008-06-11 01:00:45 +000046close(Client) when is_pid(Client) ->
David Reiss6f1cd532008-06-11 01:01:21 +000047 gen_server:cast(Client, close).
David Reiss464e3002008-06-11 01:00:45 +000048
David Reiss2c534032008-06-11 00:58:00 +000049%%====================================================================
50%% gen_server callbacks
51%%====================================================================
52
53%%--------------------------------------------------------------------
54%% Function: init(Args) -> {ok, State} |
55%% {ok, State, Timeout} |
56%% ignore |
57%% {stop, Reason}
58%% Description: Initiates the server
59%%--------------------------------------------------------------------
60init([Host, Port, Service]) ->
David Reiss4fd78182008-06-11 01:01:13 +000061 init([Host, Port, Service, infinity]);
62
63init([Host, Port, Service, Timeout]) ->
David Reiss2c534032008-06-11 00:58:00 +000064 {ok, Sock} = gen_tcp:connect(Host, Port,
65 [binary,
66 {packet, 0},
67 {active, false},
David Reiss6b3e40f2008-06-11 00:59:03 +000068 {nodelay, true}
David Reiss4fd78182008-06-11 01:01:13 +000069 ],
70 Timeout),
71
David Reiss6b3e40f2008-06-11 00:59:03 +000072 {ok, Transport} = thrift_socket_transport:new(Sock),
David Reiss2c534032008-06-11 00:58:00 +000073 {ok, BufTransport} = thrift_buffered_transport:new(Transport),
David Reiss6b3e40f2008-06-11 00:59:03 +000074 {ok, Protocol} = thrift_binary_protocol:new(BufTransport),
David Reiss2c534032008-06-11 00:58:00 +000075 {ok, #state{service = Service,
76 protocol = Protocol,
77 seqid = 0}}.
78
79%%--------------------------------------------------------------------
80%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
81%% {reply, Reply, State, Timeout} |
82%% {noreply, State} |
83%% {noreply, State, Timeout} |
84%% {stop, Reason, Reply, State} |
85%% {stop, Reason, State}
86%% Description: Handling call messages
87%%--------------------------------------------------------------------
88handle_call({call, Function, Args}, _From, State = #state{service = Service,
89 protocol = Protocol,
90 seqid = SeqId}) ->
91 Result =
92 try
93 ok = send_function_call(State, Function, Args),
94 receive_function_result(State, Function)
95 catch
96 throw:{return, Return} ->
97 Return;
98 error:function_clause ->
99 ST = erlang:get_stacktrace(),
100 case hd(ST) of
101 {Service, function_info, [Function, _]} ->
102 {error, {no_function, Function}};
103 _ -> throw({error, {function_clause, ST}})
104 end
105 end,
David Reiss6b3e40f2008-06-11 00:59:03 +0000106
David Reiss6f1cd532008-06-11 01:01:21 +0000107 {reply, Result, State}.
David Reiss2c534032008-06-11 00:58:00 +0000108
109%%--------------------------------------------------------------------
110%% Function: handle_cast(Msg, State) -> {noreply, State} |
111%% {noreply, State, Timeout} |
112%% {stop, Reason, State}
113%% Description: Handling cast messages
114%%--------------------------------------------------------------------
David Reiss6f1cd532008-06-11 01:01:21 +0000115handle_cast(close, State=#state{protocol = Protocol}) ->
116%% error_logger:info_msg("thrift_client ~p received close", [self()]),
117 {stop,normal,State};
David Reiss2c534032008-06-11 00:58:00 +0000118handle_cast(_Msg, State) ->
119 {noreply, State}.
120
121%%--------------------------------------------------------------------
122%% Function: handle_info(Info, State) -> {noreply, State} |
123%% {noreply, State, Timeout} |
124%% {stop, Reason, State}
125%% Description: Handling all non call/cast messages
126%%--------------------------------------------------------------------
127handle_info(_Info, State) ->
128 {noreply, State}.
129
130%%--------------------------------------------------------------------
131%% Function: terminate(Reason, State) -> void()
132%% Description: This function is called by a gen_server when it is about to
133%% terminate. It should be the opposite of Module:init/1 and do any necessary
134%% cleaning up. When it returns, the gen_server terminates with Reason.
135%% The return value is ignored.
136%%--------------------------------------------------------------------
David Reiss6f1cd532008-06-11 01:01:21 +0000137terminate(Reason, State = #state{protocol = Protocol}) ->
138%% error_logger:info_msg("thrift_client ~p terminating due to ~p", [self(), Reason]),
David Reiss464e3002008-06-11 01:00:45 +0000139 thrift_protocol:close_transport(Protocol),
David Reiss2c534032008-06-11 00:58:00 +0000140 ok.
141
142%%--------------------------------------------------------------------
143%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
144%% Description: Convert process state when code is changed
145%%--------------------------------------------------------------------
146code_change(_OldVsn, State, _Extra) ->
147 {ok, State}.
148
149%%--------------------------------------------------------------------
150%%% Internal functions
151%%--------------------------------------------------------------------
152send_function_call(#state{protocol = Proto,
153 service = Service,
154 seqid = SeqId},
155 Function,
156 Args) ->
157 Params = Service:function_info(Function, params_type),
158 {struct, PList} = Params,
David Reissc5257452008-06-11 00:59:27 +0000159 if
160 length(PList) =/= length(Args) ->
David Reiss2c534032008-06-11 00:58:00 +0000161 throw({return, {error, {bad_args, Function, Args}}});
David Reissc5257452008-06-11 00:59:27 +0000162 true -> ok
David Reiss2c534032008-06-11 00:58:00 +0000163 end,
164
165 Begin = #protocol_message_begin{name = atom_to_list(Function),
166 type = ?tMessageType_CALL,
167 seqid = SeqId},
168 ok = thrift_protocol:write(Proto, Begin),
169 ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
170 ok = thrift_protocol:write(Proto, message_end),
171 thrift_protocol:flush_transport(Proto),
172 ok.
173
David Reiss2c534032008-06-11 00:58:00 +0000174receive_function_result(State = #state{protocol = Proto,
175 service = Service},
176 Function) ->
177 ResultType = Service:function_info(Function, reply_type),
178 read_result(State, Function, ResultType).
179
180read_result(_State,
181 _Function,
182 async_void) ->
183 {ok, ok};
184
185read_result(State = #state{protocol = Proto,
186 seqid = SeqId},
187 Function,
188 ReplyType) ->
189 case thrift_protocol:read(Proto, message_begin) of
190 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
191 {error, {bad_seq_id, SeqId}};
David Reiss6b3e40f2008-06-11 00:59:03 +0000192
David Reiss2c534032008-06-11 00:58:00 +0000193 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
194 handle_application_exception(State);
David Reiss6b3e40f2008-06-11 00:59:03 +0000195
David Reiss2c534032008-06-11 00:58:00 +0000196 #protocol_message_begin{type = ?tMessageType_REPLY} ->
197 handle_reply(State, Function, ReplyType)
198 end.
199
David Reiss2c534032008-06-11 00:58:00 +0000200handle_reply(State = #state{protocol = Proto,
201 service = Service},
202 Function,
203 ReplyType) ->
204 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
205 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
David Reiss2c534032008-06-11 00:58:00 +0000206 {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
207 ReplyList = tuple_to_list(Reply),
208 true = length(ReplyList) == length(ExceptionFields) + 1,
209 ExceptionVals = tl(ReplyList),
210 Thrown = [X || X <- ExceptionVals,
211 X =/= undefined],
David Reiss2c534032008-06-11 00:58:00 +0000212 Result =
213 case Thrown of
214 [] when ReplyType == {struct, []} ->
215 {ok, ok};
216 [] ->
217 {ok, hd(ReplyList)};
218 [Exception] ->
219 {exception, Exception}
220 end,
221 ok = thrift_protocol:read(Proto, message_end),
222 Result.
David Reiss6b3e40f2008-06-11 00:59:03 +0000223
David Reiss55ff70f2008-06-11 00:58:25 +0000224handle_application_exception(State = #state{protocol = Proto}) ->
225 {ok, Exception} = thrift_protocol:read(Proto,
226 ?TApplicationException_Structure),
227 ok = thrift_protocol:read(Proto, message_end),
228 XRecord = list_to_tuple(
229 ['TApplicationException' | tuple_to_list(Exception)]),
David Reiss1af18682008-06-11 01:01:36 +0000230 error_logger:error_msg("X: ~p~n", [XRecord]),
David Reiss55ff70f2008-06-11 00:58:25 +0000231 true = is_record(XRecord, 'TApplicationException'),
232 {exception, XRecord}.