Praktikum Internet-Technologie

von Prof. Jürgen Plate

Praktikum Internet-Technologie

Anmerkung: Es werden je nach Anzahl der Teilnehmer nicht alle Aufgaben bearbeitet oder einzelne Aufgaben auf verschiedene Gruppen verteilt. Außerdem wird in der Regel zusätzlich ein gemeinsames Projekt entwickelt und bearbeitet. Das Praktikum umfasst Aufgaben aus den Bereichen Client-Server-Programmierung und Web-Anwendungen.

Vorbereitung

Das Praktikum kann entweder im Programmierlabor R BG.009 oder im Labor E 204 stattfinden - je nach Anzahl der Teilnehmer. Deshalb folgen nun für jedes der Labore eine Anleitung:

Labor R BG.009 mit Ray-Terminals

Die Ray-Terminals sind an die Server lc1, lc2 und lc3 angeschlossen. Ausserdem spielt der Server blackhole bei einigen Aufgaben eine Rolle.

Die Domain für alle Rechner ist da.private.hm.edu. Dabei steht "da" für den Standort Dachauerstr./Lothstr. und "private" bedeutet, daß der Rechner im privaten (von aussen nicht erreichbaren) Netz steht.

Der Apache-Webserver ist nur auf lc1 installiert. Da Sie nur an X-Terminals sitzen und doch jeder sein eigenes Webverzeichins haben soll, verwenden wir die Möglichkeit der sogenannten User-Directories:

Über die URL http://lc1/~username/ kann nun jedes User-Verzeichnis angesprochen werden und über die URL http://lc1/~username/cgi-bin/xyz.cgi erreicht man die die CGI-Scripte.

Die Dateien in ihrem Home-Directory sind natürlich auf allen drei Systemen (lc1, lc2 und lc3) verfügbar, auch wenn der Webserver nur auf lc1 läuft.

Die MySQL-Datenbank befindet sich (wie der Webserver) nur auf lc1. Der Datenbank-Zugriff für Ihre CGI-Scripte erfolgt dann mit folgenden Angaben:

Der Zugriff auf der Kommandozeile kann beispielsweise mit folgendem Kommando erfolgen:
 mysql -u tom -p -h lc1
Einfacher geht es jedoch mit einer webbasierten Oberfläche namens "phpmyadmin". Diese wird über folgende URL aufgerufen:
http://lc1/phpmyadmin
Auch hier ist danach User- und Passworteingabe gefordert.

Labor E 204 mit Linux-Rechnern

Informieren Sie sich über den Namen Ihres Rechners (Planeten bzw. Kleinplaneten). Ausserdem spielt der Server blackhole bei einigen Aufgaben eine Rolle.

Die Domain für alle Rechner ist ebenfalls da.private.hm.edu. Dabei steht "da" für den Standort Dachauerstr./Lothstr. und "private" bedeutet, daß der Rechner im privaten (von aussen nicht erreichbaren) Netz steht.

Für einige Aufgaben ist die Root-Berechtigung notwendig. Der Wechsel in die Rolle des Superusers erfolgt mit dem Kommando sudo. Um eine Root-Shell zu erlangen, geben Sie demnach sudo su ein. Der Login auf anderen Rechnern erfolgt mittels der Secure Shell (ssh <user>@<rechner>) und zum Kopieren wird Secure Copy (scp) verwendet.

Der Apache-Webserver ist auf allen Rechnern installiert. Unter Ubuntu befinden sich die beiden Verzeichnisse, auf die es ankommt, an verschiedenen Stellen:

