Add a thrift client to alterl bindings
Test plan:
f(), {ok, C} = thrift_client:start_link("localhost", 9090, thriftTest_thrift).
thrift_client:call(C, testAsync, [1234]).
thrift_client:call(C, testVoid, []).
thrift_client:call(C, testList, [[1,2,3,4,5]]).
- Should write an actual test suite that exercises all of ThriftTest but I'm lazy right now
git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@666418 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/alterl/src/thrift_client.erl b/lib/alterl/src/thrift_client.erl
new file mode 100644
index 0000000..9553e07
--- /dev/null
+++ b/lib/alterl/src/thrift_client.erl
@@ -0,0 +1,218 @@
+%%%-------------------------------------------------------------------
+%%% File : thrift_client.erl
+%%% Author : Todd Lipcon <todd@lipcon.org>
+%%% Description : A client which connects to a thrift service
+%%%
+%%% Created : 24 Feb 2008 by Todd Lipcon <todd@lipcon.org>
+%%%-------------------------------------------------------------------
+-module(thrift_client).
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/3, call/3]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+
+-include("thrift_constants.hrl").
+-include("thrift_protocol.hrl").
+
+-record(state, {service, protocol, seqid}).
+
+%%====================================================================
+%% API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+%% Description: Starts the server
+%%--------------------------------------------------------------------
+start_link(Host, Port, Service) when is_integer(Port), is_atom(Service) ->
+ gen_server:start_link(?MODULE, [Host, Port, Service], []).
+
+
+call(Client, Function, Args)
+ when is_pid(Client), is_atom(Function), is_list(Args) ->
+ case gen_server:call(Client, {call, Function, Args}) of
+ R = {ok, _} -> R;
+ R = {error, _} -> R;
+ {exception, Exception} -> throw(Exception)
+ end.
+
+
+
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% Description: Initiates the server
+%%--------------------------------------------------------------------
+init([Host, Port, Service]) ->
+ {ok, Sock} = gen_tcp:connect(Host, Port,
+ [binary,
+ {packet, 0},
+ {active, false},
+ {nodelay, true}]),
+ {ok, Transport} = thrift_socket_transport:new(Sock),
+ {ok, BufTransport} = thrift_buffered_transport:new(Transport),
+ {ok, Protocol} = thrift_binary_protocol:new(BufTransport),
+ {ok, #state{service = Service,
+ protocol = Protocol,
+ seqid = 0}}.
+
+%%--------------------------------------------------------------------
+%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call({call, Function, Args}, _From, State = #state{service = Service,
+ protocol = Protocol,
+ seqid = SeqId}) ->
+ Result =
+ try
+ ok = send_function_call(State, Function, Args),
+ receive_function_result(State, Function)
+ catch
+ throw:{return, Return} ->
+ Return;
+ error:function_clause ->
+ ST = erlang:get_stacktrace(),
+ case hd(ST) of
+ {Service, function_info, [Function, _]} ->
+ {error, {no_function, Function}};
+ _ -> throw({error, {function_clause, ST}})
+ end
+ end,
+
+ {reply, Result, State}.
+
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+send_function_call(#state{protocol = Proto,
+ service = Service,
+ seqid = SeqId},
+ Function,
+ Args) ->
+ Params = Service:function_info(Function, params_type),
+ {struct, PList} = Params,
+ case length(PList) of
+ N when N =/= length(Args) ->
+ throw({return, {error, {bad_args, Function, Args}}});
+ _ -> ok
+ end,
+
+ Begin = #protocol_message_begin{name = atom_to_list(Function),
+ type = ?tMessageType_CALL,
+ seqid = SeqId},
+ ok = thrift_protocol:write(Proto, Begin),
+ ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}),
+ ok = thrift_protocol:write(Proto, message_end),
+ thrift_protocol:flush_transport(Proto),
+ ok.
+
+
+receive_function_result(State = #state{protocol = Proto,
+ service = Service},
+ Function) ->
+ ResultType = Service:function_info(Function, reply_type),
+ read_result(State, Function, ResultType).
+
+read_result(_State,
+ _Function,
+ async_void) ->
+ {ok, ok};
+
+read_result(State = #state{protocol = Proto,
+ seqid = SeqId},
+ Function,
+ ReplyType) ->
+ case thrift_protocol:read(Proto, message_begin) of
+ #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
+ {error, {bad_seq_id, SeqId}};
+
+ #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
+ handle_application_exception(State);
+
+ #protocol_message_begin{type = ?tMessageType_REPLY} ->
+ handle_reply(State, Function, ReplyType)
+ end.
+
+
+handle_reply(State = #state{protocol = Proto,
+ service = Service},
+ Function,
+ ReplyType) ->
+ {struct, ExceptionFields} = Service:function_info(Function, exceptions),
+ ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
+ io:format("RSD: ~p~n", [ReplyStructDef]),
+ {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef),
+ ReplyList = tuple_to_list(Reply),
+ true = length(ReplyList) == length(ExceptionFields) + 1,
+ ExceptionVals = tl(ReplyList),
+ Thrown = [X || X <- ExceptionVals,
+ X =/= undefined],
+ io:format("RL: ~p~nEV:~p~n", [ReplyList, ExceptionVals]),
+ Result =
+ case Thrown of
+ [] when ReplyType == {struct, []} ->
+ {ok, ok};
+ [] ->
+ {ok, hd(ReplyList)};
+ [Exception] ->
+ {exception, Exception}
+ end,
+ ok = thrift_protocol:read(Proto, message_end),
+ Result.
+
+
+handle_application_exception(_State) ->
+ not_yet_impl.