Related
It's my first attempt to write anything in Erlang, so maybe the question is silly.
I'm writing a quite simple HTTP server using cowboy
db_name() -> "DB_test".
timestamp() ->
calendar:datetime_to_gregorian_seconds(calendar:universal_time()).
sha(Str) ->
<<X:256/big-unsigned-integer>> = crypto:hash(sha256, Str),
lists:flatten(io_lib:format("~64.16.0b", [X])).
handle_post(Req0, State) ->
Link = binary_to_list(cowboy_req:header(<<"link">>, Req0)),
dets:open_file(db_name(), []),
dets:insert(db_name(), {hashed_url(Link), Link, timestamp()}),
Req = cowboy_req:reply(200,
#{<<"content-type">> => <<"text/plain">>},
sha(Link),
Req0),
{ok, Req, State}.
The idea is that a POST HTTP request contains a 'link' header with some link. After recieving such request my server should store it's hash in dets table along with the link and its timestamp. The problem is that the "DB_test" file isn't created. Why?
Based on your example code, it's hard to say exactly why, since you're ignoring the return values from both dets:open_file/2 and dets:insert/2.
Both of them return different values for the success and failure cases; but do not throw exceptions.
See the official documentation for more details: http://erlang.org/doc/man/dets.html
The simplest solution to this is to crash the cowboy connection handling process in non-success cases. You can do that by doing something like the following:
{ok, Ref} = dets:open_file(db_name(), []),
ok = dets:insert(Ref, {hashed_url(Link), Link, timestamp()}),
This will crash with a badmatch exception in the failure cases, when the value returned cannot be pattern matched to the left-hand side of the statement, subsequently causing cowboy to return HTTP 500 to the client.
You'll then see details on what the actual error was in the stacktrace logged
A second solution would be to explicitly handle the failure cases, you can use the 'case' keyword for that.
An example would be something like:
case dets:open_file(db_name(), []) of
{ok, Ref} ->
do_success_things();
{error, Reason}=E ->
io:format("Unable to open database file: ~p~n", [E]),
do_failure_things();
end
For further reading, I'd highly recommend the Syntax in functions and Errors and exceptions chapters of Learn you some Erlang for great good: http://learnyousomeerlang.com/
I have skimmed through the Mochiweb code, but have not found any sign of the State variable.
Does something similar to gen_server's State variable exist in Mochiweb?
I need to store some small amount of state-related server-side (not session-related) data on the server and I do not want to use ETS or Mnesia for that.
I think you have somewhat a misunderstanding of what gen_server state is.
First, let me explain briefly how mochiweb works.
Mochiweb doesn't produce a gen_server process per client. Instead, it just spawns a new process using proc_lib:spawn/3 and creates a parametrized module, which is, basically, a tuple of the following kind:
{mochiweb_request, #Port<0.623>, get, "/users", {1, 1}, []}
which is
{mochiweb_request, Socket, Method, RawPath, HTTPVersion, Headers}
This tuple is used as an argument to a function that you pass as a loop parameter to mochiweb_http:start/1. So, when this "loop" function is called, it will look like this:
handle_request(Req) ->
%% The pattern matching below just shows what Req really is
{mochiweb_request, _, _, _, _, _} = Req,
...
Now, to explanation of gen_server state.
Basically, gen_server is a process with approximately the following structure. Of course, IRL it's more complicated, but this should give you the general idea:
init(Options)
State = ...
loop(Module, State).
loop(Module, State)
NewState = receive
{call, Msg, From} -> Module:handle_call(Msg, From, State)
{cast, Msg} -> Module:handle_cast(Msg, State)
Info -> Module:handle_info(Info, State)
end,
loop(Module, NewState).
So, state is just an argument that you drag through all the function calls and change inside your loop. It doesn't actually matter if your process is a gen_server or not, it doesn't have what lifetime it has. In the following example the term [1, 2, 3] is a state too:
a() ->
b([1, 2, 3], now()).
b(State, Timestamp) ->
Result = do_something(Timestamp)
c(State, Result).
c(State, Payload) ->
exit({State, Payload}).
Now, back to mochiweb.
If you need to create a state of your own, you can just add an extra function argument:
handle_request(Req) ->
User = Req:get(path),
UserData = load_user_data(User),
handle_request(Req, UserData).
handle_request(Req, UserData) ->
...
Now UserData is a state too. You can loop this process, or let it respond and end right away – but you won't lose UserData as long as you pass it as an argument.
Finally, if you really want to make this process a gen_server (which is really unreasonable in most cases), you can use gen_server:enter_loop/3 function that will make your current process a gen_server. And The 3rd argument of this function will be your state that will be stored inside the started gen_server.
I want to extend gen_server (create a gen_server_extra) with some additional functionality. The requirements are:
The gen_server_extra processes should behave like a regular gen_server's. E.g, they should accept calls via gen_server:call, integrate with SASL, fit OTC supervision tree, etc.
gen_server_extra processes should have an additional functionality, provided by gen_server_extra. That basically means some of the messages will be handled by gen_server_extra code, without passing them to the callback module. The rest of the messages are passed to callback module as is.
gen_server_extra functionality requires its own state which should be hidden from the callback module.
What is the simplest approach to do that?
The best, most modular approach would be to implement a new behavior in a module (e.g. gen_ext_server) and wrap the gen_server behavior from there.
First, make sure your behavior is identical to gen_server:
-module(gen_ext_server).
-behavior(gen_server).
% Exports...
behaviour_info(Type) -> gen_server:behaviour_info(Type).
Implement all callbacks needed for gen_server, keep the name of the callback module that implements your behavior in your state:
init([Mod|ExtraArgs]) ->
% ...
ModState = Mod:init(ExtraArgs),
#state{mod = Mod, mod_state = ModState, internal = [...]}
Then, in each gen_server callback, implement your behavior and then call the callback module if needed:
handle_call(internal, _From, State) ->
% Do internal stuff...
{reply, ok, State};
handle_call(Normal, From, State = #state{mod = Mod, mod_state = ModState}) ->
case Mod:handle_call(Normal, From, ModState) of
{reply, Reply, NewState} ->
{reply, Reply, #state{mod_state = NewState};
... ->
...
end.
Implement similar functionality for handle_cast/2, handle_info/2, terminate/1 etc.
well, i would not call it customization, but rather a new behavior. You need to define your own behavior. A tutorial which will take you through this is found at trapexit.org.
However, the requirements are not very proper.
The main essence of giving the call back functions access to the server state is to write normal sequential code manipulating the state of your application when and where you want without interrupting the concurrency details.
Otherwise if that is the way to go, implement your own behavior.
I have erlang gen_fsm, my first state:
begin({Nick}, _From, State) ->
{reply, true, next_state, State}.
Then i have:
next_state(_Event, _From, State) ->
io:format("Test \n"),
{reply, ok, begin, State}.
But i don't seen Test note in shell
How correctly transit to a new state?
First of all, ensure that begin is the actual initial state of your FSM. You specify the initial state of your FSM by returning, in your init function, something like:
{ok, begin, State}
Where begin is your initial state.
Also, note that you're defining a Module:StateName/3 function, which will be called any time a gen_fsm:sync_send_event is performed on your FSM. If you're trying to send events to the FSM using gen_fsm:send_event, you should instead define a function Module:StateName/2, which is its asynchronous version.
Finally, try to debug your modules by tracing them, rather than adding printouts. It's much simpler and it avoids you recompiling your code time after time.
More information are avilable here.
you may find some examples here:
http://spawnlink.com/articles/an-introduction-to-gen_fsm-erlybanks-atm/index.html
and here:
http://pdincau.wordpress.com/2010/09/07/an-introduction-to-gen_fsm-behaviour/
Hope it helps
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 :
start(Host, _Opt) : This is an ejabberd function to load my module. I get the Host atom here
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.
get_translation(XmlData): filter_packet() calls get_translation() in a loop
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?
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.
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}.
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
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.
Say you are filtering incoming packets, then To#jid.lserver might be your host.
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).
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)
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.