Damit die Verzeichnisse leichter erreichbar sind, und von Ihnen auch mit Dateien gefüllt werden können, sollten Sie folgende Kommandos eingeben. Als Beispieluser dient hier 'mond', Sie müssen natürlich Ihre Userkennung verwenden:
sudo bash                                 # root-Berechtigung erlangen
chown -R mond /var/www /usr/lib/cgi-bin   # Verzeichnisse an user 'mond' uebergeben
chmod 775 /var/www /usr/lib/cgi-bin       # Zugriffsrechte setzen
ln -s /var/www www                        # Document-Root nun per Link leicht erreichbar
ln -s /usr/lib/cgi-bin cgi-bin            # das Gleiche fuer cgi-bin
exit                                      # root-bash wieder verlassen
Sie können nun vom Homedirectory direkt in die beiden Verzeichnisse des Webservers wechseln und dort auch Dateien anlegen. Denken Sie daran, dass Programme im Verzeichnis cgi-bin immer erst Execute-Berechtigung bekommen müssen (chmod 755 proggi.pl).

Anmerkung:
Bei Linux ist Perl per Default dabei. Wenn Sie unter Windows mit Perl arbeiten wollen, können Sie sich das kostenlose Activestate-Perl herunterladen: www.activestate.com/activeperl/downloads

(Wer will, kann sich auch gleich noch ein Python holen: www.activestate.com/activepython/downloads)

Vorübung (Hausarbeit): Perl-fork() und SIGCHILD

fork() ist ein Perl-Wrapper für den Systemcall fork. fork erzeugt einen neuen Prozess der dasselbe Perlscript ausführt. Wenn fork erfolgreich war, kehren beide Prozesse (Eltern- und Kindprozess) aus dieser Funktion zurück. Als Rückgabewert erhält der (ursprüngliche) Elternprozess die Prozess-ID des Kindprozesses, das Kind erhält null. Schlägt fork fehl, so wird undef zurückgegeben und $! entsprechend gesetzt.

Sowohl Eltern- als auch Kindprozess arbeiten das Perlscript weiter ab, beginnend mit der Anweisung nach dem fork-Aufruf. Klassischerweise ist dies eine Verzweigung, deren einer Zweig vom ursprünglichen Prozess ausgeführt wird, der andere vom Kind. Damit sich das Kind beendet, wenn es seinen Teil erledigt hat (und die Programmteile des ursprünglichen Prozesses weiter abarbeitet), muss am Ende exit aufgerufen werden.

Wenn sich Kindprozesse beenden bleiben sie in Unix-Umgebungen zunächst im System, bis der Elternprozess diese wegräumt, indem er wait aufruft. wait wartet, bis ein Kindprozess gestorben ist und liefert dann die Prozess-ID dieses Kindes zurück. Falls es keine Kindprozesse gibt, kehrt wait sofort mit dem Rückgabewert -1 zurück.

Erzeugt ein Prozess viele Kinder so ist es am geschicktesten, die Zombies automatisiert abzuräumen. Dies lässt sich mit Hilfe eines Signalhandlers einrichten. Ein Signalhandler ist eine Funktion, die immer dann ausgeführt wird, wenn ein Signal eintrifft. Zu jedem Signal kann ein Prozess einen Signalhandler aufsetzen, der immer dann aufgerufen wird, wenn das Signal eintrifft. Um die Kinder automatisch abzuräumen muss ein Signalhandler für das Signal SIGCHLD installiert werden, der wait() aufruft:

sub sigchldHandler
  {
  wait();
  $SIG{CHLD} = \&sigchldHandler;
  }

Damit der Handler auch aufgerufen wird, muss er aufgesetzt werden. Bei Perl ist das aufsetzen von Signalhandlern sehr einfach: Eine Referenz auf die Funktion wird einfach zu dem entsprechenden Schlüssel, dem Signalnamen, in den Hash %SIG eingetragen. Sobald die Eintragung geschehen ist, wird der Signalhandler aufgerufen, wenn das entsprechende Signal ankommt. Ein Handler für SIGCHLD sollte also aufgesetzt werden, bevor Kindprozesse erzeugt werden! Für die Handlerfunktion sigchldHandler aus den obigen Beispielen sieht die Eintragung wie folgt aus:

