blob: e1e78e77d65b5a17bb26dbf334ccd30d60bdac28 [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
13-export([start_link/3, call/3]).
14
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
43%%====================================================================
44%% gen_server callbacks
45%%====================================================================
46
47%%--------------------------------------------------------------------
48%% Function: init(Args) -> {ok, State} |
49%% {ok, State, Timeout} |
50%% ignore |
51%% {stop, Reason}
52%% Description: Initiates the server
53%%--------------------------------------------------------------------
54init([Host, Port, Service]) ->
55 {ok, Sock} = gen_tcp:connect(Host, Port,
56 [binary,
57 {packet, 0},
58 {active, false},
David Reiss6b3e40f2008-06-11 00:59:03 +000059 {nodelay, true}
60 ]),
61 {ok, Transport} = thrift_socket_transport:new(Sock),
David Reiss2c534032008-06-11 00:58:00 +000062 {ok, BufTransport} = thrift_buffered_transport:new(Transport),
David Reiss6b3e40f2008-06-11 00:59:03 +000063 {ok, Protocol} = thrift_binary_protocol:new(BufTransport),
David Reiss2c534032008-06-11 00:58:00 +000064 {ok, #state{service = Service,
65 protocol = Protocol,
66 seqid = 0}}.
67
68%%--------------------------------------------------------------------
69%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
70%% {reply, Reply, State, Timeout} |
71%% {noreply, State} |
72%% {noreply, State, Timeout} |
73%% {stop, Reason, Reply, State} |
74%% {stop, Reason, State}
75%% Description: Handling call messages
76%%--------------------------------------------------------------------
77handle_call({call, Function, Args}, _From, State = #state{service = Service,
78 protocol = Protocol,
79 seqid = SeqId}) ->
80 Result =
81 try
82 ok = send_function_call(State, Function, Args),
83 receive_function_result(State, Function)
84 catch
85 throw:{return, Return} ->
86 Return;
87 error:function_clause ->
88 ST = erlang:get_stacktrace(),
89 case hd(ST) of
90 {Service, function_info, [Function, _]} ->
91 {error, {no_function, Function}};
92 _ -> throw({error, {function_clause, ST}})
93 end
94 end,
David Reiss6b3e40f2008-06-11 00:59:03 +000095
David Reiss2c534032008-06-11 00:58:00 +000096 {reply, Result, State}.
97
98
99%%--------------------------------------------------------------------
100%% Function: handle_cast(Msg, State) -> {noreply, State} |
101%% {noreply, State, Timeout} |
102%% {stop, Reason, State}
103%% Description: Handling cast messages
104%%--------------------------------------------------------------------
105handle_cast(_Msg, State) ->
106 {noreply, State}.
107
108%%--------------------------------------------------------------------
109%% Function: handle_info(Info, State) -> {noreply, State} |
110%% {noreply, State, Timeout} |
111%% {stop, Reason, State}
112%% Description: Handling all non call/cast messages
113%%--------------------------------------------------------------------
114handle_info(_Info, State) ->
115 {noreply, State}.
116
117%%--------------------------------------------------------------------
118%% Function: terminate(Reason, State) -> void()
119%% Description: This function is called by a gen_server when it is about to
120%% terminate. It should be the opposite of Module:init/1 and do any necessary
121%% cleaning up. When it returns, the gen_server terminates with Reason.
122%% The return value is ignored.
123%%--------------------------------------------------------------------
124terminate(_Reason, _State) ->
125 ok.
126
127%%--------------------------------------------------------------------
128%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
129%% Description: Convert process state when code is changed
130%%--------------------------------------------------------------------
131code_change(_OldVsn, State, _Extra) ->
132 {ok, State}.
133
134%%--------------------------------------------------------------------
135%%% Internal functions
136%%--------------------------------------------------------------------
137send_function_call(#state{protocol = Proto,
138 service = Service,
139 seqid = SeqId},
140 Function,
141 Args) ->
142 Params = Service:function_info(Function, params_type),
143 {struct, PList} = Params,
David Reissc5257452008-06-11 00:59:27 +0000144 if
145 length(PList) =/= length(Args) ->
David Reiss2c534032008-06-11 00:58:00 +0000146 throw({return, {error, {bad_args, Function, Args}}});
David Reissc5257452008-06-11 00:59:27 +0000147 true -> ok
David Reiss2c534032008-06-11 00:58:00 +0000148 end,
149
150 Begin = #protocol_message_begin{name = atom_to_list(Function),
151 type = ?tMessageType_CALL,
152 seqid = SeqId},
153 ok = thrift_protocol:write(Proto, Begin),
154 ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
155 ok = thrift_protocol:write(Proto, message_end),
156 thrift_protocol:flush_transport(Proto),
157 ok.
158
David Reiss2c534032008-06-11 00:58:00 +0000159receive_function_result(State = #state{protocol = Proto,
160 service = Service},
161 Function) ->
162 ResultType = Service:function_info(Function, reply_type),
163 read_result(State, Function, ResultType).
164
165read_result(_State,
166 _Function,
167 async_void) ->
168 {ok, ok};
169
170read_result(State = #state{protocol = Proto,
171 seqid = SeqId},
172 Function,
173 ReplyType) ->
174 case thrift_protocol:read(Proto, message_begin) of
175 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
176 {error, {bad_seq_id, SeqId}};
David Reiss6b3e40f2008-06-11 00:59:03 +0000177
David Reiss2c534032008-06-11 00:58:00 +0000178 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
179 handle_application_exception(State);
David Reiss6b3e40f2008-06-11 00:59:03 +0000180
David Reiss2c534032008-06-11 00:58:00 +0000181 #protocol_message_begin{type = ?tMessageType_REPLY} ->
182 handle_reply(State, Function, ReplyType)
183 end.
184
David Reiss2c534032008-06-11 00:58:00 +0000185handle_reply(State = #state{protocol = Proto,
186 service = Service},
187 Function,
188 ReplyType) ->
189 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
190 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
David Reiss2c534032008-06-11 00:58:00 +0000191 {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
192 ReplyList = tuple_to_list(Reply),
193 true = length(ReplyList) == length(ExceptionFields) + 1,
194 ExceptionVals = tl(ReplyList),
195 Thrown = [X || X <- ExceptionVals,
196 X =/= undefined],
David Reiss2c534032008-06-11 00:58:00 +0000197 Result =
198 case Thrown of
199 [] when ReplyType == {struct, []} ->
200 {ok, ok};
201 [] ->
202 {ok, hd(ReplyList)};
203 [Exception] ->
204 {exception, Exception}
205 end,
206 ok = thrift_protocol:read(Proto, message_end),
207 Result.
David Reiss6b3e40f2008-06-11 00:59:03 +0000208
David Reiss2c534032008-06-11 00:58:00 +0000209
David Reiss55ff70f2008-06-11 00:58:25 +0000210handle_application_exception(State = #state{protocol = Proto}) ->
211 {ok, Exception} = thrift_protocol:read(Proto,
212 ?TApplicationException_Structure),
213 ok = thrift_protocol:read(Proto, message_end),
214 XRecord = list_to_tuple(
215 ['TApplicationException' | tuple_to_list(Exception)]),
216 io:format("X: ~p~n", [XRecord]),
217 true = is_record(XRecord, 'TApplicationException'),
218 {exception, XRecord}.