多執行緒Http代理伺服器 Java實現

ethenjean發表於2010-10-13

最近心血來潮,想熟悉一下Socket程式設計,就在網上看了一些資料,對Socket有了一個比較基本的瞭解,無意間竟發現用Java開發的簡易HttpProxy的Demo,現對源程式進行了一些小的修改,使可讀性變強,以供大家參考。

import java.io.*;
import java.net.*;
public class MyHttpProxy extends Thread { 
	static public int CONNECT_RETRIES=5;	//嘗試與目標主機連線次數
	static public int CONNECT_PAUSE=5;	//每次建立連線的間隔時間
	static public int TIMEOUT=50;	//每次嘗試連線的最大時間
	static public int BUFSIZ=1024;	//緩衝區最大位元組數
	static public boolean logging = false;	//是否記錄日誌
	static public OutputStream log_S=null;	//日誌輸出流
	static public OutputStream log_C=null;	//日誌輸出流
//	static public String LOGFILENAME_S="log_S.txt";
//	static public String LOGFILENAME_C="log_C.txt";
	// 與客戶端相連的Socket
	protected Socket csocket;	
    public MyHttpProxy(Socket cs) { 
	csocket=cs;
	start(); 
    }
    public void writeLog(int c, boolean browser) throws IOException {
    	if(browser) log_C.write((char)c);
    	else log_S.write((char)c);
    }

    public void writeLog(byte[] bytes,int offset, int len, boolean browser) throws IOException {
   	for (int i=0;i<len;i++) 
   		writeLog((int)bytes[offset+i],browser);
    }
    public void run(){
    	String buffer = "";		//讀取請求頭
    	String URL="";			//讀取請求URL
    	String host="";			//讀取目標主機host
    	int port=80;			//預設埠80
    	Socket ssocket = null;
         //cis為客戶端輸入流,sis為目標主機輸入流
    	InputStream cis = null,sis=null;
         //cos為客戶端輸出流,sos為目標主機輸出流
    	OutputStream cos = null,sos=null;	    	
       	try{
    		csocket.setSoTimeout(TIMEOUT);
    		cis=csocket.getInputStream();
    		cos=csocket.getOutputStream();
    		while(true){
    			int c=cis.read();
    			if(c==-1) break;		//-1為結尾標誌
    			if(c=='\r'||c=='\n') break;//讀入第一行資料
    			buffer=buffer+(char)c;
    			if (logging) writeLog(c,true);
    		}
    	//抽取URL(http://www.baidu.com/)  	
   		URL=getRequestURL(buffer);		
	
		int n;
    	//抽取host
  		n=URL.indexOf("//");
 		if (n!=-1) 	
                		host=URL.substring(n+2);	// www.baidu.com/
  		n=host.indexOf('/');
   		if (n!=-1) 	
                  		host=host.substring(0,n);// www.baidu.com
    	    
    	// 分析可能存在的埠號
  		n=host.indexOf(':');
   		if (n!=-1) { 
   			port=Integer.parseInt(host.substring(n+1));
   			host=host.substring(0,n);
  		}
   		int retry=CONNECT_RETRIES;
   		while (retry--!=0) {
   			try {
    				ssocket=new Socket(host,port);	//嘗試建立與目標主機的連線
    				break;
    			} catch (Exception e) { }
                 		// 等待
   			Thread.sleep(CONNECT_PAUSE);
   		}
   		if(ssocket!=null){
   			ssocket.setSoTimeout(TIMEOUT);
   			sis=ssocket.getInputStream();
   			sos=ssocket.getOutputStream();
   			sos.write(buffer.getBytes());		//將請求頭寫入
   			pipe(cis,sis,sos,cos);				//建立通訊管道
   		}    			
          	}catch(Exception e){
    		e.printStackTrace();
    	}
    	finally {
		try { 
		    	csocket.close();
		    	cis.close();
		    	cos.close();
		} 
		catch (Exception e1) {
		    	System.out.println("\nClient Socket Closed Exception:");
		    	e1.printStackTrace();
		}
		try { 
		    	ssocket.close();
		    	sis.close();
		    	sos.close();
		} 
		catch (Exception e2) {
		    	System.out.println("\nServer Socket Closed Exception:");
		    	e2.printStackTrace();
		}
       	}
    }
    public String getRequestURL(String buffer){
    	String[] tokens=buffer.split(" ");
    	String URL="";
    	for(int index=0;index<tokens.length;index++){
    		if(tokens[index].startsWith("http://")){
    			URL=tokens[index];
    			break;
    		}
    	}
    	return URL;    	
    }
    public void pipe(InputStream cis,InputStream sis,OutputStream sos,OutputStream cos){
    	try {
    	    int length;
    	    byte bytes[]=new byte[BUFSIZ];
    	    while (true) {
    	    	try {
    	    		if ((length=cis.read(bytes))>0) {
    	    			sos.write(bytes,0,length);
    	    			if (logging) writeLog(bytes,0,length,true); 	    			
    	    		}
    	    		else if (length<0)
    	    			break;
    	    	}
    	    	catch(SocketTimeoutException e){}
    	    	catch (InterruptedIOException e) { 
    	    		System.out.println("\nRequest Exception:");
    	    		e.printStackTrace();
    	    	}
    	    	try {
    	    		if ((length=sis.read(bytes))>0) {
    	    			cos.write(bytes,0,length);
    	    			if (logging) writeLog(bytes,0,length,false);
    	    		}
    	    		else i[align=left][/align]f (length<0) 
    	    			break;
    	    	}
    	    	catch(SocketTimeoutException e){}
    	    	catch (InterruptedIOException e) {
    	    		System.out.println("\nResponse Exception:");
    		    	e.printStackTrace();
    	    	}
    	    }
    	} catch (Exception e0) {
    	    System.out.println("Pipe異常: " + e0);
    	}
    }
}

 

 
下面這張圖可以清晰地闡明HttpProxy的實現原理:


