Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Buch: Java ist auch eine Insel - Zum Katalog
gp Kapitel 17 Netzwerkprogrammierung
  gp 17.1 Grundlegende Begriffe
    gp 17.1.1 Internet-Standards und RFC
  gp 17.2 URL-Verbindungen
    gp 17.2.1 URL-Objekte erzeugen
    gp 17.2.2 Informationen über eine URL
    gp 17.2.3 Der Zugriff auf die Daten über die Klasse URL
  gp 17.3 Die Klasse URLConnection
    gp 17.3.1 Methoden und Anwendung von URLConnection
    gp 17.3.2 Protokoll- und Content-Handler
    gp 17.3.3 Im Detail: Von URL zu URLConnection
  gp 17.4 Das Common Gateway Interface
    gp 17.4.1 Parameter für ein CGI-Programm
    gp 17.4.2 Codieren der Parameter für CGI-Programme
    gp 17.4.3 Eine Suchmaschine ansprechen
  gp 17.5 Host-Adresse und IP-Adressen
    gp 17.5.1 IPv6 für Java mit Jipsy
  gp 17.6 Socket-Programmierung
    gp 17.6.1 Das Netzwerk ist der Computer
    gp 17.6.2 Standarddienste unter Windows nachinstallieren
    gp 17.6.3 Stream-Sockets
    gp 17.6.4 Informationen über den Socket
    gp 17.6.5 Mit telnet an den Ports horchen
    gp 17.6.6 Ein kleines Ping – lebt der Rechner noch?
  gp 17.7 Client/Server-Kommunikation
    gp 17.7.1 Ein Multiplikations-Server
  gp 17.8 Webprotokolle mit NetComponents nutzen
    gp 17.1.1 Internet-Standards und RFC
    gp 17.2.1 URL-Objekte erzeugen
    gp 17.2.2 Informationen über eine URL
    gp 17.2.3 Der Zugriff auf die Daten über die Klasse URL
    gp 17.3.1 Methoden und Anwendung von URLConnection
    gp 17.3.2 Protokoll- und Content-Handler
    gp 17.3.3 Im Detail: Von URL zu URLConnection
    gp 17.4.1 Parameter für ein CGI-Programm
    gp 17.4.2 Codieren der Parameter für CGI-Programme
    gp 17.4.3 Eine Suchmaschine ansprechen
    gp 17.5.1 IPv6 für Java mit Jipsy
    gp 17.6.1 Das Netzwerk ist der Computer
    gp 17.6.2 Standarddienste unter Windows nachinstallieren
    gp 17.6.3 Stream-Sockets
    gp 17.6.4 Informationen über den Socket
    gp 17.6.5 Mit telnet an den Ports horchen
    gp 17.6.6 Ein kleines Ping – lebt der Rechner noch?
    gp 17.7.1 Ein Multiplikations-Server
    gp 17.9.1 Wie eine E-Mail um die Welt geht
    gp 17.9.2 Übertragungsprotokolle
    gp 17.9.3 Das Simple Mail Transfer Protocol
    gp 17.9.4 Demoprogramm, welches eine E-Mail abschickt
    gp 17.10.1 Das Hypertext Transfer Protocol (HTTP)
    gp 17.10.2 Anfragen an den Server
    gp 17.10.3 Die Antworten vom Server
    gp 17.11.1 Die Klasse DatagramSocket
    gp 17.11.2 Datagramme und die Klasse DatagramPacket
    gp 17.11.3 Auf ein hereinkommendes Paket warten
    gp 17.11.4 Ein Paket zum Senden vorbereiten
    gp 17.11.5 Methoden der Klasse DatagramPacket
    gp 17.11.6 Das Paket senden
    gp 17.11.7 Die Zeitdienste und ein eigener Server und Client
    gp 17.12.1 Ping
  gp 17.9 E-Mail verschicken
    gp 17.9.1 Wie eine E-Mail um die Welt geht
    gp 17.9.2 Übertragungsprotokolle
    gp 17.9.3 Das Simple Mail Transfer Protocol
    gp 17.9.4 Demoprogramm, welches eine E-Mail abschickt
  gp 17.10 Arbeitsweise eines Web-Servers
    gp 17.10.1 Das Hypertext Transfer Protocol (HTTP)
    gp 17.10.2 Anfragen an den Server
    gp 17.10.3 Die Antworten vom Server
  gp 17.11 Datagram-Sockets
    gp 17.11.1 Die Klasse DatagramSocket
    gp 17.11.2 Datagramme und die Klasse DatagramPacket
    gp 17.11.3 Auf ein hereinkommendes Paket warten
    gp 17.11.4 Ein Paket zum Senden vorbereiten
    gp 17.11.5 Methoden der Klasse DatagramPacket
    gp 17.11.6 Das Paket senden
    gp 17.11.7 Die Zeitdienste und ein eigener Server und Client
  gp 17.12 Internet Control Message Protocol (ICMP)
    gp 17.12.1 Ping
  gp 17.13 Multicast-Kommunikation

Kapitel 17 Netzwerkprogrammierung

Sicherheit beruht auf der vermeintlichen Kenntnis und
der tatsächlichen Unkenntnis der Zukunft.
– Helmut Nahr

