blob: d2ac692106f78602bf0c9e98960cc114893884e6 [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 Reiss464e3002008-06-11 01:00:45 +000013-export([start_link/3, 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%%--------------------------------------------------------------------
32start_link(Host, Port, Service) when is_integer(Port), is_atom(Service) ->
33 gen_server:start_link(?MODULE, [Host, Port, Service], []).
34
David Reiss2c534032008-06-11 00:58:00 +000035call(Client, Function, Args)
36 when is_pid(Client), is_atom(Function), is_list(Args) ->
37 case gen_server:call(Client, {call, Function, Args}) of
38 R = {ok, _} -> R;
39 R = {error, _} -> R;
40 {exception, Exception} -> throw(Exception)
41 end.
David Reiss2c534032008-06-11 00:58:00 +000042
David Reiss464e3002008-06-11 01:00:45 +000043close(Client) when is_pid(Client) ->
44 gen_server:call(Client, close).
45
David Reiss2c534032008-06-11 00:58:00 +000046%%====================================================================
47%% gen_server callbacks
48%%====================================================================
49
50%%--------------------------------------------------------------------
51%% Function: init(Args) -> {ok, State} |
52%% {ok, State, Timeout} |
53%% ignore |
54%% {stop, Reason}
55%% Description: Initiates the server
56%%--------------------------------------------------------------------
57init([Host, Port, Service]) ->
58 {ok, Sock} = gen_tcp:connect(Host, Port,
59 [binary,
60 {packet, 0},
61 {active, false},
David Reiss6b3e40f2008-06-11 00:59:03 +000062 {nodelay, true}
63 ]),
64 {ok, Transport} = thrift_socket_transport:new(Sock),
David Reiss2c534032008-06-11 00:58:00 +000065 {ok, BufTransport} = thrift_buffered_transport:new(Transport),
David Reiss6b3e40f2008-06-11 00:59:03 +000066 {ok, Protocol} = thrift_binary_protocol:new(BufTransport),
David Reiss2c534032008-06-11 00:58:00 +000067 {ok, #state{service = Service,
68 protocol = Protocol,
69 seqid = 0}}.
70
71%%--------------------------------------------------------------------
72%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
73%% {reply, Reply, State, Timeout} |
74%% {noreply, State} |
75%% {noreply, State, Timeout} |
76%% {stop, Reason, Reply, State} |
77%% {stop, Reason, State}
78%% Description: Handling call messages
79%%--------------------------------------------------------------------
80handle_call({call, Function, Args}, _From, State = #state{service = Service,
81 protocol = Protocol,
82 seqid = SeqId}) ->
83 Result =
84 try
85 ok = send_function_call(State, Function, Args),
86 receive_function_result(State, Function)
87 catch
88 throw:{return, Return} ->
89 Return;
90 error:function_clause ->
91 ST = erlang:get_stacktrace(),
92 case hd(ST) of
93 {Service, function_info, [Function, _]} ->
94 {error, {no_function, Function}};
95 _ -> throw({error, {function_clause, ST}})
96 end
97 end,
David Reiss6b3e40f2008-06-11 00:59:03 +000098
David Reiss464e3002008-06-11 01:00:45 +000099 {reply, Result, State};
David Reiss2c534032008-06-11 00:58:00 +0000100
David Reiss464e3002008-06-11 01:00:45 +0000101handle_call(close, _From, State = #state{protocol = Protocol}) ->
102 {stop, shutdown, ok, State}.
David Reiss2c534032008-06-11 00:58:00 +0000103
104%%--------------------------------------------------------------------
105%% Function: handle_cast(Msg, State) -> {noreply, State} |
106%% {noreply, State, Timeout} |
107%% {stop, Reason, State}
108%% Description: Handling cast messages
109%%--------------------------------------------------------------------
110handle_cast(_Msg, State) ->
111 {noreply, State}.
112
113%%--------------------------------------------------------------------
114%% Function: handle_info(Info, State) -> {noreply, State} |
115%% {noreply, State, Timeout} |
116%% {stop, Reason, State}
117%% Description: Handling all non call/cast messages
118%%--------------------------------------------------------------------
119handle_info(_Info, State) ->
120 {noreply, State}.
121
122%%--------------------------------------------------------------------
123%% Function: terminate(Reason, State) -> void()
124%% Description: This function is called by a gen_server when it is about to
125%% terminate. It should be the opposite of Module:init/1 and do any necessary
126%% cleaning up. When it returns, the gen_server terminates with Reason.
127%% The return value is ignored.
128%%--------------------------------------------------------------------
David Reiss464e3002008-06-11 01:00:45 +0000129terminate(_Reason, State = #state{protocol = Protocol}) ->
130 thrift_protocol:close_transport(Protocol),
David Reiss2c534032008-06-11 00:58:00 +0000131 ok.
132
133%%--------------------------------------------------------------------
134%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
135%% Description: Convert process state when code is changed
136%%--------------------------------------------------------------------
137code_change(_OldVsn, State, _Extra) ->
138 {ok, State}.
139
140%%--------------------------------------------------------------------
141%%% Internal functions
142%%--------------------------------------------------------------------
143send_function_call(#state{protocol = Proto,
144 service = Service,
145 seqid = SeqId},
146 Function,
147 Args) ->
148 Params = Service:function_info(Function, params_type),
149 {struct, PList} = Params,
David Reissc5257452008-06-11 00:59:27 +0000150 if
151 length(PList) =/= length(Args) ->
David Reiss2c534032008-06-11 00:58:00 +0000152 throw({return, {error, {bad_args, Function, Args}}});
David Reissc5257452008-06-11 00:59:27 +0000153 true -> ok
David Reiss2c534032008-06-11 00:58:00 +0000154 end,
155
156 Begin = #protocol_message_begin{name = atom_to_list(Function),
157 type = ?tMessageType_CALL,
158 seqid = SeqId},
159 ok = thrift_protocol:write(Proto, Begin),
160 ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
161 ok = thrift_protocol:write(Proto, message_end),
162 thrift_protocol:flush_transport(Proto),
163 ok.
164
David Reiss2c534032008-06-11 00:58:00 +0000165receive_function_result(State = #state{protocol = Proto,
166 service = Service},
167 Function) ->
168 ResultType = Service:function_info(Function, reply_type),
169 read_result(State, Function, ResultType).
170
171read_result(_State,
172 _Function,
173 async_void) ->
174 {ok, ok};
175
176read_result(State = #state{protocol = Proto,
177 seqid = SeqId},
178 Function,
179 ReplyType) ->
180 case thrift_protocol:read(Proto, message_begin) of
181 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
182 {error, {bad_seq_id, SeqId}};
David Reiss6b3e40f2008-06-11 00:59:03 +0000183
David Reiss2c534032008-06-11 00:58:00 +0000184 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
185 handle_application_exception(State);
David Reiss6b3e40f2008-06-11 00:59:03 +0000186
David Reiss2c534032008-06-11 00:58:00 +0000187 #protocol_message_begin{type = ?tMessageType_REPLY} ->
188 handle_reply(State, Function, ReplyType)
189 end.
190
David Reiss2c534032008-06-11 00:58:00 +0000191handle_reply(State = #state{protocol = Proto,
192 service = Service},
193 Function,
194 ReplyType) ->
195 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
196 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
David Reiss2c534032008-06-11 00:58:00 +0000197 {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
198 ReplyList = tuple_to_list(Reply),
199 true = length(ReplyList) == length(ExceptionFields) + 1,
200 ExceptionVals = tl(ReplyList),
201 Thrown = [X || X <- ExceptionVals,
202 X =/= undefined],
David Reiss2c534032008-06-11 00:58:00 +0000203 Result =
204 case Thrown of
205 [] when ReplyType == {struct, []} ->
206 {ok, ok};
207 [] ->
208 {ok, hd(ReplyList)};
209 [Exception] ->
210 {exception, Exception}
211 end,
212 ok = thrift_protocol:read(Proto, message_end),
213 Result.
David Reiss6b3e40f2008-06-11 00:59:03 +0000214
David Reiss2c534032008-06-11 00:58:00 +0000215
David Reiss55ff70f2008-06-11 00:58:25 +0000216handle_application_exception(State = #state{protocol = Proto}) ->
217 {ok, Exception} = thrift_protocol:read(Proto,
218 ?TApplicationException_Structure),
219 ok = thrift_protocol:read(Proto, message_end),
220 XRecord = list_to_tuple(
221 ['TApplicationException' | tuple_to_list(Exception)]),
222 io:format("X: ~p~n", [XRecord]),
223 true = is_record(XRecord, 'TApplicationException'),
224 {exception, XRecord}.