# Embedding Perl in HTML with Mason Chapter 11: Recipes- P1

Chia sẻ: Thanh Cong | Ngày: | Loại File: PDF | Số trang:19

0
54
lượt xem
7

## Embedding Perl in HTML with Mason Chapter 11: Recipes- P1

Mô tả tài liệu

Tham khảo tài liệu 'embedding perl in html with mason chapter 11: recipes- p1', công nghệ thông tin, kỹ thuật lập trình phục vụ nhu cầu học tập, nghiên cứu và làm việc hiệu quả

Chủ đề:

Bình luận(0)

Lưu

## Nội dung Text: Embedding Perl in HTML with Mason Chapter 11: Recipes- P1

1. Chapter 11: Recipes- P1 No, we are not going teach you how to make a delicious tofu and soybean stew. But this is almost as good. This chapter shows how to do some common Mason tasks, some of them with more than one implementation. Sessions For many of our session examples, we will be using the Apache::Session module. Despite its name, this module doesn't actually require mod_perl or Apache, though that is the context in which it was born and in which it's most often used. It implements a simple tied hash interface to a persistent object.1 It has one major gotcha: you must make sure that the session object gets cleaned up properly (usually by letting it go out of scope), so that it will be written to disk after each access. Nowadays, you might want to take a look at MasonX::Request::WithApacheSession on CPAN, if you really want to integrate sessions into your Mason code. Again, I'd prefer Catalyst for session management. Dave, 2007 Without Touching httpd.conf Here is an example that doesn't involve changing any of your Apache configuration settings. The following code should be placed in a top-level autohandler. Any component that needs to use the session will have to inherit from this component, either directly or via a longer inheritance chain. It uses cookies to store the session.
2. use Apache::Cookie; use Apache::Session::File; my %c = Apache::Cookie->fetch; my $session_id = exists$c{masonbook_session} ? $c{masonbook_session}->value : undef; First, it loads the necessary modules. Normally we recommend that you do this at server startup via a PerlModule directive in your httpd.conf file or in your handler.pl file to save memory, but we load them here just to show you which ones we are using. The component uses the Apache::Cookie module to fetch any cookies that might have been sent by the browser. Then we check for the existence of a cookie called masonbook_session, which if it exists should contain a valid session ID. local *MasonBook::Session; eval { tie %MasonBook::Session, 'Apache::Session::File',$session_id, { Directory => '/tmp/sessions', LockDirectory => '/tmp/sessions', };
3. }; if ($@) { die$@ unless $@ =~ /Object does not exist/; # Re-throw$m->redirect('/bad_session.html'); } The first line ensures that when this component ends, the session variable will go out of scope, which triggers Apache::Session's cleanup mechanisms. This is quite important, as otherwise the data will never be written to disk. Even worse, Apache::Session may still be maintaining various locks internally, leading to deadlock. We use local() to localize the symbol table entry *MasonBook::Session; it's not enough to localize just the hash %MasonBook::Session, because the tie() magic is attached to the symbol table entry. It's also worth mentioning that we use a global variable rather than a lexical one, because we want this variable to be available to all components. If the value in the $session_id variable is undef, that is not a problem. The Apache::Session module simply creates a new session ID. However, if$session_id is defined but does not represent a valid session, an exception will be thrown. This means either that the user's session has expired or that she's trying to feed us a bogus ID. Either way, we
4. want to tell her what's happened, so we redirect to another page that will explain things. To trap the exception, we wrap the tie() in an eval {} block. If an exception is thrown, we check $@ to see whether the message indicates that the session isn't valid. Any other error is fatal. If the session isn't valid, we use the redirect() method provided by the request object. Finally, we send the user a cookie: Apache::Cookie->new($r, name => 'masonbook_session', value => $MasonBook::Session{_session_id}, path => '/', expires => '+1d', )->bake; This simply uses the Apache::Cookie module to ensure that a cookie will be sent to the client with the response headers. This cookie is called 'masonbook_session' and is the one we checked for earlier. It doesn't hurt to send the cookie every time a page is viewed, though this will reset the expiration time of the cookie each time it is set. If you want the cookie to persist for only a certain fixed length of time after the session is created, don't resend the cookie.$m->call_next;
5. This line simply calls the next component in the inheritance chain. Presumably, other components down the line may change the contents of %MasonBook::Session, and those modifications will be written to disk at the end of the request. Example 11-1 shows the entire component. Example 11-1. session-autohandler-Apache-Session.comp use Apache::Cookie; use Apache::Session::File; my %c = Apache::Cookie->fetch; my $session_id = exists$c{masonbook_session} ? $c{masonbook_session}->value : undef; local *MasonBook::Session; eval { tie %MasonBook::Session, 'Apache::Session::File',$session_id, { Directory => '/tmp/sessions', LockDirectory => '/tmp/sessions',
6. }; }; if ($@) { die$@ unless $@ =~ /Object does not exist/; # Re-throw$m->redirect('/bad_session.html'); } Apache::Cookie->new( $r, name => 'masonbook_session', value =>$MasonBook::Session{_session_id}, path => '/', expires => '+1d', )->bake; $m->call_next; Predeclaring the Global via an httpd.conf File 7. It'd be nice to be able to simply use the global session variable without having to type the fully qualified name, %MasonBook::Session in every component. That can be done by adding this line to your httpd.conf file: PerlSetVar MasonAllowGlobals %session Of course, if you're running more than one Mason-based site that uses sessions, you may need to come up with a unique variable name. Adding this to your httpd.conf means you can simply reference the %session variable in all of your components, without a qualifying package name. The %session variable would actually end up in the HTML::Mason::Commands package, rather than MasonBook. Predeclaring the Global via a handler.pl Script If you have a handler.pl script, you could also use the session-making code we just saw. If you wanted to declare a %session global for all your components, you'd simply pass the allow_globals parameter to your interpreter when you make it, like this: my$ah = HTML::Mason::ApacheHandler->new( comp_root => ..., data_dir => ..., allow_globals => [ '%session' ] );
8. You might also choose to incorporate the session-making code into your handler subroutine rather than placing it in a component. This would eliminate the need to make sure that all components inherit from the session- making component. Using Cache::Cache for Sessions Just to show you that you don't have to use Apache::Session, here is a simple alternate using Cache::Cache , which is integrated into Mason via the request object's cache() method. This version also sets up the session in a top-level autohandler just like our first session example. It looks remarkably similar. use Apache::Cookie; use Cache::FileCache; use Digest::SHA1; Again, for memory savings, you should load these modules at server startup. my $cache = Cache::FileCache->new( { namespace => 'Mason-Book-Session', cache_root => '/tmp/sessions', 9. default_expires_in => 60 * 60 * 24, # 1 day auto_purge_interval => 60 * 60 * 24, # 1 day auto_purge_on_set => 1 } ); This creates a new cache object that will be used to store sessions. Without going into too much detail, this creates a new caching object that will store data on the filesystem under /tmp/sessions.2 The namespace is basically equivalent to a subdirectory in this case, and the remaining options tell the cache that, by default, stored data should be purged after one day and that it should check for purgeable items once per day. my %c = Apache::Cookie->fetch; if (exists$c{masonbook_session}) { my $session_id =$c{masonbook_session}- >value; $MasonBook::Session =$cache- >get($session_id); }$MasonBook::Session ||= { _session_id => Digest::SHA1::sha1_hex( time, rand, $$) }; 10. These lines simply retrieve an existing session based on the session ID from the cookie, if such a cookie exists. If this fails or if there was no session ID in the cookie, we make a new one with a randomly generated session ID. The algorithm used earlier for generating the session ID is more or less the same as the one provided by Apache::Session's Apache::Session::Generate::MD5 module, except that it uses the SHA1 digest module. This algorithm should provide more than enough randomness to ensure that there will never be two identical session IDs generated. It may not be enough to keep people from guessing possible session IDs, though, so if you want make sure that a session cannot be hijacked, you should incorporate a secret into the digest algorithm input. Apache::Cookie->new( r, name => 'masonbook_session', value => MasonBook::Session->{_session_id}, path => '/', expires => '+1d', )->bake; We then set a cookie in the browser that contains the session ID. This cookie will expire in one day. Again, this piece is identical to what we saw when using Apache::Session. eval { m->call_next }; 11. cache->set( MasonBook::Session->{_session_id} => MasonBook::Session ); Unlike with Apache::Session, we need to explicitly tell our cache object to save the data. This means we need to wrap the call to m- >call_next() in an eval {} block in order to catch any exceptions thrown in other components. Otherwise, this part looks almost exactly like our example using Apache::Session. die @ if @; After saving the session, we rethrow any exception we may have gotten. The entire component is shown in Example 11-2. Example 11-2. session-autohandler-Cache-Cache.comp use Apache::Cookie; use Digest::SHA1; my cache = Cache::FileCache->new( namespace => 'Mason- Book-Session', cache_root => '/tmp/sessions', 12. default_expires_in => 60 * 60 * 24, # 1 day auto_purge_interval => 60 * 60 * 24, # 1 day auto_purge_on_set => 1 } ); my %c = Apache::Cookie->fetch; if (exists c{masonbook_session}) { my session_id = c{masonbook_session}- >value; MasonBook::Session = cache- >get(session_id); } MasonBook::Session ||= { _session_id => Digest::SHA1::sha1_hex( time, rand,$$ ) }; Apache::Cookie->new( $r, name => 'masonbook_session', 13. value =>$MasonBook::Session->{_session_id}, path => '/', expires => '+1d', )->bake; eval { $m->call_next };$cache->set( $MasonBook::Session->{_session_id} =>$MasonBook::Session ); die $@ if$@; Sessions with Cache::Cache have these major differences from those with Apache::Session: • The session itself is not a tied hash. Objects are faster than tied hashes but not as transparent. • No attempt is made to track whether or not the session has changed. It is always written to the disk at the end of a session. This trades the performance boost of Apache::Session's behavior for the assurance that the data is always written to disk.
14. When using Apache::Session, many programmers are often surprised that changes to a nested data structure in the session hash, like: $session{user}{name} = 'Bob'; are not seen as changes to the top-level %session hash. If no changes to this hash are seen, Apache::Session will not write the hash out to storage. As a workaround, some programmers may end up doing something like:$session{force_a_write}++; or: $session{last_accessed} = time( ); after the session is created. Using Cache::Cache and explicitly saving the session every time incurs the same penalty as always changing a member of an Apache::Session hash. Putting the Session ID in the URL If you don't want to, or cannot, use cookies, you can store the session ID in the URL. This can be somewhat of a hassle because it means that you have to somehow process all the URLs you generate. Using Mason, this isn't as bad as it could be. There are two ways to do this: One would be to put a filter in your top-level autohandler that looks something like this: s/href="([^"])+"/add_session_id($1)/eg;
15. s/action="([^"])+"/add_session_id($1)/eg; The add_session_id() subroutine, which should be defined in a module, would look something like this: sub add_session_id { my$url = shift; return $url if$url =~ m{^\w+://}; # Don't alter external URLs if ($url =~ /\?/) {$url =~ s/\?/?session_id=$MasonBook::Session{_session_id}&/ ; } else {$url .= "?session_id=$MasonBook::Session{_session_id}"; } return$url; } This routine accounts for external links as well as links with or without an existing query string. However, it doesn't handle links with fragments properly.
16. The drawback to putting this in the is that it filters URLs only in the content body, not in headers. Therefore you'll need to handle those cases separately. The other solution would be to create all URLs (including those intended for redirects) via a dedicated component or subroutine that would add the session ID. This latter solution is probably a better idea, as it handles redirects properly. The drawback with this strategy is that you'll have a Mason component call for every link, instead of just regular HTML. We'll add a single line (bolded in Example 11-3) to the /lib/url.mas component we saw in Chapter 8. Now this component expects there to be a variable named %UserSession. Example 11-3. url-plus-session-id.mas $scheme => 'http'$username => undef $password => ''$host => undef $port => undef$path %query => ( ) $fragment => undef 17. my$uri = URI->new; if ($host) {$uri->scheme($scheme); if (defined$username) { $uri->authority( "$username:$password" ); }$uri->host($host);$uri->port($port) if$port; } # Sometimes we may want to path in a query string as part of the # path but the URI module will escape the question mark. my $q; if ($path =~ s/\?(.*)$// ) {$q = $1; } 18.$uri->path($path); # If there was a query string, we integrate it into the query # parameter. if ($q) { %query = ( %query, split /[&=]/, $q ); }$query{session_id} = $UserSession{session_id}; #$uri->query_form doesn't handle hash ref values properly while ( my ( $key,$value ) = each %query ) { $query{$key} = ref $value eq 'HASH' ? [ %$value ] : $value; }$uri->query_form(%query) if %query; $uri->fragment($fragment) if \$fragment;
19. canonical | n %>\