Verbindungen von Rechnern unter Java aufzubauen, ist ein Kinderspiel – somit ist die Netzwerkprogrammierung, die heutzutage noch aufwändig und kompliziert ist, schnell erledigt. Die API-Funktionen sind in ein eigenes Paket geflossen: java.net. Für Sun Microsystems sind Netzwerke das zentrale Computerthema, und sie haben deshalb den Slogan »The network is the computer« gleich als Warenzeichen eingetragen.


Galileo Computing

17.1 Grundlegende Begriffe  downtop

Genauso wie in anderen Bereichen, gibt es in der Netzwerktechnik eine Reihe von Begriffen, deren Bedeutungen bekannt sein sollten. Wir wollen daher für die wichtigsten eine Definition angeben.

gp  Host: Eine Maschine im Netzwerk, die durch eine eindeutige Adresse (IP-Nummer) angesprochen werden kann.
gp  IP-Nummer: Eine eindeutige Adresse, die jeden Host im Internet kennzeichnet. Die Adresse ist eine 32-Bit Zahl, die in die Teile Host und Netzwerk unterteilt ist.
gp  Hostname: Ein symbolischer Name für die IP-Nummer. Durch Techniken wie DNS (Domain Name Service) und Suns NIS (Network Information Services) werden diese auf die IP-Adressen abgebildet.
gp  Paket (engl. Packet): Eine einzelne, über das Netzwerk verschickte Nachricht.
gp  Router: Ein Host, der Pakete zwischen verschiedenen Netzwerken weiterreicht.
gp  IETF: Die Internet Engineering Task Force. Eine Gruppe, die sich um Standards im Internet kümmert.

Galileo Computing

17.1.1 Internet-Standards und RFC  downtop

Das Kürzel RFC – die Buchstaben stehen für Request For Comment – wird uns im Folgenden noch öfter begegnen. RFCs sind frei verfügbare Artikel, in denen Standardisierungsvorschläge offiziell gemacht werden, die sich dann zum Standard etablieren sollen. Sie sind nicht so förmlich wie Normen (DIN, ISO oder IEEE), aber dennoch sehr weitreichend, und gelten als de facto-Standard. Jedes RFC wird durch eine eigene Nummer referenziert, so ist das Internet Protocol (das IP in TCP/IP) in der RFC 791 und das Protokoll, mit den E-Mails befördert werden, in RFC 821 beschrieben. Der Diskussionsprozess selbst ist in der RFC 1310 beschrieben. Der Titel ist »The Internet Standards Process«. Wer selbst Ideen für einen Standard (Proposed Standard) hat, übergibt diesen der Internet Engineering Task Force (IETF). Die Vorschläge werden dann diskutiert und können dann, falls stabil, sinnvoll und verständlich, zur RFC werden. Falls zwei unterschiedliche Implementierungen existieren, kann dieser Vorschlag dann nach spätestens einem Jahr offiziell werden.

