Nitrogen 2.2 in single page context

64 views
Skip to first unread message

Hazim Sultan

unread,
Dec 8, 2014, 5:47:14 AM12/8/14
to nitro...@googlegroups.com
Hi,

I are using Nitrogen 2.2.2 to build our front-end GUI and integrate it with a back-end Erlang system. The way I designed the system is that all the data is used within the context of same page, while the sidebar links clicked all the modules responsible for that application. As an example my index.erl is something like this:


-module(index).


-include("wf.hrl").


%% API


-export([main/0, title/0, body/0, event/1]).


-export([body/2]).

event(E)->


    wf:wire(#alert { text=wf:f("~p", [E]) }),

   ?debug("~p~n",[E],dbg).


title() -> "OAM".

main() ->

   #template{file=env_file:filepath(?TEMPLATE)}.


body() ->

   User = wf:user(),

  body(wf:session(logging_in), User).

body(undefined, User) ->

    login_page_body(User);

body(true, User) ->

   wf:session(logging_in, undefined),

   case get_login_state(User) of

       active ->

          #panel{id = "MAIN_PANEL",

                   body = get_body(User)};

       _ ->

           logout(User),
           wf
:redirect_to_login("/")

   end.



In the get_body, I send down a sidebar with different links the user can click and the post backs go to individual application modules for that (using delegate = ?MODULE in each postback)

PROBLEM / ISSUE # 1: I have seen that after a few minutes of usage, the web GUI starts to be less and less responsive, taking a few seconds (it takes even 10 seconds once) to get postbacks back to the module responsible, I saw that in the process responsible for the nitrogen/jnet simple bridge had quite a bit process dictionary, I am attaching the process_info on that process at one time while the GUI was really slow responding. We also use state variable (wf:state) to move data between modules and clicks, that can include quite a bit of data like a table of 100 rows is saved in the state variable while the user moves to some other information in that page, and then when they click on "Back" link I want to show the same table (instead of going back to the module and asking it to generate the exact same table again).

Is there something anyone can help with? Maybe we are doing something wrong at the design stage, or there are some extra things that we need to take care of?

Hope to hear from you guys soon :).

Best Regards,
Hazim Sultan



process_info.txt

Jesse Gumm

unread,
Dec 8, 2014, 2:28:03 PM12/8/14
to nitrogenweb
Hello Hazim,

Things will definitely start getting hairy when the page context gets
that huge. If you intend on using such a heavy amount of state data
such as you have, it may become more relevant to store that
information into custom state handler which only stores most of that
data on the server, and have it expire after some amount of time. The
nature of the standard state handler is to transfer the entire state
value from client to server and back with each request, that way
nothing has to be stored server-side to track it - the drawback comes
in that if that state is loaded with data, that you're transferring a
ton of data back and forth with each request.

You could make your own custom state handler that stored the data in a
cache of sorts, then provide a fallback for if the cache's TTL went
stale, you can regenerate it, then the only thing you need to store in
the actual state value that gets transferred from client to server is
a simple reference (like erlang:make_ref) as the key for the state
data.

You could almost certainly just copy the default session handler and
make a few changes to control the timeout.

If you look at the current state handler:
https://github.com/nitrogen/nitrogen_core/blob/master/src/handlers/state/default_state_handler.erl

..you'll see it's just a basic proplist that gets stored.

Whereas with the session handler:
https://github.com/nitrogen/nitrogen_core/blob/master/src/handlers/session/simple_session_handler.erl

...you'll see that in the get_session_pid function
(https://github.com/nitrogen/nitrogen_core/blob/master/src/handlers/session/simple_session_handler.erl),
it fires up a process with the process registry handler that serves as
a simple server for a key-value lookup also using a basic list, and
specifies a timeout from the config (which you could set to whatever
you want).

This could very well be the ideal solution for you, though it
undoubtedly will use more memory on the server, since all users' will
have to stored server-side rather than client side, but it'll give you
a better response time, that's for sure.

If you have any questions, let me know.

-Jesse
> --
> You received this message because you are subscribed to the Google Groups
> "Nitrogen Project / The Nitrogen Web Framework for Erlang" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to nitrogenweb...@googlegroups.com.
> To post to this group, send email to nitro...@googlegroups.com.
> Visit this group at http://groups.google.com/group/nitrogenweb.
> For more options, visit https://groups.google.com/d/optout.



--
Jesse Gumm
Owner, Sigma Star Systems
414.940.4866 || sigma-star.com || @jessegumm

Hazim Sultan

unread,
Dec 9, 2014, 3:47:39 AM12/9/14
to nitro...@googlegroups.com
Hi Jesse,

That is a very interesting solution :). I dont have memory restrains on the server side for now, so this does look like something that should work for us.
I will start implementing this into our application and then see how it works out.

