Einfacher Proxy Server

  • Themenstarter Themenstarter blöb
  • Beginndatum Beginndatum
B

blöb

Gast
Hallo,

ich weiß, dass dieses Thema schon öfter hier aufgekommen ist, aber ich glaube, dass mir keiner der bisherigen Threads hilft.
Ich habe also folgendes vor:
Ich möchte in Firefox localhost als Proxy eintragen, und diesen Proxy in Java schreiben. Der Proxy soll erst mal nur die Daten so weiterleiten, wie er sie bekommt, genau wie die Antworten.
Das Ziel ist, bestimmte angeforderte Seiten zu modifizieren und dann an FF zurück zu geben.
Mein Problem ist, dass ich nicht weiß, wie ich anfangen soll. Ich könnte einen TCP-Server aufsetzen, um Nachrichten von FF entgegen zu nehmen. Aber dann wie weiter? FF wird in den meisten Fällen HTTP sprechen wollen. Eigentlich habe ich aber keine Lust, das HTTP zu parsen und selbst eine HTTP-Anfrage an den geforderten Server zu schicken. Und wenn dann mal z.B. eine FTP-Anfrage rein kommt, oder was anderes, muss ich das auch wieder getrennt behandeln.
Das klingt also wenig praktisch für mich.
Nebenbei: Wird Firefox jemals eine Anfrage nicht in TCP senden? Dann wäre selbst dieser Ansatz schon hinfällig.

Gruß
 
Ein HTTP Proxy muss HTTP sprechen können. Wenn Du das nicht selbst implementieren willst, solltest Du nach bestehenden Implementierungen schauen. Vielleicht kann man mit Jetty oder CXF leicht einen Proxy basteln. (Natürlich gibt es auch SOCKS, aber wenn das Ziel ist, HTTP zu modifizieren, ist das sicher nicht so praktisch.)

HTTP geht grundsätzlich über TCP. Andere Protokolle werden nicht prinzipiell ausgeschlossen, aber da ist mir nichts bekannt (ich würde auch nicht davon ausgehen).
 
Hmja, ich hatte sowas vermutet.
Meine Hoffnung war halt, dass ich die Anfragen, die rein kommen, einfach an TCP weiter reichen kann oder so, ohne großartig selbst HTTP sprechen zu müssen, da die Anfrage selbst ja eigentlich schon in HTTP ist und grundsätzlich dazu geeignet wäre, sie irgendwem zu geben, ohne sie groß interpretieren zu müssen.
 
Das würde man dann aber kaum als Proxy bezeichnen 🙂

Kannst ja auch eine Art Firewall bauen, die nichts anderes macht als Pakete weiter zu leiten, wenn die Zieladresse nicht auf einer Blacklist ist.

Aber vergiss nicht - über jede Instanz des Browsers schön Buch zu führen, denn die Antworten sollen ja im richtigen Fenster erscheinen ;-)
 
Nenn es wie du willst. Ich will nichts blacklisten, ich will wirklich den Inhalt der Rückgabe verändern. Aber die eigentliche Frage ist halt: Wie soll ich das angehen?
 
Indem du erst einmal ein Progrämmchen schreibst, dass die Requests auf 127.0.0.1:80 entgegen nimmt und unverändert weitergibt. (Das kann natürlich auch etwas anderes als 80 sein.)

Und kümmere dich erst mal darum wie du die verschiedenen Browserinstanzen bzw. deren Tabs erkennst, damit du weisst wohin mit den Requests, die ja hoffentlich irgendwann eintreffen.

Wenn das tut - also so in 2 - 3 Wochen oder Monaten - kannst du so ganz langsam daran gehen einen html-Parser zu bauen und den Inhalt der Requests zu verändern. Wir bekommen dann nächstes Jahr den ersten einigermassen funktionierenden Code von dir zu sehen.

Diese Schätzungen sind bei deinem Kenntnissstand nicht ironisch, sondern eher realistisch. Bitte nicht beleidigt sein, aber eine realistische Selbsteinschätzung ist etwas sehr wichtiges und so nebenbei, ich wäre auch nicht schneller - vor allem mit dem html-Parser nicht.
 