Der Text einer RFC kann über verschiedene Stellen in den Rechner kommen: Über Internetseiten, FTP, E-Mail oder CD-ROM. Die Fachhochschule Köln (http://rfc.fh-koeln.de/rfc.html) hat eine große Sammlung von RFCs im HTML-Format. Daneben besitzt die Universität von Ohio die Dokumente. Ist eine spezielle RFC gewünscht, zum Beispiel RFC 1500, so setzen wir die URL http://www.cis.ohio-state.edu/htbin/rfc/rfc1500.html in unseren Browser ein.


Galileo Computing

17.2 URL-Verbindungen  downtop

Eine Verbindung über eine URL (Uniform Resource Locator) ist am leichtesten zu benutzen und zu programmieren. Eine URL (RFC 1738) ist das Adressenformat für eine Ressource im Web. Sie ist also für das Internet so etwas wie ein Dateiname für das Dateisystem. Die allgemeinste Form einer URL ist Folgende:

Schema:Spezialisierung des Schemas

Eine URL enthält den Namen des benutzten Schemas und anschließend nach einem Doppelpunkt erfolgt eine Spezialisierung, die vom Schema abhängt. Nach dem RFC 1738 werden folgende Schemas unterschieden:

Tabelle 17.1   Schemas nach dem RFC 1738
Schema Bedeutung des Schemas
ftp File Transfer Protocol
http Hypertext Transfer Protocol
gopher Gopher Protocol
mailto Elektronische Mail
news USENET News
nntp USENET Newsmit NNTP Zugang
telnet Interaktive Session
wais Wide Area Information Servers
file Hostspezifischer Dateiname
prospero Prospero Directory Service

Wir wollen die Schemas im Folgenden auch Protokoll nennen.

Das Protokoll bestimmt die Zugriffsart und das am meisten verwendete Protokoll ist mittlerweile HTTP (Hypertext Transfer Protocol) mit dem auf Inhalte des Webs zugegriffen wird. Die URL für die Dienste im Web beginnt mit »http«. Sun unterstützt im JDK folgende Protokolle: »doc«, »file«, »ftp«, »gopher«, »http«, »jar«, »mailto«, »system«, »verbatim«. Unterschiedliche Implementierungen haben mehr oder weniger unterstützte Protokolle.

Während die Syntax für oben nicht genannte Adressierungen schwanken, so lässt sich für die IPbasierten Protokolle (IP für »Internet Protocol«) eine Syntax für den schemenspezifischen Teil ausmachen. Dieser beginnt nach einem Doppel-Slash. Somit ist deutlich, dass diese Angabe dem Internet-Schema folgt:

//user:password@host:port/url-path

Einige oder alle Teile können bei einer URL ausgelassen werden. So sind »user:password@«, »:password«, »:port« und »/url-path« optional. Sind Benutzername und dass Passwort angegeben, so folgt ein At-Zeichen »@«. Natürlich dürfen im Passwort und Benutzernamen Doppelpunkt, At-Zeichen oder Slash nicht vorkommen.

Die einzelnen Komponenten haben folgende Bedeutung:

gp  user
Ein optimaler Benutzername. Dieser kann für den Zugriff über ftp vergeben werden.
gp  password
Ein optionales Passwort. Es folgt getrennt durch einen Doppelpunkt nach dem Benutzernamen. Ein leerer Benutzername und ein leeres Passwort (etwa ftp://@host.com) sind etwas anders als kein Benutzername bzw. Passwort (zum Bsp. ftp://host.com). Ein Passwort ohne Benutzernamen kann nicht angegeben werden. Umgekehrt natürlich schon: so hat ein ftp://oing:@host.com einen Benutzernamen, aber ein leeres Passwort.
gp  host
Auf die Angabe des Protokolles folgt der Name der Domäne oder die IP-Adresse des Servers. Name und IP-Adressen sind in der Regel gleichwertig, da von einem besonderen Dienst der Name in eine IP-Adresse umgesetzt wird – verlangt doch eine achtstellige IP-Adresse ein zu gutes Gedächtnis.
gp  port
Eine Verbindung zu einem Rechner entsteht immer durch eine Art Tür, die Port genannt wird. Diese Port-Nummer lässt den Server die Dienste kategorisieren. Jeder Dienst bekommt eine andere Portnummer, damit sie sich unterscheiden lassen. Normalerweise horcht der HTTP-Server auf Port 80.
gp  url-path
Auf den Servernamen folgt die Angabe der Datei, auf der wir via HTTP oder FTP zugreifen wollen. Da sie in einem Verzeichnis liegen kann, beschreibt die folgende Angabe den Weg zur Datei. Ist keine Datei vorhanden und endet die Angabe der URL mit einem Slash »/«, so versucht der Web-Server, auf eine der Dateien index.html oder index.htm zuzugreifen.

Galileo Computing

17.2.1 URL-Objekte erzeugen  downtop

Um ein URL-Objekt zu erzeugen, ist es am einfachsten, über eine String-Repräsentation der URL-Adresse zu gehen.

Beispiel Um eine Verbindung zum Host der Universität Paderborn zu bekommen, nehmen wir die bekannte URL und erzeugen damit das Objekt:
URL uniURL = new URL( "http://uni-paderborn.de/index.html" 
);

Die URL-Klasse besitzt noch zusätzliche Konstruktoren; diese sind dann nützlich, wenn Komponenten der Adresse, also Zugriffsart (beispielsweise das HTTP), Hostname und Dateireferenz getrennt gegeben sind.

Beispiel Eine Alternative zur oberen Form
URL uniURL = new URL( "http", "uni-paderborn.de", 
"index.html" );

Das zweite Argument ist die Basisadresse der URL. Was dem String im dritten Parameter übergeben wird, ist der Ressource-Namen relativ zur Basisadresse. Ist diese Basisadresse null, was möglich ist, dann ist die zweite Angabe absolut zu nehmen. Und ist der zweite Parameter in absoluter Notation formuliert, wird alles im ersten Parameter ignoriert.

Da eine URL auch einen entfernten Rechner an einem anderen Port ansprechen kann, existiert auch dafür ein Konstruktor:

URL url = new URL( "http", "uni-paderborn.de", 
80, "index.html" );

Die URL des Objekts wurde durch eine absolute Adresse erzeugt. Diese enthält dann alle Informationen, die für den Aufbau zum Host nötig sind. Es können jedoch auch URL-Objekte erzeugt werden, wo nur eine relative Angabe bekannt ist. Relative Angaben werden häufig bei HTML-Seiten verwendet, da somit die Seite besser vor Verschiebungen geschützt ist. Damit die Erzeugung eines URL-Objekts mit relativer Adressierung gelingt, muss eine Basisadresse bekannt sein. Ein Konstruktor für relative Adressen erwartet diese Basisadresse als Parameter.

Beispiel Für die Uni-Seite nutzen wir ein URL-Objekt, welches auf die Datei index.html zeigt:
URL uniURL   = new URL( "http://uni-paderborn.de" 
);
URL uniIndex = new URL( uniURL, "index.html");

Diese Art und Weise der URL-Objekt-Erzeugung ist besonders praktisch für Referenzen innerhalb von Web-Seiten (Named Anchors).

Beispiel Besteht für eine Seite ein Unterteilung in TOP und BOTTOM, so kann der URL-Konstruktor für relative URLs verwendet werden.
URL virtuellURL       = new URL( 
"http://bum.bum.org" );
URL virutellBottomURL = new URL( virtuellURL, "#BOTTOM" );

Jeder der Konstruktoren wirft eine MalformedURLException, wenn der Parameter im Konstruktor entweder null ist oder er ein unbekanntes Protokoll (wie in telepatic:\\ulli\brain\java) beschreibt. Somit ist der Code in der Regel von einem Block der folgenden Art umgeben:

try {
URL myURL = new URL( . . . )
}
catch ( MalformedURLException e ) {
// Fehlerbehandlung
}
class java.net.URL
implements Serializable, Comparable

gp  URL( String protocol, String host, int port, String file )
throws MalformedURLException
Erzeugt ein URL-Objekt mit dem gegebenen Protokoll, Hostnamen, Portnummer und Datei. Ist die Portnummer -1, so wird der Standard-Port verwendet, zum Beispiel für das WWW der Port 80.
gp  URL( String protocol, String host, String file )
throws MalformedURLException
Das Gleiche wie URL(protocol, host, -1, file).
gp  URL( String ) throws MalformedURLException
Erzeugt ein Objekt aus der URL-Zeichenkette.
gp  URL( URL, String ) throws MalformedURLException
Erzeugt relativ zur URL ein neues URL-Objekt.
Abbildung


Galileo Computing

17.2.2 Informationen über eine URL  downtop

Ist das URL-Objekt einmal angelegt, so lassen sich die Attribute des Objekts nicht mehr ändern. Es gibt zwar Setter-Methoden, aber diese sind protected und den Unterklassen vorbehalten. Uns normalen Klassenbenutzern bietet die URL-Klasse nur Methoden zum Zugriff. So lassen sich Protokoll, Hostname, Port Nummer und Dateinamen mit Zugriffsmethoden erfragen. Es lassen sich jedoch nicht alle URL-Adressen so detailliert aufschlüsseln und außerdem sind manche der Zugriffsmethoden nur für HTTP sinnvoll.

gp  String getProtocol()
Liefert das Protokoll der URL.
gp  String getHost()
Liefert den Hostnamen der URL, falls dies möglich ist. Für das Protokoll »file« ist dies ein leerer String.
gp  int getPort()
Lierfert die Portnummer. Ist sie nicht gesetzt, liefert getPort() eine -1.
gp  String getFile()
Gibt den Dateinamen der URL zurück.
gp  String getRef()
Gibt die relative Adresse der URL zurück.
final class java.net.URL
implements Serializable, Comparable

Das kleine nachfolgende Programm erzeugt ein URL-Objekt zu http://java.sun.com. Alle Möglichkeiten zur Angabe von URL-Informationen werden ausgenutzt. Anschließend erfolgt ein Auslesen aller Attribute.

Listing 17.1   ParseURL.java
import java.net.*;
import java.io.*;

class ParseURL
{
public static void main( String args[] )
{
try {
URL aURL = new URL(
"http://java.sun.com:80/tutorial/intro.html#DOWNLOADING");

System.out.println( "protocol = " + aURL.getProtocol() );
System.out.println( "host = " + aURL.getHost() );
System.out.println( "filename = " + aURL.getFile() );
System.out.println( "port = " + aURL.getPort() );
System.out.println( "ref = " + aURL.getRef() );
} catch ( MalformedURLException e ) {
System.out.println( "MalformedURLException: " + e );
}
}
}

Und dies ist die Ausgabe:

protocol = http
host = java.sun.com
filename = /tutorial/intro.html
port = 80
ref = DOWNLOADING

Verweisen die URLs auf die gleiche Seite?

Die Methode equals() aus der Klasse Object ist uns bekannt. Sie soll von jeder Klasse so implementiert werden, dass gleiche Objekte true zurückliefern. Jede Klasse soll aber selbst ihre Inhalte vergleichen und nicht nur ihre Objektreferenzen. So muss also die URL-Klasse untersuchen, ob alle Komponenten der einen URL mit der anderen URL übereinstimmen. equals() untersucht dafür zuerst, ob es sich bei der vergleichenden Klasse um ein Exemplar von URL handelt. Wenn ja, wird untersucht, ob die Komponenten Referenzen besitzen oder nicht. Dies wird erreicht, indem Protokoll, Host, Port und Datei untersucht werden. Hierfür bietet sich auch die öffentliche Methode sameFile()an. Ein Anker ist für den Vergleich nicht bestimmend.

public boolean sameFile(URL other) 
{
// AVH: should we not user getPort to compare ports?
return protocol.equals(other.protocol) &&
hostsEqual(host, other.host) &&
(port == other.port) &&
file.equals(other.file);
}

Die Implementierung verrät uns, dass sich die Entwickler nicht einig sind, ob die Ports nicht besser mit getPort() zu vergleichen sind.

final class java.net.URL
implements Serializable, Comparable

gp  boolean sameFile( URL )
Vergleicht zwei URL-Objekte. Die Methode liefert true, falls beide Objekte auf die gleiche Ressource zeigen. Der Anker der HTML-Dateien ist unwichtig.
Beispiel equals() für URL-Objekte Listing 17.2   URLContentsTheSame.java
import java.net.*;

class URLContentsTheSame
{
public static void main( String args[] )
{
try
{
URL sunsite = new URL(
"http://sunsite.unc.edu/javafaq/oldnews.html");
URL helios = new URL(
"http://helios.oit.unc.edu/javafaq/oldnews.html");

if ( sunsite.equals(helios) )
System.out.println( sunsite + " = " + helios );
else
System.out.println( sunsite + " != " + helios );
}
catch ( MalformedURLException e ) {
System.err.println(e);
}
}
}


Galileo Computing

17.2.3 Der Zugriff auf die Daten über die Klasse URL  downtop

Um auf die auf dem Web-Server gespeicherten Daten zuzugreifen, gibt es drei Möglichkeiten. Zwei davon nutzen Streams, und zwar einmal über die Klasse URL und einmal über eine URLConnection. Bei der dritten Möglichkeit ist Handarbeit angesagt und fällt deshalb in das Kapitel über Sockets.

Jedes URL-Objekt besitzt die Methode openStream(), die einen InputStream zum Weiterverarbeiten liefert, sodass wir dort die Daten auslesen können:

InputStream in = uniURL.openStream();
final class java.net.URL
implements Serializable, Comparable

gp  final InputStream openStream() throws IOException
Öffnet eine Verbindung zur Server und liefert einen InputStream zurück. Diese Methode ist eine Abkürzung für openConnection().getInputStream().
gp  URLConnection openConnection() throws IOException
Liefert ein URLConnection Objekt, welches die Verbindung zum entfernten Objekt vertritt. openConnection() wird vom Protokoll-Handler immer dann aufgerufen, wenn eine neue Verbindung geöffnet wird.

Verweist die URL auf eine Textdatei, dann erweitern wir oft den InputStream zu einem BufferedReader, da dieser eine readLine()-Methode besitzt. Folgender Programmcode liest solange Zeilen, bis das Ende der Eingabe signalisiert wird. Glücklicherweise ist uns die Vorgehensweise schon bekannt, da sich ja das Lesen von einer Datei nicht vom Lesen eines entfernten URL-Objekts unterscheidet:

BufferedReader in = new BufferedReader(
new InputStreamReader( url.openStream() ) );

String line = "";

while ( (line = in.readLine()) != null )
System.out.println( line );

Sind die Daten gelesen, schließt close() den Datenstrom – close() bezieht sich allerdings nicht auf das URL-Objekt, sondern auf den Datenstrom. Es sei anschließend noch einmal ein vollständiges, lauffähiges Programm aufgeführt.

Listing 17.3   OpenURLStream.java
import java.net.*;
import java.io.*;

class OpenURLStream
{
public static void main( String args[] )
{
try
{
URL spiegelURL = new URL( "http://www.spiegel.de" );

BufferedReader in = new BufferedReader(
new InputStreamReader( spiegelURL.openStream() ) );

String s;

while ( ( s = in.readLine() ) != null )
System.out.println( s );

in.close();

} catch ( MalformedURLException e ) {
System.out.println( "MalformedURLException: " + e );

} catch ( IOException e ) {
System.out.println( "IOException: " + e );
}
}
}

Wir erzeugen ein URL-Objekt und rufen darauf die openStream()-Methode auf. Diese liefert einen InputStream auf den Dateiinhalt. In der API-Beschreibung wurde aber schon kurz erwähnt, dass diese Funktion eigentlich nur eine Abkürzung für openConnection().getInputStream() ist. openConnection() erzeugt ein URLConnection-Objekt und sendet diesem die Nachricht getInputStream().

Wir wollen uns im nächsten Abschnitt mit dem URLConnection-Objekt beschäftigen, denn damit wird die Verbindung über das Netzwerk zum Inhalt aufgebaut. Die URL-Klasse besitzt nur deshalb die Abkürzung über openStream(), da zum einen nicht jeder wissen muss, dass URLConnection dahinter steckt, und zweitens, weil es Tipperei erspart.

Das Beispiel zeigt auch, dass bei openConnection() ein try/catch-Block notwendig ist. Denn geht etwas daneben, zum Beispiel, wenn der Dienst nicht verfügbar ist, so wird eine IOException ausgelöst:

try {
URL ohoURL = new URL( "http://www.oho.com/" );
ohoURL.openConnection();
} catch ( MalformedURLException e ) { // new URL() ging daneben
...
} catch ( IOException e ) { // openConnection() schlug fehl
...
}

Galileo Computing

17.3 Die Klasse URLConnection  downtop

Die Objekte der Klasse URLConnection sind für den Empfang der Inhalte der URL-Objekte verantwortlich. Die Klasse ist abstrakt und die Unterklassen implementieren die Protokolle, mit denen die Verbindung zum Inhalt aufgebaut wird. Die Unterklassen bedienen sich dabei Objekten der Klasse URLStreamHandler, mit denen der eigentliche Inhalt ausgelesen wird. (Siehe hierzu Abbildung 17.2 auf der folgenden Seite.)

Abbildung


Galileo Computing

17.3.1 Methoden und Anwendung von URLConnection  downtop

Die Klasse URLConnection ist ein wenig HTTP-lastig, denn viele Methoden haben nur für URLs auf Web-Seiten eine Bedeutung. So stellt die Klasse Methoden bereit, um die Verbindung aufzubauen, den HTTP-Header zu lesen und den Inhalt eines Dokuments zu holen. Da eine Datei, die vom Web-Server kommt, den Inhalt (engl. Content) immer ankündigt (wenn wir später mit direkten Socket-Verbindungen arbeiten, lässt sich dies am zurückgeschickten Inhalt erkennen), so erkennt die Klasse URLConnection mit einem Content-Handler den Inhalt und bietet uns Benutzern Methoden an, um mit dem Header und den Inhalten zurechtzukommen.

Handelt es sich bei der Verbindung um das Protokoll HTTP, so schreibt die HTTP 1.1 Spezifikation im RFC 2616 einige Header vor, die durch spezielle Methoden der Klasse URLConnection abgefragt werden können.

Beispiel Um zu erfahren, wann die Datei auf dem Server gelandet ist, kann getDate() bzw. getLastModified() verwendet werden. Werfen wir dazu einen Blick auf die Methode printHeader().
void printHeader()
{
try
{
URLConnection c = connect.openConnection();
System.out.println( connect);
long d = c.getDate();
System.out.println( "Date : " + d);
Date dt = new Date( d );
System.out.println( " : " + dt);
d = c.getLastModified();
System.out.println( "Last Modified : " + d);
dt = new Date(d);
System.out.println( " : " + dt);
System.out.println( "Content encoding: " +
c.getContentEncoding());
System.out.println( "Content length : " +
c.getContentLength());
}
catch (Exception e) { System.out.println (e + ":" + connect); }
}

Die Methoden und Attribute von URLConnection

Die meisten der Attribute werden durch eine der Funktionen getHeaderField(), getHeaderFieldInt() oder getHeaderFieldDate() verarbeitet. getHeaderFieldInt() ist eine Hilfsfunktion und bedient sich getHeaderField() wie folgt: Integer.parseInt(getHeaderField(name)). Ebenso wandelt getHeaderFieldDate() mittels getHeaderField() den String in ein long um: return Date.parse(getHeaderField(name)). Schauen wir uns zwei der Methoden an:

public String getContentType() {
return getHeaderField("content-type");
}

public long getLastModified() {
return getHeaderFieldDate("last-modified", 0);
}

Wie nun getHeaderField() wirklich implementiert ist, können wir nicht sehen, da es sich dabei um Funktionen handelt, die von den Unterklassen überschrieben werden. Prinzipiell ist die URLConnection-Klasse zwar für alle Protokolle gleichwertig, doch an anderer Stelle wurde erwähnt, dass sie mehr zugunsten von HTTP entscheidet. Deshalb muss ein Rückgabewert von getLastModified() von einer FTP-Verbindung mit Vorsicht genossen werden.


Galileo Computing

17.3.2 Protokoll- und Content-Handler  downtop

Der Inhalt eines URL-Objekts lässt sich mit getContent() vom Server holen, falls ein passender Content-Handler eingetragen ist. Für Bilder ist etwa so ein Handler eingetragen, der als Rückgabewert ein URLImageSource liefert. Mit wenigen Zeilen können wir dann ein Bild in Form eines Image-Objekts erzeugen, das auf dem Server weilt:

public static Image fetchimage( 
String url )
throws MalformedURLException, IOException
{
URL u = new URL( url );
Toolkit tk = Toolkit.getDefaultToolkit();
return tk.createImage((ImageProducer)u.getContent());
}

Wenn wir konkret ein Bild über eine URL laden wollen, dann bietet sich sicherlich die Methode getImage(URL) an.

Mit getContent() an Daten zu gelangen, funktioniert für alle Objekte – natürlich muss ein passendes Protokoll installiert sein. Bei Content-Handlern gilt das Gleiche wie für Protokoll-Handler: Unterschiedliche Umgebungen implementieren unterschiedliche Handler. Für HTML-Dateien liefert getContent() ein Objekt vom Typ sun.net.www.MeteredStream zurück und für normale Textdateien ein sun.net.www.content.text.PlainTextInputStream Objekt; also nur Datenströme. Für Texte und HTML-Seiten können wir dann mit Hilfe des InputStreams (MeteredStream und PlainTextInputStream sind Unterklassen) die Datei zeilenweise auslesen. Leider gibt es keine Methode in der Bibliothek, die sofort die Daten in einem String bereitstellt.

Mit einer kleinen Zeile können wir erfragen, was für ein Handler-Objekt eine URL-Klasse für den Datenstrom einsetzt:

Object o = u.getContent();
System.out.println( "Schnapp: Ich habe einen " + o.getClass().getName() );

getContent() erkennt nun an Hand der Endung beziehungsweise der ersten Bytes den Typ der Datei. Dann konvertiert ein Content-Handler die Bytes seines Datenstroms in ein Java-Objekt. Der Protokoll-Handler überwacht die Verbindung zum Server und stellt dann die Verbindung zu einem konkreten Content-Handler her, der die Konvertierung in ein Objekt übernimmt.

Stellen wir zusammenfassend noch einmal den Content- und Protokoll-Handler gegenüber:

gp  Content-Handler: Durch einen Content-Handler wird die Funktionalität der URL-Klasse erweitert. Es können Quellen verschiedener MIME-Typen durch die Methode getContent() als Objekte zurückgegeben werden. Leider beschreibt die Java Spezifikation nicht, welche Content-Handler bereitgestellt werden müssen. Für GIFs und JPGs gibt es Handler, die gleich ImageProducer anlegen.
gp  Protokoll-Handler: Auch ein Protokoll-Handler erweitern die Möglichkeiten der URL-Klassen. Das Protokoll ist der erste Teil einer URL und gibt bei Übertragungen wie »http« die Kommunikationsmethode an. Auch hier gibt es keine verbindliche Verpflichtung, diese bei einer JVM auszuliefern. So unterstützt das JDK Protokolle wie »file«, »ftp«, »jar«, »mailto«, doch schon Netscape benutzt andere Implementierungen der Klasse URLConnection. Noch anders sieht es beim Microsoft Explorer aus. Also hilft nur das Selberprogrammieren .
final class java.net.URLConnection
implements Serializable, Comparable

gp  Object getContent() throws IOException, UnknownServiceException
Liefert den Inhalt, auf den die URL verweist. UnknownServiceException ist eine Unterklasse von IOException, es reicht also ein catch auf IOException aus.
final class java.net.URL
implements Serializable, Comparable

gp  final Object getContent() throws IOException
Liefert den Inhalt, auf den die URL verweist. Die Methode ist eine Abkürzung für openConnection().getContent().Wegen der Umleitung auf das URLConnection-Objekt kann auch hier eine UnknownServiceException auftauchen.

Galileo Computing

17.3.3 Im Detail: Von URL zu URLConnection  downtop

Die Klasse URLConnection ist abstrakt. Wird openStream() von einem URL-Objekt aufgerufen, so weiß diese Methode, wie die Verbindung zum Dienst aufzubauen ist. Denn für Web-Seiten mit dem HTTP-Protokoll sieht dies anders aus als eine Dateiübertragung mit dem FTP-Protokoll. openConnection() von URL macht nichts weiteres als vom jeweiligen Handler wiederum openConnection() aufzurufen. Die Handler wissen für ihr Protokoll, wie die Verbindung aufzubauen ist. Der Handler von URLConnection ist vom Typ URLStreamHandler, eine abstrakte Superklasse, die von allen Stream-Protokoll-Handlern implementiert wird. Leider können wir diese Implementierung nicht im Quelltext sehen. Im Konstruktor des URL-Objekts werden die Protokoll-Handler initialisiert. Denn an dieser Stelle ist klar, um was für einen Dienst es sich handelt. Ein URL-Parser zerpflückt dann die URL und ruft in dem Protokoll die getURLStreamHandler()-Methode auf. Sie würde null zurückliefern, falls sie mit dem Protokoll nichts anzufangen weiß – dies bekämen wir zu spüren, denn eine null heißt: MalformedURLException().

getURLStreamHandler()static synchronized gekennzeichnet – ist die eigentliche Arbeitsstelle. Hier wird zum Präfix sun.net.www.protocol. der Name des Handler gehängt (zum Beispiel ftp, http) und anschließend ein .Handler drangesetzt. Nun wird über Class.forName(clsName) nachgeschaut, ob die Klasse schon im System geladen wurde. Wenn nicht, dann versucht der Klassenlader über loadClass(clsName) an die Klasse zu kommen. Falls die Klasse geladen werden konnte, wird sie mit newInstance() initialisiert und als URLStreamHandler dem aufrufenden Konstruktor übergeben.

Soweit der Weg vom Konstruieren über ein URL-Objekt zum Laden des Handlers. Anschließend liegt der Handler URLStreamHandler vor und wird in der privaten Variablen handler abgelegt. Kommen wir nun noch einmal zur Methode openConnection(). Wir haben gesagt, dass diese Methode wissen muss, welches URLConnection-Objekt es zurückgibt, da das Protokoll bekannt ist. Und da das Protokoll vom Typ URLStreamHandler in der Variablen handler liegt, ist es ein einfaches, sich die openConnection()-Methode vorzustellen:

public URLConnection openConnection() 
throws java.io.IOException
{
return handler.openConnection(this);
}

Der Handler übernimmt selbst das Öffnen. Nun gibt es eine URLConnection und wir können damit auf die Referenz lesend (wir holen uns also Informationen beispielsweise von der Web-Seite) und schreibend (zum Beispiel für eine CGI-Abfrage) reagieren. Es muss betont werden, dass bei der Erzeugung eines URLConnection-Objekts noch keine Verbindung aufgebaut wird. Dies folgt mit den Methoden getOutputStream() oder getInputStream().

final class java.net.URLConnection
implements Serializable, Comparable

gp  URLConnection openConnection() throws IOException
Liefert ein URLConnection-Objekt, das die Verbindung zum entfernten Objekt vertritt. openConnection() wird vom Protokoll-Handler immer dann aufgerufen, wenn eine neue Verbindung geöffnet wird.

Galileo Computing

17.4 Das Common Gateway Interface  downtop

CGI (Common Gateway Interface) ist eine Beschreibung einer Schnittstelle http://cgi-spec.golux.com., mit der externe Programme mit Informations-Servern, meistens Web-Servern, Daten austauschen. Die aktuelle Version ist CGI/1.1. Diese ausgeführten Programme werden kurz »CGI-Programme« genannt und können in allen erdenklichen Programmiersprachen verfasst sein. Häufig sind es Shell- oder Perl-Skripte (oft wird dann die Bezeichnung CGI-Skripte verwendet). Die Unterscheidung zwischen Skript und Programm ist bei CGI schwammig. Traditionell ist eine compilierte Quelldatei ein Programm und Programme, die mit einem Interpreter arbeiten, ein Skript. Wir werden im Folgenden allerdings »Programm« und »Skript« austauschbar verwenden. Für uns ist es erst einmal egal, ob ein Programm oder Skript ausgeführt wird. Denn wir wollen diese Programme aus Java nutzen und nicht selber schreiben. Auf der Server-Seite ergänzen Servlets immer mehr CGI-Programme.

Die CGI-Programme werden von einem Browser durch eine ganz normale URL angesprochen. Der Browser baut eine Verbindung zum Server auf und dieser erkennt anhand des Pfads in der URL, ob es sich um eine ganz normale Web-Seite handelt oder um ein Skript. Wenn es ein Skript ist, dann führt der Server das Skript aus, welches eine HTML-Datei erzeugt. Diese wird übertragen und im Browser dargestellt. Der Aufrufer einer URL merkt keinen Unterschied zwischen erstellten, also dynamischen, und statischen Seiten. Die CGI-Programme sind also immer eine Angelegenheit des Servers, der uns mit aktuellen Daten versorgt.


Galileo Computing

17.4.1 Parameter für ein CGI-Programm  downtop

Beim Aufruf eines CGI-Programms können Parameter übergeben werden, bei einer Suchmaschine etwa der Suchbegriff. Es gibt nun zwei Möglichkeiten, wie diese Parameter zum Skript kommen und somit vom Web-Server verarbeitet werden.

gp  Die Parameter (genannt auch Query-String) werden an die URL angehängt (GET-Methode). Das Skript liest die Daten aus der Umgebungsvariablen QUERY_STRING aus.
gp  Die Daten werden zur Standardeingabe des Web-Servers gesendet (POST-Methode). Das Skript muss dann aus dieser Eingabe lesen.

GET und POST unterscheiden sich auch in der Länge der übertragenen Daten. Bei vielen Systemen ist die Länge einer GET-Anfrage beschränkt auf 1024 Byte. Der Content-Type (application/x-www-form-urlencoded) ist für GET- und POST-Anfragen identisch.

Daten werden nach der GET-Methode verschickt

Die Daten sind mit dem CGI-Programmnamen verbunden und gehen beide zusammen auf die Reise. Der Anfragestring (Query-String) wird hinter ein Fragezeichen gesetzt, das et-Zeichen »&« trennt mehrere Anfragezeichenketten. Unter Java setzen wir einfach einen Befehl ab, indem wir ein neues URL-Objekt erzeugen und anschließend den Inhalt auslesen:

meineURL = new URL( "http", "...", 
"cgi-bin/trallala?tri" );

Das CGI-Skript holt sich seinerseits die Daten aus der Umgebungsvariable QUERY_STRING. Das folgende Kapitel zeigt, wie diese Abfrage-Zeichenketten komfortabel durch die Klasse URLEncoder zusammengebaut werden. Werfen wir jedoch erst einen Blick auf die Variablen.

Daten holen nach der POST-Methode

Die Klasse URLConnection bietet die schon bekannte Methode getOutputStream() an, die eine Verbindung zur Eingabe des CGI-Scripts möglich macht (POST-Methode):

// CGI-Script schickt die Daten 
zurück

urlout PrintStream = new PrintStream(
cgiConnection.getOutputStream());
urlout.println( data );
urlout.close();

Galileo Computing

17.4.2 Codieren der Parameter für CGI-Programme  downtop

Wenn aus einer HTML-Datei mit Formularen über Submit Daten an das CGI-Programm übermittelt werden, dann werden diese Daten kodiert. Dies liegt daran, dass viele Zeichen in URL nicht erlaubt sind. Betrachten wir daher folgenden Ausschnitt aus einer Web-Seite:

<FORM  METHOD="GET" ACTION="/cgi-bin/caller.cgi">
<P>Name:
<INPUT TYPE = "text" NAME = "name" VALUE = "">
<P>E-Mail:
<INPUT TYPE="text" NAME="email" VALUE = "">
<P>
<INPUT TYPE="submit" name="submit" >
</FORM>

Die Seite besitzt zwei Felder mit den Namen name und email. Dazu kommt noch ein Submit-Button, der, falls aktiviert, die Daten an das CGI-Programm caller.cgi weitergibt. Wenn wir die Felder mit irgendeinem Inhalt füllen und Submit drücken, sehen wir URL häufig in der Adressleiste des Browsers. Dort erscheint, ohne Zeilenumbruch, zum Beispiel:

http://oho.de/cgi-bin/caller.cgi?
name=Ulli+Ullenboom&email=ulliull@ulli.com&submit=Submit

Da in einer URL keine Leerzeichen erlaubt sind, werden sie durch Plus-Zeichen kodiert. Es gibt noch weitere Zeichen, die kodiert werden, so das Plus- oder das Gleichheitszeichen o