HTTP-Proxyserver programmieren
Andreas Korthaus
- java
Hallo!
Da ich mich mit Java und Kommunikation über HTTP beschäftigen muss, habe ich mir überlegt zu "Übungszwecken" einen transparenten HTTP-Proxy zu programmieren. Gut, ich habe mir das RFC 1919 angesehen, naja, wenn ich es richtig machen will wird das wohl recht kompliziert, aber es soll ja nur eine Übung sein, daher will ich nur dass es grundlegend funktioniert, also dass ich den Proxy im Browser eintragen kann und mich normal im Internet bewegen kann.
Im Prinzip funktioniert das auch schon, aber da sind noch einige Probleme die ich irgendwie nicht gelöst bekomme. Vor allem habe ich noch Probleme mit dem Lesen und Schreiben der Streams bei Binärdaten (Bilder, gzip...), ein anderes Problem ist das Parsen der HTTP-Header, da diese ja verändert/ausgetauscht werden müssen. Zur Vereinfachung habe ich mir überlegt, dass ich zunächst keine persistenten Verbindungen implementieren werde.
Erstmal zum Parsing-Problem: Zum lesen verwende ich
BufferedReader in = new BufferedReader(new InputStreamReader(sverb.getInputStream()));
Dann lese ich in einer Schleife mit
line = in.readLine(); (was vermutlich für die Probleme mit Binärdaten verantwortlich ist)
Die erste Zeile des HTTP-Requests vom Browser parse ich so
StringTokenizer tokenizer = new StringTokenizer( line ); methode = tokenizer.nextToken(); url = tokenizer.nextToken(); proto = tokenizer.nextToken();
URL u = new URL( url ); host = u.getHost(); path = u.getPath(); query = u.getQuery();
Die Frage ist jetzt, wie kann ich die restlichen Header-Zeilen parsen. Bis jetzt schicke ich die einfach unverändert weiter, aber z.B. "proxy-conection", muss ich rauslöschen und dafür "connection" einfügen. Ich würde aber auch gerne andere Header lesen, entfernen oder verändern können. Ich kann einfach nicht glauben dass das ganze nur so kompliziert geht wie ich es in der ersten Zeile des Requests gemacht habe, außerdem ist das fehleranfällig. Es gibt zwar die spezielle URL und URLConnection Klasse, aber die sind IMHO nicht flexibel genug.
Das Problem ist grundsätzlicher Art. Sollte ich den Request zeilenweise parsen, oder sollte ich das anders einlesen? Wie bekomme ich die einzelnen Header am einfachsten aus dem Request/Response?
Das 2. Problem besteht darin, dass binbäre Daten nicht korrekt übertragen werden. Das betrifft also nur den eingehenden Stream vom Webserver und den ausgehenden Stream an den Client. Bisher verwende ich sowohl readline() zum lesen und println() zum schreiben auf den Stream. Wobei, theoretisch müsste readline() binäre Daten ja in einer Zeile einlesen, und println() dann auch als eine Zeile ausgeben, Problem würe dann evtl. ein Zeilenumbruch zu viel am Ende. Wenn Zeichen innerhalb binärer Daten als Umbruch interpretiert würden, müsste das doch entsprechend bei println() genaus wiederhergestellt werden. Naja, aber man sollte es wohl lieber anders machen, kann man nicht den eingehenden Stream direkt zum Client weiterleiten?
Naja, die Konzepte von Streams und Tolkens sind noch recht neu für mich, ich bin mir sicher dass das sehr mächtig ist und sich das ganze erheblich schöner erledigen lässt, nur komme ich nicht so recht dahinter, wie ;-)
Java kommt hier zwar nicht ganz so oft vor, aber ich hoffe das es hier den ein oder anderne gibt der vielleicht ne kleinen Tipp auf Lager hat :)
Viele Grüße Andreas
PS: Bisher sieht der komplette Code wie folgt aus:
import java.io.; import java.net.; import java.util.*;
public class proxy {
public static void main(String args[]) {
int anz = 1; try { ServerSocket ss = new ServerSocket(7788); while(true) { Socket sverb = ss.accept(); System.out.println(" Verbindung " + anz); new ServerThread(sverb,anz).start(); anz++; } } catch (Exception e) { System.out.println(e); } } }
class ServerThread extends Thread { Socket sverb; int nverb;
ServerThread(Socket s , int n) { this.sverb = s; this.nverb = n; }
public void run () {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(sverb.getInputStream()));
PrintStream out = new PrintStream(sverb.getOutputStream());
boolean weiter = true; boolean firstline = true; String retline = ""; String line = ""; String methode = ""; String url = ""; String host = ""; String path = ""; String query = ""; String proto = ""; String request = "";
while (weiter) {
line = in.readLine(); if (firstline) {
StringTokenizer tokenizer = new StringTokenizer( line ); methode = tokenizer.nextToken(); url = tokenizer.nextToken(); proto = tokenizer.nextToken();
URL u = new URL( url ); host = u.getHost(); path = u.getPath(); query = u.getQuery();
firstline = false; continue; } else { if (line.trim().equals("")) {
try {
Socket t = new Socket( host, 80 );
BufferedReader from_serv = new BufferedReader( new InputStreamReader( t.getInputStream()));
PrintStream to_serv = new PrintStream( t.getOutputStream() ); to_serv.println( methode + " " + path + "?" + query + " " + proto + "\r\nhost: " + request ); while (true) { retline = from_serv.readLine(); if (retline == null) { break; } out.println( retline ); } } catch (IOException e) { System.out.println(e); }
break; } else { request += line + "\r\n"; continue; } } } sverb.close(); } catch (IOException e) { System.out.println(e); } } }
Hallo Andreas,
Da ich mich mit Java und Kommunikation über HTTP beschäftigen muss, habe ich mir überlegt zu "Übungszwecken" einen transparenten HTTP-Proxy zu programmieren. Gut, ich habe mir das RFC 1919 angesehen, naja, wenn ich es richtig machen will wird das wohl recht kompliziert, aber es soll ja nur eine Übung sein, daher will ich nur dass es grundlegend funktioniert, also dass ich den Proxy im Browser eintragen kann und mich normal im Internet bewegen kann.
Hast Du den Ehrgeiz, den Code selbst aus den RFCs abzuleiten? Ich gehe in solchen Fällen immer von funktionierenden Beispielen aus. Daran lässt sich am einfachsten die gundlegende Funktion erkennen. Speziell für Java sollte es da doch was geben.
ungetestet:
http://www.nsftools.com/tips/JavaTips.htm#javaproxy
http://www.nsftools.com/tips/jProxy.java
viele Grüße
Axel
Hallo Alex!
Hast Du den Ehrgeiz, den Code selbst aus den RFCs abzuleiten?
Das nicht, ich habe ja nicht vor die Menscheit mit noch einem Proxyserver zu beglücken ;-) Es geht mir nur darum ein bisschen an diesem Beispiel zu lernen, da man hier Streams auf verschiedene Weise einsetzt, dazu Tokens... halt so Sachen die ich von PHP nicht kenne. Und das ganze dann noch kombiniert mit HTTP Client und Server Funktionalitäten. Und erst wenn man wirklich was praktisches probiert sieht man was man nicht kann ;-)
Ich gehe in solchen Fällen immer von funktionierenden Beispielen aus.
Ja, das mache ich normalerweise aus, aber was bringt mir dass wenn ich Code verwende den ich nichtmal so richtig verstehe bzw. in zukünftigen Projekten selber verwenden könnte?
Daran lässt sich am einfachsten die gundlegende Funktion erkennen.
Das stimmt, angucken würde ich mir gerne mal was, aber ehrlich gesagt habe ich bisher wirkoich _überhaupt_ nichts zu dem Thema gefunden, sondern im Prinzip alles selber zusammengebastelt. Naja, dem entsprechend schlecht funktioniet es.
Wenn ich das mal mit PHP vergleiche, wen ich da einen HTTP-Header habe, dann würde ich den Erstmal zeilenweise in ein Array lesen, und dann die Elemente mit explode entsprechend zerteilen. Ja, in Java gibt es auch Arrays, und es gibt auch Split, aber zum einen finde ich das in PHP einfacher, und zum anderen denke ich das man in Java durch entsprechende Methoden zum lesen von Streams bzw. die Verwendung von Tokens andere Möglichkeitenhat, die ich gerne mal kennenlernen würde, nur eben nicht so recht weiß ich wie man das macht.
Speziell für Java sollte es da doch was geben.
ungetestet:
http://www.nsftools.com/tips/JavaTips.htm#javaproxy
http://www.nsftools.com/tips/jProxy.java
Vielen Dank, das werde ich mir gleich mal ansehen.
Eine 2. "Unart" die ich mir bei PHP angewöhnt habe ist wie ich mit Datenströmen umgehe, da lese ich normalerweise auch alles zeilenweise ein, kopiere am besten jeden einzelne geparste Zeile mindestens einmal in eine andere Variable(Copy by Value), lese splitte das dann nochmal in einen Array, oder bearbeite es mit regulären Ausdrücken... und heute wird das ja noch schlimer, da wird oft erst die ganze Dateit in eien Array kopiert, der dann Element für Element abgearbeitet wird. Ich denke mit den Stream-Klassen und Tokenizer-Klassen in Java kann man das effektiver machen, nur mache ich das jetzt _mit_ diesen Klassen eigentlich genauso wie ich es vorher mit PHP gemacht habe - das ist mein Problem. Irgendwie kann ich die Vorteile der vorhandenen Methoden noch nicht in der Praxis umsetzen.
Viele Grüße
Andreas
Hallo,
du solltest wohl nur die Header einschließlich Content-Type mit readline einlesen (am Ende des Headers sind wohl zwei Newline Zeichen statt eins, daran sollte man das erkennen. Bei meinen Versuchen war Content Type immer der letzte Header Eintrag, keine Ahnung ob das immer so sein muss).
den Rest kannst du z.B. mit int read(byte[] b), also NICHT als Text, lesen - und mit write beim OutputStream wieder schreiben.
Die Einzelheiten wie immer im Sun JavaDoc.
Dominik
Hallo!
du solltest wohl nur die Header einschließlich Content-Type mit readline einlesen (am Ende des Headers sind wohl zwei Newline Zeichen statt eins, daran sollte man das erkennen. Bei meinen Versuchen war Content Type immer der letzte Header Eintrag, keine Ahnung ob das immer so sein muss).
den Rest kannst du z.B. mit int read(byte[] b), also NICHT als Text, lesen - und mit write beim OutputStream wieder schreiben.
Die Einzelheiten wie immer im Sun JavaDoc.
Ja, so mache ich das inzwischen auch, und es funktioniert im Prinzip super. Ich kann den Proxy schon fürs alltägliche Surfen nehmen nur kann ich noch keine Daten per POST verschicken, aber das werde ich auch noch einbauen ;-)
Aber jetzt habe ich ein anderes Problem, und zwar würde ich gerne den HTML-Quelltext verändern können. Das heißt ich will z.B. jeden enthaltenen String "Computer" gegen "Kaltschrank" ersetzen. Nur ist das dann wieder komplizierter als ich vermutet hätte.
So funktioniert es wunderbar, indem ich die Bytes direkt vom Server an den Client weiterreiche:
byte[] buf = new byte[4096];
int bytesIn = 0;
while ( ((byteCount < contentLength) || (waitForDisconnect))
&& ((bytesIn = from_serv.read(buf)) >= 0) )
{
out.write(buf, 0, bytesIn);
byteCount += bytesIn;
}
Nur so funktioniert es nicht mehr richtig:
byte[] buf = new byte[4096];
int bytesIn = 0;
String str_buf = "";
if (contentType.equals("text/html")) {
while ( ((byteCount < contentLength) || (waitForDisconnect))
&& ((bytesIn = from_serv.read(buf)) >= 0) )
{
String str = new String(buf);
str = str.replaceAll("Computer", "Kaltschrank");
out.write(str.getBytes(), 0, str.length());
byteCount += bytesIn;
}
}
Hierbei wandele ich in jedem Durchlauf den Puffer-Array(Bytes) in einen String um, wende die Methode replaceAll() drauf an, ermottel dei Stringlenge, wandle es wieder in einen Byte-Array um und schreibe diesen mit der Länge des Strings in den Stream zum Client.
Es wird auch ersetzt, aber leider hat die Seite dann viele Fehler und wird meist nicht zu Ende übertragen. Ich stelle auch sicher dass es sich bei dem hereinkommenden Stream um unkomprimierten HTML-Quellcode handelt, nur muss ich den ja dann in Strings umwandeln um replaceAll verwenden zu können, und vermutlich stimmt dann irgendwas mit der Länge der Bytes nicht mehr. Ist nicht jedes Zeichen im String ein Byte? Oder wie ermittele ich die Länge des Byte-Array?
Ein Problem ist auch, dass ich ja willkürlich den Code zerteile. Daher sollte ich wohl lieber alle bytes in einen Puffer schreiben, nur ist das so besonders effektiv? Das können nämlich schonmal ein paar Bytes mehr werden. Naja, dann könnte ich im Sonderfall von HTML-Daten ja doch per readline einlesen und schreiben, dann könnte ich wenigstens direkt auf String-Ebene arbeiten, ohne die Konvertierungen. Nur war das mit den Strings _sehr_ viel langsamer als jetzt mit den bytes.
Wie würdest Du das machen?
Viele Grüße
Andreas
Hallo,
hmm, die einfachen Probleme hast du ja schon gelöst...
Wenn du es schaffst HTTP POST zu implementieren wäre der Proxy wohl ganz brauchbar; ich hätte auch interesse daran :-)
String Funktionen sind relativ langsam, am besten du nimmst immer wenn du viele (spätestens ab einigen 100) Stringoperationen durchführst StringBuffer. In deinem Fall würde es die Sache wohl aber nicht beschleunigen, weil du immer neue String Objekte erzeugst und nicht viele Operationen auf einem Objekt ausführst.
Ich bin mir nicht ganz sicher warum es nicht klappt.
Ich vermute es liegt daran, dass Java Unicode benutzt und ein Zeichen immer zwei Byte belegt.
Ich denke der write Befehl schreibt dann nur die erste Hälfte des Strings raus...
Lag ich richtig?
Dominik
Hi!
hmm, die einfachen Probleme hast du ja schon gelöst...
;-)
Wenn du es schaffst HTTP POST zu implementieren wäre der Proxy wohl ganz brauchbar; ich hätte auch interesse daran :-)
Naja, aber vertrauen würde ich dem nicht, aber da ist ja gerade das lustige dran, ich parse alle Request Header einzelnd und kann dann entscheiden ob ich die weiterleite, verwerfe oder verändere.
Post klappt inzwischen auch, aber leider kann ich da snur mit einem char Array mit fester Länge machen udn ich bekomme den String nicht auf die richtige Länge gekürzt, also schicke ich andauernd 13 KB durchs netz mit fast nur Leerzeichen. Naja.
String Funktionen sind relativ langsam, am besten du nimmst immer wenn du viele (spätestens ab einigen 100) Stringoperationen durchführst StringBuffer. In deinem Fall würde es die Sache wohl aber nicht beschleunigen, weil du immer neue String Objekte erzeugst und nicht viele Operationen auf einem Objekt ausführst.
Werde ich Berücksichtigen.
Ich bin mir nicht ganz sicher warum es nicht klappt. Ich vermute es liegt daran, dass Java Unicode benutzt und ein Zeichen immer zwei Byte belegt. Ich denke der write Befehl schreibt dann nur die erste Hälfte des Strings raus... Lag ich richtig?
ich glaube nicht. Byte ist ein Byte, Char sind 2 Byte. Das ist irgendwie sehr diffus, ich habe es jetzt anders probiert indem ich eien 2. byte-Buffer für den veränderten String verwende, aber das geht genauso schlecht.
Naja, ich habe das komplette Programm mal angehängt, vielleicht verstehst Du ja warum das so nicht geht, so wie es ist funktioniert das ganze bis auf das kleien POST-Problem und das Austauschen der Wörter wunderbar, letzteres habe ich unten auskommentiert, das müsste dann so lauten(nur erzeugt diese Variante nur noch Kauderwelsch, liegt vermutlich an der Konvertierung in Bytes?!):
if (contentType.equals("text/html")) { while ( ((byteCount < contentLength) || (waitForDisconnect)) && ((bytesIn = from_serv.read(buf)) >= 0) ) {
String str = new String(buf); str = str.replaceAll("Computer", "Kaltschrank"); buf2 = str.getBytes();
out.write(buf2, 0, buf2.length); byteCount += bytesIn;
} }
So, hier das Programm, ist noch sehr durcheinander, liegt vor allem daran dass ich wild rumprobiert habe das Problem auf verschiedenen Wegen zu lösen ;-)
Grüße Andreas
import java.io.; import java.net.; import java.util.*;
public class proxy2 {
public static void main(String args[]) {
int anz = 1; try { ServerSocket ss = new ServerSocket(7788); while(true) { Socket sverb = ss.accept(); System.out.println(" Verbindung " + anz); new ServerThread(sverb,anz).start(); anz++; } } catch (Exception e) { System.out.println(e); } } }
class ServerThread extends Thread { Socket sverb; int nverb;
ServerThread(Socket s , int n) { this.sverb = s; this.nverb = n; }
public void run () {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(sverb.getInputStream()));
//PrintStream out = // new PrintStream(sverb.getOutputStream());
//BufferedInputStream clientIn = new BufferedInputStream(sverb.getInputStream()); BufferedOutputStream out = new BufferedOutputStream(sverb.getOutputStream());
boolean weiter = true; String retline = ""; String line = ""; String methode = ""; String url = ""; String host = ""; String path = ""; String query = ""; String proto = ""; String client_request = ""; String post_str = ""; char post_data[] = new char[13000]; int pos = -1; int contentLength = 0;
StringTokenizer tokenizer = new StringTokenizer( in.readLine() ); methode = tokenizer.nextToken(); url = tokenizer.nextToken(); proto = tokenizer.nextToken();
URL u = new URL( url ); host = u.getHost(); path = u.getPath(); query = u.getQuery();
client_request = methode + " " + path ; if (query != null) { client_request += "?" + query ; } // client_request += " " + proto + "\r\n"; client_request += " HTTP/1.0\r\n";
while ((line = in.readLine()) != null) {
// the header ends at the first blank line if (line.length() == 0) { if (methode.toLowerCase().equals("post")) { //post_data = in.read () //System.out.println("POST"); //post_data.length = contentLength; //char post_data[] = new char[contentLength]; in.read(post_data, 0, contentLength); //System.out.println(post_data); } break; }
pos = line.toLowerCase().indexOf("host:"); if (pos >= 0) { //host = line.substring(pos + 5).trim()); //request += "host: " + host + "\r\n"; client_request += line + "\r\n"; }
pos = line.toLowerCase().indexOf("content-length:"); if (pos >= 0) { contentLength = Integer.parseInt(line.substring(pos + 15).trim()); client_request += line + "\r\n"; }
pos = line.toLowerCase().indexOf("user-agent:"); if (pos >= 0) { client_request += line + "\r\n"; }
pos = line.toLowerCase().indexOf("authorization:"); if (pos >= 0) { client_request += line + "\r\n"; }
pos = line.toLowerCase().indexOf("accept-encoding:"); if (pos >= 0) { client_request += line + "\r\n"; } /* pos = line.toLowerCase().indexOf("accept-language:"); if (pos >= 0) { client_request += line + "\r\n"; }
pos = line.toLowerCase().indexOf("accept-charset:"); if (pos >= 0) { client_request += line + "\r\n"; }
pos = line.toLowerCase().indexOf("cookie:"); if (pos >= 0) { client_request += line + "\r\n"; }
pos = line.toLowerCase().indexOf("referer:"); if (pos >= 0) { client_request += line + "\r\n"; } */ } client_request += "Connection: close\r\n"; client_request += "\r\n"; if (methode.toLowerCase().equals("post")) { post_str += new String(post_data); client_request += post_str.substring(0, contentLength).trim(); }
try { // Öffne Socket-Verbindung zum Webserver Socket t = new Socket( host, 80 );
//BufferedReader from_serv = new BufferedReader( // new InputStreamReader( t.getInputStream())); BufferedInputStream from_serv = new BufferedInputStream(t.getInputStream());
PrintStream to_serv = new PrintStream( t.getOutputStream() );
// Sende Request weiter an Webserver to_serv.print( client_request ); System.out.print( post_str ); /* String client_response = "";
while (( retline = from_serv.readLine()) != null) { client_response += retline + "\r\n"; } out.print( client_response ); //System.out.print( client_response ); */ StringBuffer header = new StringBuffer(""); String data = ""; String firstline = ""; String contentType = ""; int responseCode = -1; contentLength = 0; int byteCount = 0;
firstline = readLine(from_serv); System.out.print( firstline ); pos = firstline.toLowerCase().indexOf(" "); if (pos >= 0) { responseCode = Integer.parseInt(firstline.substring(pos, pos + 4).trim()); } header.append(firstline + "\r\n"); while ((data = readLine(from_serv)) != null) { // the header ends at the first blank line if (data.length() == 0) break; header.append(data + "\r\n");
// check for the Content-Length header pos = data.toLowerCase().indexOf("content-length:"); if (pos >= 0) contentLength = Integer.parseInt(data.substring(pos + 15).trim()); pos = data.toLowerCase().indexOf("content-type:"); if (pos >= 0) { if(data.length() <= 24) { contentType = data.substring(pos + 14).trim(); } else { contentType = data.substring(pos + 14, pos + 23).trim(); } System.out.println( "contentType:" + contentType );} }
// add a blank line to terminate the header info header.append("Connection: close\r\n"); header.append("\r\n");
// convert the header to a byte array, and write it to our stream out.write(header.toString().getBytes(), 0, header.length());
if ((responseCode != 200) && (contentLength == 0)) { out.flush(); sverb.close(); t.close(); System.out.println (" Status: " + responseCode ); //System.out.println (header ); return; } boolean waitForDisconnect = true; if (contentLength > 0) waitForDisconnect = false; try { byte[] buf = new byte[4096]; byte[] buf2 = new byte[5000]; int bytesIn = 0; //String str_buf = ""; if (contentType.equals("text/html")) { while ( ((byteCount < contentLength) || (waitForDisconnect)) && ((bytesIn = from_serv.read(buf)) >= 0) ) { /out.write(buf, 0, bytesIn); String str = new String(buf); str = str.replaceAll("Computer", "Kaltschrank"); //System.out.println(str.length()); buf2 = str.getBytes(); System.out.println(buf2.length); out.write(buf2, 0, buf2.length); byteCount += bytesIn;/ out.write(buf, 0, bytesIn); byteCount += bytesIn; } } else { while ( ((byteCount < contentLength) || (waitForDisconnect)) && ((bytesIn = from_serv.read(buf)) >= 0) ) { out.write(buf, 0, bytesIn); byteCount += bytesIn; } } } catch (Exception e) { String errMsg = "Error getting HTTP body: " + e; System.out.println(errMsg); System.out.println(host + path); }
t.close(); //flush the OutputStream and return try { out.flush(); } catch (Exception e) {} } catch (IOException e) { System.out.println("1" + e); } sverb.close(); } catch (IOException e) { System.out.println("2" + e); } }
private String readLine (InputStream in) { // reads a line of text from an InputStream StringBuffer data = new StringBuffer(""); int c;
try { // if we have nothing to read, just return null in.mark(1); if (in.read() == -1) return null; else in.reset();
while ((c = in.read()) >= 0) { // check for an end-of-line character if ((c == 0) || (c == 10) || (c == 13)) break; else data.append((char)c); }
// deal with the case where the end-of-line terminator is \r\n if (c == 13) { in.mark(1); if (in.read() != 10) in.reset(); } } catch (Exception e) { System.out.println("Error getting header: " + e); }
// and return what we have return data.toString(); } }
Hallo,
Aber jetzt habe ich ein anderes Problem, und zwar würde ich gerne den HTML-Quelltext verändern können. Das heißt ich will z.B. jeden enthaltenen String "Computer" gegen "Kaltschrank" ersetzen. Nur ist das dann wieder komplizierter als ich vermutet hätte.
So funktioniert es wunderbar, indem ich die Bytes direkt vom Server an den Client weiterreiche:
Nur so funktioniert es nicht mehr richtig:
byte[] buf = new byte[4096];
int bytesIn = 0;
String str_buf = "";
if (contentType.equals("text/html")) {
while ( ((byteCount < contentLength) || (waitForDisconnect))
&& ((bytesIn = from_serv.read(buf)) >= 0) )
{
String str = new String(buf);
str = str.replaceAll("Computer", "Kaltschrank");
out.write(str.getBytes(), 0, str.length());
byteCount += bytesIn;
}
}
Ist nicht jedes Zeichen im String ein Byte?
Nicht immer. Das ist abhängig vom Charset. Bei UTF-8 hat ein Zeichen manchmal zwei Byte.
class Main {
public static void main(String[] args) {
try {
String str = new String("01234\u20AC56789");
byte[] def = str.getBytes();
byte[] utf = str.getBytes("UTF-8");
byte[] iso = str.getBytes("ISO-8859-1");
System.out.println(str);
System.out.println(str.length());
System.out.println(def.length);
System.out.println(utf.length);
System.out.println(iso.length);
} catch (java.io.UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
Allerdings ist das, wie ich glaube, nicht Dein Problem. Das liegt wohl darin, dass Du _nachträglich_ durch Deine Manipulationen die contentLength veränderst, welche Du aber schon im Header übertragen hast.
viele Grüße
Axel
Allerdings ist das, wie ich glaube, nicht Dein Problem. Das liegt wohl darin, dass Du _nachträglich_ durch Deine Manipulationen die contentLength veränderst, welche Du aber schon im Header übertragen hast.
ich habe mir gerade mal so einen HTTP Header angesehen. Bei text/html gibt es wohl keine Content length? Evtl. nur bei Binärdaten?
Dominik
Hallo,
Allerdings ist das, wie ich glaube, nicht Dein Problem. Das liegt wohl darin, dass Du _nachträglich_ durch Deine Manipulationen die contentLength veränderst, welche Du aber schon im Header übertragen hast.
ich habe mir gerade mal so einen HTTP Header angesehen. Bei text/html gibt es wohl keine Content length? Evtl. nur bei Binärdaten?
Ja, stimmt. Ich nehme alles zurück und behaupte das Gegentel ;-)) Bei text/html ist waitForDisconnect gleich true. Daran liegt es also auch nicht.
Dann kann ich mir nur denken, dass da zwischen in.read() und out.write() zuviel Zeit vergeht, die Manipulation also zu lange dauert. Da könnte man eventuell die Puffergröße mal verkleinern:
byte[] buf = new byte[1024];
Damit sollte das replaceAll() eventuell schneller werden.
viele Grüße
Axel
Hallo,
Dann kann ich mir nur denken, dass da zwischen in.read() und out.write() zuviel Zeit vergeht, die Manipulation also zu lange dauert. Da könnte man eventuell die Puffergröße mal verkleinern:
byte[] buf = new byte[1024];
Damit sollte das replaceAll() eventuell schneller werden.
Wo ich das jetzt nochmal lese, fällt mir ein rundsätzlicher Fehler in der Idee der Manipulation des Datenstromes auf:
Wer stellt denn sicher, dass die Worte, welche ersetzt werden sollen, wirklich vollständig im Puffer enthalten sind? Es könnte Folgendes auftreten:
buf1 = ...blah bluh blub Com
buf2 = puter blah ...
Hier wird das Wort Computer nicht ersetzt.
viele Grüße
Axel
Hallo!
Dann kann ich mir nur denken, dass da zwischen in.read() und out.write() zuviel Zeit vergeht, die Manipulation also zu lange dauert. Da könnte man eventuell die Puffergröße mal verkleinern:
Das glaube ich nicht. Wie groß sind HTML-Seiten? Was soll da so lange dauern? Und was sol daran schlimm sein? PHP ist siehr deutlich langsamer, aber da habe ich schon erheblich "wildere" Sachen gemacht ;-)
byte[] buf = new byte[1024];
Damit sollte das replaceAll() eventuell schneller werden.
Aber insgesamt langsamer, oder?
Wo ich das jetzt nochmal lese, fällt mir ein rundsätzlicher Fehler in der Idee der Manipulation des Datenstromes auf:
Wer stellt denn sicher, dass die Worte, welche ersetzt werden sollen, wirklich vollständig im Puffer enthalten sind? Es könnte Folgendes auftreten:
Niemand. Das war mir aber bewußt. Ich will duch nur das es funkitoniert ;-)
buf1 = ...blah bluh blub Com
buf2 = puter blah ...Hier wird das Wort Computer nicht ersetzt.
Ich sehe hier 3 Lösungsmöglichkeiten:
1. Ich lese erstmal alles in den Puffer, mache daraus einen String und verwende replace, konvertiere das wieder in bytes und schreibe es weiter.
2. Ich lese und schreibe die Daten nicht byteweise(nur bei content-type html), sondern zeilenweise mit readline als String, und verwende replace auf jede line, und schreibe den veränderten String dann wieder auf "out"
3. Ich schreieb eine replace-Funktion für Byt-Arrays, müsste ich die zu ersetzenden Strings in Bytes umwandeln, und dann den byte nach einer passenden "byte-Sequenz" abuchen, wobei, dann man kann da ja nicht so einfach einen längeren Array einfügen, oder? Nur denke ich wäre das das performanteste.
Ich habe unten mal die aktuele Version mitgepostet, muss man nur speichern, kompiliern und dann im Browser als Proxy localhost auf Port 7788 eintragen, das funktioniert dann auch weitgehend. Der Teil der Daten ersetzt ist auskommentiert.
Ich versuche jetzt aber nochmal "methode 2." ;-)
Denn so wie ich es zulezt gemacht habe bekomme ich mehr oder weniger Buchstabensalat raus.
Grüße
Andreas
Hallo,
Dann kann ich mir nur denken, dass da zwischen in.read() und out.write() zuviel Zeit vergeht, die Manipulation also zu lange dauert. Da könnte man eventuell die Puffergröße mal verkleinern:
Das glaube ich nicht. Wie groß sind HTML-Seiten? Was soll da so lange dauern? Und was sol daran schlimm sein? PHP ist siehr deutlich langsamer, aber da habe ich schon erheblich "wildere" Sachen gemacht ;-)
Ähm:
Bei PHP _weiß_ der Server von der Langsamkeit. Der Server liefert erst das durch PHP gelaufene HTML via HTTP aus. Der Client merkt von _dieser_ Langsamkeit nichts. Er sieht einen langsamen Server.
Bei einem Proxy denkt der Server, er liefere an einen User-Agent. Der Client meint, er erhielte von einem Server. Keiner der beiden nimmt Rücksicht auf Verzögerungen im Proxy.
byte[] buf = new byte[1024];
Damit sollte das replaceAll() eventuell schneller werden.
Aber insgesamt langsamer, oder?
Ja, aber der Stream wird insgesamt flüssiger. Nicht:
- Ich lese und schreibe die Daten nicht byteweise(nur bei content-type html), sondern zeilenweise mit readline als String, und verwende replace auf jede line, und schreibe den veränderten String dann wieder auf "out"
Ich versuche jetzt aber nochmal "methode 2." ;-)
Probiers aus.
viele Grüße
Axel
Hi!
Ähm:
Bei PHP _weiß_ der Server von der Langsamkeit. Der Server liefert erst das durch PHP gelaufene HTML via HTTP aus. Der Client merkt von _dieser_ Langsamkeit nichts. Er sieht einen langsamen Server.
Bei einem Proxy denkt der Server, er liefere an einen User-Agent. Der Client meint, er erhielte von einem Server. Keiner der beiden nimmt Rücksicht auf Verzögerungen im Proxy.
Das ist schon klar, ich meinte aber wenn ich mit PHP auf HTTP direkt zugreife, als Client. Nur versteeh ich nicht warum Verzögerungen ein problem sein sollen. Ich habe z.B. folgende Seite geladen: http://www.couponmountain.de/Computer_%26_Software_alles.html, da wurden fast. 200(!) Requests erzeugt, und Java hat so einige Sekunden mit 100% Prozessorauslastung gewerkelt. Das warem aber vor allem Bilder, und die übertrage ich ja eigentlich recht flott. Vielleicht sollte ich dann doch irgendwann über keep-Alive nachdenken, aber das wird dann schon schieriger, mal sehen.
Ich habe es jetzt in Gang bekommen, das ersetzen und umwandeln mit den Strings braucht sehr viele Resourcen. Das würde ich jetzt noch gerne optimieren ;-)
Das Problem lag meiner Meinung nach irgendwo bei den Längenangaben der Bytes, ich verstehe das Problem allerdings noch nicht.
Ich versuche jetzt aber nochmal "methode 2." ;-)
Probiers aus.
Gemacht:
Wenn ich jetzt bei altavista nach "Computer" suche bekomme ich als Ergebnis:
Kaltschrank - PCs und mehr zu Spitzenpreisen von DELL
CCC | Chaos Kaltschrank Club e.V.
[Home] Kaltschrank Zeitung Online
...
;-)
Hierzu war auf alle Fälle die Anpassung des Content-Length Headers notwendig, was ich "mal eben" in Quick-and-Dirty Manier gemacht habe :)
Naja, jetzt muss es nur noch schneller gehen. Und das POST-Problem, beheben.
ZUm POST Problem:
char post_data[] = new char[13000];
while ((line = in.readLine()) != null) {
// the header ends at the first blank line
if (line.length() == 0) {
if (methode.toLowerCase().equals("post")) {
in.read(post_data, 0, contentLength);
}
break;
}
[...]
}
if (methode.toLowerCase().equals("post")) {
post_str += new String(post_data);
client_request += post_str.substring(0, contentLength).trim();
}
Das Problem, ich habe jetzt einen 13000 Zeiche langen Char-Array, weil ich vorher nicht weiß wie lang die POST Daten sind, ich aber Vorher "post_data" initiieren muss. Ich dachte wenn ich das dann in einen Array übertage und mit trim() bearbeite dann wären die Leerzeichen weg, bei PHP ist das so, bei Java anscheinend nicht.
Wie kann ich das sonst machen?
und das andere Problem:
if (contentType.equals("text/html")) {
while ((data = readLine(from_serv)) != null) {
html_data += data.replaceAll("Computer", "Kaltschrank") + "\r\n";
}
// add a blank line to terminate the header info
header.append("Content-Length: " + html_data.length() + "\r\n");
header.append("Connection: close\r\n");
header.append("\r\n");
// convert the header to a byte array, and write it to our stream
out.write(header.toString().getBytes(), 0, header.length());
out.write(html_data.getBytes(), 0, html_data.length());
}
Ich meine, im Prinzip funktioniert das, nur eben nicht so besonders flott. Aber ich denke so viel schneller geht das einfach nicht, da ich ja nunmal die Daten in den String konvertieren muss, darauf replaceAll() anwenden muss, dann wieder zurück konvertieren, und schreiben. Ich kann mir nur vorstellen das ich noch ein wenig effektiver einlesen kann.
Aber irgend2wei ist da noch der Wurm drin, manche Seite funktionieren, macnhe nicht, und wonach sich das richtet habe ich bisher noch nicht herausbekommen können. Aber ich glaube es liegt schon vorher das Problem, da der Request manchmal nichtmal den Proxy verlässt. Leider kann ich mit Ethereal nicht localhost belauschen, dann würde ich wissen was da so lokal genau abläuft.
Irgebdwie ist da noch ein dicker Fehler drin. Das was mich nur wundert, alle Seiten die im Mozilla nicht funkitonieren, funktionieren dafür mit Telnet, das heißt wenn ich da manuelel Requests an den proxy schicke funktiniert das immer wunderbar. Und mit Telnet kann ich wenigstens sehen was Mozilla empfängt, aber da kann ich keine Fehler entdecken. Ich tippe trotzdem auf ein Problem mit Content-Length, da wenn das nicht stimmt Mozilla gart nichst anzeigt. Kann sein das es sich auch um Caching-Probleme handelt, das vermute ich fast.
Naja, wenn das denn endlich komplett läuft, werde ich den Code verschönern ;-) Und dann werde ich eine Liste mit zu ersetzenden Wörtern aus einer Datei einlesen, wobei das ganze dann natürlich noch langsamer wird, wenn ich z.B. 10 mal replaceAll drüberlaufen lasse, daher will ich das irgendwie schneller bekommen. Weißt Du vielleicht einen Ansatz wie ich das machen könnte?
Auf jeden Fall vielen Dank Euch beiden! Schön dass man hier auch zu Java gute Hilfe bekommt ;-)
Grüße
Andreas
Hi!
Naja, irgendwie ist es seltsam, im IE laufen wirklch alle Seiten über den proxy, nur der Mozilla macht bei alten Seite nichts, es bleibt einfach weiß. Naja, und das Forum ist schon ein dicker Happen, das durchaufen der Startdatei dauert ca. 20 Sekunden bei voller CPU-Auslastung.
Sind ca. 320 KB. Was ja nun wirklich nicht viel ist, mit PHP habe ich das _erheblich_ schneller durchlaufen. ABer in Java muss das doch auch schneller gehen. Vielleicht versuche ich es mal mit StringBuffer, nur gibt es hierfür kein replaceAll()... wobei, wenn ich die originale Content-Length habe, kann ich ja auch direkt mit read() alles auf einmal lesen, wobei ich vom selfserver ausgerechnet das nicht empfange. Hm.
Also ich habe es jetzt nal auf stingBuffer umgestellt, udn das ist auf alle Fälle ein großer Schritt nach vorne:
Hierfür habe ich mir eine eigene replaceAll Methode geschrieben:
private StringBuffer replaceAll (StringBuffer s , String search, String replace ) {
int start = 0, pos = 0;
StringBuffer result = new StringBuffer (s.length());
while ( ( pos = s.indexOf( search, start ) ) >= 0 ) {
s.replace(pos, pos + search.length(), replace);
}
return s;
}
while ((data = readLine(from_serv)) != null) {
data_buffer.append(data + "\r\n");
}
replaced_html = replaceAll(data_buffer, "Kaltschrank", "Kaltschrank");
header.append("Content-Length: " + replaced_html.length() + "\r\n");
header.append("Connection: close\r\n");
header.append("\r\n");
out.write(header.toString().getBytes(), 0, header.length());
out.write(replaced_html.toString().getBytes(), 0, replaced_html.length();
So. Meint Ihr ich kann das noch weiter verbessern? Denn richtig flott ist das noch lange nicht. Aber der Unterschied zur String-Methode ist fast unglaublich, das ist sicher 5 mal schneller!
Grüße
Andreas
Moin!
Bei PHP _weiß_ der Server von der Langsamkeit. Der Server liefert erst das durch PHP gelaufene HTML via HTTP aus. Der Client merkt von _dieser_ Langsamkeit nichts. Er sieht einen langsamen Server.
Bei einem Proxy denkt der Server, er liefere an einen User-Agent. Der Client meint, er erhielte von einem Server. Keiner der beiden nimmt Rücksicht auf Verzögerungen im Proxy.
Sorry, aber meiner Meinung nach erzählst du hier ziemlichen Unsinn.
Es ist alltäglichstes Brot einer asynchronen Datenverbindung, dass Daten nicht gleichmäßig schnell übertragen werden, sondern per Handshake in Häppchen, so schnell, wie die Gegenseite sie verarbeiten kann, abgefordert werden.
Denn ein Webserver mag zwar an einer 100MBit-Karte hängen, aber was ist, wenn ein Modem-Benutzer ankommt? Der zieht Stück für Stück seine IP-Pakete vom Server, und zwar höllisch langsam. Der Server aber wird ja nicht deswegen die Karte auf ultra-slow schalten, sondern weiterhin alle paar Sekunden ein IP-Paket für den Modemuser in maximaler Geschwindigkeit ausgeben.
Dieses Datenpaket wird zum zentralen Router gelangen, der es über den Provider-Uplink auf einer 1-GBit-Glasfaserleitung weiterleitet - es wird also nochmal beschleunigt. Und so nimmt das Datenpaket den Weg durchs Internet, bis es im Modemschrank des Zugangsproviders landet. Dort kommt es in einen Puffer und wartet darauf, langsam über die Telefonleitung versendet zu werden. Wenn es angekommen ist, wird ein Quittungspaket (bei TCP) zum Server versendet, der daraufhin das nächste Paket losschickt. [1]
[1] Weil das etwas lang dauern kann, haben die Netzwerker die Technik soweit entwickelt, dass der Sender zunächst eine ganze Anzahl von Paketen ohne Bestätigung senden kann. Wenn keines verloren geht, beschleunigt das die Übertragung ganz schön.
Ja, aber der Stream wird insgesamt flüssiger. Nicht:
- l a n g e P a u s e -4096Byte, sondern:
-kurze Pause-1024Byte-kurze Pause-1024Byte-kurze Pause-1024Byte-kurze Pause-1024Byte
Aber bringt das was? Bestimmt nicht dem Server, denn der schickt ganz andere Pakete. Bei Ethernet beispielsweise 1500 Bytes im Block[2].
[2] Wobei ich erstens für die exakte Zahl nicht meine Hand ins Feuer lege und zweitens von dieser Zahl noch Protokoll-Overhead abgehen könnte - krumm im Verhältnis zu 1024 Byte ist diese Zahl aber in jedem Fall, und bei anderen Verbindungen als Ethernet liegt diese maximale Paketgröße noch bei ganz anderen Werten (sowohl wesentlich größer als auch kleiner.
- Sven Rautenberg
Hallo,
Bei PHP _weiß_ der Server von der Langsamkeit. Der Server liefert erst das durch PHP gelaufene HTML via HTTP aus. Der Client merkt von _dieser_ Langsamkeit nichts. Er sieht einen langsamen Server.
Bei einem Proxy denkt der Server, er liefere an einen User-Agent. Der Client meint, er erhielte von einem Server. Keiner der beiden nimmt Rücksicht auf Verzögerungen im Proxy.
Sorry, aber meiner Meinung nach erzählst du hier ziemlichen Unsinn.
Es ist alltäglichstes Brot einer asynchronen Datenverbindung, dass Daten nicht gleichmäßig schnell übertragen werden, sondern per Handshake in Häppchen, so schnell, wie die Gegenseite sie verarbeiten kann, abgefordert werden.
Denn ein Webserver mag zwar an einer 100MBit-Karte hängen, aber was ist, wenn ein Modem-Benutzer ankommt? Der zieht Stück für Stück seine IP-Pakete vom Server, und zwar höllisch langsam. Der Server aber wird ja nicht deswegen die Karte auf ultra-slow schalten, sondern weiterhin alle paar Sekunden ein IP-Paket für den Modemuser in maximaler Geschwindigkeit ausgeben.
Dieses Datenpaket wird zum zentralen Router gelangen, der es über den Provider-Uplink auf einer 1-GBit-Glasfaserleitung weiterleitet - es wird also nochmal beschleunigt. Und so nimmt das Datenpaket den Weg durchs Internet, bis es im Modemschrank des Zugangsproviders landet. Dort kommt es in einen Puffer und wartet darauf, langsam über die Telefonleitung versendet zu werden. Wenn es angekommen ist, wird ein Quittungspaket (bei TCP) zum Server versendet, der daraufhin das nächste Paket losschickt. [1]
[1] Weil das etwas lang dauern kann, haben die Netzwerker die Technik soweit entwickelt, dass der Sender zunächst eine ganze Anzahl von Paketen ohne Bestätigung senden kann. Wenn keines verloren geht, beschleunigt das die Übertragung ganz schön.
Ja, aber der Stream wird insgesamt flüssiger. Nicht:
- l a n g e P a u s e -4096Byte, sondern:
-kurze Pause-1024Byte-kurze Pause-1024Byte-kurze Pause-1024Byte-kurze Pause-1024ByteAber bringt das was? Bestimmt nicht dem Server, denn der schickt ganz andere Pakete. Bei Ethernet beispielsweise 1500 Bytes im Block[2].
Ja, aber hier geht es gar nicht um die Geschwindigkeit der _Netzwerkübertragung_, sondern es wurde eine Software, der Java-Proxy, zwischengeschaltet, die die HTTP-Daten, im speziellen Problem den BODY, abfängt, manipuliert und dann erst an den Client weiterreicht. Die HTTP HEADER sind lange weg. Beim direkten Durchreichen funktionierte es ja. Für den Client sieht das dann so aus (Proxy gedanklich ausgeblendet):
GET -> Server sendet HEADER
Pause, weil der Proxy erstmal 4096 Byte in String wandelt, eine Ersetzung durchführt, wieder in Byte[] wandelt ...
-> Server sendet 4096 Byte BODY
Meine Vermutung war nun, dass diese Pause zu lang ist. Die letzte, jetzt funktionierende, Lösung ist, wie ich es verstanden habe, diese Arbeitsweise des Proxys bei text/html:
-alles Zeilenweise einlesen und dabei gleich manipulieren
-HEADER komplettieren mit neuer Content-Length(??)
-HEADER senden
-BODY senden
Auch hier ist die Pause zwischen Header und Body nun weg. Die Frage ist, ob das der Grund für das funktionieren ist, oder, wie Andreas glaubt, der "richtige" Content-Length-HEADER, der aber, meiner Meinung nach, bei text/html _wirklich_ keine Bedeutung hat.
viele Grüße
Axel
Moin!
GET -> Server sendet HEADER
Pause, weil der Proxy erstmal 4096 Byte in String wandelt, eine Ersetzung durchführt, wieder in Byte[] wandelt ...
-> Server sendet 4096 Byte BODYMeine Vermutung war nun, dass diese Pause zu lang ist.
Kann ich nicht nachvollziehen, warum das so sein sollte. Denn es gibt überall Puffer dazwischen, die den Effekt einerseits dramatischer machen können, andererseits ihn auch ausgleichen.
Der Header ist ja kein einzelnes Datenpaket, was man schon mal vorab wegschicken könnte, sondern direkt im Anschluß kommt der Body. Folglich wird, in Puffern und Datenpaketen gerechnet, der Header wirklich erst dann ankommen, wenn noch soviel Body hinterherkommt, dass ein Paket voll ist. Oder (was natürlich auch geht) es werden halbvolle Pakete nach einer gewissen Zeit verschickt.
Das alles hat aber gegenüber den höheren Protokollschichten transparent zu sein. Eine TCP-Verbindung kann stundenlang offen sein, ohne dass irgendein Datenaustausch stattfindet, aber der Empfänger noch auf Daten wartet.
Die letzte, jetzt funktionierende, Lösung ist, wie ich es verstanden habe, diese Arbeitsweise des Proxys bei text/html:
-alles Zeilenweise einlesen und dabei gleich manipulieren
-HEADER komplettieren mit neuer Content-Length(??)
-HEADER senden
-BODY senden
Da würde ich ganz schlicht behaupten, dass der einzige und wichtigste Aspekt die Korrektur des Headers auf korrekte Werte ist. Das zeilenweise Durchreichen hingegen würde ich eher für uninteressant halten.
Allerdings: Man kann eine Content-Length für den vorliegenden Fall eines manipulierenden Proxys natürlich erst dann berechnen, wenn die komplette Ressource einmal eingelesen wurde, dann die Ersetzungsoperationen durchgeführt werden, und dann die neue Länge ermittelt wird.
Die Alternative wäre (sofern das laut Standard möglich ist), die Content-Length im Response komplett wegzulassen und dann einfach solange Daten zu senden, wie Originaldaten reinkommen.
- Sven Rautenberg