Benutzer:TS/Zugriffszähler mit Onlineanzeige

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

In diesem Artikel wird gezeigt, wie eine Zugriffszählung mit IP-Sperre und eine Onlineanzeige realisiert werden können. Das Ergebnis kann berechtigten Besuchern mittels einer Grafik angezeigt werden.

Der Nutzen dieser Zugriffszählung wird zwar in der Fachwelt immer belächelt, eignet sich aber als praktisches Beispiel hervorragend, um den Zusammenhang zwischen HTML, JavaScript und PHP aufzuzeigen und für die Vertiefung des Umgangs mit Dateien. Zum praktischen Einsatz kommt hier auch das File Locking.

Zielvorgabe

Für eine Webseite soll eine Zählung der Seitenbesucher erstellt werden, sowie eine "Online-Anzeige" für gerade in der Seite beschäftigte Besucher. Eine Anmeldung der Benutzer soll hierfür nicht notwendig sein. Fehler, die durch das Verfahren Besucher einfach an ihrer IP festzumachen entstehen, werden hingenommen.

Damit aber Mehrfachbesuche derselben Besucher innerhalb kurzer Zeitabstände (1-3 Stunden, zusätzlich nachgetriggert) nicht doppelt gezählt werden und auch Counter-Floading verhindert werden kann, sind die IPs der Besucher für diese Sperrzeit zu registrieren und von der Zählung auszunehmen. Als "online" soll ein Besucher gelten, der innerhalb der letzten 30-60 Sekunden einen Request ausgelöst hat, bzw. die Seite noch im Browser geöffnet hält.

Programmteile

Welche Programmelemente benötigen wir? Welches Programmelement übernimmt welche Aufgabe?

Das Zählscript in PHP