其中流程具體如下:
1、客戶端通過瀏覽器向代理伺服器傳送HttpRequest(GET/POST);
2、代理伺服器讀取請求頭,抽取出請求的具體目標伺服器HOST和PORT;
3、代理伺服器把請求頭髮送給目標伺服器;
4、代理伺服器建立管道,供客戶端和目標伺服器通過兩個Socket通訊。

遺留問題:
最終經過測試,發現在訪問百度時會出現非常詭異的錯誤,不知道為何?
問題如下:
1、訪問百度主頁,正常顯示。
2、在百度主頁點選貼吧或者其他連結時會發現連結到一些非常詭異的頁面,不知道為何。還待以後研究。
簡單的測試

public static  void startProxy(int port,Class clobj) { 
    try { 
        ServerSocket ssock=new ServerSocket(port); 
        while (true) { 
        Class [] sarg = new Class[1]; 
        Object [] arg= new Object[1]; 
        sarg[0]=Socket.class; 
        try { 
        java.lang.reflect.Constructor cons = clobj.getDeclaredConstructor(sarg); 
        arg[0]=ssock.accept(); 
        cons.newInstance(arg); // 建立HttpProxy或其派生類的例項 
        } catch (Exception e) { 
        Socket esock = (Socket)arg[0]; 
        try { esock.close(); } catch (Exception ec) {} 
        } 
        } 
    } catch (IOException e) { 
    System.out.println("\nStartProxy Exception:"); 
    e.printStackTrace(); 
    } 
    } 


        // 測試用的簡單main方法 
    static public void main(String args[]) throws FileNotFoundException { 
    System.out.println("在埠808啟動代理伺服器\n"); 
    OutputStream file_S=new FileOutputStream(new File(LOGFILENAME_S)); 
    OutputStream file_C=new FileOutputStream(new File(LOGFILENAME_C)); 
    MyHttpProxy.log_S=file_S; 
    MyHttpProxy.log_C=file_C; 
    MyHttpProxy.logging=true; 
    MyHttpProxy.startProxy(808,MyHttpProxy.class); 
    }

 

 程式碼如下:

相關文章