I will get back to you with how it works out.

Thanks a lot for your help and suggestion.

//Hazim

Jesse Gumm

unread,
Dec 9, 2014, 6:46:35 AM12/9/14
to nitrogenweb

Thanks. I look forward to hearing the results. It could perhaps be a worthwhile alternative state handler for others in a similar situation as you.

-Jesse

--
Jesse Gumm
Owner, Sigma Star Systems
414.940.4866 || sigma-star.com || @jessegumm

Hazim Sultan

unread,
Dec 10, 2014, 9:00:40 AM12/10/14
to nitro...@googlegroups.com
Hi Jesse,

We have been able to get the page context down significantly by making server side state variables. As a solution, we realized that we already had a state handler in our application (which we call ngui), which was basically for management of our roles/rights and some specific stuff that we wanted to handle outside wf. so, to that we added the additional state variable handler, we were using ets tables to manage our data already so we added a new ets table during the init:

init() ->
    ...
    ets:new(ngui_var, [named_table, public]),
    ...

logout() ->
   ...
   clear_statevar(),
   ...

state_variable(Varname) ->
    Session = wf:session(session),
    case ets:lookup(ngui_var, {Session, Varname}) of
[{_, Val}] ->
   Val;
[] ->
   undefined
    end.

state_variable(Varname, undefined) ->
    Session = wf:session(session),
    ets:delete(ngui_var, {Session, Varname}),
    ok;
state_variable(Varname, Val) ->
    Session = wf:session(session),
    ets:insert(ngui_var, {{Session,Varname}, Val}),
    ok.

state_variable_default(Varname,Default) ->
    Session = wf:session(session),
    case ets:lookup(ngui_var, {Session, Varname}) of
[{_, Val}] ->
   Val;
[] ->
   Default
    end.

clear_statevar() ->
    Session = wf:session(session),
    clear_statevar(Session).

clear_statevar(Session) ->
    ets:match_delete(ngui_var, {{Session, '_'},'_'}),
    ok.

Now this wf:session(session) is a session id reference that is pushed down when the user logs in and is used to keep track of the session on our application end. I use that {SessionID, Var} as the key in my ets table.
Now if we need to do the same in the wf framework I would recommend the following:

state_variable(Varname) ->
    Ref = wf:state(Varname),
    case ets:lookup(ngui_var, {Ref, Varname}) of
[{_, Val}] ->
    Val;
[] ->
    undefined
    end.

state_variable(Varname, undefined) ->
    Ref = wf:state(Varname),
    ets:delete(ngui_var, {Session, Varname}),
    ok;
state_variable(Varname, Val) ->
    Ref= erlang:make_ref(),
    wf:state(Varname, Ref),
    ets:insert(ngui_var, {{Ref,Varname}, Val}),
    ok.

state_variable_default(Varname,Default) ->
    Ref = wf:state(Varname),
    case ets:lookup(ngui_var, {Session, Varname}) of
[{_, Val}] ->
    Val;
[] ->
    Default
    end.

In this case we push the ref down to the session for each variable and then correlate that with the variable when we want to get the value back. This would be more generic and can be used with proplists as well. If you think, I can try and make that sort of handler into the nitrogen github as well.

//Hazim

Hazim Sultan

unread,
Dec 10, 2014, 9:02:32 AM12/10/14
to nitro...@googlegroups.com
Also, we didnt implement a TTL, because we already have a TTL handling where the user is forced logged out and then the state is cleared with that.
Also when the user moves from one module to next the previous module is responsible for clearing its states as we basically move within the context of one page but different modules handling it

//Hazim

Jesse Gumm

unread,
Dec 10, 2014, 3:30:43 PM12/10/14
to nitrogenweb
Awesome! That all sounds pretty great! If you want, feel free to
submit it as a patch for mainline Nitrogen, or maybe better yet,
create it as a plugin application, then a user could just add it as a
rebar dependency, and modify their entry-point module
(nitrogen_main_handler.erl in in 2.3, or
nitrogen_[mochiweb|cowboy|...] in 2.2.2 and below. )

My plan was to have the cache handler ready for 2.3, but there are
already so many changes baked into 2.3 that I've decided it's time to
lock the API, now just bug-hunting.

-Jesse
Reply all
Reply to author
Forward
0 new messages