David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 1 | %%%------------------------------------------------------------------- |
| 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 Reiss | 4fd7818 | 2008-06-11 01:01:13 +0000 | [diff] [blame] | 13 | -export([start_link/3, start_link/4, call/3, close/1]). |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 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 | |
David Reiss | 914ebb4 | 2008-06-11 01:01:48 +0000 | [diff] [blame] | 23 | -record(state, {service, protocol, seqid, |
| 24 | strict_read = true, |
| 25 | strict_write = true, |
| 26 | framed = false, |
| 27 | connect_timeout = infinity |
| 28 | }). |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 29 | |
| 30 | %%==================================================================== |
| 31 | %% API |
| 32 | %%==================================================================== |
| 33 | %%-------------------------------------------------------------------- |
| 34 | %% Function: start_link() -> {ok,Pid} | ignore | {error,Error} |
| 35 | %% Description: Starts the server |
| 36 | %%-------------------------------------------------------------------- |
David Reiss | 4fd7818 | 2008-06-11 01:01:13 +0000 | [diff] [blame] | 37 | start_link(Host, Port, Service) -> |
David Reiss | 914ebb4 | 2008-06-11 01:01:48 +0000 | [diff] [blame] | 38 | start_link(Host, Port, Service, []). |
David Reiss | 4fd7818 | 2008-06-11 01:01:13 +0000 | [diff] [blame] | 39 | |
David Reiss | 914ebb4 | 2008-06-11 01:01:48 +0000 | [diff] [blame] | 40 | start_link(Host, Port, Service, Options) when is_integer(Port), is_atom(Service), is_list(Options) -> |
| 41 | gen_server:start_link(?MODULE, [Host, Port, Service, Options], []). |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 42 | |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 43 | call(Client, Function, Args) |
| 44 | when is_pid(Client), is_atom(Function), is_list(Args) -> |
| 45 | case gen_server:call(Client, {call, Function, Args}) of |
| 46 | R = {ok, _} -> R; |
| 47 | R = {error, _} -> R; |
| 48 | {exception, Exception} -> throw(Exception) |
| 49 | end. |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 50 | |
David Reiss | 464e300 | 2008-06-11 01:00:45 +0000 | [diff] [blame] | 51 | close(Client) when is_pid(Client) -> |
David Reiss | 6f1cd53 | 2008-06-11 01:01:21 +0000 | [diff] [blame] | 52 | gen_server:cast(Client, close). |
David Reiss | 464e300 | 2008-06-11 01:00:45 +0000 | [diff] [blame] | 53 | |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 54 | %%==================================================================== |
| 55 | %% gen_server callbacks |
| 56 | %%==================================================================== |
| 57 | |
| 58 | %%-------------------------------------------------------------------- |
| 59 | %% Function: init(Args) -> {ok, State} | |
| 60 | %% {ok, State, Timeout} | |
| 61 | %% ignore | |
| 62 | %% {stop, Reason} |
| 63 | %% Description: Initiates the server |
| 64 | %%-------------------------------------------------------------------- |
David Reiss | 914ebb4 | 2008-06-11 01:01:48 +0000 | [diff] [blame] | 65 | init([Host, Port, Service, Options]) -> |
| 66 | State = parse_options(Options, #state{}), |
David Reiss | 4fd7818 | 2008-06-11 01:01:13 +0000 | [diff] [blame] | 67 | |
David Reiss | c920faf | 2008-06-11 01:01:56 +0000 | [diff] [blame] | 68 | TcpOptions = [binary, |
| 69 | {packet, 0}, |
| 70 | {active, false}, |
| 71 | {nodelay, true}], |
| 72 | TcpTimeout = State#state.connect_timeout, |
David Reiss | 4fd7818 | 2008-06-11 01:01:13 +0000 | [diff] [blame] | 73 | |
David Reiss | 672968d | 2008-06-11 01:02:03 +0000 | [diff] [blame^] | 74 | case catch gen_tcp:connect(Host, Port, TcpOptions, TcpTimeout) of |
David Reiss | c920faf | 2008-06-11 01:01:56 +0000 | [diff] [blame] | 75 | {ok, Sock} -> |
| 76 | {ok, Transport} = thrift_socket_transport:new(Sock), |
| 77 | {ok, BufTransport} = |
| 78 | case State#state.framed of |
| 79 | true -> thrift_framed_transport:new(Transport); |
| 80 | false -> thrift_buffered_transport:new(Transport) |
| 81 | end, |
| 82 | {ok, Protocol} = thrift_binary_protocol:new(BufTransport, |
David Reiss | 672968d | 2008-06-11 01:02:03 +0000 | [diff] [blame^] | 83 | [{strict_read, State#state.strict_read}, |
| 84 | {strict_write, State#state.strict_write}]), |
David Reiss | 914ebb4 | 2008-06-11 01:01:48 +0000 | [diff] [blame] | 85 | |
David Reiss | c920faf | 2008-06-11 01:01:56 +0000 | [diff] [blame] | 86 | {ok, State#state{service = Service, |
| 87 | protocol = Protocol, |
| 88 | seqid = 0}}; |
| 89 | Error -> |
| 90 | {stop, Error} |
| 91 | end. |
David Reiss | 914ebb4 | 2008-06-11 01:01:48 +0000 | [diff] [blame] | 92 | |
| 93 | parse_options([], State) -> |
| 94 | State; |
| 95 | parse_options([{strict_read, Bool} | Rest], State) when is_boolean(Bool) -> |
| 96 | parse_options(Rest, State#state{strict_read=Bool}); |
| 97 | parse_options([{strict_write, Bool} | Rest], State) when is_boolean(Bool) -> |
| 98 | parse_options(Rest, State#state{strict_write=Bool}); |
| 99 | parse_options([{framed, Bool} | Rest], State) when is_boolean(Bool) -> |
| 100 | parse_options(Rest, State#state{framed=Bool}); |
| 101 | parse_options([{connect_timeout, TO} | Rest], State) when TO =:= infinity; is_integer(TO) -> |
| 102 | parse_options(Rest, State#state{connect_timeout=TO}). |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 103 | |
| 104 | %%-------------------------------------------------------------------- |
| 105 | %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | |
| 106 | %% {reply, Reply, State, Timeout} | |
| 107 | %% {noreply, State} | |
| 108 | %% {noreply, State, Timeout} | |
| 109 | %% {stop, Reason, Reply, State} | |
| 110 | %% {stop, Reason, State} |
| 111 | %% Description: Handling call messages |
| 112 | %%-------------------------------------------------------------------- |
| 113 | handle_call({call, Function, Args}, _From, State = #state{service = Service, |
| 114 | protocol = Protocol, |
| 115 | seqid = SeqId}) -> |
| 116 | Result = |
| 117 | try |
| 118 | ok = send_function_call(State, Function, Args), |
| 119 | receive_function_result(State, Function) |
| 120 | catch |
| 121 | throw:{return, Return} -> |
| 122 | Return; |
| 123 | error:function_clause -> |
| 124 | ST = erlang:get_stacktrace(), |
| 125 | case hd(ST) of |
| 126 | {Service, function_info, [Function, _]} -> |
| 127 | {error, {no_function, Function}}; |
| 128 | _ -> throw({error, {function_clause, ST}}) |
| 129 | end |
| 130 | end, |
David Reiss | 6b3e40f | 2008-06-11 00:59:03 +0000 | [diff] [blame] | 131 | |
David Reiss | 6f1cd53 | 2008-06-11 01:01:21 +0000 | [diff] [blame] | 132 | {reply, Result, State}. |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 133 | |
| 134 | %%-------------------------------------------------------------------- |
| 135 | %% Function: handle_cast(Msg, State) -> {noreply, State} | |
| 136 | %% {noreply, State, Timeout} | |
| 137 | %% {stop, Reason, State} |
| 138 | %% Description: Handling cast messages |
| 139 | %%-------------------------------------------------------------------- |
David Reiss | 6f1cd53 | 2008-06-11 01:01:21 +0000 | [diff] [blame] | 140 | handle_cast(close, State=#state{protocol = Protocol}) -> |
| 141 | %% error_logger:info_msg("thrift_client ~p received close", [self()]), |
| 142 | {stop,normal,State}; |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 143 | handle_cast(_Msg, State) -> |
| 144 | {noreply, State}. |
| 145 | |
| 146 | %%-------------------------------------------------------------------- |
| 147 | %% Function: handle_info(Info, State) -> {noreply, State} | |
| 148 | %% {noreply, State, Timeout} | |
| 149 | %% {stop, Reason, State} |
| 150 | %% Description: Handling all non call/cast messages |
| 151 | %%-------------------------------------------------------------------- |
| 152 | handle_info(_Info, State) -> |
| 153 | {noreply, State}. |
| 154 | |
| 155 | %%-------------------------------------------------------------------- |
| 156 | %% Function: terminate(Reason, State) -> void() |
| 157 | %% Description: This function is called by a gen_server when it is about to |
| 158 | %% terminate. It should be the opposite of Module:init/1 and do any necessary |
| 159 | %% cleaning up. When it returns, the gen_server terminates with Reason. |
| 160 | %% The return value is ignored. |
| 161 | %%-------------------------------------------------------------------- |
David Reiss | 6f1cd53 | 2008-06-11 01:01:21 +0000 | [diff] [blame] | 162 | terminate(Reason, State = #state{protocol = Protocol}) -> |
| 163 | %% error_logger:info_msg("thrift_client ~p terminating due to ~p", [self(), Reason]), |
David Reiss | 464e300 | 2008-06-11 01:00:45 +0000 | [diff] [blame] | 164 | thrift_protocol:close_transport(Protocol), |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 165 | ok. |
| 166 | |
| 167 | %%-------------------------------------------------------------------- |
| 168 | %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} |
| 169 | %% Description: Convert process state when code is changed |
| 170 | %%-------------------------------------------------------------------- |
| 171 | code_change(_OldVsn, State, _Extra) -> |
| 172 | {ok, State}. |
| 173 | |
| 174 | %%-------------------------------------------------------------------- |
| 175 | %%% Internal functions |
| 176 | %%-------------------------------------------------------------------- |
| 177 | send_function_call(#state{protocol = Proto, |
| 178 | service = Service, |
| 179 | seqid = SeqId}, |
| 180 | Function, |
| 181 | Args) -> |
| 182 | Params = Service:function_info(Function, params_type), |
| 183 | {struct, PList} = Params, |
David Reiss | c525745 | 2008-06-11 00:59:27 +0000 | [diff] [blame] | 184 | if |
| 185 | length(PList) =/= length(Args) -> |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 186 | throw({return, {error, {bad_args, Function, Args}}}); |
David Reiss | c525745 | 2008-06-11 00:59:27 +0000 | [diff] [blame] | 187 | true -> ok |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 188 | end, |
| 189 | |
| 190 | Begin = #protocol_message_begin{name = atom_to_list(Function), |
| 191 | type = ?tMessageType_CALL, |
| 192 | seqid = SeqId}, |
| 193 | ok = thrift_protocol:write(Proto, Begin), |
| 194 | ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}), |
| 195 | ok = thrift_protocol:write(Proto, message_end), |
| 196 | thrift_protocol:flush_transport(Proto), |
| 197 | ok. |
| 198 | |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 199 | receive_function_result(State = #state{protocol = Proto, |
| 200 | service = Service}, |
| 201 | Function) -> |
| 202 | ResultType = Service:function_info(Function, reply_type), |
| 203 | read_result(State, Function, ResultType). |
| 204 | |
| 205 | read_result(_State, |
| 206 | _Function, |
| 207 | async_void) -> |
| 208 | {ok, ok}; |
| 209 | |
| 210 | read_result(State = #state{protocol = Proto, |
| 211 | seqid = SeqId}, |
| 212 | Function, |
| 213 | ReplyType) -> |
| 214 | case thrift_protocol:read(Proto, message_begin) of |
| 215 | #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId -> |
| 216 | {error, {bad_seq_id, SeqId}}; |
David Reiss | 6b3e40f | 2008-06-11 00:59:03 +0000 | [diff] [blame] | 217 | |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 218 | #protocol_message_begin{type = ?tMessageType_EXCEPTION} -> |
| 219 | handle_application_exception(State); |
David Reiss | 6b3e40f | 2008-06-11 00:59:03 +0000 | [diff] [blame] | 220 | |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 221 | #protocol_message_begin{type = ?tMessageType_REPLY} -> |
| 222 | handle_reply(State, Function, ReplyType) |
| 223 | end. |
| 224 | |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 225 | handle_reply(State = #state{protocol = Proto, |
| 226 | service = Service}, |
| 227 | Function, |
| 228 | ReplyType) -> |
| 229 | {struct, ExceptionFields} = Service:function_info(Function, exceptions), |
| 230 | ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields}, |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 231 | {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef), |
| 232 | ReplyList = tuple_to_list(Reply), |
| 233 | true = length(ReplyList) == length(ExceptionFields) + 1, |
| 234 | ExceptionVals = tl(ReplyList), |
| 235 | Thrown = [X || X <- ExceptionVals, |
| 236 | X =/= undefined], |
David Reiss | 2c53403 | 2008-06-11 00:58:00 +0000 | [diff] [blame] | 237 | Result = |
| 238 | case Thrown of |
| 239 | [] when ReplyType == {struct, []} -> |
| 240 | {ok, ok}; |
| 241 | [] -> |
| 242 | {ok, hd(ReplyList)}; |
| 243 | [Exception] -> |
| 244 | {exception, Exception} |
| 245 | end, |
| 246 | ok = thrift_protocol:read(Proto, message_end), |
| 247 | Result. |
David Reiss | 6b3e40f | 2008-06-11 00:59:03 +0000 | [diff] [blame] | 248 | |
David Reiss | 55ff70f | 2008-06-11 00:58:25 +0000 | [diff] [blame] | 249 | handle_application_exception(State = #state{protocol = Proto}) -> |
| 250 | {ok, Exception} = thrift_protocol:read(Proto, |
| 251 | ?TApplicationException_Structure), |
| 252 | ok = thrift_protocol:read(Proto, message_end), |
| 253 | XRecord = list_to_tuple( |
| 254 | ['TApplicationException' | tuple_to_list(Exception)]), |
David Reiss | 1af1868 | 2008-06-11 01:01:36 +0000 | [diff] [blame] | 255 | error_logger:error_msg("X: ~p~n", [XRecord]), |
David Reiss | 55ff70f | 2008-06-11 00:58:25 +0000 | [diff] [blame] | 256 | true = is_record(XRecord, 'TApplicationException'), |
| 257 | {exception, XRecord}. |