$SIG{CHLD} = \&sigchldHandler;
Schreiben Sie ein Perl-Programm, das zu Beginn die Anzahl der zu erzeugenden Kinder einliest, den Signalhandler installiert (wie oben beschrieben) und dann entsprechend viele Kindprozesse erzeugt.
Jeder Kindprozess soll beim Starten die Zeile "Kind xx gestartet" ausgeben, dann 5 Sekunden schlafen (sleep(5)) und zum Schluss "Kind xx beendet" ausgeben.
Die letzten zwei Zeilen des Programms sollten lauten:
print "Beenden? ";
$input = <STDIN>
Sie bewirken, dass es sich nicht beendet, solange Sie nichts eingeben. Dies gibt Ihnen Zeit, mit dem ps-Kommando zu überprüfen, ob die Kinder tatsächlich aufgeräumt wurden. Sobald Sie das Programm beenden, werden die von diesem Programm erzeugten Kinder vom System aufgeräumt und Sie können nicht mehr feststellen, ob der Signalhandler korrekt funktioniert hat.
Was fällt Ihnen dabei auf? Wann erscheint die Fertigmeldung?

1. Anwendung von Systemdiensten

Es gibt viele Rechner, die Webcam-Bilder erzeugen. Einige Beispiele dafür sind:

Marienplatz in Müchen
(http://blog.muenchen.de/marienplatzcam/marienplatzgross001.jpg)
Hof des Deutschen Museums
(http://www.deutsches-museum.de/fileadmin/Content/data/webcam/mushof_f.jpg)
Isar in München
(www.wwa-webcams.de/m/isar-plan/isar-plan.jpg)
Allianz-Arena
(webcam.allianz-arena.de/aa-cam-01/full_2.jpg)
Zugspitze
(http://cms.zugspitze.de/static/webcams/k04.jpg)

Mit dem Perl-Modul LWP::Simple ist es möglich, innerhalb eines Programms mit dem Befehl getstore $url, $file; eine durch $url spezifizierte Datei von einem fernen Server zu holen und unter dem Dateinamen $file (ggf. mit Pfadangabe im Dateinamen) auf dem lokalen Rechner abzuspeichern. Ein Programmfragment sieht dann etwa folgendermaßen aus:

#!/usr/bin/perl
use LWP::Simple;
  .
  .
  .
getstore $url, $file;
  .
  .
  .
Schreiben Sie ein Perl-Programm, das ein Bild von einer beliebigen Webcam holt und lokal abspeichert. Dabei sollen rotierend immer 4 Bilder (b1.jpg - b4.jpg) zur Verfügung stehen, wobei immer b1.jpg das älteste und b4.jpg das jüngste Bild ist. Die Bilder müssen in der "Document-Root" des Servers abgelegt werden. Da das Script kein CGI-Script ist, sollte es sich in Ihrem Heimatverzeichnis befinden.

Hinweise:

Erzeugen Sie eine einfache HTML-Seite (ebenfalls in der "Document-Root" des Servers), in der alle 4 Bilder untereinander dargestellt werden und testen Sie damit die Funktion Ihres Programms.

2. Automatisierter Zugriff auf Infos im Web

Das Programm der vorhergehenden Aufgabe soll nun nicht manuell, sondern per crontab aufgerufen werden - und zwar alle 2 Minuten.

Erzeugen Sie einen crontab-Eintrag für den zweiminütigen Aufruf des Programms der vorhergehenden Aufgabe und testen Sie die Funktion des Gesamtsystems.

Das crontab-Kommando

crontab datei Fügt die Jobs in datei zur Crontab Datei des Benutzers hinzu.
crontab -r Löscht die Crontab-Datei des Benutzers.
crontab -l Listet die Jobs des Benutzers auf.
crontab -e Erlaubt das Editieren der Crontab-Datei.

Ein einzelner Job wird durch eine Zeile definiert, die das Zeitintervall und das genaue Kommando vorgibt. Die einzelnen Felder in der Datei sind durch Leerzeichen oder durch einen Tabulator getrennt. Da beim Aufruf eines Programms per crontab-Kommando der gesetzte Pfad (Variable PATH) nicht bekannt ist, sollten Sie alle Programme mit absolutem Pfad angeben.

Minute Stunde Tag Monat Wochentag Kommando
0-59 0-23 1-31 1-12 0-6 ....

Bei der Angabe der Zeit sind einzelne Zahlen, Intervalle (z.B.: 3-8), Aufzählungen (z.B.: 1,3,5), sowie "*" für alle, möglich.

Beispiel: Die folgenden Zeilen definieren zwei Jobs:

  # mm hh DoM MM DoW command
  #
  30 * * * * /bin/foo
  30 23 * * * /bin/bar > /dev/null 2>&1

Immer wenn der Minutenzeiger auf 30 steht wird das Programm /bin/foo gestartet. Und um 23:20 wird der Befehl /bin/bar ausgeführt. Dabei werden alle Ausgaben des Befehls ins Nirvana umgeleitet. Weitere Informationen können Sie mit dem Kommando man crontab abrufen.

Denken Sie daran, Ihren crontab-Eintrag zum Praktikumsende wieder zu deaktivieren, indem Sie mit crontab -e den Editor aufrufen und vor alle Zeilen ein Kommentarzeichen (#) setzen.

Dann können Sie im folgenden Termin die crontab-Einträge wieder schnell aktivieren, indem Sie das Kommentarzeichen löschen.

3. Server-Client-Kommunikation

Hinweis: Damit der Client erkennen kann, wann eine (mehrzeilige) Antwort vom Server zu Ende ist, sollen Sie sich eine Endekennung für die Nachrichten überlegen.

Damit sich die einzelnen Berabeitungsgruppen nicht gegenseitig behindern, müssen zu Beginn des Praktikums die Portnummern für jede Gruppe vergeben werden:

In dieser Aufgabe sind ein Server und ein passender Client zu programmieren. Beginnen Sie mit dem Server und testen Sie diesen mit Hilfe des telnet-Programms. Schreiben Sie dann den zugehörigen Client. Server und Client können auf ein und demselben System programmiert werden, indem "localhost" als Server und Client dient. Auch die hardwarespezifischen Aktionen, die später auf einem Raspberry Pi ablaufen sollen, lassen sich weitgehend simulieren. Wenn Die Simulation läuft, kann einer der Raspberryies als Server dienen, indem Sie Ihre Server-Software hochladen.

Die Koordination der Gruppen erfolgt zum jeweiligen Praktikumstermin. Dabei arbeiten immer zwei Gruppen miteinander: eine Gruppe schreibt den Client, die andere den Server. Auch die Login-Daten für die Raspberry Pi Systeme erfahren Sie im Praktikum.

Die Namen und IP-Adressen der Raspberry-Systeme sind:

RechnerIP-Adresse
chiron10.27.210.230
pholus10.27.210.231
nessus10.27.210.232

Damit nicht jeder die Systeme ansprechen kann, wird zuerst ein Passwort abgefragt. Damit ergibt sich der im Folgenden skizzierte Dialog zwischen Client und Server:

ClientServer
[Verbindungsaufnahme]Rechnername, nice to meet you
PASS
[Passwort]falls Passwort akzeptiert wird: ACK
sonst: NAK [Verbindungsabbruch]
[Kommando][ACK/NAK (ggf. Daten zum Kommando)]

Die letzte Zeile kann sich beliebig oft wiederholen. Als Kommandos des Client sond möglich:

KommandoGewünschte Daten
WHOAusgabe der Kommandos:
hostname
uname -or
uptime
GET nWert des GPIO-Pins n
SET n [0|1]Setzen des GPIO-Pins n auf 0 oder 1
QUIT"Bye"
[Beenden der Verbindung]

Der Server soll seine Aktivitäten auf dem Bildschirm protokollieren, indem er bei Verbindungsaufnahme die Client-IP ausgibt (print "Anfrage von ", $client->peerhost(), ":", $client->peerport(), "\n";) und dann den Dialog anzeigt.

Schreiben Sie nun einen Client, der eine Verbindung zum Server aufbaut, sich authentisiert und dann abhüngig von der Kommandozeilenoption eines oder mehrere der oben aufgeführten Abfragekommandos ausführt. Als Kommandozeilenoptionen kommen beliebige Kombinationen von -g (GET) -s (SET) und -w (WHO) in Frage. Danach beendet die Client die Verbindung (QUIT).

Die drei Systeme haben an vier GPIO-Ports eine Vierfarb-Power-LED angeschlossen, deren einzelne LEDs per GET- oder SET-Befehl ein- und ausgeschaltet werden können:

GPIO22GPIO23GPIO24GPIO25
ROT GRUEN BLAU WEISS

Für die Client- und Serverprogramme gilt: Das Passwort, die Portnummer(n) und die Serveradresse werden fest im Programm als Konstante verankert. zum Beispiel:

# --------------- Konfiguration --------------------
use constant PASSWD => "lassmichrein";

use constant MYPORT => 6666;

use constant SERVER => "nessus"; # nur beim Client
# --------------- Konfiguration --------------------

4. CGI-Schnittstelle

Für einen ersten Test des User-Directories legen Sie zwei Dateien an: Machen Sie das Perl-Script ausführbar (Shellkommando: chmod +x hello.cgi) Nun können Sie die beiden Default-Dateien testweise mit dem Browser aufrufen.

Einstieg in die CGI-Programmierung

Erweitern Sie den Client der Client-Server-Aufgabe zu einem CGI-Script. Ändern Sie das Programm dahingehend, dass es die Befehle nicht mehr über die Kommandozeile annimmt, sondern entsprechende Eingaben über die POST-Parameter eines HTML-Formulars entgegen nimmt. Achten Sie bei der Auswertung der Parameter auf Sicherheitsaspekte und Fehlerbehandlung. Nur korrekte Parameter sollen zu einer Aktion führen. Der Client nimmt seinerseits dann wie vorher Kontakt zum entsprechenden Raspberry Pi auf und setzt die Formulareingaben in die entsprechenden Kommandos um. Nachdem alle Parameter abgearbeitet wurden, sendet der Client ein QUIT.

Denken Sie auch daran, dass die Ausgabe des CGI-Programms eine korrekte HTML-Seite ergeben muss. Das HTML-Formular für die Benutzereingaben befindet sich zunächst in einer getrennten Datei. Das Formular verwendet die HTTP-Methode POST zur Datenübermittlung. Das Feld "Port-Status" im Formular veranlasst den Client, alle vier GPIO-Ports abzufragen und deren Stand komplett zurück zu melden. Installieren Sie Formular und CGI-Programm auf einem der lc-Server.

Das Formular zur Aufgabe (Achtung: Bei "action" den Pfad ergänzen/ändern → USER, Programmname!):

<form action="http://lc1/~USER/cgi-bin/prakt.pl" method="post">
<table border="1" cellpadding="3" cellspacing="0" bgcolor="#CCCCCC">
<tr>
<td bgcolor="#EEFF99" valign="top">SET GPIO 22</td>
<td bgcolor="#EEFF99" valign="top">
<input type="radio" name="GPIO22" value="1">On
<input type="radio" name="GPIO22" value="0">Off </td>
</tr><tr>
<td bgcolor="#EEFF99" valign="top">SET GPIO 23</td>
<td bgcolor="#EEFF99" valign="top">
<input type="radio" name="GPIO23" value="1">On
<input type="radio" name="GPIO23" value="0">Off </td>
</tr><tr>
<td bgcolor="#EEFF99" valign="top">SET GPIO 24</td>
<td bgcolor="#EEFF99" valign="top">
<input type="radio" name="GPIO24" value="1">On
<input type="radio" name="GPIO24" value="0">Off </td>
</tr><tr>
<td bgcolor="#EEFF99" valign="top">SET GPIO 25</td>
<td bgcolor="#EEFF99" valign="top">
<input type="radio" name="GPIO25" value="1">On
<input type="radio" name="GPIO25" value="0">Off </td>
</tr><tr>
<td bgcolor="#EEEEEE" valign="top">Who</td>
<td bgcolor="#EEEEEE" valign="top">
<input type="radio" name="WHO" value="-">
</tr><tr>
<td bgcolor="#EEEEEE" valign="top">Port-Status</td>
<td bgcolor="#EEEEEE" valign="top">
<input type="radio" name="STAT" value="-">
</tr><tr>
<td bgcolor="#FF6666" valign="top">Passwort:
    <input type="password" name="PASS" length="10"></td>
<td bgcolor="#FF6666" valign="top">
    <input type="submit" value=" Absenden "></td>
</tr>
</table>
</form>

Optisch stellt sich das Formular dann folgendermaßen dar:

SET GPIO 22 On Off
SET GPIO 23 On Off
SET GPIO 24 On Off
SET GPIO 25 On Off
Who
Port-Status
Passwort:

Beachten Sie, daß das Formular noch in eine korrekte HTML-Seite eingebettet werden muß.

CGI-Schnittstelle erweitern

Bei der vorhergehenden Aufgabe waren Formular und Programm noch getrennt. Erweitern Sie nun Ihr Programm so, dass Damit ist das Formular im Programm integriert.

5. Datenbank-Anwendung

Datenbank-Modellierung

Ziel dieser Praktikumsaufgabe ist die Realisierung eines Forums mithilfe von CGI-Scripten. Es ist sicher nicht sinnvoll, die gesamte Funktionalität auf eine einzige Seite zu packen. Überlegen Sie sich eine Struktur von Seiten (und auch die Links dazwischen), die folgende Aufgaben erfüllen sollen:

Es soll eine MySQL-Datenbank entwickelt werden, welche die Daten des Schwarzen Bretts speichert. Konzipieren Sie eine Tabelle inklusive der benötigten Datentypen und entwerfen Sie eine "CREATE TABLE"-Anweisunge für diese Tabelle. Hierbei müssen sich alle Teilnehmer auf eine Tabellenbeschreibung einigen, da diese Tabelle auch gemeinsam genutzt wird. Die Tabelle wird daher gemeinsam im Praktikum erstellt. Denken Sie auch daran, dass ein Timestamp-Feld vorgesehen werden muss.

Web-Interface für die Ein- und Ausgabe

Erstellen Sie das Web-Interface als CGI-Script in Perl. Es gibt zwei Möglichkeiten:

In jedem Fall wird dann wieder zum Anfang gesprungen (Ausgabe).

Überlegen Sie, wie Sie ein "Blättern" in der Ausgabe erreichen können (erst Beitrag 1 - 20, dann 21 -40 usw.). An die Implementierung gehen Sie erst, wenn die Suche (siehe unten) funktioniert.

Web-Interface für die Suche

Erstellen Sie ein einfache Suchformular für Datenbankabfragen. Abgefragt werden können (Teil-)-Strings Autor, Titel und natürlich Stichworte. Alternativ wird angegeben wie "alt" der Beitrag sein darf. Orientieren Sie sich bei der Ergebnis-Ausgabe am Output diverser Suchmaschinen: Fundstelle als Link (Titel), darunter Autor und Erstellungsdatum.

Versuchen Sie dann, logische Verknüpfungen der Suchanfrage zu realisieren und überlegen Sie, wie man ein Ranking der Suchergebnisse gestalten könnte. Lassen sich Eingabe, Ausgabe und Suche in einem Script vereinigen (Stichwort: State-Machine)?