自己用java寫一個http和https代理服務器

         本文是基於socket實現的http,https代理服務器,資源利用率上肯定也是沒有nio實現的效率要好。但是,秉持學習的態度,我還是來來實踐一下。當然,如果這個實現的代理器只是你自己用的話或者少數幾個人用的話,我覺得完全沒問題,自己也試了,看視頻啥的也沒啥問題(如果你看的視頻需要全部下載到本地後才能播放,那就只能把socket的過期時間設置長點了,不過現在一般都是可以緩衝一段就可以播放了,所以你自己斟酌吧——當別人還在用網上下的shadowsock來翻牆時,你自己用自己寫的腳本來流暢的看視頻的感覺肯定會不一樣吧哈哈,後續如果有時間的話,我也會實現代理客戶端來實現身份認證,別讓壞蛋佔用你的帶寬。)

         首先,我覺得你很有必要去看看http協議詳解,網上有很多,大家自行查詢,這裏我簡單說下主要使用了http的請求行(請求方法 host 協議版本),請求頭(host,keep-alive等等),至於https,其實就是http和ssl結合,這裏的結合比較巧妙,因爲考慮到安全和性能,https會事先請求建立socket鏈接,同時https利用http進行牽手雙方交換了客戶端傳遞信息加密的祕鑰(就是這個過程使用了ssh),以後傳遞的消息都在客戶端和服務器端建立的這個socket並且利用牽手約定好的祕鑰進行加密消息傳遞信息,這時你要是解析這些消息會發現全是亂碼(包括請求頭等都經過加密)。我這裏只是儘量簡約但是比較形象的解釋了https與http的關係,以便讓讀者可很方便的編程實現,詳細的過程可以去自查http詳解。

         有了上面的知識儲備,實現http代理服務器,其實就是代理服務器去解析http請求頭消息找到目標服務器,然後建立代理服務器到目標服務器的socket連接,將http的全部消息全部轉發給目標服務器即可。而針對https的代理,主要是實現牽手過程即可,這個牽手過程主要是利用http請求獲取目標服務器地址來建立socket連接,並在建立連接的同時告訴客戶端連接已經建立好(返回HTTP/1.1 200 Connection Established\r\n\r\n),接下來客戶端就會自動通過代理服務器建立的與目標服務器的連接發消息給目標服務器進行交換祕鑰,然後,也會利用此連接傳遞消息,當然,傳遞消息時代理服務器只需要轉發即可(你也看不懂加密後傳遞的消息)。

         下面給出我的代碼,當然歡迎大家給出意見或建議,在此先謝謝大家:

public class test {

	@SuppressWarnings("resource")
	public static void main(String[] s) {
		
			ServerSocket ss = null;
			try {
				ss = new ServerSocket(11111);
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			while(true) {
				try {
				Socket socket = ss.accept();
				socket.setSoTimeout(1000*60);//設置代理服務器與客戶端的連接未活動超時時間
				String line = "";
				InputStream is = socket.getInputStream();
				String tempHost="",host;
				int port =80;
				String type=null;
				OutputStream os = socket.getOutputStream();
				BufferedReader br = new BufferedReader(new InputStreamReader(is));
//				System.out.println("========+++++++++++++=======");
				int temp=1;
				StringBuilder sb =new StringBuilder();
				while((line = br.readLine())!=null) {
					System.out.println(line+"-----------------");
					if(temp==1) {       //獲取請求行中請求方法,下面會需要這個來判斷是http還是https
//						System.out.println("+++++++++"+line);
						type = line.split(" ")[0];
						if(type==null)continue;
					}
					temp++;
					String[] s1 = line.split(": ");
					if(line.isEmpty()) {
						break;
					}
					for(int i=0;i<s1.length;i++) {
						if(s1[i].equalsIgnoreCase("host")) {
							tempHost=s1[i+1];
						}
					}
					sb.append(line + "\r\n");
					line=null;
				}
					sb.append("\r\n");          //不加上這行http請求則無法進行。這其實就是告訴服務端一個請求結束了
					if(tempHost.split(":").length>1) {
						port = Integer.valueOf(tempHost.split(":")[1]);
					}
					host = tempHost.split(":")[0];
					Socket proxySocket = null;
					if(host!=null&&!host.equals("")) {
						proxySocket = new Socket(host,port);
						proxySocket.setSoTimeout(1000*60);//設置代理服務器與服務器端的連接未活動超時時間
						OutputStream proxyOs = proxySocket.getOutputStream();
						InputStream proxyIs = proxySocket.getInputStream();
						if(type.equalsIgnoreCase("connect")) {     //https請求的話,告訴客戶端連接已經建立(下面代碼建立)
							os.write("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes());
							os.flush();
						}else {//http請求則直接轉發
							proxyOs.write(sb.toString().getBytes("utf-8"));
							proxyOs.flush();
						}
						new ProxyHandleThread(is, proxyOs,host).start();//監聽客戶端傳來消息並轉發給服務器
						new ProxyHandleThread(proxyIs, os,host).start(); //監聽服務器傳來消息並轉發給客戶端
					}
			} catch (IOException e) {
				e.printStackTrace();
			}
			}
			
		
	}
}
class ProxyHandleThread extends Thread {

    private InputStream input;
    private OutputStream output;
    private String host;//debug時需要的東西,實際不需要
    public ProxyHandleThread(InputStream input, OutputStream output,String host) {
        this.input = input;
        this.output = output;
        this.host = host;
    }

    @Override
    public void run() {
        try {
            while (true) {
            	BufferedInputStream bis = new BufferedInputStream(input);
				byte[] buffer =new byte[1024];
				int length=-1;
				while((length=bis.read(buffer))!=-1) {//這裏最好是字節轉發,不要用上面的InputStreamReader,因爲https傳遞的都是密文,那樣會亂碼,消息傳到服務器端也會出錯。
					output.write(buffer, 0, length);
					length =-1;
//					System.out.println("客戶端通過代理服務器給服務器發送消息"+input+host);
				}
				output.flush();
				try {
					Thread.sleep(10);     //避免此線程獨佔cpu
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
            }
        } catch (SocketTimeoutException e) {
        	try {
				input.close();
				output.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
        }catch (IOException e) {
			System.out.println(e);
		}finally {
			try {
				input.close();
				output.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
    }
}

          寫代碼的過程中有些地方可能會有疑問,不要略過它們,因爲它們能讓你對問題認識更加深刻。我也在想把http和https分開,因爲好多http的Connection不是keep-alive,那麼就可以在進行一次請求響應後就可以斷開連接,這樣就可以提前斷開socket連接結束線程,更早的釋放資源了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章