Netzwerkclients in Java

Martin Kompf

Mit der Programmiersprache Java und ihrer reichhaltigen Standard-API ist der Zugriff auf Ressourcen im Netzwerk sehr einfach und flexibel möglich - kein Wunder, hat doch Java-Erfinder Sun Microsystems bereits in den Neunzigerjahren das visionäre Motto Das Netzwerk ist der Computer herausgegeben. Dieser Artikel stellt verschiedene Methoden der Netzwerkprogrammierung vor - angefangen von URL bis hin zu Sockets.

Alleskönner URL

Der einfachste Weg, um mit dem Internet Verbindung aufzunehmen, führt über die Klasse java.net.URL. Jede Ressource im Internet, wie zum Beispiel eine Webseite, ist durch einen eindeutigen Uniform Resource Locator (URL) gekennzeichnet. Die Einstiegsseite der Homepage des Authors ist beispielsweise http://www.kompf.de. Aus dieser Angabe lässt sich in Java ein Objekt vom Typ URL konstruieren. Dieses wiederum erlaubt mit die Methode openStream() den Zugriff auf den Inhalt der Ressource über einen InputStream:

import java.io.*;
import java.net.*;

public class NetworkClient {

  public void urlOpen() throws IOException {
    URL url = new URL("http://www.kompf.de");
    InputStream in = url.openStream();
    try {
      copyStream(in, System.out);
    } finally {
      System.out.println();
      in.close();
    }
  }

  // ...
}

Die Methode copyStream kopiert den InputStream - hier also den Inhalt der Ressource - in einen OutputStream. Somit erscheint auf der Standardausgabe der Quelltext der Webseite.

  private void copyStream(InputStream in, OutputStream out) throws IOException {
    BufferedInputStream bin = new BufferedInputStream(in);
    BufferedOutputStream bout = new BufferedOutputStream(out);
    int c = bin.read();
    while (c != -1) {
      out.write((char) c);
      c = bin.read();
    }
    bout.flush();
  }

Die Klasse URL ist ein wahrer Alleskönner. So erkennt sie, dass die URL http://www.kompf.de in Wirklichkeit ein HTTP-Redirect auf die HTML-Seite http://www.kompf.de/index.html ist. Außerdem kann sie nicht nur das Protokoll http verarbeiten, sondern akzeptiert auch verschlüsselte SSL-Kommunikation per https und den Zugriff auf lokale Dateien über das file Protokoll.

Mehr Kontrolle mit HttpURLConnection

Benötigt man mehr Kontrolle über die HTTP Verbindung, dann ist die Verwendung der Klasse java.net.HttpURLConnection angesagt. Hiermit ist es zum Beispiel möglich, neben HTTP GET auch POST Requests abzusetzen und diverse Timeouts einzustellen. Außerdem lassen sich die zum Server gesendeten Daten im HTTP Header beeinflussen.

Das folgende Beispiel greift über eine verschlüsselte HTTPS Verbindung auf einen Webservice von random.org zu. Der Aufruf liefert die Zahlen von 1 bis 49 in einer zufälligen Reihenfolge. Eine Bedingung für die Benutzung dieses Service ist, dass der HTTP Header User-Agent den Mailadresse des Benutzers enthält. Dieser lässt sich mit der Methode addRequestProperty setzen. Außerdem will der Client bei eventuellen Verbindungsproblemen nicht ewig auf eine Antwort warten, deshalb stellt er den Timeout für den Verbindungsaufbau auf 5 s und für das Lesen der Daten auf 30 s ein.

Aus HttpURLConnection lässt sich wiederum per getInputStream ein Stream öffnen, der den Inhalt der durch die URL adressierten Ressource enthält - hier die Antwort des Webservice. Vorher testet der Client allerdings noch mittels getResponseCode, ob der Status der HTTP Response OK ist:

  public void urlConnection() throws IOException {
    URL url = new URL("https://www.random.org/sequences/?min=1&max=49&col=6&format=plain&rnd=new");
    HttpURLConnection con = (HttpURLConnection) url.openConnection();
    con.setConnectTimeout(5000);
    con.setReadTimeout(30000);
    con.addRequestProperty("User-Agent", "your-email-here");
    
    int responseCode = con.getResponseCode();
    if (responseCode != HttpURLConnection.HTTP_OK) {
      throw new IOException("HTTP status: " + con.getResponseMessage());
    }
      
    InputStream in = con.getInputStream();
    try {
      copyStream(in, System.out);
    } finally {
      System.out.println();
      in.close();
      con.disconnect();
    }
  }

Socket - Nicht alles ist HTTP

Fasst man den Begriff Internet etwas weiter, dann trifft man unter Umständen auf Dienste, die nicht per HTTP(S) erreichbar sind. Ein Beispiel dafür sind die Internet Zeitdienste des US-amerikanischen National Institute of Standards and Technology (NIST).

So betreibt das NIST auf dem Server utcnist.colorado.edu einen Daytime Service. Der Internet Standard RFC-867 beschreibt das Daytime Protokoll: Um an die aktuelle Zeit zu gelangen, muss der Client eine Verbindung zum TCP Port 13 aufbauen. Die Serverantwort besteht aus einer Zeichenketten mit dem aktuellen Datum und der Zeit in UTC.

Ein einfacher Weg zu einem Javaprogramm, das den Daytime Service benutzt, führt über ein Objekt der Klasse java.net.Socket. Dieses konstruiert man in der Regel aus der Adresse des Server und dem Port. Socket hat dann wiederum - analog zu HttpURLConnection - Methoden, um Daten per OutputStream in das Socket zu schreiben und per InputStream zu lesen:

  public void socket2() throws IOException  {
    InetAddress hostAddress = InetAddress.getByName("utcnist.colorado.edu"); 
    // see tf.nist.gov/tf-cgi/servers.cgi
    final int port = 13; // standard daytime port (RFC-867)
    Socket socket = new Socket(hostAddress, port);
    try {
      socket.setSoTimeout(5000);
      
      // send request
      OutputStream out = socket.getOutputStream();
      String request = "\r\n";
      out.write(request.getBytes());
      
      // read answer
      InputStream in = socket.getInputStream();
      try {
        copyStream(in, System.out);
      } finally {
        System.out.println();
        in.close();
      }   
    } finally {
      socket.close();
    }
  }

Das Socket stellt bildlich gesprochen eine Verlängerung des Server-Ports über das Netzwerk hinweg in den Client hinein dar. Im vorliegenden einfachen Beispiel sendet der Client CRLF an den Server und gibt die empfangene Antwort ohne weitere Interpretation mittels der Methode copyStream an der Konsole aus.

Fazit

Die Programmierung von Netzwerkclients in Java ist sehr einfach und effektiv möglich. Für simple Anwendungsfälle gelangt man mit dem Einzeiler new URL("...").openStream() an eine Netzwerkverbindung - man vergleiche hierzu nur einmal den in C geschriebenen Klassiker. Weitergehende Aufgabenstellungen lassen sich mit den Klassen HttpURLConnection und Socket bewältigen. Des weiteren gibt es eine Reihe von frei verfügbaren APIs, die den Umgang mit Netzwerkverbindungen vereinfachen sollen. Populär ist der HTTP Client von Apache. Der an und für sich gute Ansatz leidet allerdings unter einem Wirrwarr von in den letzten Jahren entstandenen Versionen, die untereinander nicht kompatibel sind.