%%%------------------------------------------------------------------- %% @doc grpcbox supervisor for set of services on a port. %% @end %%%------------------------------------------------------------------- -module(grpcbox_services_sup). -behaviour(supervisor). -export([start_link/5, services_sup_name/1, pool_name/1, name/2]). -export([init/1]). -include("grpcbox.hrl"). start_link(ServerOpts, GrpcOpts, ListenOpts, PoolOpts, TransportOpts) -> ServicePbModules = maps:get(service_protos, GrpcOpts), load_services(ServicePbModules), %% give the services_sup a name because in the future we might want to reference it easily for %% debugging purposes or live configuration changes Name = services_sup_name(ListenOpts), supervisor:start_link({local, Name}, ?MODULE, [ServerOpts, GrpcOpts, ListenOpts, PoolOpts, TransportOpts]). interceptor(Type, Opts) -> case maps:get(Type, Opts, undefined) of {Module, Function} -> fun Module:Function/4; Fun when is_function(Fun, 4) -> Fun; undefined -> undefined end. init([ServerOpts, GrpcOpts, ListenOpts, PoolOpts, TransportOpts]) -> AuthFun = get_authfun(maps:get(ssl, ListenOpts, false), GrpcOpts), UnaryInterceptor = interceptor(unary_interceptor, GrpcOpts), StreamInterceptor = interceptor(stream_interceptor, GrpcOpts), ChatterboxOpts = #{stream_callback_mod => grpcbox_stream, stream_callback_opts => [AuthFun, UnaryInterceptor, StreamInterceptor]}, %% unique name for pool based on the ip and port it will listen on Name = pool_name(ListenOpts), RestartStrategy = #{strategy => rest_for_one}, Pool = #{id => grpcbox_pool, start => {grpcbox_pool, start_link, [Name, chatterbox:settings(server, ServerOpts), ChatterboxOpts, TransportOpts]}}, Socket = #{id => grpcbox_socket, start => {grpcbox_socket, start_link, [Name, ListenOpts, PoolOpts]}}, {ok, {RestartStrategy, [Pool, Socket]}}. %% services_sup_name(ListenOpts) -> name("grpcbox_services_sup", ListenOpts). pool_name(ListenOpts) -> name("grpcbox_pool", ListenOpts). name(Prefix, ListenOpts) -> Port = maps:get(port, ListenOpts, 8080), IPAddress = maps:get(ip, ListenOpts, {0, 0, 0, 0}), list_to_atom(Prefix ++ "_" ++ inet_parse:ntoa(IPAddress) ++ "_" ++ integer_to_list(Port)). get_authfun(true, Options) -> case maps:get(auth_fun, Options, undefined) of undefined -> case maps:get(client_cert_dir, Options, undefined) of undefined -> undefined; Dir -> grpc_lib:auth_fun(Dir) end; Fun -> Fun end; get_authfun(_, _) -> undefined. %% grpc requests are of the form `./` in camelcase. For this reason we %% have gpb keep the service definitions in their original form and convert to snake case here %% to know what module:function to call for each. load_services([]) -> ok; load_services([ServicePbModule | Rest]) -> ServiceNames = ServicePbModule:get_service_names(), [begin {{service, _}, Methods} = ServicePbModule:get_service_def(ServiceName), SnakedServiceName = atom_snake_case(ServiceName), try lists:keyfind(exports, 1, SnakedServiceName:module_info()) of {exports, Exports} -> [begin SnakedMethodName = atom_snake_case(Name), case lists:member({SnakedMethodName, 2}, Exports) of true -> ets:insert(?SERVICES_TAB, #method{key={atom_to_binary(ServiceName, utf8), atom_to_binary(Name, utf8)}, module=SnakedServiceName, function=SnakedMethodName, proto=ServicePbModule, input={Input, InputStream}, output={Output, OutputStream}, opts=Opts}); false -> %% TODO: error? log? insert into ets as unimplemented? unimplemented_method end end || #{name := Name, input := Input, output := Output, input_stream := InputStream, output_stream := OutputStream, opts := Opts} <- Methods] catch _:_ -> %% TODO: error? log? insert into ets as unimplemented? unimplemented_service end end || ServiceName <- ServiceNames], load_services(Rest). atom_snake_case(Name) -> NameString = atom_to_list(Name), Snaked = lists:foldl(fun(RE, Snaking) -> re:replace(Snaking, RE, "\\1_\\2", [{return, list}, global]) end, NameString, [%% uppercase followed by lowercase "(.)([A-Z][a-z]+)", %% any consecutive digits "(.)([0-9]+)", %% uppercase with lowercase %% or digit before it "([a-z0-9])([A-Z])"]), Snaked1 = string:replace(Snaked, ".", "_", all), Snaked2 = string:replace(Snaked1, "__", "_", all), list_to_atom(string:to_lower(unicode:characters_to_list(Snaked2))).