blob: 5c74adc75b85386b3951f224ae5858e027c04991 [file] [log] [blame]
%%
%% Licensed to the Apache Software Foundation (ASF) under one
%% or more contributor license agreements. See the NOTICE file
%% distributed with this work for additional information
%% regarding copyright ownership. The ASF licenses this file
%% to you under the Apache License, Version 2.0 (the
%% "License"); you may not use this file except in compliance
%% with the License. You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
-module(thrift_client).
%% API
-export([new/2, call/3, send_call/3, close/1]).
-include("thrift_constants.hrl").
-include("thrift_protocol.hrl").
-record(tclient, {service, protocol, seqid}).
new(Protocol, Service)
when is_atom(Service) ->
{ok, #tclient{protocol = Protocol,
service = Service,
seqid = 0}}.
-spec call(#tclient{}, atom(), list()) -> {#tclient{}, {ok, any()} | {error, any()}}.
call(Client = #tclient{}, Function, Args)
when is_atom(Function), is_list(Args) ->
case send_function_call(Client, Function, Args) of
{Client1, ok} ->
receive_function_result(Client1, Function);
Else ->
Else
end.
%% Sends a function call but does not read the result. This is useful
%% if you're trying to log non-oneway function calls to write-only
%% transports like thrift_disk_log_transport.
-spec send_call(#tclient{}, atom(), list()) -> {#tclient{}, ok}.
send_call(Client = #tclient{}, Function, Args)
when is_atom(Function), is_list(Args) ->
send_function_call(Client, Function, Args).
-spec close(#tclient{}) -> ok.
close(#tclient{protocol=Protocol}) ->
thrift_protocol:close_transport(Protocol).
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-spec send_function_call(#tclient{}, atom(), list()) -> {#tclient{}, ok | {error, any()}}.
send_function_call(Client = #tclient{protocol = Proto0,
service = Service,
seqid = SeqId},
Function,
Args) ->
Params = Service:function_info(Function, params_type),
case Params of
no_function ->
{Client, {error, {no_function, Function}}};
{struct, PList} when length(PList) =/= length(Args) ->
{Client, {error, {bad_args, Function, Args}}};
{struct, _PList} ->
Begin = #protocol_message_begin{name = atom_to_list(Function),
type = ?tMessageType_CALL,
seqid = SeqId},
{Proto1, ok} = thrift_protocol:write(Proto0, Begin),
{Proto2, ok} = thrift_protocol:write(Proto1, {Params, list_to_tuple([Function | Args])}),
{Proto3, ok} = thrift_protocol:write(Proto2, message_end),
{Proto4, ok} = thrift_protocol:flush_transport(Proto3),
{Client#tclient{protocol = Proto4}, ok}
end.
-spec receive_function_result(#tclient{}, atom()) -> {#tclient{}, {ok, any()} | {error, any()}}.
receive_function_result(Client = #tclient{service = Service}, Function) ->
ResultType = Service:function_info(Function, reply_type),
read_result(Client, Function, ResultType).
read_result(Client, _Function, oneway_void) ->
{Client, {ok, ok}};
read_result(Client = #tclient{protocol = Proto0,
seqid = SeqId},
Function,
ReplyType) ->
{Proto1, MessageBegin} = thrift_protocol:read(Proto0, message_begin),
NewClient = Client#tclient{protocol = Proto1},
case MessageBegin of
#protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
{NewClient, {error, {bad_seq_id, SeqId}}};
#protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
handle_application_exception(NewClient);
#protocol_message_begin{type = ?tMessageType_REPLY} ->
handle_reply(NewClient, Function, ReplyType)
end.
handle_reply(Client = #tclient{protocol = Proto0,
service = Service},
Function,
ReplyType) ->
{struct, ExceptionFields} = Service:function_info(Function, exceptions),
ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
{Proto1, {ok, Reply}} = thrift_protocol:read(Proto0, ReplyStructDef),
{Proto2, ok} = thrift_protocol:read(Proto1, message_end),
NewClient = Client#tclient{protocol = Proto2},
ReplyList = tuple_to_list(Reply),
true = length(ReplyList) == length(ExceptionFields) + 1,
ExceptionVals = tl(ReplyList),
Thrown = [X || X <- ExceptionVals,
X =/= undefined],
case Thrown of
[] when ReplyType == {struct, []} ->
{NewClient, {ok, ok}};
[] ->
{NewClient, {ok, hd(ReplyList)}};
[Exception] ->
throw({NewClient, {exception, Exception}})
end.
handle_application_exception(Client = #tclient{protocol = Proto0}) ->
{Proto1, {ok, Exception}} =
thrift_protocol:read(Proto0, ?TApplicationException_Structure),
{Proto2, ok} = thrift_protocol:read(Proto1, message_end),
XRecord = list_to_tuple(
['TApplicationException' | tuple_to_list(Exception)]),
error_logger:error_msg("X: ~p~n", [XRecord]),
true = is_record(XRecord, 'TApplicationException'),
NewClient = Client#tclient{protocol = Proto2},
throw({NewClient, {exception, XRecord}}).