blob: f5f4415e59bec2736cde08b38987452736bd7b9a [file] [log] [blame]
David Reissea2cba82009-03-30 21:35:00 +00001%%
2%% Licensed to the Apache Software Foundation (ASF) under one
3%% or more contributor license agreements. See the NOTICE file
4%% distributed with this work for additional information
5%% regarding copyright ownership. The ASF licenses this file
6%% to you under the Apache License, Version 2.0 (the
7%% "License"); you may not use this file except in compliance
8%% with the License. You may obtain a copy of the License at
9%%
10%% http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing,
13%% software distributed under the License is distributed on an
14%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15%% KIND, either express or implied. See the License for the
16%% specific language governing permissions and limitations
17%% under the License.
18%%
19
David Reiss2c534032008-06-11 00:58:00 +000020-module(thrift_client).
21
David Reiss2c534032008-06-11 00:58:00 +000022%% API
David Reiss3f660a42010-08-30 22:05:29 +000023-export([new/2, call/3, send_call/3, close/1]).
David Reiss2c534032008-06-11 00:58:00 +000024
25-include("thrift_constants.hrl").
26-include("thrift_protocol.hrl").
27
Sergei Elin45764092022-09-23 23:21:31 +030028-record(tclient, {
29 service :: module(),
30 protocol :: thrift_protocol:state(),
31 seqid :: non_neg_integer()
32}).
33-type tclient() :: #tclient{}.
Björn Svenssond966d662025-06-04 16:51:05 +020034-export_type([tclient/0]).
David Reissfc427af2008-06-11 01:11:57 +000035
Sergei Elin45764092022-09-23 23:21:31 +030036new(Protocol, Service) when
37 is_atom(Service)
38->
39 {ok, #tclient{
40 protocol = Protocol,
41 service = Service,
42 seqid = 0
43 }}.
David Reiss5e530af2009-06-04 02:01:24 +000044
David Reissf4494ee2010-08-30 22:06:03 +000045-spec call(#tclient{}, atom(), list()) -> {#tclient{}, {ok, any()} | {error, any()}}.
Sergei Elin45764092022-09-23 23:21:31 +030046call(Client = #tclient{}, Function, Args) when
47 is_atom(Function), is_list(Args)
48->
49 case send_function_call(Client, Function, Args) of
50 {ok, Client1} -> receive_function_result(Client1, Function);
51 {{error, X}, Client1} -> {Client1, {error, X}}
52 end.
David Reissa2f45972008-06-11 01:13:33 +000053
David Reiss65cf7202008-06-11 01:12:20 +000054%% Sends a function call but does not read the result. This is useful
David Reissc51986f2009-03-24 20:01:25 +000055%% if you're trying to log non-oneway function calls to write-only
David Reiss65cf7202008-06-11 01:12:20 +000056%% transports like thrift_disk_log_transport.
David Reiss3f660a42010-08-30 22:05:29 +000057-spec send_call(#tclient{}, atom(), list()) -> {#tclient{}, ok}.
Sergei Elin45764092022-09-23 23:21:31 +030058send_call(Client = #tclient{}, Function, Args) when
59 is_atom(Function), is_list(Args)
60->
alisdair sullivan7bdba5c2014-09-30 22:03:34 -070061 case send_function_call(Client, Function, Args) of
Sergei Elin45764092022-09-23 23:21:31 +030062 {ok, Client1} -> {Client1, ok};
63 Else -> Else
alisdair sullivan7bdba5c2014-09-30 22:03:34 -070064 end.
David Reiss65cf7202008-06-11 01:12:20 +000065
David Reiss3f660a42010-08-30 22:05:29 +000066-spec close(#tclient{}) -> ok.
Sergei Elin45764092022-09-23 23:21:31 +030067close(#tclient{protocol = Protocol}) ->
David Reiss3f660a42010-08-30 22:05:29 +000068 thrift_protocol:close_transport(Protocol).
David Reiss464e3002008-06-11 01:00:45 +000069
David Reiss2c534032008-06-11 00:58:00 +000070%%--------------------------------------------------------------------
71%%% Internal functions
72%%--------------------------------------------------------------------
Андрей Веселов54790992015-08-26 17:52:19 +030073-spec send_function_call(#tclient{}, atom(), list()) -> {ok | {error, any()}, #tclient{}}.
alisdair sullivan7bdba5c2014-09-30 22:03:34 -070074send_function_call(Client = #tclient{service = Service}, Function, Args) ->
Sergei Elin45764092022-09-23 23:21:31 +030075 {Params, Reply} =
76 try
77 {
78 Service:function_info(Function, params_type),
79 Service:function_info(Function, reply_type)
80 }
81 catch
82 error:function_clause -> {no_function, 0}
83 end,
84 MsgType =
85 case Reply of
86 oneway_void -> ?tMessageType_ONEWAY;
87 _ -> ?tMessageType_CALL
88 end,
89 case Params of
90 no_function ->
91 {{error, {no_function, Function}}, Client};
92 {struct, PList} when length(PList) =/= length(Args) ->
93 {{error, {bad_args, Function, Args}}, Client};
94 {struct, _PList} ->
95 write_message(Client, Function, Args, Params, MsgType)
96 end.
alisdair sullivan7bdba5c2014-09-30 22:03:34 -070097
98-spec write_message(#tclient{}, atom(), list(), {struct, list()}, integer()) ->
Sergei Elin45764092022-09-23 23:21:31 +030099 {ok | {error, any()}, #tclient{}}.
alisdair sullivan7bdba5c2014-09-30 22:03:34 -0700100write_message(Client = #tclient{protocol = P0, seqid = Seq}, Function, Args, Params, MsgType) ->
Sergei Elin45764092022-09-23 23:21:31 +0300101 try
102 {P1, ok} = thrift_protocol:write(P0, #protocol_message_begin{
103 name = atom_to_list(Function),
104 type = MsgType,
105 seqid = Seq
106 }),
107 {P2, ok} = thrift_protocol:write(P1, {Params, list_to_tuple([Function | Args])}),
108 {P3, ok} = thrift_protocol:write(P2, message_end),
109 {P4, ok} = thrift_protocol:flush_transport(P3),
110 {ok, Client#tclient{protocol = P4}}
111 catch
112 error:{badmatch, {_, {error, _} = Error}} -> {Error, Client}
113 end.
David Reiss2c534032008-06-11 00:58:00 +0000114
David Reissf4494ee2010-08-30 22:06:03 +0000115-spec receive_function_result(#tclient{}, atom()) -> {#tclient{}, {ok, any()} | {error, any()}}.
David Reiss3f660a42010-08-30 22:05:29 +0000116receive_function_result(Client = #tclient{service = Service}, Function) ->
Bryan Duxburyd3879f82010-08-19 05:06:02 +0000117 ResultType = Service:function_info(Function, reply_type),
David Reiss3f660a42010-08-30 22:05:29 +0000118 read_result(Client, Function, ResultType).
Bryan Duxburyd3879f82010-08-19 05:06:02 +0000119
David Reiss3f660a42010-08-30 22:05:29 +0000120read_result(Client, _Function, oneway_void) ->
121 {Client, {ok, ok}};
Sergei Elin45764092022-09-23 23:21:31 +0300122read_result(
123 Client = #tclient{
124 protocol = Proto0,
125 seqid = SeqId
126 },
127 Function,
128 ReplyType
129) ->
Jens Geyera6b328f2014-03-18 23:51:23 +0200130 case thrift_protocol:read(Proto0, message_begin) of
Sergei Elin45764092022-09-23 23:21:31 +0300131 {Proto1, {error, Reason}} ->
132 NewClient = Client#tclient{protocol = Proto1},
133 {NewClient, {error, Reason}};
134 {Proto1, MessageBegin} ->
135 NewClient = Client#tclient{protocol = Proto1},
136 case MessageBegin of
137 #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId ->
138 {NewClient, {error, {bad_seq_id, SeqId}}};
139 #protocol_message_begin{type = ?tMessageType_EXCEPTION} ->
140 handle_application_exception(NewClient);
141 #protocol_message_begin{type = ?tMessageType_REPLY} ->
142 handle_reply(NewClient, Function, ReplyType)
143 end
Bryan Duxburyd3879f82010-08-19 05:06:02 +0000144 end.
145
Sergei Elin45764092022-09-23 23:21:31 +0300146handle_reply(
147 Client = #tclient{
148 protocol = Proto0,
149 service = Service
150 },
151 Function,
152 ReplyType
153) ->
David Reiss2c534032008-06-11 00:58:00 +0000154 {struct, ExceptionFields} = Service:function_info(Function, exceptions),
155 ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields},
David Reiss82862952010-08-30 22:05:32 +0000156 {Proto1, {ok, Reply}} = thrift_protocol:read(Proto0, ReplyStructDef),
157 {Proto2, ok} = thrift_protocol:read(Proto1, message_end),
158 NewClient = Client#tclient{protocol = Proto2},
David Reiss2c534032008-06-11 00:58:00 +0000159 ReplyList = tuple_to_list(Reply),
160 true = length(ReplyList) == length(ExceptionFields) + 1,
161 ExceptionVals = tl(ReplyList),
Sergei Elin45764092022-09-23 23:21:31 +0300162 Thrown = [
163 X
164 || X <- ExceptionVals,
165 X =/= undefined
166 ],
David Reiss3f660a42010-08-30 22:05:29 +0000167 case Thrown of
168 [] when ReplyType == {struct, []} ->
David Reiss82862952010-08-30 22:05:32 +0000169 {NewClient, {ok, ok}};
David Reiss3f660a42010-08-30 22:05:29 +0000170 [] ->
David Reiss82862952010-08-30 22:05:32 +0000171 {NewClient, {ok, hd(ReplyList)}};
David Reiss3f660a42010-08-30 22:05:29 +0000172 [Exception] ->
David Reiss82862952010-08-30 22:05:32 +0000173 throw({NewClient, {exception, Exception}})
David Reiss3f660a42010-08-30 22:05:29 +0000174 end.
David Reiss6b3e40f2008-06-11 00:59:03 +0000175
Sergei Elin45764092022-09-23 23:21:31 +0300176-spec handle_application_exception(tclient()) -> no_return().
David Reiss82862952010-08-30 22:05:32 +0000177handle_application_exception(Client = #tclient{protocol = Proto0}) ->
178 {Proto1, {ok, Exception}} =
179 thrift_protocol:read(Proto0, ?TApplicationException_Structure),
180 {Proto2, ok} = thrift_protocol:read(Proto1, message_end),
David Reiss55ff70f2008-06-11 00:58:25 +0000181 XRecord = list_to_tuple(
Sergei Elin45764092022-09-23 23:21:31 +0300182 ['TApplicationException' | tuple_to_list(Exception)]
183 ),
David Reiss1af18682008-06-11 01:01:36 +0000184 error_logger:error_msg("X: ~p~n", [XRecord]),
David Reiss55ff70f2008-06-11 00:58:25 +0000185 true = is_record(XRecord, 'TApplicationException'),
David Reiss82862952010-08-30 22:05:32 +0000186 NewClient = Client#tclient{protocol = Proto2},
187 throw({NewClient, {exception, XRecord}}).