aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Lange <code@nerdmind.de>2015-04-15 21:29:19 +0200
committerThomas Lange <code@nerdmind.de>2015-04-15 21:29:19 +0200
commitbf996f7247133c536511c23b6ad30aa222bfd6d9 (patch)
tree1d9388e6331454fbc9b68b5006a64c4563c202cd
downloadbigpipe-bf996f7247133c536511c23b6ad30aa222bfd6d9.tar.gz
bigpipe-bf996f7247133c536511c23b6ad30aa222bfd6d9.tar.xz
bigpipe-bf996f7247133c536511c23b6ad30aa222bfd6d9.zip
Initial commit
-rwxr-xr-xinclude/classes/class.bigpipe.php79
-rwxr-xr-xinclude/classes/class.pagelet.php84
-rwxr-xr-xinclude/functions.php12
-rwxr-xr-xindex.php79
-rwxr-xr-xstatic/bigpipe-callbacks.js27
-rwxr-xr-xstatic/bigpipe.js274
-rwxr-xr-xstatic/blue.php17
-rwxr-xr-xstatic/delayJS.php16
-rwxr-xr-xstatic/green.php17
-rwxr-xr-xstatic/red.php17
10 files changed, 622 insertions, 0 deletions
diff --git a/include/classes/class.bigpipe.php b/include/classes/class.bigpipe.php
new file mode 100755
index 0000000..1edefc8
--- /dev/null
+++ b/include/classes/class.bigpipe.php
@@ -0,0 +1,79 @@
+<?php
+class BigPipe {
+ public static $enabled = TRUE;
+ private static $pagelets = [];
+ private static $count = 0;
+
+ #====================================================================================================
+ # Gibt TRUE zurück wenn BigPipe eingeschaltet ist
+ #====================================================================================================
+ public static function isEnabled() {
+ return self::$enabled ? TRUE : FALSE;
+ }
+
+ #====================================================================================================
+ # Neues Pagelet zur Pipeline hinzufügen
+ #====================================================================================================
+ public static function addPagelet(Pagelet $Pagelet, $priority) {
+ self::$pagelets[$priority][] = $Pagelet;
+ self::$count++;
+ }
+
+ #====================================================================================================
+ # Gibt einen einzelnen Pagelet-Response aus
+ #====================================================================================================
+ private static function pageletResponse(Pagelet $Pagelet, $async = FALSE, $last = FALSE) {
+ $data = [
+ 'ID' => $Pagelet->getID(),
+ 'RESOURCES' => ['CSS' => $Pagelet->getCSSFiles(), 'JS' => $Pagelet->getJSFiles(), 'JS_CODE' => removeLineBreaksAndTabs($Pagelet->getJSCode())]
+ ];
+
+ if($last) {
+ $data['IS_LAST'] = true;
+ }
+
+ echo '<code class="hidden" id="_'.$data['ID'].'"><!-- '.str_replace('--', '&#45;&#45;', removeLineBreaksAndTabs($Pagelet->getHTML())).' --></code>'."\n";
+ echo '<script>BigPipe.onPageletArrive('.json_encode($data).($async ? ', document.getElementById("_'.$Pagelet->getID().'").innerHTML' : NULL).');</script>'."\n\n";
+ }
+
+ #====================================================================================================
+ # Sendet den Output-Buffer so weit wie möglich in Richtung User
+ #====================================================================================================
+ public static function flushOutputBuffer() {
+ ob_flush(); flush();
+ }
+
+ #====================================================================================================
+ # Alle Pagelets an Client schicken
+ #====================================================================================================
+ public static function render($async = FALSE) {
+ self::flushOutputBuffer();
+
+ $i = 0;
+
+ ksort(self::$pagelets);
+
+ foreach(array_reverse(self::$pagelets) as $priority => $pagelets) {
+ foreach($pagelets as $Pagelet) {
+ if(!self::isEnabled()) {
+ if($Pagelet->getJSCode()) {
+ echo '<script>'.$Pagelet->getJSCode().'</script>'."\n";
+ }
+
+ foreach($Pagelet->getCSSFiles() as $CSSFile) {
+ echo '<link href="'.$CSSFile.'" rel="stylesheet" />'."\n";
+ }
+
+ foreach($Pagelet->getJSFiles() as $JSFile) {
+ echo '<script src="'.$JSFile.'"></script>'."\n";
+ }
+ }
+
+ else {
+ self::pageletResponse($Pagelet, $async, (self::$count === ++$i));
+ self::flushOutputBuffer();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/include/classes/class.pagelet.php b/include/classes/class.pagelet.php
new file mode 100755
index 0000000..c1f211a
--- /dev/null
+++ b/include/classes/class.pagelet.php
@@ -0,0 +1,84 @@
+<?php
+class Pagelet {
+ private $ID = NULL;
+ private $HTML = NULL;
+ private $JSCode = "";
+ private $CSSFiles = [];
+ private $JSFiles = [];
+ private static $count = 0;
+
+ public function __construct($priority = 50) {
+ $this->ID = 'P'.++self::$count;
+ BigPipe::addPagelet($this, $priority);
+ }
+
+ #====================================================================================================
+ # ID zurückgeben
+ #====================================================================================================
+ public function getID() {
+ return $this->ID;
+ }
+
+ #====================================================================================================
+ # HTML-Code zurückgeben
+ #====================================================================================================
+ public function getHTML() {
+ return $this->HTML;
+ }
+
+ #====================================================================================================
+ # CSS-Ressourcen zurückgeben
+ #====================================================================================================
+ public function getCSSFiles() {
+ return $this->CSSFiles;
+ }
+
+ #====================================================================================================
+ # JS-Ressourcen zurückgeben
+ #====================================================================================================
+ public function getJSFiles() {
+ return $this->JSFiles;
+ }
+
+ #====================================================================================================
+ # JS-Code zurückgeben
+ #====================================================================================================
+ public function getJSCode() {
+ return $this->JSCode;
+ }
+
+ #====================================================================================================
+ # HTML-Code hinzufügen
+ #====================================================================================================
+ public function addHTML($HTML) {
+ $this->HTML .= $HTML;
+ }
+
+ #====================================================================================================
+ # CSS-Ressource hinzufügen
+ #====================================================================================================
+ public function addCSS($file) {
+ $this->CSSFiles[] = $file;
+ }
+
+ #====================================================================================================
+ # JS-Ressource hinzufügen
+ #====================================================================================================
+ public function addJS($file) {
+ $this->JSFiles[] = $file;
+ }
+
+ #====================================================================================================
+ # JS-Code hinzufügen
+ #====================================================================================================
+ public function addJSCode($code) {
+ $this->JSCode .= $code;
+ }
+
+ #====================================================================================================
+ # Magische Methode: __toString()
+ #====================================================================================================
+ public function __toString() {
+ return '<div id="'.$this->getID().'">'.((!BigPipe::isEnabled()) ? $this->getHTML() : NULL).'</div>';
+ }
+} \ No newline at end of file
diff --git a/include/functions.php b/include/functions.php
new file mode 100755
index 0000000..443d594
--- /dev/null
+++ b/include/functions.php
@@ -0,0 +1,12 @@
+<?php
+#====================================================================================================
+# FUNCTION: Entfernt alle Zeilenumbrüche und Tabulatoren aus einem String
+#====================================================================================================
+function removeLineBreaksAndTabs($mixed, $replace = NULL) {
+ if(is_array($mixed)) {
+ return array_map(__FUNCTION__, $mixed);
+ }
+
+ return is_string($mixed) ? str_replace(["\r\n", "\r", "\n", "\t"], $replace, $mixed) : $mixed;
+}
+?> \ No newline at end of file
diff --git a/index.php b/index.php
new file mode 100755
index 0000000..7f3de69
--- /dev/null
+++ b/index.php
@@ -0,0 +1,79 @@
+<?php
+#====================================================================================================
+# Cache deaktivieren
+#====================================================================================================
+header('Cache-Control: no-cache, no-store, must-revalidate');
+
+#====================================================================================================
+# Klassen und Funktionen einbinden
+#====================================================================================================
+require_once 'include/classes/class.bigpipe.php';
+require_once 'include/classes/class.pagelet.php';
+require_once 'include/functions.php';
+
+#====================================================================================================
+# Pagelet mit rotem Hintergrund
+#====================================================================================================
+$PageletRed = new Pagelet();
+$PageletRed->addHTML('<section id="red" class="text">I AM JUST A PAGELET WITH RED BACKGROUND</section>');
+$PageletRed->addCSS('static/red.php');
+$PageletRed->addJS('static/delayJS.php');
+$PageletRed->addJSCode("document.getElementById('red').innerHTML += ' [JS executed]';document.getElementById('red').style.borderRadius = '30px';");
+
+#====================================================================================================
+# Pagelet mit blauem Hintergrund
+#====================================================================================================
+$PageletBlue = new Pagelet(60);
+$PageletBlue->addHTML('<section id="blue" class="text">I AM JUST A PAGELET WITH BLUE BACKGROUND</section>');
+$PageletBlue->addCSS('static/blue.php');
+$PageletBlue->addJS('static/delayJS.php');
+$PageletBlue->addJSCode("document.getElementById('blue').innerHTML += ' [JS executed]';document.getElementById('blue').style.borderRadius = '30px';");
+
+#====================================================================================================
+# Pagelet mit grünem Hintergrund
+#====================================================================================================
+$PageletGreen = new Pagelet();
+$PageletGreen->addHTML('<section id="green" class="text">I AM JUST A PAGELET WITH GREEN BACKGROUND</section>');
+$PageletGreen->addCSS('static/green.php');
+$PageletGreen->addJS('static/delayJS.php');
+$PageletGreen->addJSCode("document.getElementById('green').innerHTML += ' [JS executed]';document.getElementById('green').style.borderRadius = '30px';");
+?>
+<!DOCTYPE html>
+<html lang="de">
+<head>
+ <meta charset="UTF-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <style>
+ html{margin:0;padding:0;background:#B9C3D2;font-family:Calibri,Sans-Serif;}
+ body{max-width:1200px;margin:0 auto;}
+ .text{color:white;margin-bottom:30px;padding:40px;border-radius:4px;font-weight:600;text-align:center;border:4px solid black;}
+ </style>
+ <script>
+ var globalExecution = function globalExecution(code) {
+ window.execScript ? window.execScript(code) : window.eval.call(window, code);
+ };
+ </script>
+ <script src="static/bigpipe.js"></script>
+ <script src="static/bigpipe-callbacks.js"></script>
+ <title>BigPipe Example</title>
+</head>
+<body>
+<h1>BigPipe Example</h1>
+<p>Auf dieser Beispielseite werden insgesamt 3 Pagelets gerendert von denen alle jeweils eine CSS- und eine JS-Ressource haben. Wobei jede der CSS-Ressourcen die Hintergrundfarbe des zugehörigen Pagelets ändert.
+BigPipe wird hingehen und diese Pagelets der Reihe nach rendern. Dabei werden zuerst die zugehörigen CSS-Ressourcen geladen und dann der HTML-Code injiziert. Wenn dann von allen Pagelets die CSS-Ressourcen geladen
+und der HTML-Code injiziert ist, dann wird BigPipe die JS-Ressourcen der Pagelets einbinden und den statischen Javascript-Code (falls vorhanden) ausführen. Damit man den Pipeline-Effekt auf dieser Beispielseite auch
+sieht werden die CSS- und JS-Ressourcen über ein Delayscript geleitet. Debuginformationen findest du in der Javascript-Konsole.</p>
+
+<p><b>Weitere Informationen:</b> <a href="https://blackphantom.de/artikel/bigpipe-website-pipelining-und-schnellerer-aufbau-durch-einzelne-pagelets/" target="_blank">https://blackphantom.de/artikel/bigpipe-website-pipelining-und-schnellerer-aufbau-durch-einzelne-pagelets/</a></p>
+
+<?php
+echo $PageletRed;
+echo $PageletBlue;
+echo $PageletGreen;
+?>
+
+<?php
+BigPipe::render();
+?>
+</body>
+</html> \ No newline at end of file
diff --git a/static/bigpipe-callbacks.js b/static/bigpipe-callbacks.js
new file mode 100755
index 0000000..a9f48f1
--- /dev/null
+++ b/static/bigpipe-callbacks.js
@@ -0,0 +1,27 @@
+// Folgende Phasen stehen zur Auswahl: PAGELET_STARTED, RESOURCE_DONE, PAGELET_HTML_RENDERED, PAGELET_JS_EXECUTED und BIGPIPE_PAGELETS_RENDERED
+
+var debugLine = "------------------------------------------------------------------------------------------------------------------------";
+
+BigPipe.registerPhaseDoneCallback('PAGELET_STARTED', function(Pagelet) {
+ console.log(debugLine);
+ console.log(Pagelet.pageletID + ":\t" + "Die Ausführung des Pagelets wurde gestartet.");
+});
+
+BigPipe.registerPhaseDoneCallback('RESOURCE_DONE', function(Resource) {
+ console.log(Resource.pageletID + ":\t" + 'Die Ressource ' + Resource.file + ' wurde geladen.');
+});
+
+BigPipe.registerPhaseDoneCallback('PAGELET_HTML_RENDERED', function(Pagelet) {
+ console.log(Pagelet.pageletID + ":\t" + 'Die Platzhalter wurden mit HTML-Code befüllt.');
+});
+
+BigPipe.registerPhaseDoneCallback('BIGPIPE_PAGELETS_RENDERED', function() {
+ console.log(debugLine);
+ console.log('BP' + ":\t" + 'Die Platzhalter von allen Pagelets wurden mit ihrem HTML-Code befüllt.');
+ console.log(debugLine);
+});
+
+BigPipe.registerPhaseDoneCallback('PAGELET_JS_EXECUTED', function(Pagelet) {
+ console.log(Pagelet.pageletID + ":\t" + 'Der zugehörige Javascript-Code des Pagelets wurde ausgeführt.');
+ console.log(debugLine);
+}); \ No newline at end of file
diff --git a/static/bigpipe.js b/static/bigpipe.js
new file mode 100755
index 0000000..fcd35e9
--- /dev/null
+++ b/static/bigpipe.js
@@ -0,0 +1,274 @@
+//===================================================================================================
+// REVEALING MODLUE PATTERN: BigPipe
+//===================================================================================================
+var BigPipe = (function() {
+ //===================================================================================================
+ // PROTOTYPE: PageletResource-Konstruktor
+ //===================================================================================================
+ function PageletResource(file, type, pageletID) {
+ this.pageletID = pageletID;
+ this.callbacks = [];
+ this.done = false;
+ this.file = file;
+ this.type = type;
+ }
+
+ //===================================================================================================
+ // PROTOTYPE: Startet den Ladevorgang der Ressource
+ //===================================================================================================
+ PageletResource.prototype.start = function() {
+ if(this.type === 0) {
+ var element = document.createElement('link');
+ element.setAttribute('rel', 'stylesheet');
+ element.setAttribute('href', this.file);
+ }
+
+ else {
+ var element = document.createElement('script');
+ element.setAttribute('src', this.file);
+ element.async = true;
+ }
+
+ document.head.appendChild(element);
+
+ element.onload = function() {
+ BigPipe.executePhaseDoneCallbacks('RESOURCE_DONE', this);
+ this.executeCallbacks();
+ }.bind(this);
+
+ element.onerror = function() {
+ BigPipe.executePhaseDoneCallbacks('RESOURCE_DONE', this);
+ this.executeCallbacks();
+ }.bind(this);
+ };
+
+ //===================================================================================================
+ // PROTOTYPE: Registriert eine Callback-Funktion
+ //===================================================================================================
+ PageletResource.prototype.registerCallback = function(callback) {
+ return this.callbacks.push(callback);
+ };
+
+ //===================================================================================================
+ // PROTOTYPE: Führt alle registrierten Callback-Funktionen aus
+ //===================================================================================================
+ PageletResource.prototype.executeCallbacks = function() {
+ if(!this.done) {
+ this.done = true;
+
+ this.callbacks.forEach(function(callback) {
+ callback();
+ });
+ }
+ };
+
+ //===================================================================================================
+ // PROTOTYPE: Pagelet-Konstruktor
+ //===================================================================================================
+ function Pagelet(data) {
+ this.pageletID = data.ID;
+ this.HTML = data.HTML || "";
+ this.CSSFiles = data.RESOURCES.CSS;
+ this.JSFiles = data.RESOURCES.JS;
+ this.JSCode = data.RESOURCES.JS_CODE;
+
+ this.phase = 0; // 1 => Laden von CSS-Ressourcen, 2 => CSS-Ressourcen geladen, 3 => HTML wurde injiziert, 4 => JS-Ressourcen geladen und JS-Code ausgeführt
+ this.CSSResources = [];
+ this.JSResources = [];
+ }
+
+ //===================================================================================================
+ // PROTOTYPE: Startet die Initialisierung des Pagelets und startet die Pagelet-Ressourcen
+ //===================================================================================================
+ Pagelet.prototype.start = function() {
+ BigPipe.executePhaseDoneCallbacks('PAGELET_STARTED', this);
+ this.CSSFiles.forEach(function(file) {
+ this.attachCSSResource(new PageletResource(file, 0, this.pageletID));
+ }.bind(this));
+
+ this.JSFiles.forEach(function(file) {
+ this.attachJSResource(new PageletResource(file, 1, this.pageletID));
+ }.bind(this));
+
+ this.CSSResources.forEach(function(resource) {
+ this.phase = 1;
+ resource.start();
+ }.bind(this));
+
+ if(this.phase === 0) {
+ this.injectHTML();
+ }
+ };
+
+ //===================================================================================================
+ // PROTOTYPE: Fügt eine CSS-Ressource hinzu
+ //===================================================================================================
+ Pagelet.prototype.attachCSSResource = function(resource) {
+ resource.registerCallback(this.onloadCSS.bind(this));
+ return this.CSSResources.push(resource);
+ };
+
+ //===================================================================================================
+ // PROTOTYPE: Fügt eine JS-Ressource hinzu
+ //===================================================================================================
+ Pagelet.prototype.attachJSResource = function(resource) {
+ resource.registerCallback(this.onloadJS.bind(this));
+ return this.JSResources.push(resource);
+ };
+
+ //===================================================================================================
+ // PROTOTYPE: Führt den statischen JS-Code des Pagelets aus
+ //===================================================================================================
+ Pagelet.prototype.executeJSCode = function() {
+ try {
+ globalExecution(this.JSCode);
+ BigPipe.executePhaseDoneCallbacks('PAGELET_JS_EXECUTED', this);
+ } catch(e) {
+ console.error(this.pageletID + ":\t" + e);
+ }
+ };
+
+ //===================================================================================================
+ // PROTOTYPE: Pagelet-Methode
+ //===================================================================================================
+ Pagelet.prototype.onloadJS = function() {
+ if(this.phase === 3 && this.JSResources.every(function(resource){
+ return resource.done;
+ })) {
+ this.executeJSCode();
+ this.phase = 4;
+ }
+ };
+
+ //===================================================================================================
+ // PROTOTYPE: Pagelet-Methode
+ //===================================================================================================
+ Pagelet.prototype.onloadCSS = function() {
+ if(this.CSSResources.every(function(resource){
+ return resource.done;
+ })) {
+ this.injectHTML();
+ }
+ };
+
+ //===================================================================================================
+ // PROTOTYPE: Injiziert den HTML-Code des Pagelets in den DOM
+ //===================================================================================================
+ Pagelet.prototype.injectHTML = function() {
+ this.phase = 2;
+ if(placeholder = document.getElementById(this.pageletID)) {
+ if(this.HTML) {
+ placeholder.innerHTML = this.HTML;
+ }
+
+ else {
+ var content = document.getElementById('_' + this.pageletID);
+ placeholder.innerHTML = content.innerHTML.substring(5, content.innerHTML.length - 4);
+ document.body.removeChild(content);
+ }
+ }
+
+ this.phase = 3;
+
+ BigPipe.executePhaseDoneCallbacks('PAGELET_HTML_RENDERED', this);
+ BigPipe.executeNextPagelet();
+
+ if(BigPipe.phase === 2 && BigPipe.pagelets[BigPipe.pagelets.length - 1].pageletID === this.pageletID) {
+ BigPipe.executePhaseDoneCallbacks('BIGPIPE_PAGELETS_RENDERED');
+ BigPipe.loadJSResources();
+ }
+ };
+
+ //===================================================================================================
+ // BigPipe-Hauptobjekt
+ //===================================================================================================
+ var BigPipe = {
+ pagelets: [],
+ phase: 0, // 1 => Erstes Pagelet gestartet, 2 => Alle Pagelets angekommen, 3 => JS-Ressourcen geladen + JS-Code ausgeführt
+ offset: 0,
+ phaseDoneCallbacks: {},
+
+ executeNextPagelet: function() {
+ if(this.pagelets[this.offset]) {
+ this.pagelets[this.offset++].start();
+ }
+
+ else if(this.phase < 2) {
+ setTimeout(this.executeNextPagelet.bind(this), 30);
+ }
+ },
+
+ registerPhaseDoneCallback: function(phase, callback) {
+ if(!this.phaseDoneCallbacks[phase]) {
+ this.phaseDoneCallbacks[phase] = [];
+ }
+ return this.phaseDoneCallbacks[phase].push(callback);
+ },
+
+ executePhaseDoneCallbacks: function(phase, param) {
+ if(this.phaseDoneCallbacks[phase]) {
+ this.phaseDoneCallbacks[phase].forEach(function(callback) {
+ callback(param);
+ });
+ }
+ },
+
+ onPageletArrive: function(data) {
+ if(this.pagelets.push(new Pagelet(data)) && this.phase === 0 && !data.IS_LAST) {
+ this.phase = 1;
+ this.executeNextPagelet();
+ }
+
+ else if(data.IS_LAST) {
+ this.phase = 2;
+ if(this.pagelets.length === 1) {
+ this.executeNextPagelet();
+ }
+ }
+ },
+
+ loadJSResources: function() {
+ this.phase = 3;
+ var isLoading = false;
+
+ this.pagelets.forEach(function(Pagelet) {
+ if(Pagelet.JSResources.length === 0) {
+ Pagelet.onloadJS();
+ }
+ });
+
+ this.pagelets.forEach(function(Pagelet) {
+ Pagelet.JSResources.forEach(function(Resource) {
+ Resource.start();
+ isLoading = true;
+ });
+ });
+
+ if(!isLoading) {
+ this.pagelets.forEach(function(Pagelet) {
+ Pagelet.onloadJS();
+ });
+ }
+ }
+ };
+
+ //===================================================================================================
+ // REVEALING MODULE PATTERN: Öffentliche API
+ //===================================================================================================
+ return {
+ onPageletArrive: function(data) {
+ BigPipe.onPageletArrive(data);
+ },
+
+ registerPhaseDoneCallback: function(phase, callback) {
+ BigPipe.registerPhaseDoneCallback(phase, callback);
+ },
+
+ reset: function() {
+ BigPipe.phaseDoneCallbacks = {};
+ BigPipe.pagelets = [];
+ BigPipe.offset = 0;
+ BigPipe.phase = 0;
+ }
+ };
+})(); \ No newline at end of file
diff --git a/static/blue.php b/static/blue.php
new file mode 100755
index 0000000..ddffcf6
--- /dev/null
+++ b/static/blue.php
@@ -0,0 +1,17 @@
+<?php
+#====================================================================================================
+# Cache deaktivieren
+#====================================================================================================
+header('Cache-Control: no-cache, no-store, must-revalidate');
+
+#====================================================================================================
+# Content-Type setzen
+#====================================================================================================
+header('Content-Type: text/css');
+
+#====================================================================================================
+# Lange Ladezeit simulieren
+#====================================================================================================
+usleep(intval(rand(60, 100).'0000'));
+?>
+#blue{background:blue;} \ No newline at end of file
diff --git a/static/delayJS.php b/static/delayJS.php
new file mode 100755
index 0000000..1307ca0
--- /dev/null
+++ b/static/delayJS.php
@@ -0,0 +1,16 @@
+<?php
+#====================================================================================================
+# Cache deaktivieren
+#====================================================================================================
+header('Cache-Control: no-cache, no-store, must-revalidate');
+
+#====================================================================================================
+# Content-Type setzen
+#====================================================================================================
+header('Content-Type: text/javascript');
+
+#====================================================================================================
+# Lange Ladezeit simulieren
+#====================================================================================================
+usleep(intval(rand(10, 40).'0000'));
+?> \ No newline at end of file
diff --git a/static/green.php b/static/green.php
new file mode 100755
index 0000000..3e45fc6
--- /dev/null
+++ b/static/green.php
@@ -0,0 +1,17 @@
+<?php
+#====================================================================================================
+# Cache deaktivieren
+#====================================================================================================
+header('Cache-Control: no-cache, no-store, must-revalidate');
+
+#====================================================================================================
+# Content-Type setzen
+#====================================================================================================
+header('Content-Type: text/css');
+
+#====================================================================================================
+# Lange Ladezeit simulieren
+#====================================================================================================
+usleep(intval(rand(60, 100).'0000'));
+?>
+#green{background:green;} \ No newline at end of file
diff --git a/static/red.php b/static/red.php
new file mode 100755
index 0000000..9ad993e
--- /dev/null
+++ b/static/red.php
@@ -0,0 +1,17 @@
+<?php
+#====================================================================================================
+# Cache deaktivieren
+#====================================================================================================
+header('Cache-Control: no-cache, no-store, must-revalidate');
+
+#====================================================================================================
+# Content-Type setzen
+#====================================================================================================
+header('Content-Type: text/css');
+
+#====================================================================================================
+# Lange Ladezeit simulieren
+#====================================================================================================
+usleep(intval(rand(60, 100).'0000'));
+?>
+#red{background:red;} \ No newline at end of file