Document

Werbung
5
Implementierung eines HTTP-Servers
Ziel dieses Kapitels ist es, eine Einfü hru n g in das Hypertext
Transfer Protocol (HTIP) zu geben und einen einfachen Webserver zu entwickeln.
5.1
Das Protokoll HTTP
HTTP ist ein Protokoll der Anwendungsschicht im TCP/IPSchichtenmodell und regelt insbesondere, wie ein Webbrowser
mit einem Webserver im World Wide Web (WWW) kommuniziert HTIP verwendet auf der Transportschicht TCP,
Damit ein Webbrowser eine Webseite im \XT\Xf\Xl abrufen kann,
muss er sie zunächst adressieren.
Ein Uniform Resource Locator (URL) ist eine standardisierte URL
Adresse, mit der eine beliebige Ressource (z. B. eine HTML-Seite,
ein GIF-Bild, eine PDF-Datei, ein Programm) lokalisiert werden
kann.
So lokalisiert
z:
B,
ht tp://www hs nied errhein.de/index. html
die Website der Hochschule Niederrhein.
Der URL hat im Allgemeinen den folgenden Aufbau:
protoco1 :I /hos t[ :port] [! path] [/ f i 1e] [#sect i on]
Hierbei sind die eingeklammerten Teile optional.
Die Angaben bedeuten:
pr ot oco1
Protokollname, hier: http
hast
Domain-Name oder IP-Adresse des Rechners
port
Portnummer, unter der der Server läuft; die standardmäßige Portnummer für einen HTTP-Server ist 80 und
muss nicht angegeben werden
path
Name eines Verzeichnisses auf dem Server; die Angabe ist relativ zur Wurzel des Webverzeichnisses
fi l e
Name einer Datei in dem spezifizierten
Verzeichnis
sectlon
verweist auf eine bestimmte Stelle innerhalb
einer HTML-Seite
path und fl le müssen keine reale Datei bezeichnen. Sie können
auf der Serverseite als logischer Name zur eindeutigen Bezeichnung einer Ressource interpretiert werden.
5
166
Ablaufeiner
llTJP-Transaktion
Implementierung eines HTTP-Servers
Die Interaktion zwischen HTIP-Client und HTIP-Server für eine
Anfrage umfasst die folgenden Schritte,
1. Der Server wartet auf eine eingehende HTIP-Anfrage.
2. Der eIient erzeugt einen URL http:/ t .
3. Der eIient versucht. eine TCP-Verbindung zum Server aufzubauen.
4. Der Server akzeptiert den Verbindungswunsch.
5. Der eIient sendet eine Nachricht (HTIP-Anfrage) an den
Server und fordert die Ressource mit dem spezifizierten URL
an.
6. Der Server verarbeitet die Anfrage (z. B. Ausführung einer
Datenbankabfrage und Generierung einer HTML-Seite mit
dem Abfrageergebnis).
7. Der Server sendet eine Rückantwort (HTTP-Antwort) an den
Client, die die angeforderte Ressource oder eine Fehlermeldung enthält.
8. Der Client verarbeitet die Antwort.
9. Der eIient und/oder der Server schließen die TCP-Verbindung.
HTIP ist
zustandslos
Der Server hat keine Kenntnis über vorangegangene Anfragen
desselben eIient. Jede Anfrage wird unabhängig von vorhergehenden Anfragen bearbeitet. HTTP ist also ein zustandsloses
Protokoll.
Für jede HTIP-Anfrage wird bei HTIP 1.0 in der Regel eine
neue TCP-Verbindung aufgebaut. Enthält z. B. eine angeforderte
HTML-Seite mehrere Grafiken. so muss jede dieser Grafiken
separat angefordert werden (jeweils mit Verbindungsaufbau und
-abbau).
eaa s«,
GET Iprodukte/index.html HTTP/1.1
Eine HTTP-
.00
•
Transaktion
HTTP
HTIP/1 .1 200 OK
Conte nt-Type: text/htm I
Webbrowser
< htm I> ... </htm I>
Webserver
5.1 Das Protokoll HTIP
HITP 1.0 ist im RPC 1945 der IETP spezifiziert (siehe http:
//www.i etf.org/rfc/rfcI945.txt).
167
HTIP 1.0
Die Version HTTP 1.1 unterstützt so genannte persistente Verbin - HTIP 1.1
dungen. Während die TCP-Verbindung steht. können mehrere
HTTP-Anfragen über diese Verbindung durchgeführt werden.
Die Verbindung kann vom Client oder Server abgebaut werden.
HTTP 1.1 ist im RFC 2616 der IETF spezifiziert (siehe http:
//www.ietf.org/rfc/rfc2616.txt).
Das folgende Programm zeigt den Inhalt der Nachricht (HTIPAnfrage). die ein Webbrowser an den Webserver schickt.
lmport
lmport
lmport
lmport
lmport
lmport
lmport
lmport
java.lo FlleNotFoundExceptlon;
java.lo FlleOutputStream;
java.lo. IOExceptlon;
java.lo. InputStream;
java.lo.OutputStream;
java net.ServerSocket;
java net.Sock et;
java net. SocketTl meoutExceptlon;
public class Reporter (
prl vate lnt port;
prl vate OutputStream out;
prl vate ServerSocket server;
public Repor ter(int port . String file)
throws Fi l eNotF oundExcept i on (
this.port ~ port:
out ~ new Fi l eOut put St ream(fi le ) :
Runtime.getRuntime().addShutdown Hook(new Thread()
public void run() {
try {
if (out !~ null )
out. fl usht ):
out.close():
}
i f (server !~ null)
server. cl ase () ;
catch (IO Exception e )
}
}) :
Programm 5.1
5
168
Implementierung eines HTTP-Servers
public void doWork ()
try (
server = new ServerSocket(port);
whi le (true) (
Socket cllent = server.accept();
client.setSoTimeout(3000);
Input St ream in ~ client.get lnputStream();
try {
l nt c :
while ((c ~ in.read()) !~ -1) (
out.write(c);
}
catch (SocketTimeoutException e)
finally {
try {
if (in !~ null)
in.close();
if (client !~ null )
cl i ent. cl ose();
out.write ('lr');
out.write(' In');
out. fl ush() ;
catch ( IOException e)
}
catch ( IOException e) (
System.err.println(e.getMessage());
publ i c static void ma in(String [J args) {
if (args.length !~ 2) (
System.err.prlntln("java Reporter <port> <flle>");
System.exit(l);
int port ~ Int eger .parsel nt (args [ OJ ) ;
String f i l e ~ args[ lJ ;
try (
new Reporter(port, file).doWork();
catch (FileNotFound Exception e)
System. err.println(e);
5.1 Das Protokoll HTIP
169
Das Programm protokolliert die empfangenen Daten in einer
Datei, deren Name beim Aufruf als Parameter mitgegeben wird.
Da das Programm keine HTIP-Antwort schickt. wird die Timeout-Steuerung verwendet, um die Transaktion zu beenden.
Aufruf des Programms,
Test
ja va -cp bin Reporter 50000 log.txt
Anforderung einer fiktiven HTML-Seite im Webbrowser (hier,
Firefox 3.5}
http://localhos t:50000/abc/xyz.html
Inhalt der Datei log.txt.
GET labc/xyz.html HTTP/1 .1
Host : loca lhost:50000
User-Agent: Mo zi l l a/ 5. 0 (Windows: U: Windows NT 6.0: de:
Accept: textih tml.ap plication/xhtml+xml.application/xml:
Accept-Language: de-de.de:q~0.8.en-us:q~0.5.en:q~0.3
Accept- Encoding: gzip.deflate
Accept-Chars et: I S O - 8 8 5 9 -1 . u t f - 8 : q ~ 0 . 7 . * : q ~ 0 . 7
Keep-Ali ve: 300
Connectl on: keep-all ve
Eine HTTP-Anfrage hat den folgenden Aufbau,
•
Kap/zeile
Sie enthält die HTTP-Methode (im Beispiel GET), den Namen
der angeforderten Ressource ohne Protokoll und DomainNamen (im Beispiel I abcl xyz. htm1) und die verwendete Protokollversion (im Beispiel HTIP/1.l).
•
Anfragepararneter (optional)
Anfrageparameter liefern dem Server zusätzliche Informationen über den Client und seine Anfrage. Jeder Parameter
benutzt eine eigene Zeile und besteht aus dem Namen, einem
Doppelpunkt und dem Wert.
•
eine Leerzeile
•
Nutzdatenteil Coptional)
Hier stehen z. B. die in einem Formular eingetragenen Daten
bei Anwendung der HTIP-Methode POST.
Kopfzeile, Anfrageparameter und Leerzeile enden jeweils mit
Carriage Return und Linefeed. IrIn.
HTIP-Anfrage
5
170
sua s.».
Implementierung eines HTTP-Servers
Me th od e Re s s our c e Ve rs io n\ r\ n
Struktur einer
HTIP-Anfrage
Kopfzeile
Name : Wer t \r \n
...
}
Anfrageparameter
\ r\ n
Leerzeile
xxxxxxxxxxxxxxxxxxxxxxx
Nutzdaten
Im einfachsten Fall reicht eine Anfrage der Form
GET lindex.html HTTP /1 . 0
aus, um bereits von einem Webserver verstanden zu werden.
HTIP-Methoden
Die HTTP-Methode spezifiziert die vom Server durchzuführende
Aktion.
Wichtige HTIP-Methoden sind,
•
GET
Diese Methode fordert eine Ressource an.
•
POST
Diese Methode überträgt Benutzerdaten an den Server.
•
HEAO
Diese Methode fordert Informationen über die Ressource an;
die Ressource selbst wird nicht benötigt.
•
PUT
Diese Methode wird verwendet, um eine Ressource auf dem
Server zu erstellen bzw. zu ändern.
•
OELETE
Diese Methode wird verwendet, um eine Ressource auf dem
Server zu löschen.
PUT und OEL ETE werden aus Sicherheitsgründen von den meisten
Webservern ignoriert.
Anfrageparameter
Anfragepararneter können in beliebiger Reihenfolge angegeben
werden. Groß- und Kleinschreibung wird ignoriert.
Die gebräuchlichsten Anfrageparameter sind (siehe obiges Testbeispiel):
•
Hast
Rechnemame und optionale Portnummer des Servers
•
Us er-Agent
Kenndaten über den HTIP-Client
5.1
Das Protokoll HTIP
171
•
Connectlon
Dieser Parameter wird benutzt, um eine persistente TCPVerbindung anzufordem bzw. zu schließen.
•
Content-Length
Länge der Daten (in Byte) im Nutzdatenteil
•
Accept-Language
Dieser Parameter gibt die vom Client bevorzugte Sprache an.
Der Server kann dann z. B. eine HTML-Seite, die in mehreren
Sprachvarianten vorliegt, in der vom Client gewünschten
Sprache senden.
•
Accept- Encodlng
Mit diesem Parameter gibt der Client an, welche Komprimierungsalgorithmen er versteht. Der Server kann z. B. große Dateien komprimieren, um die Übertragungszeit zu minimieren.
•
Accept-Charset
Dieser Parameter gibt die vom Client bevorzugten Zeichensätze an.
•
Accept
Dieser Parameter gibt die vom Client akzeptierten Medientypen an. Die gültigen Parameterwerte sind durch den MIMEStandard definiert.
MIME steht für den Standard Multipurpose Internet Mail MIME
Extension. der ursprünglich für E-Mails entworfen wurde.
MIME-Formatangaben werden von HTIP-Clients und HTIPServern benutzt. Clients nutzen sie, um dem Server mitzuteilen,
welche Medientypen sie handhaben können. Server nutzen sie,
um den Client über den Inhaltstyp der gesendeten Ressource zu
informieren.
Die MIME-Formatangabe besteht aus einer Typ- und einer Subtypangabe,
typ/subtyp
Der MIME-Standard ist im RFC 1521 der IETF spezifiziert (siehe
http:// www .i etf.org/rfc/rfc I52 1.txt).
Typ/Subtyp
Beschreibung und übliche
Erweiteru ng
text/htrn1
HTML-Datei ('.hIm, '.html)
text/piain
ASCII-Text ('.txt)
t ext/xm1
XML-Datei ('.xml, '.dtd)
image/ gi f
GIF-Bild ('.gil)
image/ jpeg
JPEG-Bild ('.jpeg, '.jpg)
Beispiele von
Medientypen
5 Impleme ntie rung eines HTTP-Servers
172
i mage / png
PNG-B ild ('png)
ap>1i cat i 00/ pdf
PDF-D alei [t.p df]
app1i cati 00/ octetstream
Binardaten [t.bin. 'exe)
epojtce t t co/ zt p
Z IP-Datei Czip)
Für nicht standardisierte subtypen wird das Präfix x- benutzt, z.
B. b ezeichn et auct io/x- wa v Audio-Dateien * .wav.
Daten zum Server
senden
Mit der H1TP -Methcde POST können Benutzerdate n zum Server
gesc hickt werde n.
Das folgende Be ispiel zeigt ein HTML-Formular, dessen Daten
durc h Betätige n des Buttons "Senden" zu m Server (hier Programm 5.1) ges end et werden.
H7ML-Code des
Form ulars
<htnl >
<heed'< t : t l e>POST </ ti t l e></heact>
<body>
<form act i on= "http :// local host: 50000/xxx " meth od= "POST">
<pre>
Arti kel nummer: <input t ype="text "
<input typ e="t ext "
Bezei chnung:
<input t ype="text "
Prei s:
-vpre>
Beschrei bung:<br v>
<t exteree name= "b2schr " co1s="6O "
name= "nr " size="5"/>
name= "bez" s i ze="3Q "/ >
name= "preis " s ize= "l0 "/>
rClills= "5"></textarea >
<p/>
<in put type="s ubmi t " va1 ue="Senden "/>
<i npu t type="res et " va1 ue="Z ur ücks et zen"/ >
</form>
</body>
-v ht ml>
Bild 5..3:
Ein Formular
Artlll:~ l ",,,,,,,,~r : ~'c",,'-cccc~cccccccc--B~ r ~1Ch'lU"\I :
Akku-Han dsla ubs a uge r
15.99
[ Se nde n
I[ Zurücksetze n I
Das P ro gramm Reporterprotokollie rt:
5.1
Das Protokoll HTIP
173
POST lxxx HTTP/1.1
Host: loca1host:50000
Content Type: appllcation/x-www forrn-urlencoded
Content Length: 112
nr~4711&bez~Akku-Handstaubsauger&preis~15.gg&beschr~3+Ze11en.+
Kabe11os.+Wandha1terung.+Fugend%FCse+und+B%FCrste.
Die im Formular eingetragenen Daten werden im URL-codierten URL-codiertes
Format (appli cati on/ x-www-form-urlencoded) übertragen.
Format
Die im Formular definierten Variablennamen (im Beispiel: nr,
bez, preis, beschr) sind mit den vom Benutzer eingegebenen
Werten verknüpft. Variable und Wert werden jeweils durch ein
Gleichheitszeichen voneinander getrennt. Die einzelnen Variable/Wert-Paare sind durch das Zeichen & getrennt. Alle Zeichen
außer a-z, A-Z, 0-9,
-, * werden zuerst in Bytes nach einem
Codierungsschema (z. B. 150-8859-1 oder UTF-8) konvertiert.
Jedes Byte wird dann durch ein Prozentzeichen. gefolgt vom
Hexadezimalwert des Bytes dargestellt. Leerzeichen werden
durch + ersetzt.
Formulare können auch die GET-Methode nutzen, um Daten zu
übertragen. Die URL-codierten Daten werden nach einem Fragezeichen ? an den URL angehängt. 1m HTML-Code des obigen
Formulars muss nur POST durch GET ersetzt werden. Die Kopfzeile
der HTIP-Anfrage hat dann das folgende Aussehen,
GET Ixxx?nr~4711&bez~Akku-Handstaubsauger&preis~15.gg&beschr~
3+Ze11en.+Kabe11os.+Wandha1terung.+Fugend%FCse+und+B%FCrste.
HTTPI1.1
Die so codierte Anfragezeichenkette nach dem Fragezeichen Query String
wird auch als Query String bezeichnet.
Eine HTTP-Antworthat den folgenden Aufbau,
HTIP-Antwort
•
Kap/zeile
Sie besteht aus der Protokollversion, dem Status-Code und einer optionalen Status-Meldung.
•
Antwortparameter (optional)
Antwortparameter liefern dem Client zusätzliche
mationen über den Server und die Antwort.
•
eine Leerzeile
•
Nutzdatenteil (optional)
Dieser enthält die angeforderte Ressource.
Infor-
5
174
sua s.«.
Struktur einer
HTIP-Antwort
Implementierung eines HTTP-Servers
Ve rs ion Code Meldu ng\ r\ n
Kopfzeile
Name : Wer t \r \n
}
.. .
Antwortparameter
\ r\ n
Lee rz e ile
xxxxxxxxxxxxxxxxxxxxxxx
Nutzdaten
Beispiel,
HTT P/l. 1 200 OK
Date: Fr i. 19 Feb 2010 11: 51:42 GHT
Server: Apache/1.3.41 (Oarwin) PH P/ 4. 4. 9
Content Length: 3205
Content Type : text /htrnl
<htrnl> ... </html >
Status-Code
Der Status-Code teilt dem Client mit, wie die gewünschte Aktion
vom Server ausgeführt "WUrde.
Die Status-Codes sind wie folgt gruppiert
100
199
Informative Meldungen
200
299
Die Anfrage war erfolgreich
300
399
Die Anfrage "WUrde weitergeleitet
400
499
Die Anfrage war fehlerhaft
500
599
Server-Fehler
Beispiele:
200
OK
400
Bad Request
404
Not Found
500
Internal Server Error
501
Not Irnplernented
Siehe auch:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.htrnl
5.2
Ein einfacher File-Server
Gebräuchliche Antwortparameter sind:
•
Date
Aktuelles Datum des Servers zum Zeitpunkt der Beantwortung der Anfrage
•
Server
Kenndaten über den HTIP-SelVer
•
Content-Type
MIME-Format der Nutzdaten
•
Content-Length
Länge der Daten (in Byte) im Nutzdatenteil. Werden Webseiten dynamisch erzeugt, ist die Länge oft nicht bekannt, weshalb dieser Parameter dann weggelassen wird.
5.2
175
Einige Antwort-
parameter
Ein einfacher File-Server
Das folgende Programm ist ein HTTP-Server, der als einzigen Programm 5.2
Dienst nach Verbindungsaufnahme mit einem Client immer dieselbe Datei sendet. Zu diesem Zweck muss er also die HTIPAnfrage des Client gar nicht auswerten.
Der Server wird mit dem Dateinamen als Parameter aufgerufen.
Anhand der Dateiendung wird der passende MIME-Typ festgelegt. Die HTIP-Antwort besteht aus der Kopfzeile mit StatusCode 200. den beiden Antwortparametem Content- Type und Content-Lenqt h, einer Leerzeile und dem Inhalt der Datei.
lmport
import
import
import
lmport
lmport
lmport
lmport
lmport
lmport
java.ia BufferedOutputStream;
java.ia BufferedReader;
java.ia File;
java.la Flle InputStream;
java.lo.IOExceptlon;
j ava. jo . InputSt reamReader ;
java.lo.OutputStream;
java net.InetAddress;
java net.ServerSocket;
java net.Socket;
public class SingleFileSer ver
prlvate lnt port;
pri vate File file;
pri vate String type;
public SingleFileServer(int port. File file) (
this.port ~ port;
this.file ~ file;
type ~ getMi meType(file);
5
176
Implementierung eines HTTP-Servers
public void startServer() {
try (
ServerSocket server = new ServerSocket(port);
Inet Address addr ~ InetAddress.getLocalHost();
System.out.println("SingleFileServer auf"
+ addr. getHostName() + "/" + addr. getHostAddress()
+ '"." + port + " gestartet ... ");
whi le (true) (
Socket cllent = server.accept();
new SingleFileThread(cli ent).start();
}
catch (IOException e)
System.err.println(e);
private class SingleFileThread extends Thread (
prl vate Socket ellent;
public SingleFileThread(Socket client)
thls.client = client;
public void run() {
try (
BufferedReader in ~ new BufferedReader(
new Input St reamReader (cl i ent .get l nput St ream( ) ) ) ;
String line;
whi 1e
((l
i ne
~
in. readLi ne()) !~ null
&& !line.equals("")) (
BufferedOutputStream out ~ new BufferedOutputStream(
client.getOutputStream());
String header ~ "HTTP/LO 200 OKlrln"
+ "Content-Type; " + type + "IrIn"
+ "Content-Length; " + file.length()
out.write(header.getBytes());
writeFile(file. out);
out. fl ush() ;
out.close() ;
in.close();
catch (IOException e)
System.err.println(e);
+
"IrInIrIn";
5.2
Ein einfacher File-Server
finally {
try {
if (el ient !~ null)
el ient.elose();
}
eateh (IOExeeption e)
pri vate String getMimeType(File file)
String filename ~ file.getName();
if (filename.endsWith(".html")
11 filename.endsWith(" .htm"))
return "textihtml";
else if (filename.endsWith(" .txt")
11 filename.endsWith(" .java"))
re turn "textlplaln";
else if (filename.endsWith(".gif"))
return "lmage/gl f":
else if (filename.endsWith(".jpg"))
return "image/ jpeg";
else if (filename.endsWith(" .png"))
return "image/png";
else if (filename.endsWith(".pdf"))
return "applieation/pdf";
else
return "app l i cat i on/ octet- st ream";
pri vate void writeFile(File file. OutputStream out)
throws IOExeeption (
FilelnputStream is ~ new FilelnputStream(file);
int c;
while ((e ~ iS.read()) !~ -1) (
out. write(e);
}
is .elose();
publie statie void main(String[] args) {
if (args.length !~ 2) (
System.err
.println("java SingleFlleServer <port> <file>");
System.exi t (1) ;
177
5 Implementierung eines HTIP-Servers
178
i nt port = Integer .par se l nt Cargs[ O] );
File fil e = nee Fi le(a rgs [l] );
t f Ufil e.i sFil e()) {
Sys tem . er r . pr i nt l nC "Dat ei ... + args [l]
+ .. . i st ni cht vor handen") :
Sys tem. exl t r1) ;
new Singl eFil eServer( port . f i le ) . star tServer( ):
Au fruf des Servers:
j ava -ce bin St m lel t leier ve- 50000 s rc /St rcler tl ese-v e-vj eva
Eingabe im Webbrowser:
http : / /localhost :50000/
TCP-MonilOr
Bild 505 ,
Im Internet s ind Tools verfügbar, mit denen die Deren. die über
e ine TCP-Verbindung gesendet werden, ange zeigt werden kön nen (s ieh e Q uellenanga be am Ende des Buches ). Der TCPMonitor wird zwischen Clien r und Server plat ziert . Der Gient
verbindet sich mit dem TCP-Mo nilOf. Dieser le itet die Date n
weiter an den Serv er und protoko lliert di e Ein- un d Ausg a be.
http: / /loca lhos t : 80 80/
Konfiguration
Browser
TCP-Monitor
Se rver
8080
50000
Cre ilte a New TCP Monil '" ConneCll o n:
l ocal Port:
Server
Name :
Server Port:
~IS"~~O~~~~
1127.0.0.\
150 000
1- - ",
5.3
Ein HTIP-Server für SQL-Abfragen
5.3
Ein HTTP-Server für SOL-Abfragen
Wir stellen zunächst Methoden vor, die eine Zeichenkette in ein
URL-codiertes Format transformieren bzw. eine so codierte Zeichenkette wieder decodieren können.
Die Klasse java. net. URL Encoder enthält eine Klassenmethode zur URLEncoder
URL-Codierung einer Zeichenkette.
statlc Strlng encode(Strlng s, String enc)
throws Unsupported EncodingException
wandelt Zeichen aus 5 in das URL-codierte Format x-WW'w-formur l encoded um, wobei für die Zeichenkodierung das Codierungsschema enc zugrunde gelegt wird (z. B. lSO-8859-1 oder UTF-8).
Wird das Codierungsschema enc nicht unterstützt, so löst diese
Methode eine Ausnahme vom Typ
java.io.UnsupportedEncod ingException
(Subklasse von IO Exception) aus.
Die Klasse java. net. URLDecoder dient der Decodierung einer URLDecoder
URL-codierten Zeichenkette.
static String decode(String s. String enc)
throws Unsupported EncodlngExceptlon
decodiert eine URL-codierte Zeichenkette, wobei für die Zeichenkodierung das Codierungsschema enc zugrunde gelegt wird.
Das zu entwickelnde Programm Sql Server ermöglicht es, beliebi- Programm 53
ge SQL-Anfragen (Abfragen und Änderungen) an eine Datenbank über den Webbrowser zu schicken.
Dieser spezielle HTIP-Server
•
sendet ein Eingabeformular zur Erfassung des SQL-Befehls an
den Webbrowser.
•
analysiert den SQL-Befehl und führt ihn über ]DBC aus.
•
sendet evtl. SQL-Fehlermeldungen und
•
sendet das Abfrageergebnis bzw. Informationen über Datenbankänderungen an den Webbrowser.
179
5
180
SqlSeroer
Implementierung eines HTTP-Servers
lmport
lmport
lmport
lmport
lmport
lmport
lmport
lmport
lmport
lmport
l mport
ja va
ja va
ja va
ja va
ja va
ja va
ja va
ja va
ja va
java
java
lo.BufferedReader;
lo. Flle InputStream;
lo. IO Exceptlon;
lo. InputStreamReader;
lo.PrlntWrlter;
net. InetAddress;
net.ServerSocket;
net.Socket;
net.URL Decoder;
sql .Connectlon;
sql . Drl verManager ;
i mpor t
i mpor t
i mpor t
import
import
lmpor t
java
java
java
ja va
ja va
java
sql.ResultSet ;
sql.ResultSet l'etaData;
sql .SQL Exception;
sql.Staterrent;
sql.Types;
utll . Propertles;
public class SqlServer (
pri vate static final int WlX_RQ WS ~ 1000;
pri vate static final int WlX_L ENGTH ~ 1000;
prlvate i nt port;
private Properties prop;
public SqlServer(int port)
this port ~ port;
public void startServer() {
try (
// Properties einlesen
prop ~ new Properties();
Fi l el nput St r e am in ~ new Fi l el nput St re am(
"dbconnect.properties");
prop.load(in) ;
in.clos e();
ServerSocket server
=
new ServerSocket(port);
I net Addr ess addr ~ Inet Addr es s . get Loc al Host ( ) ;
System. out. pri nt1n( "Sq1Ser ver auf " + addr. getHostName()
+ "/" + addr. getHostAddress () + ":" + port
+ "
gestartet ... ");
whi le (true) (
Socket client
=
server.accept();
new Sql Thread(client. prop).start();
}
catch (Exception e) (
System.err.println(e);
System.exit(l);
5.3 Ein HTIP-Server für SQL-Abfragen
pri vate class SqlThread extends Thread
private Socket client;
private Properties prop;
private Connection con;
private BufferedReader in;
private PrintWriter out;
public SqlThread(Socket client, Properties prop) (
this.client = client;
this,prop ~ prop;
publ ic void run() {
try (
// Verbindung zur Datenbank herstellen
con = Dri verManager.getConnection(prop
,getProper ty(" ur1"), prcp. get Property( "user". ""),
prop ,getProperty ("password", ""));
in
new BufferedReader(new Input St reamReader (cl i ent
,get lnputStream()));
out ~ new PrintWriter(client,getOutputStream(), true);
=
// HTTP- Request analysieren
String sql ~ readRequest();
// HTML- Formul ar senden
sendForrn(sq1);
// SOL-Befehl ausführen
if(sql !~null)
execute(sql) ;
in.close t ) ;
out,close();
catch (Exception e) (
System,err,println(e);
finally {
try {
if (con !~ null)
con.cl ose C):
if (cli ent !~ null)
cl ient.close();
}
catch (Exception e)
181
5
182
//
//
pri
pri
pri
Implementierung eines HTTP-Servers
De r Ouellcod e der folgenden Me t hoden ist weiter unten
abgedruckt:
)
vate String readRequest() throws IOE xcept i on (
vate void send Form (String sql) throws IOExcept i on (
vate void execute(String sql) throws SOL Exception (
public static voi d main(String [] args)
if (args.length !- I ) (
System.err.pr l ntln("java SqlServer <por t>");
System.exit (l):
int port - Int eger . parseI nt (args [ O] ) :
new SqlServ er(port).startServer():
Datenbankspezifische Angaben (URL der Datenbank, User und
Passwort) sind in der Datei dbconnect.properties zusammengefasst und werden zu Beginn als Properties eingelesen.
Innerhalb der run-Methode der inneren Klasse SqlThread erfolgt
die Interaktion mit dem Client.
Zunächst wird die Verbindung zur Datenbank hergestellt. Die
Methode readRequest analysiert dann die HTIP-Anfrage und
liefert den vom Benutzer eingegebenen SQL-Befehl.
Die Rückantwort des Servers enthält eine HTML-Seite, die im
oberen Teil ein Formular zur Eingabe eines SQL-Befehls und im
unteren Teil das Ergebnis der letzten Anfrage enthält.
Die Methode sendForm sendet das Formular mit dem zuletzt eingegebenen SQL-Befehl an den Client zurück
Die Methode execute führt den SQL-Befehl aus und sendet das
Ergebnis bzw. eine Fehlermeldung. Damit ist dann die HTMLSeite vollständig erzeugt.
Zu Beginn der Sitzung wird nur ein leeres Formular an den
Client geschickt.
Die Methode
readRequest
pr ivate Str ing readRequest() t hrows IOExcept i on (
String line - in.readLine():
if (line -- null)
throw new IOE xcept i on("Ke i n Input ") :
// SO L-Befehl e werden über ein Formular mittels POST
// gesendet
if (!line.startsWith("POST") )
r eturn null;
5.3
Ein HTIP-Server für SQL-Abfragen
183
// BlS zur Leerzelle lesen und Content Length ermltteln
int 1ength ~ 0;
whi1e ((line ~ in.readLine()) !~ null
&& !1i ne .equa 1s ( "" )) (
I i ne = line.toLowerCase();
i f (1 i ne. startsWi th( "content-l ength"))
1ength ~ Integer.parselnt(line.substring(16));
// Zeichen nach der Leerzeile einlesen
int c;
StringBui1der sb ~ new StringBui1der();
for (int i ~ 0; i < 1ength; i++) {
c ~ in. read () ;
if (c ~~ -1)
break;
sb.append((char) cl;
// Decodierung der "x-WW'w-form urlencoded" Zeichen
String query ~ sb.toString();
int i ~ query.indexOf('~');
i f (i < 0)
return null;
return URLlJecoder decode ique ry.xubst.ri nqt i + 1), "UTF-8");
Es wird nur die HTIP-Methode POST zugelassen. Somit muss der
Anfrageparameter Content-Length vorhanden sein. Die Länge
wird extrahiert und in eine Zahl vom Typ i nt konvertiert. Zeichenweise werden nun die Daten des Nutzdatenteils in einen
Puffer übertragen. Die Daten nach dem Gleichheitszeichen
werden extrahiert und gemäß UTF-8 decodiert.
private void sendForm(String sq1) throws IOException
out.print("HTTP/l.O 200 OKlrln"
+ "Content- Type; textihtm1 IrInIrIn" );
out print("<htm1><head><tit1e>SQL</tit1e></head>");
out print( "<body>"}:
out print("<b>" + prop.getProperty("ur1") + "</b><p/>");
out pri nt( "<form method~' PQST' accept-charset~' UTF -8' >") ;
out pri nt( "<textarea co 15=' 80' rows=' 4' narre=' sq 1 '>"):
if(sq1 !~null)
out. print(sq1);
out print("</textarea><p/>");
out pri nt( "<i nput type=' submi t ' val ue=' Senden' ><pi>");
out print("</form>");
Die Methode
sendForm
5
184
Implementierung eines HTTP-Servers
i f (sql ~~ null)
out. pri nt ("</ body></htm1>") ;
out. fl ush () ;
Ist der Parameter sq1 gleich null (d. h. es wurden keine Benutzerdaten gesendet). so erzeugt diese Methode die komplette
HTIP-Antwort ein HTML-Formular. Andernfalls generiert sie nur
den ersten Teil der Antwort: ein Formular mit dem zuletzt gesendeten SQL-BefehI. Für die Dateneingabe ist das Codierungsschema vorgegeben; accept-charset~"UTF-8".
Die Methode
execute
private void execute(String sql) throws SQLException
try (
Statement stmt
if
=
con.createStatement();
(! stmt. execute (sq 1)) (
out.println(stmt.getUpdateCount() + " Zeile(n)");
stmt.close();
return;
// Ausgabe der Zeilen in Form einer HTML-Tabelle
ResultSet rs ~ stmt.getResultSet();
ResultSet MetaData rm ~ rS.getMetaData();
int n ~ rm.getColumnCount();
String[J align ~ new String[nJ;
out.println("<table
+
"border~'l'
bgcolor~'white'
cellpadding~'5'>");
// Spaltenüberschriften der Tabelle
out.println("<tr>") ;
for (int i ~ 1; i <~ r: i++) (
// Zahlen werden rechtsbündig ausgerichtet
if (rm.getColumnType(i) ~~ Types.TINYINT
11
rm.getColumnType(i) ~~ Types.SMALLINT
11
rm.getColumnType(i) ~~ Types. INTEGER
11
rm.getColumnType(i) ~~ Types.BIGINT
11
rm. getColumnType (i) ~~ Types. REAL
11
rm.getColumnType(i) ~~ Types.FLOAT
11
rm.getColumnType(i) ~~ Types.DOUBLE
11
rm.getColumnType(i) ~~ Types.NUMERIC
1 1 rm. getCo1umn Type (i) ~~ Types. DEC lMAl)
align[i
1J
~
"right";
5.3
Ein HTIP-Server für SQL-Abfragen
else
al ign[i
IJ
~
185
"left";
out.println("<th aligw'" + align[i
IJ + "'>"
+ rm.getColumnNalTl2(i) + "</th>");
)
out.println("</tr>");
// Tabellenzeilen
// Anzahl Zellen und Länge elnes Spaltenwerts slnd
// begrenzt
int count = 0;
while (rs.next()) {
if (++count > MAX ROWS)
break;
out. pri nt 1n( "<tr>");
for tint l = 1; l <= n ; l++)
String s ~ rS.getString(i);
if (rs.wasNull ()) {
s ~ "[NULL]";
else (
if (s.length() > MAX_LENGTH) (
s ~ s.substring(Q, MAX LENGTH) + "
".
)
out.println("<td valigw'top' aligw'"
+ allgn[l
1J + "'>" + 5 + "<Ztd>"):
)
out. pri nt 1n( "</tr>") ;
out. pri nt 1n( "</tab1e>");
if (count> MAX_ROWS)
out.println("Es werden maximal
+ "
+ MAX ROWS
Zellen angezelQt.");
rs .close();
stmt. cl ose () ;
catch (SOLException e)
out.println(e);
finally (
out. pri nt 1n( "</body></html >");
Der SQL-Befehl wird ausgeführt (Details zur Handhabung des
JDBC-API findet man im Kapitel 2). Das Ergebnis der Datenbankänderung bzw. Abfrage ergibt den zweiten Teil der Antwort an
den Client. Das Abfrageergebnis wird in einer HTML-Tabelle
5
18 6
Implementieru ng ein es
HITP ~Servers
dargestellt. Um die an den Browser ZU ü benragende Datenmenge zu begre nzen , we rde n maximal MAX_RQWS Zeilen und je Spa lten w ert einer Zeile maximal M\X_LENGTH Zeichen gesen det.
Bild 5 .6 ,
SQL-A bfrage
Iab.
I
I-
I
I
I
ufo.
lit t'!
-
-
1 3.~s7-:~0998.3 1 Did:ms, C!>afb I ~"olas :-'~
1 3-1~8.33()().1-6 1 Dieb ..., Chafb ~,:i<olars ;"Iddd>y
13-338-06658-: Did:mo;, C'1Iafb :-"-oddas ~dId>y
I
13-538-06982-1 Didms. Chafb
I
I:-.tdda< :-'Iddd>y
5.4
Ein einfacher Webserver
SUfi bietet s eit Java SE 6 ein einfaches API zur Entw icklu ng von
Hl'Tl'<Servem. Hier bei han delr es sich um die be ide n Pakete
com. sun. net. httpserv er und com. sun. net. ht tcserver . sct.
Hupserver
Die abstrakte Klasse com. sun. net . httpserverHt tpSer ver besitzt
die statisch e Methode cr eate, mit d er eine konkrete Implementieru ng bereitgestellt wird:
st at t c HttpServe r cre at e ( Ine t Socke tAddr es s addr, i nt backlo g)
t hr ows IOExcept i on
j eva. ne t . Ine tSocketAddres s repräs entiert eine So cke t-Adress e.
Der Konstruktor InetSocketAddr es s ( i nt por- t ) erzeugt ein en
Sock et mit der vorgegebenen Porrnummer. back1og be stimmt die
maximale Länge der Warteschl ange (siehe Kapire14 .2).
Methoden der Klasse HttpServer :
abstract HttpCon text createContext (St r i ng path,
HttpHandler handl erl
liefert eine Inst anz vom Typ com. sun. net. nttose- ver . HttpContext,
die eine Ver bindung eines Pfads (z. B. ~r) mit einer Instanz vom
Typ des Interfaces com.sun. net. httpser ver . HttpHandler, die die
HTfP-Anfrage bearbeitet, festlegt.
Pur ein en HTfP-5erver k önnen mehrere Kontext e (mit unterschi edlich en Handlem) ein gericht et werden.
5.4
Ein einfacher Webserver
187
Ein vom Browser übermittelter URL wird auf den so genannten
Kontext-Pfad "bestmöglich" abgebildet.
Beispiele,
Kontext 1, 1
Kontext 2, 1demol
http 111 oca1host: 500001 wird abgebildet auf Kontext 1
http 1Ilocalhost:50000/demo/xyz wird abgebildet auf Kontext 2
http 111 oca1host: 50000/webapp wird abgebildet auf Kontext 1
abstract vOld setExecutor( Executor executor)
legt das java . uti 1 .concurrent . Execut or-Objekt fest. Alle HTIPAnfragen werden diesem Objekt zur Ausführung übergeben
(siehe Kapitel 4.8).
abstract void start()
startet den Server .
abstract voi d stop(int delay)
stoppt den Server, wobei maximal de1ay Sekunden auf die Beendigung der laufenden Anfragen gewartet wird.
Im Folgenden entwickeln wir mit Hilfe dieses API einen einfa- Programm 5.4
ehen Webserver, der nur die HTIP-Methode GET versteht, Query
Strings aber nicht verarbeitet. Das Programm wird mit zwei Parametern aufgerufen:
•
Portnummer port und
•
Name des so genannten Raot-Verzeichnisses dir.
dir ist die Wurzel des Webverzeichnisses, das alle abrufbaren
Ressourcen enthält.
lmport java.lo. IOExceptlon;
lmport java net. InetSocketAddress;
lmport java uti l .concurrent. Execut or s ;
lmport com .sun.n et.h ttpserver. HttpServer;
public class Mini WebServer (
public static void main(String[] args) throws IOExcept i on {
if (args.length !- 2 ) (
System.out.println ("java Mini WebServer <port> <root>");
System.exi t( 0) :
int port - Int eger .par selnt (ar gs [O] ) :
String root - args[ I J:
MiniWebServer
5
188
Implementierung eines HTTP-Servers
f l nal Ht t pSer ver server = Ht t pServer create(
new Inet Socket Address (por t ) , 0);
Runtime,getRuntime(),addShutdownHook(new Thread()
public void run() (
if (s erver !- null)
server,stop(O);
}
System, out, pri nt 1n( "Server gestoppt");
}
}) ;
server createContext(" t", new Mi ni Htt pHandl er (r oot ) ) ;
server setExecutor( Executors.newCachedThreadPool());
ser ver. start();
System, out, pri nt 1n( "Server gestartet ",");
Die Klasse Mi ni Ht t pHandl er implementiert Htt pH andl er und nutzt
diverse Methoden der Klasse Http Exchange,
HttpHandler
Ein Handler, der die HTIP-Anfrage bearbeitet, muss das Interface cam. sun. net. httpserver . Ht t pHandl e r implementieren.
Http Handler enthält die Methode;
void handle( Http Exchange exchange) throws IOE xcept i on
HttpExchange
Die abstrakte Klasse cam. sun. net. httpserver . HttpExchange kapseit
HTIP-Anfrage und -Antwort und stellt insbesondere die folgenden Methoden zur Verfügung;
abstract String getRequestMethod()
liefert die Il'FTl'<Methode.
abstract UR I getRequestURI()
liefert den Uniform Resource Identifier (UR!) der Anfrage, eine
Verallgemeinerung des Uniform Resource Locator (URL),
Die java .net. URI-Methode
String getPath()
liefert den Pfad des URI als Zeichenkette.
abstract Inet Socket Addr es s getRemoteAddress()
liefert die Socket-Adresse des Client.
abstract Headers getResponseHeaders()
liefert eine Map zur Speicherung der Antwortparameter.
5.4 Ein einfacher Webserver
189
In dieser Map (com.sun. net. httpserv er Headers) können mit Hilfe
der Methode
void add(Str ing key. String value)
Parameter aufgenommen werden.
abstract vOld sendResponseHeaders(lnt reode,
long responseLength) throws IOExcepti on
sendet die Kopfzeile mit dem Status-Code rCode und die Antwortparameter. responseLength gibt die Anzahl Bytes der Nutzdaten an (0 = nicht festgelegt).
abstract OutputStream getResponseBody()
liefert den Datenstrom, in den die Nutzdaten geschrieben wer-
den.
lmport
lmport
lmport
lmport
lmport
lmport
import
lmport
lmport
java.lo Flle;
java.lo Flle InputStream;
java.lo FlleNotFoundExceptlon;
java.lo. IOExceptlon;
java.lo.OutputStream;
java net. HttpURLConnectlon;
java net.UR I ;
java net.URLConnectlon;
java text.SlmpleDateFormat;
lmport java.utll.Date;
lmport com .sun net httpserver. Headers;
lmport com.sun net httpserver. HttpExchange ;
lmport com.sun net httpserver. HttpHandler;
public class Min i HttpHandler imp le ments Ht t pHandl er
prlvate Strlng root;
public Mini HttpHandler(String root) (
thls.root = root;
public voi d handle(HttpExchange t) throws IOExcept i on (
log(t) ;
String method ~ t.getRequest Method();
if (!method.equals("GET")) (
hand le Error(t Ht t pURLConnect l on HTTP NOT_IMPL EMENTED.
"Nur di e GET -Methode ist zu1ässi g");
return;
UR I uri ~ t.getRequestURI();
Stri ng path ~ uri. get Path();
MiniHttpHandler
5
190
Implementierung eines HTTP-Servers
if (path.ends With ("/"))
path ~ path + "index. htrnl":
Strlng fllename = root + path;
Fi l e file ~ new Fi l e(f i l enarre ) ;
Fi l el nput St ream fis ~ null;
OutputStream os ~ null;
try (
fis ~ new Fi l el nput St ream(f i l e ) ;
String type ~ URLConnection.getFileNa rreMap()
.getContentTypeFor(fil enarre);
i f (type ~~ null) (
type = "appllcatlon/octet-stream";
Headers headers = t .getResponseHeaders();
headers .add ("Content- Type". type);
t.sendResponseHeaders (HttpURLConnection HTTP_OK. file
.length()) ;
os ~ t.getResponseBody();
byte data[] ~ new byte[ 1024];
i nt cnt;
while ((cnt ~ fis.read(data)) !~ -1) (
os.write(data. O. cnt);
}
catch (Fil eNotFoundException e) {
handleError(t Ht t pURLConnect l on HTTP NOT_FOUNO. e
.getMessage ());
f inally {
try {
i f (fis !~ null)
fis .close();
if (os !~ null)
os. fl ush();
os.close();
}
catch (I OExcept i on e)
private void 10g(HttpExchange t) (
SimpleOateFormat f ~ new SimpleOateFormat(
"dd.MM.yyyy Hh .nm .ss'"):
System.out.println("[" + f.format(new Oate()) + " "
+ t.getRermteAddress() + "] " + t.getRequestMethod()
+ " " + t , getRequestURI ());
5.4
Ein einfacher Webserver
private void handleError(HttpExchange t. int status.
String msg) throws 10Exception (
Headers headers = t.getResponseHeaders();
headers. add( "Content- Type". "textihtml");
StringBuilder response ~ new StringBuilder();
response.append(
"<html><head><tltle>Fehler</tltle></head>");
response.append("<hl>HTTP-Status-Code; " + status
+ "</hl>");
response.append("<body>" + msg + "</body></html>");
t.sendResponseHeaders(status. response.length());
OutputStream os ~ t.getResponseBody();
os. write (response. toStri ng() .getBytes (" ISO-8859- I")) ;
oS.flush();
os .close();
Die Methode handle führt die folgenden Schritte zur Bearbeitung
einer HTIP-Anfrage aus.
1. Ausgabe einer Protokollzeile (Datum/Zeit. Adresse. Methode.
URL)
2. Ermittlung der HTIP-Methode. Stimmt diese nicht mit GET
überein, wird eine Fehlerseite zurückgesendet,
3. Ermittlung der angefragten Ressource (Datei). Endet der Pfad
mit li/li, so wird die Datei lndex.html im entsprechenden Verzeichnis angenommen.
4. Öffnen der Datei. Wird diese nicht gefunden. wird eine Fehlerseite zurückgesendet,
5. Bestimmung des MIME-Typs
6. Setzen des Headers Content- Type
7. Senden der Kopfzeile und der Antwortparameter der HTIPAntwort
8. Lesen der Datei und Schreiben der Nutzdaten der HTIPAntwort
Der MIME-Typ der Ressource wird mit der Methode
URLConnect ion. getF i 1eNameMap () .getContentTypeFor (. .. )
ermittelt.
Die abstrakte Klasse java. net. URLConnectl on ist die Superklasse
derjenigen Klassen, die eine Netzverbindung zu einer durch den
URL adressierten Ressource repräsentieren.
191
5
192
Implementierung eines HTTP-Servers
static FileNameMap getFileNameMap()
lädt die Tabelle der MIME-Typen aus der Datei lib\contenttypes.properties im ]RE-Verzeichnis.
Das Interface java .net. Fi 1eNameMap enthält die Methode
String getContentTypeFor(String fileName).
Sie liefert den MIME-Typ zu einem Dateinamen .
java. net. Ht t pURLConnect i on ist Subklasse von URLConnecti on und
enthält insbesondere die HTTP-Status-Codes.
5.5
Programm 55
Webseiten dynamisch erzeugen
Wir erweitern nun den Mini-Webserver aus Kapitel 5.4 um zusätzliche Funktionen.
So können dann z. B. Formulare ausgewertet, Daten in einer
Datenbank abgespeichert oder abgefragt werden. Zu diesem
Zweck unterstützt der neue Server auch die POST-Methode.
Die neue Anwendungsfunktionalität zur dynamischen Erzeugung
von HTML-Seiten wird in eigenen Klassen - unabhängig vom
Webserver - realisiert. Diese Klassen können nach Bedarf zur
Laufzeit des Webservers dynamisch hinzu geladen werden.
Die Klassen des erweiterten Mini-Webservers befinden sich alle
im Paket httpserver .
XMl ni We bServ e r unterscheidet sich von der Klasse Mi niW
ebServer
insbesondere dadurch, dass zwei Kontexte erzeugt werden:
XMiniWebSeroer
// Stat ische Webs ei te n
ser ver. createContext(" t", new Mi ni Htt pH andle r (r oot ) ) ;
II Dynamlsche Webselten
ser ver.createContext("/prog". new XMin i Http Handler());
Der Kontextpfad "I" wird zum Abruf statischer Webseiten verwendet. Hier wird der Mi ni Htt pH andl er aus Kapitel 5.4 zugeordnet. über den Kontextpfad "/prog" werden mit Hilfe von
XMi ni HttpHandle r Methoden zur dynamischen Erzeugung von
Webseiten ausgeführt. Der URL der verarbeitenden Anwendung
muss die folgende Form haben: /prog/Klasse/methode
5.5 Webseiten dynamisch erzeugen
193
Beispiel, /prog/Testitest1
Der Handler sorgt dann dafür, dass die statische Methode test 1
der Klasse prog.Test aufgerufen wird.
Der Methodenkopf muss wie folgt aussehen,
public static void methode(HttpQuery query. HttpExchange t)
throws IOExceptlon
XMiniHttpHandler
package httpserver ;
lmport
lmport
lmport
import
import
import
lmport
java.lo.IOExceptlon;
java.lo.InputStream;
java.lo.OutputStream;
java.lang.reflect.Method;
java.net.HttpURLConnection;
java.net.URI;
java.text.SlmpleDateFormat;
lmport java.utll.Date;
lmport com.sun net httpserver.Headers;
lmport com.sun net httpserver.HttpExchange;
lmport com.sun net httpserver.HttpHandler;
public class XMiniHttpHandler implements HttpHandler (
public void handle(HttpExchange t) throws IOException
log(t) ;
String method ~ t.getRequestMethod();
URI uri ~ t.getRequestURI();
Stri ng path ~ uri. getPath();
int lastSlash ~ path.lastlndexOf("/");
String classNarre ~ path.substring(I. lastSlash).replace(
'/'. '.');
String objectMethod
~
path.substring(lastSlash
+
I);
String q ~ null;
if (rrethod.equals("GET"))
q ~ uri .getRawQuery();
else if (method.equals("POST"))
q ~ getRawQuery(t);
else (
handleError(t. HttpURLConnection.HTTP_NOT_IMPLEMENTED.
"Nur GET und POST sind zulässig");
return ;
HttpQuery query
~
new HttpQuery(q);
5
194
Implementierung eines HTTP-Servers
try (
Cl ass<?> c = Class.forName(className);
Class<?>[] types ~ { Ht t pQuery.cl ass .
Ht t pExchange .cl ass };
Method m ~ c.getMethod(objectMethod. types);
Object[] args ~ { query. t };
m.invoke(null. args);
catch (Exception e) (
System.err.println(e);
priv at e String getRawQuery( HttpExchange t)
throws IOExcept i on (
Headers headers ~ t.getRequestHeaders();
String value ~ headers.getFirst("Content Length");
int length;
i f (value !~null ) (
length ~ Int eger .parsel nt (val ue) ;
else (
length ~ - I;
Input St ream in
~
t.getRequest BJdy();
l nt c :
StringBuilder sb ~ new String Bui lder();
for (int i ~ 0; i < length; i++) (
c ~ in. read();
if (c ~~-I)
break;
sb.append((char) cl;
return sb.toString();
privat e void log(HttpExchange t ) (
SimpleOateFormat f ~ new SimpleOateFormat(
"dd.MM.yyyy HH ;rrrn ;ss " ) ;
System.out.println ("[" + f.format (new Oate()) + " "
+ t.getRelTDteAddress() + "] " + t.getRequestMethod()
+ " " + t , getRequestURI ());
private void handleError( HttpExchange t. i nt status.
String msg) throws IOExcept i on (
Headers headers ~ t.getResponseHeaders();
headers .add ("Content- Type". "textihtm1" ) ;
StringBuilder response ~ new StringBuilder();
response.append(
"<html><head><tltle>Fehler</tltle></head>");
5.5 Webseiten dynamisch erzeugen
195
response.append ("<h1>HTIP-Status Code: " + status
+ "</h1>"):
response.append("<body>" + msg + "</body></html>"):
t.sendResponse Headers(status. response.length()):
OutputStream os ~ t.getResponseBody():
os. write (response. toStr i ng() .getBytes (" ISO-8859-1 " ) ) :
os .close():
Die Methode handle extrahiert aus dem URL den Klassennamen
mit Paketangabe sowie den Methodennamen. Dann wird der
Query-String im Falle der HTIP-Methode GET ermittelt. Hierzu
wird die java. net. UR I-Metho d e getRawCuery benutzt.
public String getRawQuery()
liefert den Query-String im URL-codierten Format.
Im Falle der HTIP-Methode POST werden die Nutzdaten der
HTTP-Anfrage entnommen. Um die Anzahl Bytes der Nutzdaten
zu bestimmen, werden zunächst die Anfrageparameter mit der
com. sun. net. httpserver . Htt pE xchange-Metho de
getRequestHeaders
ermittelt.
abstract Headers getRequestHeaders()
Die com. sun. net. httpser ver . Headers-Metho de get Fl rst liefert den
ersten Wert eines evtl. aus mehreren einzelnen Werten bestehenden Anfrageparameters (hier: Content- Length):
Str ing getFirst(Str ing key)
abstract Input St ream getRequestBody()
liefert die Nutzdaten als Eingabedatenstrom im URL-codierten
Format.
Mit Hilfe der so ermittelten Nutzdaten wird ein Objekt vom Typ
Ht t pOuery erzeugt (Quellcode siehe weiter unten).
Nun wird die Klasse prog.Klasse geladen und die aus dem URL
extrahierte Methode über das RefiRction-API bereitgestellt. Anschließend wird die (statische) Methode mit i nvoke aufgernfen.
Die Class-Methode
M2 t hod getMethod(Stri ng name. Cl ass .
throws NoSuchMethodException
parameterTypes )
5
196
Implementierung eines HTTP-Servers
liefert ein l'ethod-Objekt. narre ist der Name der gewünschten
pub1i c-Methode, parameterTypes sind die Parametertypen dieser
Methode, durch Cl ass-Objekte repräsentiert.
Die Klasse java.1 ang. refl ect. ~ t h o d repräsentiert eine Methode.
Die l'ethod-Methode
Object invoke(Object ob.j , Object.
args)
throws IllegalAccessExceptlon,
java.lang.reflect. InvocationTargetException
ruft die durch dieses l'ethod-Objekt repräsentierte Methode auf.
obj ist das Objekt, für das die Methode ausgeführt werden soll,
args sind die Argumente für den Methodenaufruf. Bei einfachen
Datentypen werden hier die entsprechenden Hüllobjekte eingesetzt. Ist die Methode statisch, so wird obj ignoriert und kann
null sein.
Dieser indirekte Mechanismus zum Laden von Klassen und Aufruf von Methoden ermöglicht es, dass die Klasse zum Zeitpunkt
der Compilierung nicht bekannt sein muss.
HttpQuery
Die Klasse HttpOuery enthält Methoden, die Parameterwerte im
URL-codierten Format in decodierter Form bereitstellen. Zur
Decodierung wird das Codierungsschema UTF-8 verwendet. Zu
beachten ist, dass ein Parametername auch mehrfach auftreten
kann. Parametername und -wert werden in einer Hashtable gespeichert. Der Parametername stellt den Schlüssel dar, die evtl,
mehrfach auftretenden Parameterwerte zum gleichen Namen
werden in einem Vektor zusammengefasst, der dann als Wert
zum Schlüssel in die Hashtable eingetragen wird.
package httpserver;
lmport java.lo.UnsupportedEncodlngExceptlon;
lmport java.net.URLDecoder;
import java. uti 1. Hashtab1e;
lmport java.utll.StrlngTokenlzer;
lmport java.utll .Vector;
public class HttpOuery (
prlvate Hashtable<Strlng, Vector<Strlng»
h;
pub1i c HttpOuery(String query) (
h = new Hashtable<String, Vector<String»();
parseOueryString(query);
public String getParameter(S tring name) (
Vector<String> v = h.get(name);
if (v ~~ null)
5.5 Webseiten dynamisch erzeugen
197
return null;
else
return v.get(O);
public String[] getParameterValues(String name) (
Vector<Strlng> v = h.get(name);
if(v~~null)
return null;
else (
String[] result ~ new String[v size()];
v.copylnto(result);
return result;
/*
* Allgemelne Form elnes Query Strlngs:
* namel=valuel&name2=value2.
* Eln Feldname kann auch mehrfach auftreten.
*/
private void parseQueryString(String query) (
StrlngTokenizer params = new StringTokenizer(query,
StringTokenizer param;
String name, value;
while (params.hasMoreTokens()) (
param = new Stri ngTokeni zer(params. nextToken(),
name = param.nextToken();
if (param.hasMoreTokens())
value = param.nextToken();
else
value
"".
"=");
if (name !~ null)
String dvalue ~ "".
try (
dvalue ~ URLl:ecoder.decode(value. "UTF-8");
catch (UnsupportedEncodingException e) (
// Prüfen. ob der Name bereits in der Hashtable
// vorkommt. Die Werte werden jeweils in einem
// Vektor gespeichert.
Vector<String> v
=
h.get(name);
if(v~~null){
v = new Vector<String>();
v.add(dvalue);
h put(na me. v);
"&");
5
198
Implementierung eines HTTP-Servers
else (
v.add(dvalue);
Die Klasse prog. Test soll die neue Funktionalität demonstrieren.
Die Methode testl wertet ein Formular aus.
posthtml
<html>
<head><title>Test (POST)</title></head>
<body>
<form actiow"/prog/Testitest1" method~"POST"
accept-charset~"UTF-8">
Name: <lnput type="text" slze="60" narre="narre"/><p/>
<lnput type="checkbox" narre="sprache"
value="Java"/>Java<br/>
<lnput type="checkbox" narre="sprache"
va l ue="C++" />C++<br/>
<input type~"checkbox" narre~"sprache"
<lnput type="submlt" value="Senden"/>
<l nput type=" reset" val ue-" Löschen" />
</form>
</body>
</html>
Test
value~"C#"/>C#<p/>
package prog;
import httpserver HttpOuery;
lmport java lo.IOExceptlon;
lmport java lO.OutputStream;
import java net. HttpURLConnecti on;
lmport com.sun net.httpserver Headers;
lmport com.sun net.httpserver HttpExchange;
public class Test (
public static void test1(HttpOuery query. HttpExchange t)
throws IOException (
Strl ng narre = query. getPararreter( "name");
String[] sprachen ~ query.getPararreterVal ues("sprache");
Headers headers = t.getResponseHeaders();
headers .add ("Content- Type". "textihtm1" ) ;
5,5
Webs eiten dynami sch e rze uge n
199
Strin[ßuilder resPJnse - new Strin[ßuilder(),
response append(
"<htm l ><head><ti t l e> Test</ti tl e></head><body>") ,
resPJnse append("<b>Name </b> " + name + "<p/ >" ) ,
response eppenoc '<b-Pron-amnerspr-echen </b><br/> "),
i f (sprachen 1- null) (
for (int i - 0, i < sprachen ierutn: i ++)
response append(sprachen[iJ + "<br v>" ) .
}
response append(
"<p>. e hret-.' /index html '>Zurück</a></p>"),
response append("</body></html> "),
t
s endkesponselieaders ( HttpURLConnection HTTP_OK, response
1enutncn:
Ou tpu tStream os
t getResPJnseBodyO,
os wr-t t erresronse toS tring() getBytes( " ISO-8859 1")) ,
os fl usno:
os ctoseö:
Bild5.7-
:\lII:ne: Hugo Mei""
Formular post btmt
"c_
::ll1a\':3
"J C#
I.. Se nde n I1
Losehen
I
.:"'a m..: Hugo ~![Oer
au s.e,
Ergebnis
P.".grammi.. rspra c~ ..a:
J",-a
C,
D ie Anwendung d er GET-M ethode (im Formular ist POST durch
I1:T zu e rsetze n) führt zu ei nem e ntsp reche nde n Erg ebnis ,
5
200
5.6
Implementierung eines HTTP-Servers
Protokollierung von HTTP-Nachrichten
Das Programm MyReport er kann genutzt werden, um die zwischen Client und Server ausgetauschten Daten aufzuzeichnen.
Ein Shutdown hook sorgt für das ordnungsgemäße Schließen der
Log-Dateien, wenn das Programm mit Strg+C beendet wird.
MyRepotter
lmport
lmport
lmport
lmport
lmport
lmport
lmport
ja va
ja va
ja va
ja va
ja va
ja va
ja va
lo. FlleOutputStream;
lo. IO Exceptlon;
lo. InputStream;
lO.OutputStream;
net.ServerSocket;
net.Socket;
net.Socket Exceptlon;
publ i c class MyReporter ext ends Thread (
prlv ate statlc OutputStream logRequest;
prlvate statlc OutputStream logResponse;
prlvate statlc ServerSocket srv;
prlvate statlc lnt counter;
prlvat e Input St ream fro mCllent;
prl vate OutputStream toServer;
public MyReporter( InputStream fro mClient,
OutputStream toServer) (
th is,fromClient ~ fro mClient;
thls.toServer = toSer ver;
public void run() {
try (
l nt c :
logRequest,write(("#" + counter + "lrln"),get Bytes());
while ((c ~ fro mClient,read()) !~ - 1) (
logRequest,write(c);
toSer ver.wrlte(c) ;
}
catch ( IO Exception e)
try (
1ogRequest, wri te("1 rlnl rln" ,get Bytes()) ;
catch ( IO Exception e) (
public static voi d main(String[J args)
int port ~ Int eger ,parsel nt (args [ OJ ) ;
String remoteHost ~ args[ IJ;
5.6
Protokollierung von HTIP-Nachrichten
int remotePort ~ Integer parseInt(args[2]);
String filel
args[3];
String file2 ~ args[4];
// ShutdownHook reglstrleren
Runtime.getRuntime().addShutdownHook(new Thread()
public void run() (
try (
if (logRequest !~ null)
logRequest.flush();
logRequest.close();
}
if (logResponse !~ null)
logResponse. fl ush();
logResponse.close();
}
if (srv !~ null)
srv.close();
catch (IOException e) (
)
}) ;
try (
logRequest ~ new FileOutputStream(filel);
logResponse ~ new FileOutputStream(file2);
srv = new ServerSocket(port);
whi 1e (true) (
Socket client ~ srv.accept();
InputStream fromClient ~ client.getInputStream();
OutputStream toClient ~ client.getOutputStream();
Socket socket = new Socket(remoteHost, remotePort);
InputStream fromServer ~ socket.getInputStream();
OutputStream toServer ~ socket.getOutputStream();
counter++;
// Welterleltung vom Cllent an den Server
(new MyReporter(fromClient. toServer)).start();
// Welterleltung vom Server an den Cllent
lnt c;
logResponse.wrlte(
("#" + counter + "lrln").getBytes());
while ((c ~ fromServer read()) !~ -1) (
logResponse.write(c);
toClient.write(c);
}
logResponse. write("lrlnlrln" .getBytes());
201
5
202
Implementierung eines HTTP-Servers
toClient. fl ush();
client.close();
socket.close();
}
catch (SocketException e)
catch ( IOException e) (
System.err.println(e);
sua s».
tos ev er
fromcne nt
Client
MyReporter
zeichnet
HTIP-Anfrage und
-Antwort auf
MyReporter
Port 40000
toClient
Icgpeqc; l
~
Server
fromServer
Port 50000
~peopcqoe
~
Das Programm kann wie folgt gestartet werden (Kommando in
einer Zeile}
java -cp bln MyReporter <localPort> <remoteHost> <remotePort>
<logRequest> <logResponse>
Beispiel,
java -cp bin MyReporter 40000 localhost 50000 10gReques t.txt
logResponse .txt
5.7
Aufgaben
1.
Entwickeln Sie ein Programm, mit dem Dateien vom Webserver mittels HTTP herunter geladen werden können. Nutzen Sie hierzu die Socket-Programmierung mit TCP (siehe
Kapitel 4).
2.
Realisieren Sie eine Variante zum Downloadprogramm in
Aufgabe 1, indem Sie die folgenden Methoden der Klassen
URL und URLConnecti on benutzen.
Die Klasse java. net . URL repräsentiert einen Uniform Re-
source Locator.
5.7 Aufgaben
URL(String spec) throws MaiformedU RL Exception
erzeugt ein URL-Objekt aus der Zeichenkette spec. java. net.
Ma lformedURL Exception ist von ja va. i o. IO Excep ti on abgeleitet.
Die URL-Methode
URLConnectlon openConnection() throws IOExcept l on
liefert ein java. net. URLConnecti on-Objekt, das eine Verbindung zu einer durch den URL adressierten Ressource repräsentiert.
URLConnect i on-Methoden:
int getContentLength ()
liefert den Wert des Antwortparameters Content Length.
Input St re am getlnputStream() throws IOExcept i on
liefert einen Eingabestrom zum Lesen über diese Verbindung .
3.
Entwickeln Sie für den erweiterten Webserver XMlnl WebSer ver aus Kapitel 5.5 eine Klasse Buecherllste, deren Methode getTable die zu einem vom Benutzer in einem Formular eingegebenen Wort Angaben zu denjenigen Büchern
aus einer Bücher-Datenbank anzeigt, deren Titel dieses
Wort enthalten. Die Daten sollen in einer HTML-Tabelle
dargestellt werden.
Entwickeln Sie eine weitere Methode, die das Ergebnis im
XML-Forrnat ausgibt.
4.
Entwickeln Sie mit Hilfe der in Aufgabe 2 genannten Methoden die Klasse BuecherCl l ent, die die Bücherliste aus
Aufgabe 3 im XML-Format anfordert und in einer Datei
(result.xml) speichert.
Aufrufbeispiel (Kommando in einer Zeile}
ja va -cp bin BuecherCiient iocaihost 50000
result xm i
prog/ Buecheriiste/get XML?titei~Topkapi
203
Herunterladen