blob: 3c6a1670afd5745322dcd6fe9d41dcee9f938538 [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) ->
47 gen_server:call(Client, close).
48
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 Reiss464e3002008-06-11 01:00:45 +0000107 {reply, Result, State};
David Reiss2c534032008-06-11 00:58:00 +0000108
David Reiss464e3002008-06-11 01:00:45 +0000109handle_call(close, _From, State = #state{protocol = Protocol}) ->
110 {stop, shutdown, ok, State}.
David Reiss2c534032008-06-11 00:58:00 +0000111
112%%--------------------------------------------------------------------
113%% Function: handle_cast(Msg, State) -> {noreply, State} |
114%% {noreply, State, Timeout} |
115%% {stop, Reason, State}
116%% Description: Handling cast messages
117%%--------------------------------------------------------------------
118handle_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 Reiss464e3002008-06-11 01:00:45 +0000137terminate(_Reason, State = #state{protocol = Protocol}) ->
138 thrift_protocol:close_transport(Protocol),
David Reiss2c534032008-06-11 00:58:00 +0000139 ok.
140
141%%--------------------------------------------------------------------
142%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
143%% Description: Convert process state when code is changed
144%%--------------------------------------------------------------------
145code_change(_OldVsn, State, _Extra) ->
146 {ok, State}.
147
148%%--------------------------------------------------------------------
149%%% Internal functions
150%%--------------------------------------------------------------------
151send_function_call(#state{protocol = Proto,
152 service = Service,
153 seqid = SeqId},
154 Function,
155 Args) ->
156 Params = Service:function_info(Function, params_type),
157 {struct, PList} = Params,
David Reissc5257452008-06-11 00:59:27 +0000158 if
159 length(PList) =/= length(Args) ->
David Reiss2c534032008-06-11 00:58:00 +0000160 throw({return, {error, {bad_args, Function, Args}}});
David Reissc5257452008-06-11 00:59:27 +0000161 true -> ok
David Reiss2c534032008-06-11 00:58:00 +0000162 end,
163
164 Begin = #protocol_message_begin{name = atom_to_list(Function),
165 type = ?tMessageType_CALL,
166 seqid = SeqId},
167 ok = thrift_protocol:write(Proto, Begin),
168 ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
169 ok = thrift_protocol:write(Proto, message_end),
170 thrift_protocol:flush_transport(Proto),
171 ok.
172
David Reiss2c534032008-06-11 00:58:00 +0000173receive_function_result(State = #state{protocol = Proto,
174 service = Service},
175 Function) ->
176 ResultType = Service:function_info(Function, reply_type),
177 read_result(State, Function, ResultType).
178
179read_result(_State,
180 _Function,
181 async_void) ->
182 {ok, ok};
183
184read_result(State = #state{protocol = Proto,
185 seqid = SeqId},
186 Function,
187 ReplyType) ->
188 case thrift_protocol:read(Proto, message_begin) of
189 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
190 {error, {bad_seq_id, SeqId}};
David Reiss6b3e40f2008-06-11 00:59:03 +0000191
David Reiss2c534032008-06-11 00:58:00 +0000192 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
193 handle_application_exception(State);
David Reiss6b3e40f2008-06-11 00:59:03 +0000194
David Reiss2c534032008-06-11 00:58:00 +0000195 #protocol_message_begin{type = ?tMessageType_REPLY} ->
196 handle_reply(State, Function, ReplyType)
197 end.
198
David Reiss2c534032008-06-11 00:58:00 +0000199handle_reply(State = #state{protocol = Proto,
200 service = Service},
201 Function,
202 ReplyType) ->
203 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
204 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
David Reiss2c534032008-06-11 00:58:00 +0000205 {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
206 ReplyList = tuple_to_list(Reply),
207 true = length(ReplyList) == length(ExceptionFields) + 1,
208 ExceptionVals = tl(ReplyList),
209 Thrown = [X || X <- ExceptionVals,
210 X =/= undefined],
David Reiss2c534032008-06-11 00:58:00 +0000211 Result =
212 case Thrown of
213 [] when ReplyType == {struct, []} ->
214 {ok, ok};
215 [] ->
216 {ok, hd(ReplyList)};
217 [Exception] ->
218 {exception, Exception}
219 end,
220 ok = thrift_protocol:read(Proto, message_end),
221 Result.
David Reiss6b3e40f2008-06-11 00:59:03 +0000222
David Reiss2c534032008-06-11 00:58:00 +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)]),
230 io:format("X: ~p~n", [XRecord]),
231 true = is_record(XRecord, 'TApplicationException'),
232 {exception, XRecord}.