// Netzprogrammierung // Gruppe 01 (Übung 1) // Aufgabe 1-4 /* ProxyServer.java Anmerkungen: - Dem Proxyserver können auf der Kommandozeile optional Port und Debug Level als int-Werte übergeben werden (Silent Mode, nur Header, etc.). Als default werden Port 10000 und Debug Level 3 genutzt. Konzepte: - Die Eingabeströme von Client und v.a. Server werden über binäre Inputstreams abgewickelt, um Bilder und andere Binärdaten zu erhalten - Aus dem Client-Request werden Server und Port geparst, so kann bspw. auch http über Port 8080 abgefragt werden - Der vom Client entgegengenommene Header wird teilweise bereinigt, da er sich an einen Proxy richtet (Zeile 148 ff.) - Die Antwort vom Server wird über die private Methode readLine() (Z. 242 ff.) abgewickelt, so kann der InputStream komfortabel gehandhabt werden - Die empfangenen Headerzeilen werden aufsummiert und durch die angeforderten Ressourcen geteilt. Das Ergebnis wird über den Header an den Client gesendet (Z. 190 ff.) - Die meisten Exceptions werden nur abgefangen, der Proxyserver läuft danach wieder an, da die meisten Fehler durch Abbruch der Verbindung durch den Client u.ä. entstehen, bemerkt man dies im Betrieb i.d.R. nicht - Die einzige gesondert behandelte Exception ist die Connection Exception (Z. 214): Tritt sie auf, wird HTTP-Fehlercode 500 an den Client übermittelt */ package uebung01.aufgabe04; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.net.ConnectException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; import java.util.Iterator; import java.util.LinkedList; /** * Ein einfacher Proxy-Server, der die durchschnittliche Anzahl empfangener Headerzeilen ermittelt. * * @author Tilman Walther */ public class ProxyServer { // da unser einfacher Proxy die content-length nicht beruecksichtigt (die // auch nicht von jedem Server gesendet wird), muessen Verbindungen oft // per Timeout beendet werden. final int SERVER_TIMEOUT = 2500; // da Java den Linefeed dem System anpasst, nutzen wir einen // String, der sich nach dem HTTP-Protokoll richtet final String LF = new String(""+((char) 13)+((char) 10)); long headerLineCount = 0; long itemCount = 0; StringBuffer lineBuffer = new StringBuffer(600); public ProxyServer(int port, int message) throws IOException { System.out.println("PROXY: Starting ProxyServer on port "+port); ServerSocket proxySocket = new ServerSocket(port); Socket client; BufferedInputStream clientIn; PrintStream clientOut; Socket server; BufferedInputStream serverIn; PrintStream serverOut; String request; String domain; int serverport; LinkedList requestStrings = new LinkedList(); String s; byte[] buffer = new byte[1024]; int in1; long headerLineAverage; while (true) { if (message > 1) System.out.println("PROXY: Listening... (press Ctrl+C to end)"); client = proxySocket.accept(); clientIn = new BufferedInputStream(client.getInputStream()); clientOut = new PrintStream(client.getOutputStream(), true); try { // Anfrage vom Browser holen s = readLine(clientIn); // Ressource extrahieren request = s.substring(0, s.indexOf(" ")+1) + s.substring(s.indexOf("/", s.indexOf("//")+2)); // Domain und Port extrahieren domain = s.substring(s.indexOf("//")+2); domain = domain.substring(0, domain.indexOf("/")); if ((in1 = domain.indexOf((int) ':')) > -1 ) { serverport = Integer.parseInt(domain.substring(in1+1)); domain = domain.substring(0, in1); } else { serverport = 80; } if (message > 1) System.out.print("PROXY: Request from client: "+s); // Restlichen Anfrage-Header holen requestStrings.clear(); do { s = readLine(clientIn); requestStrings.add(s); if (message > 4) System.out.print(" "+s); } while (!s.equals(LF)); long time = System.currentTimeMillis(); try { if (message > 1) System.out.println("PROXY: Connecting server "+domain+":"+serverport); server = new Socket(domain, serverport); server.setSoTimeout(SERVER_TIMEOUT); serverIn = new BufferedInputStream(server.getInputStream()); serverOut = new PrintStream(server.getOutputStream(), true); if (message > 1) System.out.print("PROXY: Sending request to server: "+request); serverOut.print(request); // Angepassten Header an Server schicken Iterator iter = requestStrings.iterator(); while (iter.hasNext()) { s = (String) iter.next(); if (s.startsWith("Proxy-Connection")) { serverOut.print("Connection: close"+LF); if (message > 2) System.out.print(" Connection: close"+LF); } else if (s.startsWith("Transfer-Encoding")) { serverOut.print("Transfer-Encoding: identity"+LF); if (message > 2) System.out.print(" Transfer-Encoding: identity"+LF); } else if (s.startsWith("Accept-Encoding")) { s = s.substring(0,(s.length()-2)).concat(",identity"+LF); //s = "Accept-Encoding: identity"+LF; serverOut.print(s); if (message > 2) System.out.print(" "+s); } else { serverOut.print(s); if (message > 2) System.out.print(" "+s); } } // Antwort vom Server entgegennehmen try { // Header empfangen s = readLine(serverIn); // --> Hier koennen HTTP response codes abgefangen werden if (message > 1) System.out.print("PROXY: Receiving data from server: "+s); itemCount++; while (!s.equals(LF)) { headerLineCount++; if (message > 2) System.out.print(" "+s); clientOut.print(s); s = readLine(serverIn); } // durchschnittliche Anzahl der Headerzeilen berechnen headerLineAverage = headerLineCount / itemCount; if (message > 2) System.out.print(" X-mean-headercount: "+ headerLineAverage + LF + LF); clientOut.print("X-mean-headercount: "+ headerLineAverage + LF + LF); // Daten empfangen while ((in1 = serverIn.read(buffer)) > 0) { if (message > 3) System.out.write(buffer, 0, in1); clientOut.write(buffer, 0, in1); } } catch (SocketTimeoutException ste) { if (message > 1) System.err.println("PROXY: Connection timed out"); } if (message > 1) System.out.println("PROXY: Time "+(System.currentTimeMillis()-time)+" ms"); if (message > 1) System.out.println("PROXY: Unconnect...\n"); serverIn.close(); serverOut.close(); server.close(); } catch (ConnectException ce) { if (message > 0) System.err.println("ConnectException, sending code 500 to client"); buffer = ("HTTP/1.0 500\nContent Type: text/plain\n\nError while connecting server: "+domain+"\n").getBytes(); clientOut.write(buffer, 0, ("HTTP/1.0 500\nContent Type: text/plain\n\nError while connecting server: "+domain+"\n").length()); } clientIn.close(); clientOut.close(); client.close(); } catch (SocketException se) { if (message > 4) System.err.println("SocketException"); } catch (Exception e) { // nothing, just try again if (message > 4) e.printStackTrace(); } } //proxySocket.close(); } /** * Liest eine Zeile vom binären Eingabestrom. * (Auf diese Weise bleiben binäre Daten korrekt erhalten.) */ private String readLine(InputStream in) throws IOException { int i; lineBuffer.setLength(0); while (((i = in.read()) > 0) && (i != 13)){ lineBuffer.append((char) i); } // nach HTTP 1.1 müssten die Zeilenenden mit #13#10 markiert sein // (durch die Aufteilung schlucken wir auch Unix/Macintosh) if (i == 13) { lineBuffer.append((char) 13); in.mark(1); if (in.read() != 10) in.reset(); else lineBuffer.append((char) 10); } return lineBuffer.toString(); } public static void main(String[] args) throws IOException { int port = 10000; // default port int message = 3; // default output level if (args.length > 2) { System.out.println("usage: MultServer [port number] [output level]"); } else { if (args.length > 0) { try { port = Integer.parseInt(args[0]); } catch (NumberFormatException nfe) { System.out.println(args[0]+" is not a valid port number.\nusage: MultServer [port number] [output level]"); System.exit(1); } } if (args.length > 1) { try { port = Integer.parseInt(args[1]); } catch (NumberFormatException nfe) { System.out.println(args[0]+" is not a valid port number.\nusage: MultServer [port number] [output level]"); System.exit(1); } } } new ProxyServer(port, message); } }