Von C nach Java, Teil 3: HTML-Dokumente aus dem Internet laden

Seite 4: Webzugriff unter Java

Inhaltsverzeichnis

In der Sprache C – ohne weitere plattformspezifischen Hilfsmittel, die es zweifelsohne gibt – ist es wahrhaft keine leichte Aufgabe, ein HTML-Dokument aus dem Internet zu laden. Dass es einfacher geht, zeigt jetzt das Beispiel in Java. Die Entwickler der Sprache haben schon im Vorfeld ein besonderes Augenmerk auf eine einfache Implementierung der Netzwerkklassen gelegt, was ein gegenüber dem C-Pendant schlankeres Java-Programm verspricht.

Die Aufgabenstellung ist die gleiche – gesucht ist ein Kommandozeilen-Programm, das die zu ladenden URLs als Argument übergeben bekommt, und mit den entsprechenden Kommentaren vom jeweiligen Webserver einliest. Zunächst sollen hier nur reine Socket-Klassen zum Einsatz kommen, wobei der Artikel später zeigt, wie die Abfrage sich unter Nutzung spezieller Internetklassen weiter vereinfachen lässt.

Als erstes wird die Anwendungsklasse CGetURL erstellt. Sie ist relativ simpel gestaltet und orientiert sich an der Vorgabe in C. Das Programm ruft man als Nutzer über die Kommandozeile mit einem oder mehreren URLs als Argument auf. Wichtig ist, dass der URL bei Verwendung des Root-Dokuments (wenn nur der Hostname gegeben ist) mit einem Slash abschließt.

Ist kein Argument vorhanden, beendet sich das Programm mit einem entsprechenden Nutzungshinweis. Ansonsten erzeugt es für jedes Argument eine Klasse CGetURL.

public class CGetURL {
final static String invalidURL="invalid URL String!",
invalidReturnValue="invalid/unexpected return Value!";
int port=80;
String hostname, document;

public CGetURL(String u) throws Exception {
int idx;
/**
* 1st split the URL into protocol, host, document path
*/
String[] sList=u.split("://");
if (sList.length != 2) throw new Exception(invalidURL);
if ((idx=sList[1].indexOf('/')) < 0) throw new Exception(invalidURL);
hostname=sList[1].substring(0,idx);
document=sList[1].substring(idx);
}

public String receiveHtmlDocument() throws Exception {
HttpSocket s=new HttpSocket(hostname, port);
return s.readDocument(document);
}

public static void main(String[] args) {
if (args.length < 1) {
System.err.println("usage: java "
+CGetURL.class.getSimpleName()+" URL ...");
} else for (String s: args) {
try {
CGetURL cg=new CGetURL(s);
String str=cg.receiveHtmlDocument();
System.out.printf("%d byte(s) received from %s!\n",
str.length(), cg.hostname);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

Im Konstruktor, der den URL als Argument erwartet, zerlegt das Programm den URL-String in die Bestandteile Protokoll, Host und Dokumentenpfad, die dann in den Variablen hostname und document auf ihre weitere Verarbeitung warten. Die Methode receiveHtmlDocument() liest schließlich das komplette Dokument ein und gibt es als String zurück.

Zu diesem Zweck wird eine Klasse HttpSocket definiert, und mit den Argumenten Hostname und Port erzeugt. Sie stellt die Methode readDocument bereit, die das aus dem URL kopierte Dokument einliest und in einem String vorhält:

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

public class HttpSocket extends Socket {
final static int MAX_HTML_DOC_LENGTH=5*1024*1024;
String hostname;
InputStream is;
OutputStream os;
private String[] documentLineList;
private int documentLinePos=0;

public HttpSocket(String hostname, int port) throws UnknownHostException,
IOException {
super(hostname, port);
this.hostname=hostname;
is=getInputStream();
os=getOutputStream();
}

public String readDocument(String document) throws Exception {
int cl=0;
boolean isRedirect=false;
String str, request=String.format("GET %s HTTP/1.1\r\nHost: "
+ "%s\r\nUser-Agent: %s\r\nAccept: text/html\r\nConnection: "
+ "close\r\n\r\n",
document, hostname, CGetURL.class.getSimpleName());
os.write(request.getBytes());
BufferedReader br=new BufferedReader(new InputStreamReader(is));
while ((str=br.readLine())!=null) {
if (str.isEmpty()) {
break;
}
if (str.startsWith("HTTP")) {
String[] sList=str.split(" ");
if (sList.length < 2) throw new Exception(
"Invalid Return Value");
int code=Integer.parseInt(sList[1]);
switch (code) {
case 200: // ok
break;
case 301:
case 302:
isRedirect=true;
break;
default: if (code >= 400) {
throw new Exception("HTTP Error "+code);
}
break;
}
continue;
}
if (str.startsWith("Content-Length:")) {
cl=Integer.parseInt(str.substring(15).trim());
continue;
}
if (isRedirect && str.startsWith("Location:")) {
return str;
}
}
if (cl > 0) {
byte[] b=new byte[cl];

int l, pos;
for (pos=0;pos<cl;pos+=l) {
if ((l=is.read(b,pos,cl-pos))<=0) {
throw new Exception("Unexpected EOF reading from socket!");
}
}
return new String(b);
}
int l, pos;
cl=MAX_HTML_DOC_LENGTH;
byte[] b=new byte[cl];
for (pos=0;;pos+=l) {
if ((l=is.read(b,pos,cl-pos))<=0) {
throw new Exception("Unexpected EOF reading from socket!");
}
pos+=l;
String s=new String(b,0,pos);
if (s.contains("</html>")) break;
if (pos>=MAX_HTML_DOC_LENGTH)
throw new Exception("Document length exceeds "
+ "MAX_HTML_DOC_LENGTH["+MAX_HTML_DOC_LENGTH+"]");
}
return new String(b,0,pos);
}
}

Im Wesentlichen ist hier das C-Beispiel vom Beginn dieses Artikels nachprogrammiert. Die HttpSocket-Klasse erweitert die Socket-Klasse und holt sich innerhalb des Konstruktors den Deskriptor für den Ein- und Ausgabestream, und speichert ihn für die spätere Verwendung.