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