visitcount.php
  1 <?php  ### visitcount.php ### 26.10.2016 ###
  2 
  3 # zählt die Requests und merkt sich die IP der Requestoren für die Dauer der
  4 # COUNTER_LOCKTIME. In dieser Zeit wird ein erneuter Request derselben IP 
  5 # nicht gezählt. Ein erneuter Request wird auch nach Ablauf von COUNTER_LOCKTIME
  6 # nicht gezählt, wenn zwischendurch kein Request mit einer abweichenden IP
  7 # eingetroffen ist.
  8 #
  9 # Hinweis:
 10 # IPs als Grundlage für die Client-Erkennung zu benutzen ist keine saubere
 11 # Methode. Durch NAT, Proxies und automatische Lastverteilungen, sowie
 12 # Anonymisierungsdienste können die IPs nicht einem bestimmten Client zugeordnet
 13 # werden. Für die Funktionsweise dieses Zählers ist die Genauigkeit aber 
 14 # hinreichend.
 15 
 16 # Zeitumstellung ist noch nicht berücksichtigt
 17 
 18 ## Innerhalb dieser Zeit in Sekunden wird die IP nicht als Neuzugriff gezählt
 19 ## soll Counter-Floading verhindern
 20 defined('COUNTER_LOCKTIME') or define('COUNTER_LOCKTIME', 3600); 
 21 
 22 ## Innerhalb dieser Zeit muss die Online-Meldung des Clients erfolgen
 23 ## Am Client kleiner, als die Hälfte einstellen. Es könnte auch mal ein Request 
 24 ## verloren gehen
 25 defined('COUNTER_REFRESHTIME') or define('COUNTER_REFRESHTIME', 62); 
 26 
 27 ## Counterfile entweder als '.htcounter.dat' oder außerhalb der Document_Root anlegen
 28 ## Das angegebe Verzeichnis und das File müssen für den PHP-Prozess beschreibbar sein!
 29 $counterfile = rtrim($_SERVER['DOCUMENT_ROOT'],'/') . '/../data/counter.dat';   
 30 #$counterfile = '.htcounter.dat';   
 31 
 32 ## Funktionensammlung einbinden, wenn vorhanden.
 33 @include (rtrim($_SERVER['DOCUMENT_ROOT'],'/') . '/../includes/functions.inc'); 
 34 
 35 
 36 #------------------------------------------------------------------------------
 37 ## Zähler nur anzeigen, wenn der User ihn sehen darf. Sonst nur Zählpixel ausgeben.
 38 ## Dummy zum Weiterentwickeln für Systeme mit Zugriffsrechten
 39 ## siehe hierzu Artikel "Login- und Rechtesystem ##
 40 
 41 if (!function_exists('user_access')) 
 42 {
 43     function user_access($mode, $module) { return true; } 
 44 }
 45 
 46 #------------------------------------------------------------------------------
 47 function visitcount($filename, $time, $retrigger = true, $clean = true)
 48 {
 49     ## Zählerdatei zum Lesen und Schreiben öffnen und, 
 50     ## falls nicht vorhanden, vorher anlegen
 51     $fh = fopen($filename, 'cb+');  
 52 
 53     if (!$fh) return false;
 54     if (!flock($fh, LOCK_EX))
 55     {
 56         fclose($fh);
 57         return false;
 58     }
 59 
 60     ## Schreiben sauber beenden, auch wenn die Verbindung abgebrochen wird    
 61     $userabort = ignore_user_abort("1");
 62 
 63     ## Defaults für das Array
 64 
 65     $_data = array();
 66     $_data['time'] = array();
 67     $_data['time'][$_SERVER['REMOTE_ADDR']]  = $time;
 68     $_data['count'] = 1;
 69     $_data['lastIP'] = $_SERVER['REMOTE_ADDR'];
 70 
 71 
 72     $filesize = filesize($filename);
 73 
 74     ## Datei nur lesen und auswerten, wenn auch Daten drinstehen könnten
 75     if ($filesize > 0)
 76     {
 77         $filedata = fread($fh, $filesize);
 78         
 79         ## Fileformat checken
 80         if (false === ($_data = unserialize($filedata))
 81              || !isset($_data['time'], $_data['count'], $_data['lastIP']))
 82         {
 83             ## Default-Datenstruktur wiederherstellen
 84             $_data = array();
 85             $_data['time'] = array();
 86             $_data['time'][$_SERVER['REMOTE_ADDR']]  = $time;
 87             $_data['count'] = 1;
 88             $_data['lastIP'] = $_SERVER['REMOTE_ADDR'];            
 89         }
 90         else
 91         {
 92             ## Nur berücksichtigen, wenn die Requestor-IP noch 
 93             ## nicht in der Liste der letzten Requests steht 
 94 
 95             if (!isset($_data['time'][$_SERVER['REMOTE_ADDR']])) 
 96             {    
 97 
 98                 ## IP in Liste eintragen  
 99                 $_data['time'][$_SERVER['REMOTE_ADDR']] = $time;
100                 
101                 ## nur zählen, wenn zwischendurch ein Request 
102                 ## von einer anderen IP eingetroffen ist
103                 if ($_data['lastIP'] != $_SERVER['REMOTE_ADDR'])   #
104                 {
105                     $_data['count']++; 
106                 }    
107             }
108             elseif ($_data['time'][$_SERVER['REMOTE_ADDR']] + COUNTER_LOCKTIME < $time)            
109             {
110                 $_data['time'][$_SERVER['REMOTE_ADDR']] = $time;
111 
112                 if ($_data['lastIP'] != $_SERVER['REMOTE_ADDR'])
113                 {
114                     $_data['count']++;
115                 }    
116             }
117             elseif ($retrigger)
118             {
119                 $_data['time'][$_SERVER['REMOTE_ADDR']] = $time;                
120             }
121             else
122             {
123                 ## ignore visit
124             }
125             
126             ## IPs nach Zeit wieder freigeben für die Zählung
127             if ($clean)
128             {
129                 foreach ($_data['time'] as $ip => $lasttime)
130                 {
131                     if($time - $lasttime > COUNTER_LOCKTIME)
132                     {
133                         unset($_data['time'][$ip]);
134                     }
135                 }
136             }
137         }
138     }    
139     
140     ## Onlineanzeige berechnen, alle Zugriffe, die nicht älter als
141     ## COUNTER_REFRESHTIME sind, werden als "online" gewertet
142     $useronline = 0;
143     foreach ($_data['time'] as $key => $lasttime)
144     {
145         if ($lasttime > $time - COUNTER_REFRESHTIME) $useronline++;
146     }
147     
148     ## Daten zurückschreiben
149     $filedata = serialize($_data);
150     fseek($fh, 0, SEEK_SET);
151     fwrite($fh, $filedata);
152     ftruncate($fh, strlen($filedata));
153     fclose($fh);
154     
155     ignore_user_abort("$userabort");
156     
157     $_result = array('visitors'=>$_data['count'], 'useronline'=>$useronline);
158     
159     return $_result;
160 
161 }
162 #------------------------------------------------------------------------------
165 function writetext($text, $font)
166 {
167     // content-type für das Bild setzen
168     header("Content-type: image/gif");
169 
170     $width=185;
171 
172     // Das leere Bild im Speicher erzeugen
173     $im = imagecreatetruecolor($width, 20);
174 
175     // Ein paar Farben festlegen
176     $white = imagecolorallocate($im, 255, 255, 255);
177     $grey  = imagecolorallocate($im, 128, 128, 128);
178     $black = imagecolorallocate($im, 0, 0, 0  );
179     $blue  = imagecolorallocate($im, 0, 0, 255);
180     $red   = imagecolorallocate($im, 255, 0, 0);    
181     
182     // Das leere Bild mit rotem Hintertgrund füllen
183     imagefilledrectangle($im, 0, 0, $width-1, 19, $red);
184     
185     // Das rote Bild innen weiß füllen
186     imagefilledrectangle($im, 1, 1, $width-2, 18, $white);
187 
188     // Einen Schatten hinter den Text legen
189     imagettftext($im, 13, 0, 10, 17, $grey, $font, $text);
190 
191     // Den Text ins Bild einsetzen
192     imagettftext($im, 13, 0, 9, 16, $blue, $font, $text);
193 
194     // Das Bild ausgeben. imagegif() erzeugt einen schärferen Text als imagejpeg()
195     imagegif($im);
196     imagedestroy($im);
197 }
198 #==============================================================================
199 # php main
200 #==============================================================================
201 
202 
203 $font = './ARIAL.TTF';
204 #$font = 'C:/WINNT/Fonts/GLACIERN.TTF';
205 
206 if (false !== ($_result = visitcount($counterfile, time() )))
207 {
208    $text = "Online:{$_result['useronline']}  Visits:{$_result['visitors']}";      
209 }
210 else
211 {
212    $text = "http://bitworks.de";
213 }
214 
215 if (!isset($_GET['noshow']) && user_access('view', 'visitcount')) 
216 {
217 #	header("Content-Type: image/gif");  ## Header wird bereits in writetext() gesetzt
218     writetext($text, $font);
219 }
220 else
221 {
222     header("Content-Type: image/gif");
223     readfile('1pix.gif');
224 }
225 
226 ?>

