Baba: mod_rewrite

Mod_rewrite kann einen ja ganz schön in Bedrängnis bringen. Ich denke der Hauptgrund ist, dass man nicht genau sehen oder verfolgen kann, was da eigentlich passiert. Die Regeln werden ja mehrmals durchlaufen und man sieht nicht, wie oft, mit welchen Werten für die Referenzen, etc. Ich habe keinen Zugriff auf die logs :(

However, beschäftige mich nun seit 2 Tagen damit intensiv und finde mich langsam zurecht.

Folgende Fragen habe ich nun noch, die ich nicht klären kann.

Q1:
In welche Dokumentation soll ich schauen. Es gibt 2.0, 2.2, 2.4. Bei mir steht in %{API_VERSION} "20051115:3". In $_SERVER["SERVER_SOFTWARE"] steht leider nur "Apache", ohne Version.

Ich möchte folgende Umleitung:
www.example.com -> route.example.com/www.php

Das klappt schon fast. Hier eine Auswahl meiner Regeln (.htaccess):

  
# remove trailing slash and reload  
RewriteRule (.*[^/])/$ /$1 [R=301,L]  
  
# redirect <subdomain>.example.com to route.example.com/<subdomain>.php  
RewriteCond %{IS_SUBREQ} !=true  
RewriteCond %{HTTP_HOST} [^route].*.example.de$ [NC]  
RewriteCond %{HTTP_HOST} ^(.*).example.com$ [NC]  
RewriteRule (/?.*) http://route.example.com/%1.php$1 [P]  

Macht aus

www.example.com
http://route.exmaple.com/www.php

und aus

www.example.com/page
http://route.exmaple.com/www.phppage

Q2:
Wie kann ich erreichen, dass in der Referenz der letzten RewriteRule $1 auch der Slash gespeichert wird? Leider kann ich nicht schreiben
RewriteRule (.*) http://route.example.com/%1.php/$1 [P]
denn wenn der Request www.example.com ist, habe ich am Ende der URI einen Slash. Dann springt bei mir aber wieder die erste Regel an, die das ganze per R=301 neu lädt. Endet damit, dass im Browser steht http://route.example.com/www.php

Q3:
Ich möchte diese Regel "intern" anwenden. Heisst: im Browser soll stehen www.example.com/page. Daher verwende ich Flag P. Mache ich das richtig?

Unendlich dankbar für den befreienden Tipp,
Baba

  1. Hallo Baba,
    nachdem ich versucht habe aus deiner Frage die Problemstellung abzuleiten, denke ich, dass Du ggf. mit einem nginx reverse Proxy besser fährst als mit mod_rewrite.
    Ich habe auch lange Zeit mit Apache weitergeleitet, teils mit verweisen auf Scripte, die die Anforderung ausgewertet und entsprechend weitergeleitet haben.
    Inzwischen habe ich eine virtuelle umgebung mit einem nginx-reverse-proxy installiert und kann hier, total übersichtilich und ohne viel Aufwand, meine hunderten von Umleitungen bei optimaler Performance verwalten.

    weitere info findest Du hier: http://wiki.nginx.org/Main

    Unendlich dankbar für den befreienden Tipp,
    Baba

    Auch wenn ich auf Deine Frage nicht genau eingegangen bin, hoffe ich, dass Du mit der Info was anfangen kannst!

    Gruß, Markus

    1. nachdem ich versucht habe aus deiner Frage die Problemstellung abzuleiten, denke ich, dass Du ggf. mit einem nginx reverse Proxy besser fährst als mit mod_rewrite.

      weitere info findest Du hier: http://wiki.nginx.org/Main

      Vielen Dank. Scheint mächtig zu sein. Allerdings handelt es sich hier um meinen privaten Auftritt bei http://www.celeros.de/. Da kann ich das nicht installieren.

      Danke aber für den Tipp.

      Cheers,
      Baba

  2. Tach!

    Mod_rewrite kann einen ja ganz schön in Bedrängnis bringen. Ich denke der Hauptgrund ist, dass man nicht genau sehen oder verfolgen kann, was da eigentlich passiert. Die Regeln werden ja mehrmals durchlaufen und man sieht nicht, wie oft, mit welchen Werten für die Referenzen, etc. Ich habe keinen Zugriff auf die logs :(

    Der Log-Zugriff ist das Problem, denn mit den Rewrite-Logs kann man sehr genau sehen, was da im inneren passiert. Wenn du beim Hoster kein RewriteLog anlegen kannst (geht nur als Admin), dann kannst du immer noch zu Hause ein gleichwertiges System aufzusetzen versuchen.

    Eine zutreffende Regel erzeugt einen neuen internen Request. Dieser hält sich seinerseits an die für ihn zutreffende Konfiguration. Wenn da also auch wieder Rewrite-Regeln stehen - besonders wenn der interne Request in daselbe Verzeichnis ging - dann werden diese eben nochmals anzuwenden versucht.

    In welche Dokumentation soll ich schauen. Es gibt 2.0, 2.2, 2.4. Bei mir steht in %{API_VERSION} "20051115:3". In $_SERVER["SERVER_SOFTWARE"] steht leider nur "Apache", ohne Version.

    Frag deinen Hoster. Manchmal findet man die Version in den HTTP-Response-Headern. Notfalls nimmst du eben nur die Features von Version 2.0 und schaust im Zweifelsfall ín die Doku der neueren Versione, ob sich da was geändert hat, an dem Feature, das du verwenden willst. Viel ist es in der Regel nicht, es kommt eher nur Neues hinzu.

    [code lang=css]

    Apache-Konfiguration ist kein CSS. Nimm bitte die Sprache "apache", wenn du dessen Regeln für dieses Forum auszeichnen möchtest.

    RewriteCond %{HTTP_HOST} [^route].*.example.de$ [NC]

    [] ist eine Zeichenklasse. Das heißt, ein Zeichen daraus muss es sein (oder auch nicht, bei Negation). Diese Regel matcht also bei:

    ein Zeichen, das kein r, o, u, t oder e ist
      gefolgt von einem beliebigen Zeichen und das beliebig oft
      gefolgt von einem beliebigen Zeichen
      example
      noch einem beliebigen Zeichen
      de
      sowie dem Ende

    Die Regel passt also auf alle möglichen Anfänge, die nicht mit r, o, u, t oder e anfangen. Wenn du testest, muss du nciht nur deine Anwendungsfälle probieren sondern mindestens exemplarisch auch ein paar Fälle, die nicht treffen sollen. Sei dabei kreativ und versuch das von dir ausgedachte System zu Fall zu bringen. Wenn du es nicht machst, ein anderer macht es garantiert.

    Maschinen sind dumm. Die können nicht wissen, wann du den Punkt meinst und wann du ein beliebiges Zeichen meinst. Da der Punkt eine Sonderbedeutung hat, musst du ihn maskieren, wenn er für sich selbst stehen soll.

    Wie kann ich erreichen, dass in der Referenz der letzten RewriteRule $1 auch der Slash gespeichert wird?

    Vielleicht lässt sich eine komplexe/komplizierte Regel finden, die das Slash-Problem löst. Ich würde mich aber nicht auf die Suche danach begeben, sondern die Rewrite-Regel so simpel wie möglich schreiben, dass einfach nur ein PHP-Script aufgerufen wird, ohne weitere Parameter. Im Script kannst du dann nach Herzenslust $_SERVER['REQUEST_URI') auseinandernehmen und auswerten. (Oder auch was anderes. Schau einfach nach, was da in $_SERVER alles verwendbares drinsteht.)

    Ich möchte diese Regel "intern" anwenden. Heisst: im Browser soll stehen www.example.com/page. Daher verwende ich Flag P. Mache ich das richtig?

    Nein. P steht für Proxy, nicht für intern. Intern passiert sowieso alles, wenn du es nicht explizit zu einem Redirect schickst. Ich dachte, du liest die Dokumentation, denn dann hättest du eigentlich gesehen, dass da was mit mod_proxy steht, woraufhin dir auffallen müsste, dass du damit ja gar nicht arbeitest.

    dedlfix.

    1. Wow, das sind auch sehr gute Hinweise.

      Der Log-Zugriff ist das Problem,

      das sehe ich auch so. Ich werde mal beim Hoster anfragen.

      Wenn du beim Hoster kein RewriteLog anlegen kannst (...), dann kannst du immer noch zu Hause ein gleichwertiges System aufzusetzen versuchen.

      Schwer, weil ich es nicht schaffe, mit Subdomains zu arbeiten. HTTP_HOST ist immer "localhost".

      [code lang=css]
      Apache-Konfiguration ist kein CSS. Nimm bitte die Sprache "apache"

      Perfekt!

      [] ist eine Zeichenklasse. Das heißt, ein Zeichen daraus muss es sein (oder auch nicht, bei Negation). Diese Regel matcht also bei:

      Danke für die Nachhilfe!

      Wie kann ich erreichen, dass in der Referenz der letzten RewriteRule $1 auch der Slash gespeichert wird?

      Vielleicht lässt sich eine komplexe/komplizierte Regel finden, die das Slash-Problem löst.

      Ich hätte nich gedacht, dass es so schwer ist. Das "ein Slash oder kein Slash", ausgedrückt durch /? steht in den runden Klammern. Daher müsste es doch, wenn es dabei ist, mit in die Referenz $1?

      Ich würde mich aber nicht auf die Suche danach begeben, sondern die Rewrite-Regel so simpel wie möglich schreiben, dass einfach nur ein PHP-Script aufgerufen wird, ohne weitere Parameter. Im Script kannst du dann nach Herzenslust $_SERVER['REQUEST_URI') auseinandernehmen und auswerten.

      Genau das ist ja das Problem. Diese Variable ist dann nämlich leer, wenn ich es nicht mit $1 ranhänge! Denn, genau dieses hatte ich vor. Und so mache ich das auch schon lange. Allerdings möchte ich jetzt das System um die Unterstützung von Subdomains erweitern.

      (Oder auch was anderes. Schau einfach nach, was da in $_SERVER alles verwendbares drinsteht.)

      Nirgendwo ist der ursprüngliche URI drin.

      Ich möchte diese Regel "intern" anwenden. Heisst: im Browser soll stehen www.example.com/page. Daher verwende ich Flag P. Mache ich das richtig?
      Nein. P steht für Proxy, nicht für intern. Intern passiert sowieso alles, wenn du es nicht explizit zu einem Redirect schickst.

      Das Problem war, dass ich eine Seite bekam "Document has moved here", und ich durch googlen fand, dass der [P] das löst. So war es dann auch... :) Ehrlich gesagt habe ich...

      Ich dachte, du liest die Dokumentation, denn dann hättest du eigentlich gesehen, dass da was mit mod_proxy steht, woraufhin dir auffallen müsste, dass du damit ja gar nicht arbeitest.

      das dann auch nicht voll geschnallt. Ideen, wie ich es dann ohne den Proxyrequest lösen kann?

      Vielen Dank nochmals.

      Cheers,
      Baba

      1. Tach!

        Der Log-Zugriff ist das Problem,
        das sehe ich auch so. Ich werde mal beim Hoster anfragen.

        Der wird da nichts machen. Das RewriteLog kann (außer generell) nur im VHost eingeschaltet werden und es schreibt ziemlich viele Daten pro Request. Das will der Hoster sicher nicht auf Produktivmaschinen aktiviert haben.

        Wenn du beim Hoster kein RewriteLog anlegen kannst (...), dann kannst du immer noch zu Hause ein gleichwertiges System aufzusetzen versuchen.
        Schwer, weil ich es nicht schaffe, mit Subdomains zu arbeiten. HTTP_HOST ist immer "localhost".

        Das kann eigentlich nicht sein. Da muss das drinstehen, was du in der URL angegeben hast, egal zu welcher IP-Adresse dein DNS oder deine hosts-Datei das auflöst.

        Ich würde mich aber nicht auf die Suche danach begeben, sondern die Rewrite-Regel so simpel wie möglich schreiben, dass einfach nur ein PHP-Script aufgerufen wird, ohne weitere Parameter. Im Script kannst du dann nach Herzenslust $_SERVER['REQUEST_URI') auseinandernehmen und auswerten.
        Genau das ist ja das Problem. Diese Variable ist dann nämlich leer, wenn ich es nicht mit $1 ranhänge!

        REQUEST_URI kann nie leer sein. Da muss mindestens / drinstehen. Ein Browser kann nicht "nichts" anfordern, das verhindert das HTTP. Der kleinstmögliche GET-Request (abgesehen vom irrelevanten HTTP 0.9) ist

        GET / HTTP/1.0

        Die RequestURI ist also immer mindestens ein Zeichen lang.

        (Oder auch was anderes. Schau einfach nach, was da in $_SERVER alles verwendbares drinsteht.)
        Nirgendwo ist der ursprüngliche URI drin.

        Die komplette URL steht da auch nicht drin, weil sie gar nicht so komplett vom Browser gesendet wird. Du wirst immer nur Teile darin finden. HTTP_HOST und REQUEST_URI sind immer da. Wenn nicht, ist irgendwas falsch - möglicherweise auch deine Debugausgabe-Methode.

        Ideen, wie ich es dann ohne den Proxyrequest lösen kann?

        Wie bereits von mir und Sven empfohlen, mit simplem Rewrite und Selbstauswertung von Request-URI und HTTP-Host.

        dedlfix.

        1. Hailo und nochmals vielen Dank.

          REQUEST_URI kann nie leer sein. Da muss mindestens / drinstehen.

          Das ist richtig. Ein "/" steht drin.

          Wie bereits von mir und Sven empfohlen, mit simplem Rewrite und Selbstauswertung von Request-URI und HTTP-Host.

          Gut. Ich habe jetzt einen Kompromiss gemacht aus mod_rewriterei und Selbstauswertung. Dies ist die (fast) gesamte .htaccess:

          # remove trailing slash and reload  
          RewriteCond %{HTTP_HOST} !route\.example\.com$ [NC]  
          RewriteRule (.*[^/])/$ /$1 [R=301,L]  
            
          # redirect www.<subdomain>. to <subdomain>. (duplicate content)  
          RewriteCond %{HTTP_HOST} ^www\.(.*)\.example\.de$ [NC]  
          RewriteRule (.*) http://%1.example.de/$1 [R=301,L]  
            
          # redirect <subdomain>.example.de to example.de/<subdomain>.php  
          RewriteCond %{HTTP_HOST} !route\.example\.de$ [NC]  
          RewriteCond %{HTTP_HOST} ^(.*).example.de$ [NC]  
          RewriteRule (/?.*) http://route.example.de/%1.php/$1 [P]  
            
          # redirect to /index.php if file does not exist  
          RewriteCond %{REQUEST_FILENAME} !-f  
          RewriteCond %{REQUEST_FILENAME} !-d  
          RewriteRule (.*) /index.php/$1 [QSA]  
          
          

          Vielen Dank für alle Hilfe. Auch wenn immer noch der Proxyrequest drin ist, verwende ich es jetzt so. Es tut einfach  genau, was ich möchte.

          Cheers,
          Baba

  3. Moin!

    Mod_rewrite kann einen ja ganz schön in Bedrängnis bringen. Ich denke der Hauptgrund ist, dass man nicht genau sehen oder verfolgen kann, was da eigentlich passiert. Die Regeln werden ja mehrmals durchlaufen und man sieht nicht, wie oft, mit welchen Werten für die Referenzen, etc. Ich habe keinen Zugriff auf die logs :(

    Ich würde versuchen zu vermeiden, in solch einer Situation intensiver mit Rewriting zu arbeiten, sondern wenn dann alle Logik in die Applikation hinein tun.

    Sprich: Rewriting sorgt dafür, dass faktisch alle URLs auf ein Skript laufen (idealerweise dasselbe für alle), welches dann in genehmerer Programmiersprache und Debuggbarkeit den Request auseinandernimmt.

    Das betrifft insbesondere auch den Versuch, die diversen Pfadbestandteile einer URL schon im Rewriting in den Query-String zu drücken. Das ist im Prinzip vollkommen überflüssig, sogar schwierig zu debuggen und zu pflegen, denn die Logik der Analyse der URL teilt sich jetzt unschön auf: Der Browser und die Wahrnehmung der URL sehen nur den kompletten Pfad ohne Parameter, und die Skripte sehen nur die umgeschriebene URL als Parametersammlung. Und haben dann aber wieder die Aufgabe, neue URLs zu bilden, die eben gerade diese Parameter NICHT enthalten. Gerade dafür fehlt aber die Softwarekomponente, denn für "Rewriting rückwärts" gibts beim Apachen nix.

    Mein Vorschschlag deshalb: Pauschales Rewriting einfach auf ein Skript, egal was in der URL stand. Das Skript kann dann ganz in Ruhe die URL analysieren und passend reagieren:

    * Stimmt die Domain nicht? Redirect ausspucken.
     * Ist am Ende ein Slash zuviel? Kann man doch ignorieren oder abschneiden (finde ich nämlich nicht dramatisch), im Zweifel halt auch redirecten. Wobei ich die Frage stelle, wie es zu solchen URLs kommen kann. Wenn die falsch sind, wäre 404 nämlich auch eine korrekte Antwort.
     * Und wenn sonst alles schön ist, kann man den Pfad des Requests jetzt passend nach Werten durchschauen, die man im Skript verwenden will.

    Sowas bieten übrigens alle nennenswerten MVC-Frameworks von sich aus schon an. Das Rewriting z.B. für das Zend Framework ist genau das, was ich vorschlage: Ein einzelnes Rewrite auf das Skript, es sei denn (der Test erfolgt vorher), dass die URL eine existierende Datei als Ressource trifft (also Bilder etc.). Nur in so einer Situation hat die Applikation die volle Kontrolle über die URL und kann in fast beliebiger Weise auf Requests reagieren. Und das insbesondere in der Sprache der Applikation - also der Sprache, die der Programmierer in der Regel beherrscht.

    - Sven Rautenberg

    1. Vielen Dank für Deinen Tipp. Ich sehe es vollkommen ähnlich und versuche die Regeln auf ein Mindestmaß zu reduzieren.

      Das betrifft insbesondere auch den Versuch, die diversen Pfadbestandteile einer URL schon im Rewriting in den Query-String zu drücken.

      Das mache ich bereits. Der Query wird von meinem php-Script ausgewertet. Beispiel für ein URL
      www.example.com/page/subpage/subsubpage/action/ab/hier/sind/es/get/parameter
      Das wird von mod_rewrite nicht angerührt.

      Sprich: Rewriting sorgt dafür, dass faktisch alle URLs auf ein Skript laufen (idealerweise dasselbe für alle), welches dann in genehmerer Programmiersprache und Debuggbarkeit den Request auseinandernimmt.

      Ich wollte eben nur, dass dir subdomains auf je einer eigenen php-Datei ankommen (Beispiel www.php).

      * Stimmt die Domain nicht? Redirect ausspucken.

      Was meinst Du mit "stimmt nicht"?

      * Ist am Ende ein Slash zuviel? Kann man doch ignorieren oder abschneiden (finde ich nämlich nicht dramatisch)

      Dies möchte vermeiden wegen dublicate content. Nur deshalb. Es soll
      www.example.com/page/ per 301 zu www.example.com/page umgeleitet werden.

      Vielen Dank nochmal.

      Cheers,
      Baba

      1. hi,

        Dies möchte vermeiden wegen dublicate content. Nur deshalb. Es soll
        www.example.com/page/ per 301 zu www.example.com/page umgeleitet werden.

        .htaccess:
        DirectorySlash Off

        und gut isses ;)

        Hotti

        1. Hallo und danke für Dein Tipp.

          Dies möchte vermeiden wegen dublicate content. Nur deshalb. Es soll
          www.example.com/page/ per 301 zu www.example.com/page umgeleitet werden.

          .htaccess:
          DirectorySlash Off

          und gut isses ;)

          Das ist es leider nicht :(

          Cheers,
          Baba

      2. Moin!

        * Ist am Ende ein Slash zuviel? Kann man doch ignorieren oder abschneiden (finde ich nämlich nicht dramatisch)
        Dies möchte vermeiden wegen dublicate content. Nur deshalb. Es soll
        www.example.com/page/ per 301 zu www.example.com/page umgeleitet werden.

        Bullshit! Google weiß, dass URLs mit und ohne Slash am Ende in der Regel dieselbe Seite erzeugen. Das fließt nicht abwertend ins Ranking ein.

        Duplicate Content ist für Google die Frage: "Welche Seite enthält denn jetzt eigentlich den WIRKLICHEN Inhalt, und auf welchen anderen Seiten taucht der Inhalt nur unabsichtlich auf?" Das klassische Beispiel dazu ist eine interne Suchseite, die selbstverständlich alle anderen Seiten zumindest als Ausschnitt mit anzeigt, aber eben nicht der originale Inhalt ist. Oder eine Produktseite, die, eingebunden in die Warenkorbdarstellung, ebenfalls mehrfach im URL-Baum auftauchen dürfte.

        Vernünftigste Lösung: Canonical Link Element

        - Sven Rautenberg