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

>> Home >> Publikationen >> Mod_perl 2 >> Unteranfragen

Unteranfragen und Interne Weiterleitungen

Hier geht es um diese Zeilen aus Apache2::ClickPath:

  unless( $r->is_initial_req ) {
    # sub-request oder 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 Apache::DECLINED
  }

In der gedruckten Version des Workshops konnte aus Platzgründen darauf nicht eingegangen werden.

Im ersten Teil erwähnte ich vom Apache selbst erzeugte virtuelle Anfragen, die den HTTP-Anfrage-Zyklus teilweise durchlaufen. Bei genauerer Betrachtung ergeben sich 2 Arten solcher Anfragen, interne Weiterleitungen (internal redirect) und Unteranfragen (sub-request). Im Weiteren werde ich die englischen Bezeichnungen benutzen.

Sub-Requests werden eingesetzt, wenn ein Handler in einer frühen Phase Informationen braucht, die erst in einer späteren Phase bereitgestellt werden. Man kann damit Fragen vom Typ: Was wäre, wenn ...? stellen. Dabei wird ein neues Anfrage-Objekt geschaffen, das dann alle Phasen des HTTP-Anfrage Zyklus bis kurz vor die Response Phase durchläuft. Interne Redirects durchlaufen alle Phasen einschließlich der Response Phase.

Zunächst ein paar Beispiele: Sub-Requests entstehen z.B. durch Verwendung der DirectoryIndex Anweisung. Hier können mehrere Index-Dokumente angegeben werden. Mod_dir erzeugt für jedes einen Sub-Request mit der Frage: Ist der Status-Code 200 (OK), wenn dieses Dokument angefordert würde? Wird die Frage mit ja beantwortet, dann liefert es das Dokument mittels internem Redirect aus.

Etwas seltener sind sicher RewriteCond Anweisungen mit %{LA-U:variable} als Teststring. Damit kann man z.B. REMOTE_USER in einer RewriteRule schon in der Translation-Phase benutzen, obwohl dieser Wert erst in der Authentication-Phase ermittelt wird. Hier wird auch ein Sub-Request benutzt.

Ein interner Redirect entsteht z.B., wenn ein CGI-Script nur eine Location Header-Zeile ausgibt:

#!/bin/bash

echo Location: $QUERY_STRING
echo

Aufgerufen als /cgi-bin/redir.sh?/pfad/nach/irgendwo.html erzeugt das Script einen internen Redirect nach /pfad/nach/irgendwo.html.

Eine andere Möglichkeit interne Redirects zu erzeugen, bietet die ErrorDocument Anweisung.

Mod_perl (und Apache) bieten mehrere Funktionen, um mit internen Redirects und Sub-Requests zu arbeiten. Mit lookup_uri und lookup_file werden Sub-Requests erzeugt und mit internal_redirect interne Redirects.

Wichtiger in unserem Fall, ist zu erfahren, ob unser Translation Handler für eine solche virtuelle Anfrage aufgerufen wurde, oder ob die Anfrage direkt vom Browser kam. Diese Frage beantwortet is_initial_req. Liefert sie true, so kommt die Anfrage vom Browser und der Translation Handler erzeugt die Session wie bisher. Liefert sie aber false, so müssen wir die Session von der vorhergehenden initialen oder virtuellen Anfrage übernehmen. Auf diese Weise vererbt sich die Session in alle virtuellen Anfragen. Die wichtigen Funktionen hierfür sind main und prev. main liefert im Fall eines Sub-Requests die zugehörige Hauptanfrage, die nicht unbedingt die initiale sein muss. prev gibt im Fall einer internen Weiterleitung die vorhergehende Anfrage zurück.

Apache2::ReqChain

Der interessierte Leser kann mit folgendem Modul als Translation- oder Fixup-Handler selbst experimentieren und das Zusammenspiel von internen Redirects und Sub-Requests herausfinden.

package Apache2::ReqChain;

use 5.008;
use strict;

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

our $VERSION='0.01';

our $seq_num;

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

  $seq_num=1 if( $r->is_initial_req );

  my $n=$r->pnotes( 'req#' );
  unless( defined $n ) {
    $n=$seq_num++;
    $r->pnotes( 'req#'=>$n );
  }

  warn "\n>>>> $n\n";
  warn "  r uri: ".$r->uri."\n";

  unless( $r->is_initial_req ) {
    my $pr;
    if( $pr=$r->main ) {	# sub-request
      warn "  sub-request von\n";
      warn "    main uri: ".$pr->uri."\n";
      warn "    main initial: ".$pr->is_initial_req, "\n";
      warn "    main #: ".$pr->pnotes( 'req#' ), "\n";
      warn "    main main #: ".$pr->main->pnotes( 'req#' ), "\n"
	if( $pr->main );
      warn "    main prev #: ".$pr->prev->pnotes( 'req#' ), "\n"
	if( $pr->prev );
    }

    if( $pr=$r->prev ) {	# internal redirect
      warn "  internal redirect von\n";
      warn "    prev uri: ".$pr->uri."\n";
      warn "    prev initial: ".$pr->is_initial_req, "\n";
      warn "    prev #: ".$pr->pnotes( 'req#' ), "\n";
      warn "    prev main #: ".$pr->main->pnotes( 'req#' ), "\n"
	if( $pr->main );
      warn "    prev prev #: ".$pr->prev->pnotes( 'req#' ), "\n"
	if( $pr->prev );
    }
  }

  warn "<<<< $n\n";

  return Apache2::Const::DECLINED
}

1;

Die globale Variable $seq_num spielt hier die zentrale Rolle. Sie wird mit 1 initialisiert, wenn die Anfrage vom Browser kommt. Wird eine Nummer benötigt, wird der aktuelle Wert genommen und $seq_num inkrementiert. Jedesmal wenn der Handler aufgerufen wird, prüft er, ob der aktuellen Anfrage schon eine Nummer zugeordnet ist. Wenn nicht, weist er die nächste zu und speichert sie als Pnote. Damit erhält jede Anfrage eine Nummer mit der wir sie im error_log wieder finden. Der Output nach einem Aufruf von /-TESTSESSION/cgi-bin/redir.sh?/dir/ könnte dann z.B. so aussehen (einige Kommentare wurden noch eingefügt):

# Die initiale Anfrage hat die Nummer 1.
>>>> 1
  r uri: /-TESTSESSION/cgi-bin/redir.sh
<<<< 1

# Der Location-Header erzeugt einen internen Redirect.
>>>> 2
  r uri: /dir/
  internal redirect von
    prev uri: /cgi-bin/redir.sh
    prev initial: 1
    prev #: 1
<<<< 2

# Eine DirectoryIndex-Anweisung lässt mod_dir zuerst nach index.shtml dann
nach index.html suchen.
>>>> 3
  r uri: /dir/index.shtml
  sub-request von
    main uri: /dir/
    main initial: 0
    main #: 2
    main prev #: 1
<<<< 3

>>>> 4
  r uri: /dir/index.html
  sub-request von
    main uri: /dir/
    main initial: 0
    main #: 2
    main prev #: 1
<<<< 4

Weiter mit einigen Aspekten zu mod_include