blob: 5c9f26f52a00bd06dc78f71520cc6fe399be9a2e [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 Reissac549552008-06-10 22:56:59 +000020-module(thrift_processor).
21
David Reissc11734e2008-06-11 00:59:48 +000022-export([init/1]).
David Reissac549552008-06-10 22:56:59 +000023
24-include("thrift_constants.hrl").
25-include("thrift_protocol.hrl").
26
David Reiss035979f2010-08-30 22:05:38 +000027-record(thrift_processor, {handler, protocol, service}).
David Reissac549552008-06-10 22:56:59 +000028
David Reiss5ed313d2010-08-30 22:05:57 +000029init({_Server, ProtoGen, Service, Handler}) when is_function(ProtoGen, 0) ->
David Reiss035979f2010-08-30 22:05:38 +000030 {ok, Proto} = ProtoGen(),
31 loop(#thrift_processor{protocol = Proto,
David Reissc11734e2008-06-11 00:59:48 +000032 service = Service,
33 handler = Handler}).
David Reissac549552008-06-10 22:56:59 +000034
Roger Meier3f972b12012-05-18 11:35:51 +000035loop(State0 = #thrift_processor{protocol = Proto0,
David Robakowskiae971ce2013-08-02 12:16:00 +020036 handler = Handler,
37 service = Service}) ->
38
David Reiss035979f2010-08-30 22:05:38 +000039 {Proto1, MessageBegin} = thrift_protocol:read(Proto0, message_begin),
40 State1 = State0#thrift_processor{protocol = Proto1},
David Robakowskiae971ce2013-08-02 12:16:00 +020041
42 ErrorHandler = fun
43 (HandlerModules) when is_list(HandlerModules) -> thrift_multiplexed_map_wrapper:fetch(?MULTIPLEXED_ERROR_HANDLER_KEY, HandlerModules);
44 (HandlerModule) -> HandlerModule
45 end,
46
David Reiss035979f2010-08-30 22:05:38 +000047 case MessageBegin of
David Robakowskiae971ce2013-08-02 12:16:00 +020048
David Reissae756f42008-06-10 22:57:11 +000049 #protocol_message_begin{name = Function,
David Robakowskiae971ce2013-08-02 12:16:00 +020050 type = Type,
51 seqid = Seqid} when Type =:= ?tMessageType_CALL; Type =:= ?tMessageType_ONEWAY ->
52 case string:tokens(Function, ?MULTIPLEXED_SERVICE_SEPARATOR) of
53 [ServiceName, FunctionName] ->
54 ServiceModule = thrift_multiplexed_map_wrapper:fetch(ServiceName, Service),
55 ServiceHandler = thrift_multiplexed_map_wrapper:fetch(ServiceName, Handler),
56 case handle_function(State1#thrift_processor{service=ServiceModule, handler=ServiceHandler}, list_to_atom(FunctionName), Seqid) of
57 {State2, ok} -> loop(State2#thrift_processor{service=Service, handler=Handler});
58 {_State2, {error, Reason}} ->
59 apply(ErrorHandler(Handler), handle_error, [list_to_atom(Function), Reason]),
60 thrift_protocol:close_transport(Proto1),
61 ok
62 end;
63 _ ->
64 case handle_function(State1, list_to_atom(Function), Seqid) of
65 {State2, ok} -> loop(State2);
66 {_State2, {error, Reason}} ->
67 apply(ErrorHandler(Handler), handle_error, [list_to_atom(Function), Reason]),
68 thrift_protocol:close_transport(Proto1),
69 ok
70 end
Roger Meier3f972b12012-05-18 11:35:51 +000071 end;
72 {error, timeout = Reason} ->
David Robakowskiae971ce2013-08-02 12:16:00 +020073 apply(ErrorHandler(Handler), handle_error, [undefined, Reason]),
David Reiss035979f2010-08-30 22:05:38 +000074 thrift_protocol:close_transport(Proto1),
David Reissc49dd1e2008-06-11 01:02:39 +000075 ok;
Roger Meier3f972b12012-05-18 11:35:51 +000076 {error, closed = Reason} ->
David Reissc11734e2008-06-11 00:59:48 +000077 %% error_logger:info_msg("Client disconnected~n"),
David Robakowskiae971ce2013-08-02 12:16:00 +020078 apply(ErrorHandler(Handler), handle_error, [undefined, Reason]),
Roger Meier3f972b12012-05-18 11:35:51 +000079 thrift_protocol:close_transport(Proto1),
80 exit(shutdown);
81 {error, Reason} ->
David Robakowskiae971ce2013-08-02 12:16:00 +020082 apply(ErrorHandler(Handler), handle_error, [undefined, Reason]),
David Reiss035979f2010-08-30 22:05:38 +000083 thrift_protocol:close_transport(Proto1),
David Reiss80664fe2008-06-11 01:00:30 +000084 exit(shutdown)
David Reissac549552008-06-10 22:56:59 +000085 end.
David Reissae756f42008-06-10 22:57:11 +000086
David Reiss035979f2010-08-30 22:05:38 +000087handle_function(State0=#thrift_processor{protocol = Proto0,
88 handler = Handler,
89 service = Service},
Bryan Duxburya2cceb42011-03-02 18:25:24 +000090 Function,
91 Seqid) ->
David Reiss1c1ca742008-06-10 22:57:21 +000092 InParams = Service:function_info(Function, params_type),
93
David Reiss035979f2010-08-30 22:05:38 +000094 {Proto1, {ok, Params}} = thrift_protocol:read(Proto0, InParams),
95 State1 = State0#thrift_processor{protocol = Proto1},
David Reiss76f0d112008-06-10 22:57:35 +000096
David Reiss982c72d2008-06-10 22:58:33 +000097 try
David Reiss11d855c2008-06-11 00:59:12 +000098 Result = Handler:handle_function(Function, Params),
David Reissc11734e2008-06-11 00:59:48 +000099 %% {Micro, Result} = better_timer(Handler, handle_function, [Function, Params]),
100 %% error_logger:info_msg("Processed ~p(~p) in ~.4fms~n",
101 %% [Function, Params, Micro/1000.0]),
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000102 handle_success(State1, Function, Result, Seqid)
David Reiss982c72d2008-06-10 22:58:33 +0000103 catch
David Reissb9561c82010-08-30 22:05:36 +0000104 Type:Data when Type =:= throw orelse Type =:= error ->
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000105 handle_function_catch(State1, Function, Type, Data, Seqid)
David Reiss4b0534a2010-08-30 22:05:37 +0000106 end.
David Reiss5541d652008-06-11 00:57:42 +0000107
David Reissc11734e2008-06-11 00:59:48 +0000108handle_function_catch(State = #thrift_processor{service = Service},
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000109 Function, ErrType, ErrData, Seqid) ->
David Reissfe931d12009-03-24 20:02:08 +0000110 IsOneway = Service:function_info(Function, reply_type) =:= oneway_void,
David Reiss5541d652008-06-11 00:57:42 +0000111
112 case {ErrType, ErrData} of
David Reiss6ce401d2009-03-24 20:01:58 +0000113 _ when IsOneway ->
David Reiss4ec777e2008-06-11 01:01:29 +0000114 Stack = erlang:get_stacktrace(),
David Reiss5541d652008-06-11 00:57:42 +0000115 error_logger:warning_msg(
David Reiss6ce401d2009-03-24 20:01:58 +0000116 "oneway void ~p threw error which must be ignored: ~p",
David Reiss4ec777e2008-06-11 01:01:29 +0000117 [Function, {ErrType, ErrData, Stack}]),
David Reiss035979f2010-08-30 22:05:38 +0000118 {State, ok};
David Reiss5541d652008-06-11 00:57:42 +0000119
120 {throw, Exception} when is_tuple(Exception), size(Exception) > 0 ->
David Reissc0e4b5b2010-08-30 22:05:12 +0000121 %error_logger:warning_msg("~p threw exception: ~p~n", [Function, Exception]),
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000122 handle_exception(State, Function, Exception, Seqid);
David Reiss035979f2010-08-30 22:05:38 +0000123 % we still want to accept more requests from this client
David Reiss5541d652008-06-11 00:57:42 +0000124
125 {error, Error} ->
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000126 handle_error(State, Function, Error, Seqid)
David Reiss982c72d2008-06-10 22:58:33 +0000127 end.
128
David Reiss035979f2010-08-30 22:05:38 +0000129handle_success(State = #thrift_processor{service = Service},
David Reiss982c72d2008-06-10 22:58:33 +0000130 Function,
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000131 Result,
132 Seqid) ->
David Reiss11d855c2008-06-11 00:59:12 +0000133 ReplyType = Service:function_info(Function, reply_type),
David Reisse1a79982008-06-10 22:58:14 +0000134 StructName = atom_to_list(Function) ++ "_result",
David Reiss11d855c2008-06-11 00:59:12 +0000135
David Reiss035979f2010-08-30 22:05:38 +0000136 case Result of
137 {reply, ReplyData} ->
138 Reply = {{struct, [{0, ReplyType}]}, {StructName, ReplyData}},
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000139 send_reply(State, Function, ?tMessageType_REPLY, Reply, Seqid);
David Reisse1a79982008-06-10 22:58:14 +0000140
David Reiss035979f2010-08-30 22:05:38 +0000141 ok when ReplyType == {struct, []} ->
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000142 send_reply(State, Function, ?tMessageType_REPLY, {ReplyType, {StructName}}, Seqid);
David Reiss11d855c2008-06-11 00:59:12 +0000143
David Reiss035979f2010-08-30 22:05:38 +0000144 ok when ReplyType == oneway_void ->
145 %% no reply for oneway void
146 {State, ok}
147 end.
David Reisse1a79982008-06-10 22:58:14 +0000148
David Reiss035979f2010-08-30 22:05:38 +0000149handle_exception(State = #thrift_processor{service = Service},
David Reiss982c72d2008-06-10 22:58:33 +0000150 Function,
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000151 Exception,
152 Seqid) ->
David Reiss982c72d2008-06-10 22:58:33 +0000153 ExceptionType = element(1, Exception),
David Reissc11734e2008-06-11 00:59:48 +0000154 %% Fetch a structure like {struct, [{-2, {struct, {Module, Type}}},
155 %% {-3, {struct, {Module, Type}}}]}
David Reisse1a79982008-06-10 22:58:14 +0000156
David Reiss982c72d2008-06-10 22:58:33 +0000157 ReplySpec = Service:function_info(Function, exceptions),
158 {struct, XInfo} = ReplySpec,
159
160 true = is_list(XInfo),
David Reiss982c72d2008-06-10 22:58:33 +0000161
David Reissc11734e2008-06-11 00:59:48 +0000162 %% Assuming we had a type1 exception, we'd get: [undefined, Exception, undefined]
163 %% e.g.: [{-1, type0}, {-2, type1}, {-3, type2}]
David Reiss11d855c2008-06-11 00:59:12 +0000164 ExceptionList = [case Type of
165 ExceptionType -> Exception;
166 _ -> undefined
David Reiss982c72d2008-06-10 22:58:33 +0000167 end
David Reiss11d855c2008-06-11 00:59:12 +0000168 || {_Fid, {struct, {_Module, Type}}} <- XInfo],
169
David Reiss982c72d2008-06-10 22:58:33 +0000170 ExceptionTuple = list_to_tuple([Function | ExceptionList]),
David Reiss11d855c2008-06-11 00:59:12 +0000171
David Reissc11734e2008-06-11 00:59:48 +0000172 % Make sure we got at least one defined
David Reiss982c72d2008-06-10 22:58:33 +0000173 case lists:all(fun(X) -> X =:= undefined end, ExceptionList) of
174 true ->
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000175 handle_unknown_exception(State, Function, Exception, Seqid);
David Reiss982c72d2008-06-10 22:58:33 +0000176 false ->
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000177 send_reply(State, Function, ?tMessageType_REPLY, {ReplySpec, ExceptionTuple}, Seqid)
David Reiss982c72d2008-06-10 22:58:33 +0000178 end.
179
David Reiss920959a2008-06-11 00:56:35 +0000180%%
David Reissc11734e2008-06-11 00:59:48 +0000181%% Called when an exception has been explicitly thrown by the service, but it was
182%% not one of the exceptions that was defined for the function.
David Reiss920959a2008-06-11 00:56:35 +0000183%%
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000184handle_unknown_exception(State, Function, Exception, Seqid) ->
David Reiss920959a2008-06-11 00:56:35 +0000185 handle_error(State, Function, {exception_not_declared_as_thrown,
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000186 Exception}, Seqid).
David Reiss920959a2008-06-11 00:56:35 +0000187
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000188handle_error(State, Function, Error, Seqid) ->
David Reissda070672008-06-11 00:56:42 +0000189 Stack = erlang:get_stacktrace(),
190 error_logger:error_msg("~p had an error: ~p~n", [Function, {Error, Stack}]),
David Reiss11d855c2008-06-11 00:59:12 +0000191
David Reiss920959a2008-06-11 00:56:35 +0000192 Message =
193 case application:get_env(thrift, exceptions_include_traces) of
194 {ok, true} ->
195 lists:flatten(io_lib:format("An error occurred: ~p~n",
David Reissda070672008-06-11 00:56:42 +0000196 [{Error, Stack}]));
David Reiss920959a2008-06-11 00:56:35 +0000197 _ ->
198 "An unknown handler error occurred."
199 end,
200 Reply = {?TApplicationException_Structure,
201 #'TApplicationException'{
202 message = Message,
203 type = ?TApplicationException_UNKNOWN}},
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000204 send_reply(State, Function, ?tMessageType_EXCEPTION, Reply, Seqid).
David Reiss982c72d2008-06-10 22:58:33 +0000205
Bryan Duxburya2cceb42011-03-02 18:25:24 +0000206send_reply(State = #thrift_processor{protocol = Proto0}, Function, ReplyMessageType, Reply, Seqid) ->
Roger Meier3f972b12012-05-18 11:35:51 +0000207 try
208 {Proto1, ok} = thrift_protocol:write(Proto0, #protocol_message_begin{
209 name = atom_to_list(Function),
210 type = ReplyMessageType,
211 seqid = Seqid}),
212 {Proto2, ok} = thrift_protocol:write(Proto1, Reply),
213 {Proto3, ok} = thrift_protocol:write(Proto2, message_end),
214 {Proto4, ok} = thrift_protocol:flush_transport(Proto3),
215 {State#thrift_processor{protocol = Proto4}, ok}
216 catch
217 error:{badmatch, {_, {error, _} = Error}} ->
218 {State, Error}
219 end.