Monatsarchiv für August 2010

 
 

Webserver-Umgebung erkennen mit PHP

Development oder Production – das ist hier die Frage. Bei der Webseiten-Entwicklung bietet es sich selbst bei kleinsten Projekten an, zunächst in einer lokalen Entwicklungsumgebung die Anwendung zu testen. Ist der Test erfolgreich werden die Änderungen online gestellt.

Oftmals unterscheidet sich die Produktivumgebung im Internet von der lokalen. So müssen z.B. unterschiedliche MySQL-Konfigurationen  oder Pfade berücksichtigt werden.

Doch wie kann man zwischen den verschiedenen Umgebungen in einem PHP-Skript automatisch unterscheiden? Schließlich wollen wir ja nicht vor jedem Upload die Konfiguration händisch anpassen.

Das Auslesen des Server-Namens per $_SERVER['HTTP_HOST'] scheint die einfachste Möglichkeit. Doch da DNS-Namen und IP-Adressen, speziell in Entwicklungsumgebungen, vergänglich sind, ist das nicht immer die sicherste Methode seinen Entwicklungsserver zu identifizieren.

Besser ist es, in der lokalen Webserver-Konfiguration eine Umgebungsvariable zu setzen und diese dann per PHP abzufragen. Im Falle eines Apache-Webservers mit mod_env wäre das setEnv in der conf (httpd.conf), in einer VHOST-Direktive oder per .htaccess. Beispiel .htaccess:

SetEnv DEVELOP true
Auslesen mit PHP:
if (getenv('DEVELOP')) {
    // DEVELOP
} else {
    // Production
}

Unter manchen Konfigurationen sind die Umgebungsvariablen nur in $_SERVER verfügbar und nicht per getenv (IIS z.B.)

GET-Requests minimieren. Heute: Externe CSS-Dateien

Selbst bei kleineren Webprojekten hat man oft mehrere CSS-Dateien. Zumindest das CSS-Reset ist bei mir, zwecks Wiederverwendbarkeit, immer in einer separaten Datei. Eine weitere Unterteilung in typo.css, lists.css usw. macht dann bei größeren Projekten Sinn. Zudem kommen von externen Paketen oft weitere, separate CSS-Dateien hinzu (Lightbox, TinyMCE, ..).

So sieht das dann im HTML <head> aus:

<link href="css/reset.css" rel="stylesheet" type="text/css" media="screen" />
<link href="css/style.css" rel="stylesheet" type="text/css" media="screen" />
[...]

Doch hier benötigt der Browser für jede CSS-Datei einen extra HTTP GET-Request! Das geht auch schöner: ein PHP-Skript kann alle Dateien zusammenfassen und ausgeben. Damit wir die Endung .css beibehalten können, schreiben wir mittels mod_rewrite die URI zu unser CSS-Datei um:

.htaccess:
RewriteEngine On
RewriteRule css/init.css css/init.php

Im <head>:
<link href="css/init.css" rel="stylesheet" type="text/css" media="screen" />

In unserer  Haupt-CSS-Datei (init.php) machen wir dem Browser klar, dass diese Datei CSS ausgibt. Gleichzeitig buffern wir den Output mittels ob_start(). Durch die Angabe von “ob_gzhandler” wird der Output gzip-komprimiert ausgegeben, sofern der Browser das Unterstützt. (je nach HTTP “accept” header – Keine Sorge, PHP kümmert sich von ganz allein darum).

init.php:
header("Content-Type: text/css");
ob_start("ob_gzhandler");

Danach lesen wir bestimmte CSS-Dateien in einem Verzeichnis ein, und speichern den jeweiligen Inhalt in einer Variablen:

$css = '';
foreach (explode(',', 'reset,typo,style') as $file) {
    $css .= "/* File: $file.css */\n";
    if ($_SERVER['HTTP_HOST'] == 'webserver') {
        $css .= '@import url("'.$file.'.css");';
    } else {
        $css .= file_get_contents($file . '.css');
    }
    $css .= "\n\n";
}

In diesem Fall speichern wir die Dateien “reset.css“, “typo.css” und “style.css” in $css. Wenn das Ganze lokal ausgeführt wird,  geben wir nur “@import url(..)” und nicht den eigentlichen Dateiinhalt aus. Das hat den Vorteil, dass z.B. in Firebug noch immer die richtigen Dateinamen und Zeilen angezeigt werden. “webserver” wäre der DNS-Name des lokalen Entwicklungsservers.

Der Cache

Da die CSS-Datei dynamisch generiert wird, trifft hier kein ”If-Modified-Since“-Cache, wie bei Bilddateien oder statischen CSS-Dateien.

Wir müssen uns also selbst um das Cachen unserer dynamisch generierte CSS-Datei auf Clientseite kümmern. Cache mittels ETag bietet sich hier an. Dazu hashen wir den Dateiinhalt aller Dateien ($css) mit MD5. Diesen Hash übergeben wir dann als ETag an den Client. Ändert sich eine CSS-Datei, so ändert sich auch der MD5-Hash bzw. ETag.

Hat der Client einmal einen ETag empfangen, so sendet er diesen mit jedem weiteren Request zu dieser Datei im HTTP-Header mit. Auf Serverseite können wir also in unserem PHP-Script überprüfen ob der gesendete ETag noch aktuell ist. Wenn das der Fall ist, setzen wir einen 304 Not Modified Header und brechen ab.

$etag = md5($css);
header('ETag: '.$etag);
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
 trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) {
    header("HTTP/1.1 304 Not Modified");
    die();
}
echo $css

Die komplette init.php kann hier nochmals angeguckt werden (mit gehighlightetem Syntax, yay!) .

Wir überprüfen:

(Screenshots stammen aus FireBug)

1. Request

Im Antwort-Header taucht der ETag auf. Da im Anfrage-Header kein ETag gesendet wurde, trifft hier kein Cache, und somit Status 200 OK.

2. Request

Unter IF_NONE_MATCH wird der ETag an den Server gesendet. Dieser antwortet dann mit 304 Not Modified.