16

I am writing an ejabberd module to filter packets. I need to get the hostname to pull some configs using gen_mod:get_module_opt().

I have 4 important functions :

  1. start(Host, _Opt) : This is an ejabberd function to load my module. I get the Host atom here
  2. filter_packet({From, To, XML}): This is my packet filter hook. I cannot pass custom params to this function, as it is a hook in ejabberd.
  3. get_translation(XmlData): filter_packet() calls get_translation() in a loop
  4. fetch_translation(XmlData): called recursively from get_translation(). This is where I am calling gen_mod:get_module_opt(), and hence need the Host.

My question is, how can I take Host from start() and put it in a global variable, so that fetch_translation can access it?

ymn
  • 2,175
  • 2
  • 21
  • 39
Adil
  • 395
  • 1
  • 5
  • 14

8 Answers8

9

The "easiest way" is to create a named ets table, and put it in there.

start(Host, _Opt) ->
  ets:new(my_table, [named_table, protected, set, {keypos, 1}]),
  ets:insert(my_table, {host, Host}),
  ...

fetch_translation(XmlData) ->
  [{_, Host}] = ets:lookup(my_table, host),
  ...

Note that this is a "general" solution. Ejabberd might provide facilities for what you want, but I cannot help you with that.

Zed
  • 57,028
  • 9
  • 76
  • 100
  • Thx zed. The module compiles ok but gives a 'badarg' error for 'ets:new(my_table, [named_table, protected, set, {keypos, 1}]),' – Adil Jan 06 '10 at 15:56
  • If table 'my_table' exists, you will get a badarg. You either need to check if the table already exists, or wrap it in a try-catch block – Zed Jan 06 '10 at 17:23
  • I'm not creating 'my_table' anywhere, except as given above. – Adil Jan 07 '10 at 08:59
  • I'm late but I wouldn't do this it creates unnecessary overhead. The easiest solution would be to use To#jid.lserver or if you need some more things you'll implement it as a gen_server. – Michael Weibel Sep 20 '12 at 13:17
  • this is a better solution than using a OTP process, which serialize every call and slower. – Howy Jun 03 '17 at 22:44
9

It may sound as an overkill but you may consider implementing a very basic gen_server. It contains a state that is available to its callbacks and the data can be kept there. For your case you can write a module similar to this one:

-module(your_module_name).

-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-export([start/2, filter_loop/1]).

start(Host, Opt) ->
  %% start the named gen server
  gen_server:start({local, ?MODULE}, ?MODULE, Host, []).

filter_packet({From, To, XML}) ->
  %% do your thing
  gen_server:call(?MODULE, {fetch_translation, XmlData}).

%% this will be called by gen_server:start - just pass the Host
init(Host) ->
  {ok, Host}.

handle_call({fetch_translation, XmlData}, _From, Host) ->
  %% do your thing
  {reply, ok, Host}.

%% you can ignore the rest - they are needed to be present
handle_cast(_Msg, State) ->
  {noreply, State}.
handle_info(_Info, State) ->
  {noreply, State}.
code_change(_OldVsn, State, _Extra) ->
  {ok, State}.
Martin Dimitrov
  • 4,796
  • 5
  • 46
  • 62
2

You define your global variable on your module top...like below

-define (Your Variable, "your host name here").

eg.

-define (RelayHost, "smtp.gmail.com").

and you can use this Global variable in all your method in your module.

io:fwrite("Global Value ~p", [?RelayHost]).

-AjAy

Didier Ghys
  • 30,396
  • 9
  • 75
  • 81
Ajay V
  • 157
  • 1
  • 3
  • 7
1

You could start a new message filtering process and register it using erlang:register/2, then route all filter_packet/1 requests through it (a potential bottleneck).

-define(?SERVER, msg_filter).

start(Host, Opt) ->
   {ok, Pid} = spawn(?MODULE, filter_loop, [Host, Opt]),
   register(?SERVER, Pid).

filter_loop(Host, Opt) ->
   receive
      {Pid, filter_packet, {_From, _To, XML}} ->
           Trans = get_translation(XML, Host),
           Pid ! {?SERVER, translation, Trans}, 
           filter_loop(Host, Opt)
   end.

filter_packet(Pack) ->
   ?SERVER ! {self(), filter_packet, Pack}
   receive 
      {?SERVER, translation, Trans} ->
           % wrap translation
           UpdatedPacket
   end.
1

Say you are filtering incoming packets, then To#jid.lserver might be your host.

andi5
  • 1,606
  • 1
  • 11
  • 10
  • Hi andi5. Can you explain what "To#jid.lserver" means. How can i set / get it? – Adil Jan 17 '10 at 13:22
  • This is only best guess, but I suppose that To is a variable bound to a record of type jid (see the record definition in src/jlib.hrl, at the bottom). To#jid.lserver means that you want to access the lserver field of the record, where lserver is the lower-cased version of the jid's domain. If you have problems in the shell, run rd(jid, {user,[...]}). – andi5 Jan 17 '10 at 19:48
1

guessing for your description than you are in a single-domain ejabberd deployment (no virtual hosts),

yo can get the local XMPP domain using the ?MYNAME macro (see ejabberd.hrl for the definition).

ppolv
  • 1,319
  • 8
  • 8
1

Try use persistent_term:

1> persistent_term:put(hello, <<"world">>).
ok
2> persistent_term:get(hello).       
<<"world">>
3> persistent_term:erase(hello).
true
4> persistent_term:get(hello).  
** exception error: bad argument
     in function  persistent_term:get/1
        called as persistent_term:get(hello)

vkatsuba
  • 1,411
  • 1
  • 7
  • 19
0

You cannot create global variable but you can define a record outside your functions and create an instance of that record with properties then pass it down to the methods you call. Therefore, you can only share one record via method parameter.