Torsten Förtsch
IT System Development & Security
Kaum macht man's richtig, schon geht's, ;-)

>> Home >> Publikationen >> Mod_perl 2 >> RC5

Hier finden Sie die an mod_perl-2.0.0-RC5 angepassten Beispiele zu meinem Mod_perl2 Workshop aus dem Linux-Magazin 5/2005 und 6/2005

Beispiele aus Teil1 (5/2005)

Listing 1:

<Perl>
package My::VersionComponent;
use Apache2::Directive ();

sub add {
  $_[3]->add_version_component
    ( 'User/'.Apache2::Directive::conftree()->lookup('User') );
}
</Perl>

PerlPostConfigHandler My::VersionComponent::add
ServerTokens Full

Die Änderung gegenüber der zu MP2-Versionen vor RC5 passenden Variante betrifft nur die Namensänderung von Apache:: zu Apache2::.

Listing 2:

<Perl>
package My::Location;

sub postreadrequest {
  my $r=shift;

  warn "PostReadRequest: Location=".$r->dir_config('Location')."\n";
  return Apache2::Const::DECLINED;
}

sub trans {
  my $r=shift;

  if( $r->uri eq '/L1' ) {
    $r->uri('/L2');
  }
  warn "Trans: Location=".$r->dir_config('Location')."\n";
  return Apache2::Const::DECLINED;
}

sub headerparser {
  my $r=shift;

  warn "HeaderParser: Location=".$r->dir_config('Location')."\n";;
  return Apache2::Const::DECLINED;
}
</Perl>

PerlPostReadRequestHandler My::Location::postreadrequest
PerlHeaderParserHandler My::Location::headerparser

PerlSetVar Location 0

<Location /L1>
PerlSetVar Location 1
</Location>

<Location /L2>
PerlSetVar Location 2
</Location>

Hier wurde Apache:: in Apache2::Const:: geändert.

Und zu guter Letzt noch der loopup Alias, um Methoden zu finden:

alias lookup='perl -MModPerl::MethodLookup -e print_method'

Hier ist -MApache2 weggefallen. Dieses Modul war nötig zur Anpassung des @INC Arrays in früheren Versionen. Mit der Umbenennung in den Apache2:: Namensraum wurde es überflüssig.

Beispiele aus Teil2 (6/2005)

Im 2. Teil des Artikels wurde ein konkretes Modul entwickelt. Auch hier beschränken sich die Änderungen auf Umbenennungen. Zur Verdeutlichung wurde das Modul selbst auch umbenannt:

package Apache2::ClickPath;

use 5.008;
use strict;

use Apache2::RequestRec ();
use Apache2::RequestUtil ();
use Apache2::RequestIO ();
use Apache2::Filter ();
use Apache2::Const -compile => qw(DECLINED OK);

our $VERSION='0.02';
our $tag='-';

sub handler {
  my $r=shift;		# das Anfrage-Objekt

  my $uri=$r->uri;

  unless( $r->is_initial_req ) {
    # sub-request or internal redirect
    my $pr = $r->main           # sub-request
          || $r->prev;          # internal redirect

    $r->subprocess_env( SESSION=>$pr->subprocess_env('SESSION') );
    $r->pnotes( newsession=>$pr->pnotes( 'newsession' ) );

    return Apache2::Const::DECLINED
  }

  if( $uri=~s!^/+ \Q$tag\E ( [^/]+ ) /!/!x ) {
    my $session=$1;

    $r->uri( $uri );
    $r->subprocess_env( SESSION=>$session );
  } else {
    my $session=$r->subprocess_env('UNIQUE_ID');
    $r->subprocess_env( SESSION=>$session );
    $r->pnotes( newsession=>1 );
  }

  return Apache2::Const::DECLINED
}