Der Online-Trigger in JavaScript

visitcount_trigger.js
 1 function counter_refresh(pic_source)
 2 {
 3     var pic = document.getElementById('img_counter');
 4     var jetzt = new Date();
 5 
 6     if(pic)
 7     {
 8 	pic.src = pic_source + '?time=' + jetzt.getTime();
 9 	window.setTimeout("counter_refresh(pic_source)",30000);
10     }
11 }

Der aktuelle Timestamp wird für den Request an den Ressourcebezeichner angehängt, um ein Neuladen der Ressource zu erzwingen.

die Ausgabe als Grafikdatei

Für die Ausgabe als Grafikdatei ist die Funktion writetext() im PHP-Script zuständig. Sie benötigt einen TTF-Font im angegebenen Verzeichnis. Sie erzeugt aus einem gegebenem Text (Online: n Visits: m) eine Grafik. Diese Grafik wird dann beim Aufruf des Scriptes angezeigt.

Zusammenspiel im HTML

Um den Zähler in ein HTML-Dokument einzubauen, lassen wir darin einfach ein img-Element anzeigen und in regelmäßigen Zeitabständen mittels Javascript aktualisieren. Da das Image immer dieselbe Größe und Position behält, fällt es nicht auf, dass der Browser neu rendert.

index.html
<!DOCTYPE html>
<html lang="de">

<head>
    <title>Meine Webseite</title>  
    <script src="/visitcount/visitcount_trigger.js"></script>  
</head>  


<body onLoad="counter_refresh('/visitcount/visitcount.php');">
    <div id="counter">
        <img id="img_counter" src="/visitcount/visitcount.php" alt="counter">
    </div>
</body>
</html>

Da die Pfade absolut zur Domain-Root angegeben wurden, kann der Zähler in jede Seite der Verzeichnisstruktur der Website eingebaut werden.

Verzeichnisstruktur

In der Verzeichnisstruktur des Webaccounts müssen die Komponenten folgendermaßen platziert werden:

ACCOUNT
   +---htdocs
   |      +---visitcount
   |      |        +--visitcount.php   
   |      |        +--ARIAL.TTF 
   |      |        +--1px.gif
   |      |        +--visitcount_trigger.js
   |      |                          
   |      +---index.html                             
   |
   +---includes
   |      +---functions.inc
   |      +--- ...
   |      
   +---data
   |      +---visitcount.dat
   |      +--- ...
  ...

Der User des Webservers (z. B. "www-data") muss Leserechte für das includes-Verzeichnis haben sowie Schreib- und Leserechte für das data-Verzeichnis. Die DOCUMENT_ROOT liegt auf "htdocs". Die Verzeichnisse "includes" und "data" sind per http/s nicht erreichbar.

Zubehör

  • Truetype-Font
  • GIF-Datei mit einem transparenten Pixel mit Namen "1pix.gif"

So sieht es aus