EvanCarroll has asked for the wisdom of the Perl Monks concerning the following question:

Fellow monks,


I'm having a slight problem with mason: =[


So here it is: I have an autohandler: /auth_required/autohandler and I want it to throw different useful error messages at different parts if authentication fails. These error messages are stored in /index.html, in separate method which makes another method call with content to reload the login form with the appropriate message prefixed:
<%method .non_auth> <div class="small_text"> <h1><% $m->content %>You are not logged in:</h1> If you don't have a login, Click <a href="/create_user.html">here< +/a> to create one. </div> <form method="post" action="/index.html"> <table class="form_fields"> <tr> <td>User Name: </td> <td><input type="text" name="name_user" /></td> </tr> <tr> <td>Password: </td> <td><input type="password" name="password" value="" alt="Requi +red only for admins"/></td> </tr> </table> <div style="text-align:right"> <input type="submit" value="Proceed" /> </div> </form> </%method> %# Invalid user name or password OR username deleted from table <%method .error_auth> <&| SELF:.non_auth &>101: Invalid username or password, </&> </%method>
Like that of sort
Essentially what I would like to do is create a subrequest out of a method located in a different component. So if the autohandler at /auth_required/autohandler, fails /index.html's .error_auth or similar gets invoked, which causes the error to precede 'You are not logged in'; with a useful message.

One way around this problem would be to call the method from /auth_required/autohandler. But, that should be _bad-practice_, because I would have to clear the buffer ($m->flush_buffer) and abort ($m->abort)which would mean I miss out on the header and footer information in /autohandler.

Another method would be to extract each error message into its own file so I can make a subrequest ($m->subexec) out of it or redirect ($m->redirect) to it. This isn't a "Bad" approach per se, but I would prefer to keep all of my error messages in one file, for quicker referencing.

Any ideas?


Evan Carroll
www.EvanCarroll.com

