Apache & mod_rewrite: unnötiger Subrequest?
wahsaga
- webserver
-1 eddi0 wahsaga
1 Christian Kruse
hi,
ich möchte in einem Verzeichnis per RewriteRule Anfragen an ein Script umleiten - aber nur, wenn keine physische Datei existiert, auf die der Request passen würde.
Beispiel:
Datei abc.txt ist vorhanden,
rw.php ist das Script, auf welches umgeleitet werden soll.
Options +MultiViews
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([a-z0-9.-]+)$ rw.php?param=$1 [NS]
Zur Kontrolle des Ablaufs habe ich auch das Rewrite Logging eingeschaltet, auf Level 9.
MultiViews ist aktiviert, weil ich abc.txt auch über /abc erreichen möchte - "funzt" auch.
Wenn ich also /abc anfrage, erhalte ich im Log folgende Einträge:
[rid#13c8bc8/initial] (3) [per-dir j:/web/htdocs/test/rwlog/] strip per-dir prefix: j:/web/htdocs/test/rwlog/abc.txt -> abc.txt
[rid#13c8bc8/initial] (3) [per-dir j:/web/htdocs/test/rwlog/] applying pattern '^([a-z0-9.-]+)$' to uri 'abc.txt'
[rid#13c8bc8/initial] (4) RewriteCond: input='j:/web/htdocs/test/rwlog/abc.txt' pattern='!-f' => not-matched
[rid#13c8bc8/initial] (1) [per-dir j:/web/htdocs/test/rwlog/] pass through j:/web/htdocs/test/rwlog/abc.txt
Die Bedingung, dass dem REQUEST_FILENAME _keine_ physische Datei entsprechen soll, ist ja wenn ich das richtig sehe, _nicht_ erfüllt - bedeutet also, es wurde erkannt, dass zum Request eine Datei existiert, Request wird also durchgereicht - "pass through".
Was mich hierbei aber wundert - wieso kommt "applying pattern" vor der Prüfung der RewriteCond?
Verstehe ich irgendwas fürchterlich falsch, wenn ich annehme, dass die RewriteCond zuerst geprüft werden, und die nachfolgende RewriteRule nur noch ausgewertet werden sollte, wenn die Cond gematched wurde?
So, jetzt zum Testfall, dass keine physische Datei existiert: Ich fordere /xyz an, wird auch korrekt an das Script rw.php durchgereicht - das zeigt mir in der Kontrollausgabe von $_GET an, dass [param] => xyz übergeben wurde.
Das Rewrite Log spuckt dazu folgendes aus:
[rid#13c8bc8/initial] (3) [per-dir j:/web/htdocs/test/rwlog/] strip per-dir prefix: j:/web/htdocs/test/rwlog/xyz -> xyz
[rid#13c8bc8/initial] (3) [per-dir j:/web/htdocs/test/rwlog/] applying pattern '^([a-z0-9.-]+)$' to uri 'xyz'
[rid#13c8bc8/initial] (4) RewriteCond: input='j:/web/htdocs/test/rwlog/xyz' pattern='!-f' => matched
[rid#13c8bc8/initial] (2) [per-dir j:/web/htdocs/test/rwlog/] rewrite xyz -> rw.php?param=xyz
[rid#13c8bc8/initial] (3) split uri=rw.php?param=xyz -> uri=rw.php, args=param=xyz
[rid#13c8bc8/initial] (3) [per-dir j:/web/htdocs/test/rwlog/] add per-dir prefix: rw.php -> j:/web/htdocs/test/rwlog/rw.php
[rid#13c8bc8/initial] (2) [per-dir j:/web/htdocs/test/rwlog/] strip document_root prefix: j:/web/htdocs/test/rwlog/rw.php -> /rw.php
[rid#13c8bc8/initial] (1) [per-dir j:/web/htdocs/test/rwlog/] internal redirect with /rw.php [INTERNAL REDIRECT]
[rid#13cdde0/initial/redir#1] (3) [per-dir j:/web/htdocs/test/rwlog/] strip per-dir prefix: j:/web/htdocs/test/rwlog/rw.php -> rw.php
[rid#13cdde0/initial/redir#1] (3) [per-dir j:/web/htdocs/test/rwlog/] applying pattern '^([a-z0-9.-]+)$' to uri 'rw.php'
[rid#13cdde0/initial/redir#1] (4) RewriteCond: input='j:/web/htdocs/test/rwlog/rw.php' pattern='!-f' => not-matched
[rid#13cdde0/initial/redir#1] (1) [per-dir j:/web/htdocs/test/rwlog/] pass through j:/web/htdocs/test/rwlog/rw.php
OK, bei den ersten vier Zeilen komme ich noch mit - auch wenn ich mich erneut wundere, dass "applying pattern" vor der RewriteCond kommt - das Pattern passt, und die Bedingung, dass zu xyz keine physische Datei existieren soll, ist erfüllt - es erfolgt das Umschreiben auf rw.php?param=xyz.
So, jetzt sollte hier aber m.E. "Feierabend" sein - schließlich habe ich das Flag NS angegeben, "used only if no internal sub-request".
Das wird wohl in den nächsten 4 Zeilen auch korrekt erkannt:
internal redirect with /rw.php [INTERNAL REDIRECT]
Aber warum tauchen jetzt die letzten 4 Zeilen dort auch noch auf?
[rid#13cdde0/initial/redir#1] will mir wohl sagen, dass jetzt für die intern erfolgte Umschreibung die Rewrite-Maschine noch mal angeworfen wird.
Generell ja OK - aber warum verhindert hier das Flag NS das nicht?
gruß,
wahsaga
Hallo,
RewriteRule ^([a-z0-9.-]+)$ rw.php?param=$1 [NS]
da wäre ich (offengestanden ja) etwas fauler:
RewriteRule ^(.*)$ rw.php?param=$1 [LQSA]
Verstehe ich irgendwas fürchterlich falsch, wenn ich annehme, dass die RewriteCond zuerst geprüft werden, und die nachfolgende RewriteRule nur noch ausgewertet werden sollte, wenn die Cond gematched wurde?
Das entspräche auch meinen Vorstellungen einer vernünftigen Umsetzung...
So, jetzt sollte hier aber m.E. "Feierabend" sein - schließlich habe ich das Flag NS angegeben, "used only if no internal sub-request".
Das wird wohl in den nächsten 4 Zeilen auch korrekt erkannt:
internal redirect with /rw.php [INTERNAL REDIRECT]Aber warum tauchen jetzt die letzten 4 Zeilen dort auch noch auf?
[rid#13cdde0/initial/redir#1] will mir wohl sagen, dass jetzt für die intern erfolgte Umschreibung die Rewrite-Maschine noch mal angeworfen wird.
Generell ja OK - aber warum verhindert hier das Flag NS das nicht?
mutmaßlich fehlt ihm ein "L"
Gruß aus Berlin!
eddi
hi,
RewriteRule ^([a-z0-9.-]+)$ rw.php?param=$1 [NS]
da wäre ich (offengestanden ja) etwas fauler:
RewriteRule ^(.*)$ rw.php?param=$1 [LQSA]
Nun ja, real soll mein Suchmuster nicht mehr für Requests auf Dateien in Unterverzeichnissen matchen - während ich /xyz behandeln möchte, möchte ich bspw. /ordner/xyz nicht mehr davon behandelt wissen.
Deshalb nicht die ganz so grobe Keule.
mutmaßlich fehlt ihm ein "L"
Entgegen deiner obigen Schreibweise wären mehrere Flags durch ein Komma voneinander zu trennen, bei deinem Beispiel also [L,QSA], und für meinen Fall [L,NS].
Aber auch das bewirkt keine Änderung [1] - ich erhalte immer noch die gleichen zwölf Zeilen im Log, zuerst Umschreiben des Requests, dann Erkenntnis dass es sich bei rw.php?param=xyz um einen "internal redirect with /rw.php [INTERNAL REDIRECT]" handelt - und dann noch mal Abarbeitung des RewriteRule und der RewriteCond für diesen, wobei dann auf die hierbei nicht erfüllte RewriteCond wiederum "pass through" erfolgt.
[1] Hätte ich auch nicht erwartet, weil L last rule lediglich bedeutet, dass in _diesem_ Durchgang keine nachfolgenden Rules mehr angewendet werden sollen. Das soll aber nicht verhindern, dass die intern vorgenommene Umschreibung ggf. erneut in die Rewrite-Maschinerie reinläuft - und dabei eine erneute Behandlung des Subrequests durch die Rule zu verhindern wäre, wie gesagt, m.E. Aufgabe des Flags NS.
gruß,
wahsaga
你好 wahsaga,
Was mich hierbei aber wundert - wieso kommt "applying pattern" vor der Prüfung der RewriteCond?
Das kann ich dir aus dem Stand so nicht beantworten, ich würde aber raten, dass der Gedankengang "wenn das Pattern nicht matcht, können wir uns die Bedingungen sparen" dahinter steckt. Die Arbeitsweise steht aber auch im Manual so beschrieben.
Aber warum tauchen jetzt die letzten 4 Zeilen dort auch noch auf?
Vorsicht: interner Redirect != interner Sub-Request.
再见,
克里斯蒂安
hi,
Was mich hierbei aber wundert - wieso kommt "applying pattern" vor der Prüfung der RewriteCond?
Das kann ich dir aus dem Stand so nicht beantworten, ich würde aber raten, dass der Gedankengang "wenn das Pattern nicht matcht, können wir uns die Bedingungen sparen" dahinter steckt.
Na und mein Gedankengang wäre eher "wenn die Bedingung nicht erfüllt ist, können wir uns das Prüfen des Patterns sparen" :-)
Die Arbeitsweise steht aber auch im Manual so beschrieben.
Hm, wo genau?
In der Beschreibung zur RewriteCond in der Erklärung zu mod_rewrite für den 1.3er Indianer finde ich nur:
"The RewriteCond directive defines a rule condition. Precede a RewriteRule directive with one or more RewriteCond directives. The following rewriting rule is only used if its pattern matches the current state of the URI and if these additional conditions apply too."
Gut, das kann man so oder so auslegen - eine eindeutige Aussage über die Reihenfolge der Abarbeitung trifft es nicht.
Aber warum tauchen jetzt die letzten 4 Zeilen dort auch noch auf?
Vorsicht: interner Redirect != interner Sub-Request.
Hm, was genau ist jetzt der Unterschied?
Auf das erste Umschreiben des Requests,
[rid#13c8bc8/initial] (2) [per-dir j:/web/htdocs/test/rwlog/] rewrite xyz -> rw.php?param=xyz
folgt dann
[rid#13c8bc8/initial] (3) split uri=rw.php?param=xyz -> uri=rw.php, args=param=xyz
...
Immer noch nur /initial, noch keine Rede von einem Redirect.
Ein paar Zeilen später geht's dann mit
[rid#13cdc30/initial/redir#1] (3) [per-dir j:/web/htdocs/test/rwlog/] strip per-dir prefix: j:/web/htdocs/test/rwlog/rw.php -> rw.php
weiter - hier taucht jetzt /redir#1 auf, das wäre dann jetzt ein interner Redirect?
Als mir halbwegs einleuchtende Erklärung finde ich in der Doku nur:
"For instance, sub-requests occur internally in Apache when mod_include tries to find out information about possible directory default files (index.xxx)."
Wenn das also ein interner Sub-Request wäre - wo genau ist der Unterschied zum internen Redirect?
Liegt er darin, dass der Redirect erneut in das Processing der RewriteRules etc. eingespeist wird, und der Sub-Request nicht ...?
gruß,
wahsaga
Hallo wahsaga,
Als mir halbwegs einleuchtende Erklärung finde ich in der Doku nur:
"For instance, sub-requests occur internally in Apache when mod_include tries to find out information about possible directory default files (index.xxx)."
Wenn das also ein interner Sub-Request wäre - wo genau ist der Unterschied zum internen Redirect?
Liegt er darin, dass der Redirect erneut in das Processing der RewriteRules etc. eingespeist wird, und der Sub-Request nicht ...?
Interner Redirect -> Der Apache bekommt eine neue intere URL (bzw. nur den Teil davon, der nach dem Host/Port-Teil kommt) und startet die ganze Verarbeitungsmaschinerie neu für diesen Request. Anders ausgedrückt: ein Interner Redirect ist für den Apachen nichts anderes, als ob der Benutzer die Ziel-URL des Redirects selbst eingegeben hätte *plus* zuästzlich ein paar Umgebungsvariablen (REDIRECT_*). Stell Dir das ganze so vor:
while (1) {
verarbeite_url(url,neuerurl,daten);
if (daten) {
schreibe(daten);
break;
}
url = neuerurl;
}
Das stimmt zwar so absolut nicht ;-), macht aber die Arbeitsweise des Apachen vielleicht verständlicher. Bei mod_rewrite wird halt (wenn der Apache tatsächlich exakt nach obigem Schema funktionieren _würde_) einfach ein neuer URL zurückgegeben und keine Daten -> die Schleife wird nochmal ausgeführt, es wird nicht (!) nochmal eine Funktion im *inneren* durch mod_rewrite aufgerufen, die dann das Ergebnis der Umschreibung als Daten zurückgibt.
Interner Subrequest -> Wenn Du bei mod_include (SSI) <!--#include virtual="/relativer-url"--> oder bei PHP (als Modul) virtual('/relativer-url'); machst. Dann ist der alte Request ja noch am Laufen (d.h. er ist *nicht* abgeschlossen und es heißt "du findest die Antwort unter der URL ...") und der Apache macht zusätzlich noch einen Subrequest und gibt dem Modul (PHP, mod_include, ...) den Inhalt des ausgeführten Subrequests zurück, damit das Modul etwas damit anfangen kann (in beiden Fällen primär erstmal ausgeben, in PHP kann man mit Output Buffering allerdings noch andere Dinge mit anstellen, andere Module machen vielleicht nochmal was anderes damit).
Viele Grüße,
Christian
Hi,
Wenn das also ein interner Sub-Request wäre - wo genau ist der Unterschied zum internen Redirect?
Liegt er darin, dass der Redirect erneut in das Processing der RewriteRules etc. eingespeist wird, und der Sub-Request nicht ...?
Vielleicht können diese späten rewritings einfach nicht wirklich als 'internal sub-requests' an die entsprechenden Core-APIs übergeben werden. Könnte daran liegen, dass das Rewriting, wenn es erst 'per-directory' initiiert wird (.htaccess statt httpd.conf bzw. apache2.conf) keine entsprechende API mehr erreichen kann. Zumindest verstehe ich den 2.Absatz des Abschnittes 'API Phases' der technischen Dokumentation so ('This restarts processing of the API phases.').
Das rewrite-Modul selbst kann möglicherweise dann das Ergebnis des Mappings von re-injected URLs auf files nicht vom Mapping-Ergebnis 'normaler' URLs unterscheiden. Und für das Modul selbst bestünde dann auch kein direkter Zusammenhang zwischen den in Zeile 8 und 9 geloggten Ereignissen mehr.
Falls du das lokal ausprobiert hast, würde mich jetzt natürlich interessieren, wie das log aussieht, wenn die entsprechenden Direktiven in der apache2.conf stehen...
(Bzw. das könnt ich selber gerade mal probieren...)
((...))
Und siehe da - mit folgender Direktive im virtual-host-container
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
#RewriteRule ^([a-z0-9.-]+)$ rw.php?param=$1 [NS]
RewriteRule ^(.*)$ /rw.php?param=$1 [NS]
(musste erst etwas drehen)
ergab sich folgendes Log:
[rid#8326928/initial] (2) init rewrite engine with requested uri /logtest
[rid#8326928/initial] (3) applying pattern '^(.*)$' to uri '/logtest'
[rid#8326928/initial] (4) RewriteCond: input='/logtest' pattern='!-f' => matched
[rid#8326928/initial] (2) rewrite /logtest -> /rw.php?param=/logtest
[rid#8326928/initial] (3) split uri=/rw.php?param=/logtest -> uri=/rw.php, args=param=/logtest
[rid#8326928/initial] (2) local path result: /rw.php
[rid#8326928/initial] (2) prefixed with document_root to /home/nn/htdocs/divers/rw.php
[rid#8326928/initial] (1) go-ahead with /home/nn/htdocs/divers/rw.php [OK]
Freundliche Grüße,
Sancho
Hi,
Christans Posting hatte ich beim Absenden meines Postings noch nicht gelesen -
aber beide ergänzen sich ja - streich meinen ersten Absatz (und die Hälfte des zweiten).
Es hat mit subrequests nichts zu tun - die letzten vier Zeilen in deinem Log dokumentieren jedoch ziemlich genau die eingeschränkten Möglichkeiten/den zusätzlichen Aufwand, den das rewrite-Modul im per-directory-Kontext hat. Da es erst so spät eingreifen kann (erst nach abgeschlossenem URI-File-Mapping), kann es nur über einen redirect operieren, d.h. muss den Mapping-Prozess völlig neu beginnen lassen - wie Christian schreibt:
... als ob der Benutzer die Ziel-URL des Redirects selbst eingegeben hätte *plus* zuästzlich ein paar Umgebungsvariablen (REDIRECT_*).
Wenn die rewrite-engine hingegen schon beim Apache-Start einbezogen wird, dann hat sie schon 'in the URL-to-filename phase' (also auf den Mapping-Prozess) selbst Einflussmöglichkeiten:
| So, after a request comes in and Apache has determined the
| corresponding server (or virtual server) the rewriting engine starts
| processing of all mod_rewrite directives from the per-server
| configuration in the URL-to-filename phase.
(Link.)
Grüße und angenehme Nacht,
Sancho
Das Wort 'Prozess' im obigen Posting bitte im Sinne von 'Prozedur' verstehen.
(War etwas unglücklich gewählt.)
Nu aber gute Nacht,
Sancho.
Hallo Freunde des gehobenen Forumsgenusses,
Das kann ich dir aus dem Stand so nicht beantworten, ich würde aber raten, dass der Gedankengang "wenn das Pattern nicht matcht, können wir uns die Bedingungen sparen" dahinter steckt.
Na und mein Gedankengang wäre eher "wenn die Bedingung nicht erfüllt ist, können wir uns das Prüfen des Patterns sparen" :-)
Um zu prüfen ob die Datei existiert muss man (normalerweise) den Lese-Kopf der Festplatte bewegen, das dauert unendlich lange im Vergleich zu einem regulären Ausdruck.
Gruß
Alexander Brock
你好 wahsaga,
Die Arbeitsweise steht aber auch im Manual so beschrieben.
Hm, wo genau?
Hier.
[quote]The order of rules in the ruleset is important because the rewrite engine processes them in a particular (not always obvious) order, as follows: The rewrite engine loops through the rulesets (each ruleset being made up of RewriteRule directives, with or without RewriteConds), rule by rule. When a particular rule is matched, mod_rewrite also checks the corresponding conditions (RewriteCond directives).[/quote]
Und auch aus dem Ablauf-Graphen wird es klar.
Zum Rest hat ja Christian Seiler schon etwas geschrieben.
再见,
克里斯蒂安
echo $begrüßung;
Die Arbeitsweise steht aber auch im Manual so beschrieben.
Und auch aus dem Ablauf-Graphen wird es klar.
Als Ergänzung: RewriteCond kann unter Anderem auch "RewriteRule backreferences" in seine Tests einbeziehen. Es ist natürlich sinnvoller, diese Backreferences in der RewriteRule zuerst zu ermitteln, um Tests mit ihnen durchführen zu können.
echo "$verabschiedung $name";
Hi,
Als Ergänzung: RewriteCond kann unter Anderem auch "RewriteRule backreferences" in seine Tests einbeziehen. Es ist natürlich sinnvoller, diese Backreferences in der RewriteRule zuerst zu ermitteln, um Tests mit ihnen durchführen zu können.
Das ist sogar in beide Richtungen möglich - denn in der RewriteRule wiederum können ja 'back-references (%N) to the last matched RewriteCond pattern' angegeben werden (Link - siehe etwas weiter unten, nach den 'Hints' und der 'Note')
Ein Hauptgrund für den Ablauf:
1. RewriteRule prüfen
2. über RewriteConds zusätzlich filtern
wird schon sein, dass es in dieser Reihenfolge einfach ökonomischer ist:
Da ja beliebig viele RewriteConds angegeben werden können, wird damit einfach ausgeschlossen, dass erstmal etliche RewriteConds durchgegangen werden, nur um _anschließend_ festzustellen, dass die RewriteRule gar nicht matcht ;)
Freundliche Grüße,
Sancho
hi,
vielen Dank euch allen für die guten Hinweise und hilfreichen Erläuterungen.
Die einzelnen Argumente, warum die Regeln vor den Bedingungen ausgewertet werden, sind sehr einleuchtend,
und der Unterschied in den Abläufen beim Umschreiben "in" der httpd.conf oder erst per-directory war auch sehr schön verständlich beschrieben (obwohl ich auf letzteres eigentlich auch hätte kommen müssen, wenn ich mich mit den Ausführungen zu den Processing-Schritten in der Doku etwas intensiver auseinandergesetzt hätte).
gruß,
wahsaga