sub OutputFilter {
  my $f=shift;		# das Filter Objekt
  my $sess;
  my $host;
  my $context;
  my ($re, $re1, $the_request);

  unless ($f->ctx) {    # Initialisierung
    my $r=$f->r;	# hier finden wir das Anfrage-Objekt wieder

    $sess='/'.$tag.$r->subprocess_env('SESSION');
    $host=$r->headers_in->{Host};

    if( $r->pnotes( 'newsession' ) ) {
      $the_request=$r->the_request;
      $the_request=~s/^\s*\w+\s+//;
      $the_request=~s![^/]*[\s?].*$!!;

      my $re=qr,^(https?://\Q$host\E)?(?!\w+:)(.),i;
      $r->headers_out->{Location}=~s!$re!$2 eq '/'
                                         ? $1.$sess.$2
                                         : $1.$sess.$the_request.$2
                                        !e
	if( exists $r->headers_out->{Location} );
    } else {
      $the_request="";

      my $re=qr!^(https?://\Q$host\E)?/!i;
      $r->headers_out->{Location}=~s!$re!$1$sess/!
	if( exists $r->headers_out->{Location} );
    }

    # Nur Dokumente des Typs text/html dürfen gepatcht werden
    # 
    unless( $r->content_type =~ m!text/html!i ) {
      $f->remove;
      return Apache2::Const::DECLINED;
    }

    if( $r->pnotes( 'newsession' ) ) {
      # Wenn die Session neu ist, dann müssen auch relative Links
      # geändert werden.
      # Relative Links beginnen nicht mit einem Slash (/) oder
      # Hash (# (Anker in der selben Seite)), aber auch nicht mit
      # http:, mailto: javascript:, o.ä.
      $re1=qr,(			# $1 start
	       <\s*a(?:rea)?\s+	# <a> oder <area> start
	       .*?		# evtl. target=...
               \bhref\s*=\s*	# href=
	       (["'])		# " oder ': Das ist $2 oder \2 (siehe unten)
	       (?:https?://\Q$host\E)?	# evtl. Host
	      )			# Das alles ist in $1
	      (?:/+\Q$tag\E[^/]+)?
	      (			# $3 start
	       (?!(?:\w+:|\043)).*?  # ein beliebiger nicht mit http:// o.ä.
				#   beginnender String (moeglichst kurz)
	       \2		# das schließende Quote: $2
	      )			# $3 ende
	     ,xi;
    } else {
      $re1=qr,(			# $1 start
	       <\s*a(?:rea)?\s+	# <a> oder <area> start
	       .*?		# evtl. target=...
               \bhref\s*=\s*	# href=
	       (["'])		# " oder ': Das ist $2 oder \2 (siehe unten)
	       (?:https?://\Q$host\E)?	# evtl. Host
	      )			# Das alles ist in $1
	      (?:/+\Q$tag\E[^/]+)?
	      (			# $3 start
	       /.*?		# ein beliebiger mit /
				#   beginnender String (moeglichst kurz)
	       \2		# das schließende Quote: $2
	      )			# $3 ende
	     ,xi;
    }

    # store the configuration
    $f->ctx( +{
	        extra => '',
		sess  => $sess,
		req   => $the_request,
		re    => qr/(<[^>]*)$/,
		re1   => $re1,
	      } );

    # der Output Filter ändert die Länge der Antwort. Daher muss der
    # vorher berechnete Content-Length Header gelöscht werden. Apache
    # benutzt dann Transfer-Encoding: chunked
    $r->headers_out->unset('Content-Length');
  }

  # hole den Filter Kontext
  $context=$f->ctx;

  $sess=$context->{sess};
  $re1=$context->{re1};
  $re=$context->{re};
  $the_request=$context->{req};

  # jetzt wird der Datenstrom bearbeitet
  while( $f->read(my $buffer, 1024) ) {

    # Beim letzten Aufruf könnte ein halbes HTML Tag im Buffer
    # übrig geblieben sein. Das wird hier vor den neuen Datenblock
    # gehängt.
    $buffer=$context->{extra}.$buffer if( length $context->{extra} );

    # Wenn unser aktueller Datenblock mit einem halben HTML Tag endet,
    # wird es abgeschnitten und im Filterkontext zwischengespeichert,
    # bis der nächste Datenblock kommt.
    if (($context->{extra}) = $buffer =~ m/$re/) {
      $buffer=substr( $buffer, 0, -length($context->{extra}) );
    }

    if( length $the_request ) {
      $buffer=~s!$re1!(substr($3, 0, 1) eq '/')
                      ? $1.$sess.$3
                      : $1.$sess.$the_request.$3
                     !ge;
    } else {
      $buffer=~s!$re1!$1$sess$3!g;
    }

    $f->print( $buffer );
  }

  if( $f->seen_eos ) {
    # das war's. Wir haben keine Daten mehr zu verarbeiten.

    # Hier muss keine Ersetzung durchgeführt werden, da $context->{extra}
    # für richtige HTML Dokumente leer sein muss.
    # trotzdem geben wir es aus.
    $f->print( $context->{extra} ) if( length $context->{extra} );
  }

  return Apache2::Const::OK;
}

1;

... noch ein dazu passender Ausschnitt aus der httpd.conf. In Zeile 1 muss PERLLIB noch gegen einen Pfad ersetzt werden, so dass Perl das Modul Apache2::ClickPath auch findet:

PerlSwitches -I PERLLIB

<Perl>
package Test;

use Apache2::RequestIO ();
use Apache2::Const -compile => qw(OK);
use strict;

sub handler {
  my $r=shift;

  $r->content_type( 'text/plain' );
  $r->print( "SESSION=".$r->subprocess_env( 'SESSION' )."\n" );

  return Apache2::Const::OK;
}

</Perl>

PerlModule Apache2::ClickPath
PerlTransHandler Apache2::ClickPath
PerlOutputFilterHandler Apache2::ClickPath::OutputFilter

<Location /Test>
SetHandler modperl
PerlResponseHandler Test
</Location>

Weiter mit Unteranfragen und internen Weiterleitungen