Replies are listed 'Best First'.
Seperating logic from web display using mason
by aufflick (Deacon) on Oct 02, 2005 at 01:47 UTC
    This is the problem that I see with a templating only solution. Ie. all your logic is interleaved with the display and sometimes you figure things out too late.

    This answer is, I admit, not so much a solution to your current problem, but rather a (very) different way of designing your web app that stops this being an issue.

    The way I do it with perl/mason (which is not necessarily the best way) is to have a two pass apporach to every page. I borrowed this idea from the AOLServer templating system (and others) where you have a seperate template and code page.

    In essence, each web request to one of my systems first runs a logic script (actually it instantiates an object of the right class, but you don't have to use OO) and then renders a template.

    This may not be clear, so lets take an example:

    1. Someone requests mysite.com/foo/bar
    2. my custom request processor code (which could be run by the mason_handler or the autohandler or a dhandler, depending on your setup) determines that it should instantiate an object of type My::Site::Web::foo::bar
    3. After successful instantiation, it passes the %ARGS from any form post into a method called (by convention) run_page which implements all the non-display logic for that page. The convenience here of using OO is that this method can be subclassed to allow sharing of common page tasks like security management etc.
    4. Note that the object is also passed (or has access to a global) variable %session or similar, to access a session hash managed by Apache::Session
    5. If run_page returns true, then the request processor renders the web page with the mason component /foo/bar
    6. The mason component has access to both the %session hash, but also to another mason global that I name $page which is (in this case) the My::Site::Web::foo::bar object
    That is the way a normal page render works. In the case where, say, an error occurs or someone is lacking the required permissions, then the request processor can instead render (or redirect to) a different template eg. an error page.

    There are a lot more subtleties to my actual implementation (which I hope to be able to release to CPAN with the appropriate authorisation), but the basic idea of processing logic first, and rendering the template second is relatively easy to implement using mason, and gives you a lot more power in situations like this.

    I know that mason has it's own way of allowing method calls within the mason framework, but I believe it was Randall Scwhartz (or possibly Ken Williams) who said (on the mason mailing list) something like "if something can be done in a perl module, it probably should".
      Thats exactly what I'm currently doing.
      1. page in /auth_required/ is requested
      2. /auth_required/autohandler invoked
      3. reads _session_id from cookie with Apache::Cookie;
      4. checks for session with cookie's _session_id (uses Apache::Session::Pg)
      5. validity: double checks that user_id recorded in session log is still in userdb
      The problem is the login forum is at /index.html, and the autohandler that all of my modules requireing authentication are wrapped in is in /auth_required. I believe I have solved the problem the solution I'm currently employing is to set up my <%attr> section as so:
      <%attr> http_title => 'Welcome' # Invalid user name or password OR username deleted from table 101 => 'Invalid username or password' # No cookie sent to server 201 => 'An error has occured' # The session id from cookie was invalid 301 => 'An error has occured'
      Then I set up <%args> section as so:
      <%args> $name_user => undef $error => undef </%args>
      Then from autohandler I clear the gunk out and make a code request:
      $m->clear_buffer; $m->subexec('/index.html', error => 301 ); $m->abort;
      Then inside of /index.html:
      <%method .non_auth> <div class="small_text"> <h1> <% $m->base_comp->attr_if_exists( $m->request_args->{'error'} ) +|h%>. You are not logged in. </h1> If you don't have a login, Click <a href="/create_user.html">here< +/a> to create one. </div> <form method="post" action="/index.html"> <table class="form_fields"> <tr> <td>User Name: </td> <td><input type="text" name="name_user" /></td> </tr> <tr> <td>Password: </td> <td><input type="password" name="password" value="" alt="Requi +red only for admins"/></td> </tr> </table> <div style="text-align:right"> <input type="submit" value="Proceed" /> </div> </form> </%method>
      Working for the time being, any better ideas? I usually follow MVC to a good extent.


      Evan Carroll
      www.EvanCarroll.com
      The key that I didn't make clear in my original reply is that the object instantiation and method call is all done before any data has been sent to the browser. In my case it is actually instigated from the mason handler (aka mod_perl handler), but it could just as easily be the <%init> block of the autohandler.

      Once you have started rendering components you hit the class of problem you are referring to. Another similar issue is that you might only determine (& flag) form problems in the component that defines that actual form (nice encapsulation), but then you're past the point where you could display an error summary at the top of the page (eg. "please review the 3 errors below).

      So as well as keeping logic and display seperate, I aim to have all the logic executed before any rendering commences - that way all parts of the page render have access to (or can be influenced by) all of the application logic.

      This approach can save you from a whole class of tricky situations which mason alone doesn't really solve. Mason is really powerful for templating, includes, managing the interaction with mod_perl/apache etc. What it doesn't give you is an application framework.

      I coded my own, for various reasons, along the lines described above. You can get basically the same effect with MasonX::WebApp for free from CPAN! The subtitle in the POD is "Works with Mason to do processing before Mason is invoked" which is a quick way of saying what I've been waffling on and trying to explain in these replies :)

Re: Problem with Mason, can it be solved with a subrequest?
by Arunbear (Prior) on Oct 02, 2005 at 19:26 UTC
    I would make non_auth a component:
    <div class="small_text"> <h1><% $message %>You are not logged in:</h1> If you don't have a login, Click <a href="/create_user.html">here</a +> to create one. </div> <form method="post" action="/index.html"> <table class="form_fields"> <tr> <td>User Name: </td> <td><input type="text" name="name_user" /></td> </tr> <tr> <td>Password: </td> <td><input type="password" name="password" value="" alt="Require +d only for admins"/></td> </tr> </table> <div style="text-align:right"> <input type="submit" value="Proceed" /> </div> </form> <%args> $message => '' </%args>
    Then in /auth_required/autohandler :
    <%init> my $log_in_status = My::App->authenitcate_user($r); if ($log_in_status eq 'OK') { $m->call_next; # proceeed to /auth_required/foo.html } else { my $message = $m->comp('/get_message', key => $log_in_status); $m->comp('/non_auth', message => $message); } </%init>
    where get_message looks like:
    <% $message %> <%args> $key </%args> <%init> my $message = $key ? $Messages{$key} : '&nbsp;'; </%init> <%once> my %Messages = ( http_title => 'Welcome', # Invalid user name or password OR username deleted from table 101 => 'Invalid username or password', # No cookie sent to server 201 => 'An error has occured', # The session id from cookie was invalid 301 => 'An error has occured' ); </%once>