Ich hatte nicht vor einen html-Parser zu schreiben, selbst, wenn ich einen bräuchte. Damit wäre ich wirklich ewig beschäftigt, aber das wäre auch nicht zielführend.
Requests auf 127.0.0.1:80 ist eine Sache von 5 Minuten. Mein Problem ist eben dieses "unverändert weiter geben". Mir fehlt irgendwie der Ansatz dazu.
 
Sowas hab ich auch mal gebaut, auch für mehrere Zielserver.

Man hat dann einfach auf
Code:
http://localhost:70
oder vergleichbares zugegriffen und bekam dann die Daten vom angegebenen Server dahinter.

Das diente damals zum Zugriff auf einzelne HTTP-Server innerhalb einer DMZ.
War eigentlich nicht schwer:
  • Auf einem Port horchen
  • Wenn eine Anfrage kommt Verbindung zum Zielserver aufbauen.
  • In- und Outputstreams in beide Richtungen öffnen.
  • Threads für die kommunikation starten. (Alle Daten in alle Richtungen)
  • Weiterhorchen.

Hab das Teil leider nicht mehr.
Das war allerdings kein Proxy sondern ein Tunnel.

Solange man keine Manipulation durchführen will, kann man da nahezu alles durchschleusen.
 
Ich habe mal ein wenig experimentiert.
Der Thread, der die Connections von FF entgegen nimmt macht folgendes:
Java:
OutputStream aos = applicationSocket.getOutputStream();
InputStream dis = destinationSocket.getInputStream();
Modifier mod = new Modifier(dis, aos);
Und der Modifier soll meine modifizierung der Seiten durchführen. Fasse ich nichts an, so wie hier, klappt alles (naja fast alles) wunderbar.
Probiere ich stattdessen die auskommentierte Version, um Strings zu erhalten (einzelne Characters sind blöd zur Modifizierung), kommen nur noch halbe Quelltexte in FF an. Ich versteh noch nicht, wieso.
Java:
@Override
	public void run()
	{
//		BufferedReader br = new BufferedReader(new InputStreamReader(dis));
//		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(aos));
		try
		{
			while (true)
			{
				int read = dis.read();
				if (read < 0)
					break;
				aos.write(read);
				aos.flush();
			}
			
//			while (true)
//			{
//				
//				String line = br.readLine();
//				if (line == null)
//					break;
//				bw.write(line);
//				bw.newLine();
//				bw.flush();
//			}
		}
		catch (SocketException e)
		{
//			if (!e.getMessage().equalsIgnoreCase("socket closed"))
				e.printStackTrace();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		finally
		{
//			Utils.close(br);
//			Utils.close(bw);
			
			Utils.close(dis);
			Utils.close(aos);
		}
	}
 
Ich kenne das HTTP Protokol nicht genau, aber ich vermute die HTTP Antowrt wird nicht umbedingt mit einem Newline \n abgeschlossen.

Das heiss dein Code blockiert beim
Code:
String line = br.readLine();
, da er hier vergebens auf ein \n wartet. Desweiteren kann auch der Header Content-Length problematisch sein, da dieser angibt wieviele Bytes die Antwort enthaelt und der Browser nur soviele Bytes lesen wird und denkt die Uebertragung ist abgeschlossen. Wenn du also zusaetzliche Zeichen einfuegst oder weglaesst kann es sein, dass nicht alles angezeigt wird.
 
So sollte es etwas schneller sein:
Java:
		byte[] buffer = new byte[1024];
		int count;
		while ((count = dis.read(buffer)) != -1)
	    {
	        aos.write(buffer,0,count);
	        aos.flush();
	    }
 
Ich kenne das HTTP Protokol nicht genau, aber ich vermute die HTTP Antowrt wird nicht umbedingt mit einem Newline \n abgeschlossen.

Das heiss dein Code blockiert beim
Code:
String line = br.readLine();
, da er hier vergebens auf ein \n wartet. Desweiteren kann auch der Header Content-Length problematisch sein, da dieser angibt wieviele Bytes die Antwort enthaelt und der Browser nur soviele Bytes lesen wird und denkt die Uebertragung ist abgeschlossen. Wenn du also zusaetzliche Zeichen einfuegst oder weglaesst kann es sein, dass nicht alles angezeigt wird.

Die Content-Length wird keine Probleme machen.
Erstens ist es egal ob der Browser uns das abnimmt, was wir ihm schicken.
Zweitens: wenn es ihm nicht egal wäre, hätte auch der Quellserver ein Problem.

Ich habe, wie erwähnt, bereits schon einmal einen solchen Tunnel gebastelt und da gab es nie Probleme.
 
Ich kenne das HTTP Protokol nicht genau, aber ich vermute die HTTP Antowrt wird nicht umbedingt mit einem Newline \n abgeschlossen.

Das hats gebracht, danke. newLine schreibt nur ein "\n" in den Stream, während HTTP "\r\n" als Delimiter vorsieht. Habe also die Zeile durch bw.write("\r\n"); ersetzt.
 
du musst das HTTP-Protokoll auswerten, ansonsten bekommst du das Ende nie mit. Entweder du verwendest eine lib aus dem Internet oder du schreibst dir etwas eigenes.
 
du musst das HTTP-Protokoll auswerten, ansonsten bekommst du das Ende nie mit. Entweder du verwendest eine lib aus dem Internet oder du schreibst dir etwas eigenes.
Huh? Wie kommst du denn auf diese Idee?
Irgendwann kommt ein EOF und dann ist man fertig....



Versuchs mal damit:
Java:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class DataStreamThread extends Thread {

	private Socket from;
	private InputStream in;
	private Socket to;
	private OutputStream out;
	private String protocol;
	private byte[] buffer;
	protected static int count = 0;

	DataStreamThread(Socket from, Socket to, String protocol)
			throws IOException {
		this.from = from;
		this.in = from.getInputStream();

		this.to = to;
		this.out = to.getOutputStream();

		this.protocol = protocol;

		this.buffer = new byte[Constants.BUFFERSIZE];

	}

	@Override
	public void run() {

		long totallen = 0;
		DataStreamThread.count++;
		try {
			int len;
			while ((len = in.read(buffer)) > 0) {
				if (isHTTPRequest()) {
					String strBuffer = new String(buffer, 0, len);
					if (isHTTPRequestHeader(strBuffer)) {
						strBuffer = manipulateHTTPRequestHeader(strBuffer);
						out.write(strBuffer.getBytes());
					} else /* Images, HTTPResponse */{
						out.write(buffer, 0, len);
					}
				} else {
					out.write(buffer, 0, len);
				}
				totallen += len;
			}
		} catch (Exception e) {
		} finally {
			try {
				if (out != null)
					out.close();
			} catch (Exception e) {
			}
			try {
				if (to != null)
					to.close();
			} catch (Exception e) {
			}
			try {
				if (in != null)
					in.close();
			} catch (Exception e) {
			}
			try {
				if (from != null)
					from.close();
			} catch (Exception e) {
			}
		}
		DataStreamThread.count--;
		StringBuilder sb = new StringBuilder("Data ");
		sb.append(from.getInetAddress().getHostAddress() + ":" + from.getPort());
		sb.append(" -> ");
		sb.append(to.getInetAddress().getHostAddress() + ":" + to.getPort());
		sb.append(" ");
		sb.append(totallen + " bytes");
		System.out.println(sb.toString());
		System.out.println(DataStreamThread.count + " connections left");

	}

	private boolean isHTTPRequest() {
		return protocol.equals(Constants.HTTP)
				|| protocol.equals(Constants.HTTPS);
	}

	private boolean isHTTPRequestHeader(String strBuffer) {
		return strBuffer.startsWith(Constants.GET)
				|| strBuffer.startsWith(Constants.POST);
	}

	private String manipulateHTTPRequestHeader(String strBuffer) {
		StringBuilder sb = new StringBuilder(strBuffer);

		int xForwardedForPosition = sb
				.indexOf(Constants.HTTP_HEADER_X_FORWARDED_FOR);

		if (xForwardedForPosition < 0) {
			sb.insert(sb.indexOf(Constants.HTTP_HEADER_ACCEPT),
					Constants.HTTP_HEADER_X_FORWARDED_FOR
							+ from.getInetAddress().getHostAddress()
							+ Constants.LINEBREAK);
		}

		return sb.toString();
	}
}

Da ist sogar schon eine rudimentäre Manipulation des HTML-Headers mit drin.

zum Starten einfach in beide Richtungen einen Thread öffnen
Java:
				new DataStreamThread(localSocket, remoteSocket, protocol)
						.start();
				new DataStreamThread(remoteSocket, localSocket, protocol)
						.start();

Danach den Link hier benutzen: Domain ist reserviert
 
Zuletzt bearbeitet:
Bei meinen Tests (länger her) nicht. Ich hab aber auch direkt auf dem InputStream gearbeitet und mir dann selbst etwas geschrieben, was mir das HTTP-Protokoll auswerten kann.
 
Die Daten kamen schon an. Nur bin ich nicht aus der lese-Methode herausgekommen. Ist auch schon alles ein wenig länger her. Ich meine mich dunkel zu erinnern: der BufferedReader erkennt das Ende, brachte mir aber nicht die Vorteile, die mir meine eigene Implementierung brachte.
 
Also ich habe inzwischen was halbwegs funktionierendes. Nicht wirklich anders als bisher. Die meisten Seiten (Wikipedia z.B.) sehen sehr viel rudimentärer aus als ohne Proxy. So als fehlen irgendwelche Style-Informationen oder so. Warum ist das so? Das passiert auch mit anderen Implementierungen, wie z.B. dem vorgeschlagenen Java Transparent Proxy.
 
Das groesste Problem duerfte wohl sein, wenn
Code:
Connection:keep-alive
verwendet wird. Eventuel koennte man im Request den Header durch
Code:
Connection:close
ersetzen, damit die Verbindung nach senden der Antwort geschlossen wird. Dann kann man auch bis EOF lesen und braucht sich keine Gedanken zu machen, wo der einte Request/Response aufhoert und der andere anfaengt.
 
Er glaubt mir ja nicht, dass er einen html-Parser braucht!

Ich warte gespannt auf die Lösung wie ein ode modifziert werden kann, ohne ihn zu parsen.

Ich brauche ihn vor allem deshalb nicht, weil ich eigentlich ein XML-Dokument modifizieren will 😛

Aber mal was anderes: Wenn ich im FF eingebe:
Code:
http://example.com:8080/
wie bekommt ein Proxy mit, dass ich den Port 8080 anfragen will? Oder kann der das gar nicht merken und nur versuchen, Port 80 zu erreichen?
Oder ich gebe
Code:
https://example.com/
ein. dann wird ebenfalls nicht Port 80 angesprochen. Bekomme ich das irgendwie mit?
 
In der ersten Zeile deines Request steht die sogenangte "Request-Line", welche in etwa so aussieht:
Code:
GET http://example.com:8080/ HTTP/1.1

siehe auch HTTP/1.1: Request

Tatsächlich^^
Also jetzt habe ich auch den Port aus den Headern geparst und Baue einen Socket zum richtigen Port auf. Ich hatte gehofft, dass das Problem mit den rudimentären Seiten daran liegen könnte, aber dem schient nicht so zu sein.
Den Vorschlag mit
Code:
Connection:close
einsetzen habe ich noch nicht ausprobiert, ich verstehe allerdings auch noch nicht, weshalb dann mehr funktionieren sollte.
 
Den Vorschlag mit
Code:
Connection:close
einsetzen habe ich noch nicht ausprobiert, ich verstehe allerdings auch noch nicht, weshalb dann mehr funktionieren sollte.

Wenn du
Code:
Connection: keepalive
verwendest wird die Connection nicht geschlossen sondern auch noch fuer andere Requests verwendet. Wenn du einen simplen Tunnel baust ist das eigentlich kein Problem. Wenn du aber die Antwort (XML) verändern willst. Musst du ja erst mal den XML Content aus dem Stream raussuchen. Wie willst du das Anstellen, wenn du das HTTP Protokol nicht parsen willst?

Wie willst du feststellen ob die Übertragung abgeschlossen ist? Der Server schickt dir dein EOF, da die Connection bestehen bleibt. Hierfür musst du wie schon erwähnt den Header
Code:
Content-Length
auslesen. Dieser gibt dir an wie lange die Nachricht ist die übertragen wird. Der Header
Code:
Content-Type
könnte natürlich auch recht hilfreich sein, da dir dieser sagen kann ob es sich überhaupt um ein XML Dokument handelt (falls der Server diesen Header richtig gesetzt hat). Und nachdem du dein XML modifiziert hast musst du dem Header
Code:
Content-Length
die neue Länge mitteilen, ansonsten hat dein Browser/Client ein Problem...
 

Zurück
Oben