# Einfacher Proxy Server



## blöb (13. Jul 2011)

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ß


----------



## musiKk (13. Jul 2011)

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).


----------



## blöb (13. Jul 2011)

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.


----------



## Andi_CH (13. Jul 2011)

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 ;-)


----------



## blöb (13. Jul 2011)

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?


----------



## Andi_CH (13. Jul 2011)

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.


----------



## blöb (13. Jul 2011)

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.


----------



## tagedieb (13. Jul 2011)

Am besten du startest mit einer bestehenden Proxyimplementation (write HTTP Proxy in Java - Stack Overflow, LittleProxy HTTP Proxy - oder Java Transparent Proxy) und aenderst nach deinen Beduerfnissen ab.


----------



## Ariol (13. Jul 2011)

Sowas hab ich auch mal gebaut, auch für mehrere Zielserver.

Man hat dann einfach auf 
	
	
	
	





```
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.


----------



## blöb (14. Jul 2011)

Ich habe mal ein wenig experimentiert.
Der Thread, der die Connections von FF entgegen nimmt macht folgendes:

```
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.

```
@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);
		}
	}
```


----------



## tagedieb (14. Jul 2011)

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 
	
	
	
	





```
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.


----------



## Ariol (14. Jul 2011)

So sollte es etwas schneller sein:

```
byte[] buffer = new byte[1024];
		int count;
		while ((count = dis.read(buffer)) != -1)
	    {
	        aos.write(buffer,0,count);
	        aos.flush();
	    }
```


----------



## Ariol (14. Jul 2011)

tagedieb hat gesagt.:


> 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
> 
> ...



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.


----------



## blöb (14. Jul 2011)

tagedieb hat gesagt.:


> 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.


----------



## blöb (14. Jul 2011)

Mist, falscher Alarm. Ich hatte vergessen, die erste Schleife auszukommentieren...


----------



## Ariol (14. Jul 2011)

BufferedReader ist auch nicht die richtige Wahl.


----------



## blöb (14. Jul 2011)

Sondern?


----------



## Tomate_Salat (14. Jul 2011)

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.


----------



## Ariol (14. Jul 2011)

Tomate_Salat hat gesagt.:


> 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:

```
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

```
new DataStreamThread(localSocket, remoteSocket, protocol)
						.start();
				new DataStreamThread(remoteSocket, localSocket, protocol)
						.start();
```

Danach den Link hier benutzen: Domain ist reserviert


----------



## Tomate_Salat (14. Jul 2011)

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.


----------



## Ariol (14. Jul 2011)

Und wie kamen bei dir dann Bilder oder ähnliches über die Leitung?


----------



## Tomate_Salat (14. Jul 2011)

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.


----------



## blöb (14. Jul 2011)

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.


----------



## Andi_CH (15. Jul 2011)

Tomate_Salat hat gesagt.:


> 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.



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.


----------



## tagedieb (15. Jul 2011)

Das groesste Problem duerfte wohl sein, wenn 
	
	
	
	





```
Connection:keep-alive
```
 verwendet wird. Eventuel koennte man im Request den Header durch 
	
	
	
	





```
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.


----------



## Tomate_Salat (15. Jul 2011)

Andi_CH hat gesagt.:


> 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.



Naja, erstmal den HTTP-Parser, der HTML-Parser erst (evtl) danach. Hier könnte später vllt eine "JS-Injection" helfen.


----------



## blöb (21. Jul 2011)

Andi_CH hat gesagt.:


> 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: 
	
	
	
	





```
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 
	
	
	
	





```
https://example.com/
```
 ein. dann wird ebenfalls nicht Port 80 angesprochen. Bekomme ich das irgendwie mit?


----------



## tagedieb (21. Jul 2011)

In der ersten Zeile deines Request steht die sogenangte "Request-Line", welche in etwa so aussieht:
	
	
	
	





```
GET http://example.com:8080/ HTTP/1.1
```

siehe auch HTTP/1.1: Request


----------



## blöb (22. Jul 2011)

tagedieb hat gesagt.:


> In der ersten Zeile deines Request steht die sogenangte "Request-Line", welche in etwa so aussieht:
> 
> 
> 
> ...



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 
	
	
	
	





```
Connection:close
```
 einsetzen habe ich noch nicht ausprobiert, ich verstehe allerdings auch noch nicht, weshalb dann mehr funktionieren sollte.


----------



## tagedieb (22. Jul 2011)

blöb hat gesagt.:


> Den Vorschlag mit
> 
> 
> 
> ...



Wenn du 
	
	
	
	





```
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 
	
	
	
	





```
Content-Length
```
 auslesen. Dieser gibt dir an wie lange die Nachricht ist die übertragen wird. Der Header 
	
	
	
	





```
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 
	
	
	
	





```
Content-Length
```
 die neue Länge mitteilen, ansonsten hat dein Browser/Client ein Problem...


----------

