下面我們就來分析一下源代碼,詳細的代碼可以看源碼,在這里為了使實現的思路更加的清晰,我們只對部分關鍵的代碼進行說明
- HTTP代理實現請求報文的攔截與篡改1--開篇
- HTTP代理實現請求報文的攔截與篡改2--功能介紹+源碼下載
- HTTP代理實現請求報文的攔截與篡改3--代碼分析開始
- HTTP代理實現請求報文的攔截與篡改4--從客戶端讀取請求報文並封裝
- HTTP代理實現請求報文的攔截與篡改5--將請求報文轉發至目標服務器
- HTTP代理實現請求報文的攔截與篡改6--從目標服務器接收響應報文並封裝
- HTTP代理實現請求報文的攔截與篡改7--將接收到的響應報文返回給客戶端
- HTTP代理實現請求報文的攔截與篡改8--自動設置及取消代理+源碼下載
- HTTP代理實現請求報文的攔截與篡改8補--自動設置及取消ADSL拔號連接代理+源碼下載
- HTTP代理實現請求報文的攔截與篡改9--實現篡改功能后的演示+源碼下載
- HTTP代理實現請求報文的攔截與篡改10--大結局 篡改部分的代碼分析
Fiddler 的設計思想還是相當不錯的,在程序啟動的時候,new 一個代理(Proxy)類的實例,然后調用這個實例的Start方法,來啟動代理服務,在Start方法里就是不停的異步監聽本機的8888端口(還記得剛才設置代理服務器時設置的端口嗎),如果監聽到了,就從線程池里,取出來一個線程,並在這個線程里,構造一個Session對象。一個Session對象,代表客戶端與服務器的一次會話,從開篇的兩張圖可以知道,在有代理服務器情況下的一次會話(Session)代表的是1.從客戶端讀請求,2.重新包裝客戶端的請求,轉發至目標服務器. 3.從目標服務器讀取響應信息 4.包裝接收到的響應信息並返回給客戶端。故而在Session類里,封裝一個ClientChatter類型的名為Request的對象,用來實現和客戶端的通訊,另外又封裝了一個ServiceChatter類型的名為Response的對象,用來實現和目標服務器的通訊。 ClientChatter和ServiceChatter是對通訊的高層封裝,而原始的數據流,則分別由ClientChatter里的ClientPipe(客戶端管道)和ServiceChatter里的ServicePipe(服務端管道)來進行讀取。ClientChatter 調用 ClientPipe的Receive方法來讀取原始請求流,並對原始的請求流進行分析,將分析出來的HTTP頭信息保存在 Headers變量里,並將其它相關信息,存放在相應的變量里。ServiceChatter的功能類似,只是通訊的對象由客戶端變成目標服務器而已 。
我們的代碼基本上沿襲Fiddler的 思想 。
下面來看具體的代碼。
先從Program.cs開始
1 [STAThread] 2 static void Main() 3 { 4 Application.EnableVisualStyles(); 5 Application.Run(new FrmMain()); 6 }
如果不刻意的在工程屬性里設置,這里就是整個程序的入口點。
從上面的代碼可以看出來,默認啟動了 FrmMain 窗體類
FrmMain.cs
順藤摸瓜
下一步我們來看FrmMain窗體類的構造方法,界面部分略去,直接看最后兩句。
1 proxy = new Proxy(); 2 proxy.Start(8888);
在這里,我們的核心類 Proxy 出現了,剛才已經講過,當調用Start方法時,我們的代理服務就正式啟動了。
Start方法,只有一個參數listenPort
internal bool Start(int listenPort)
就是我們的代理服務器要監聽的端口,在這里我們寫死成了8888 。 proxy.Start(8888) ;
Net/Proxy.cs
再到Proxy.Start方法體里看看有些什么。
1 internal bool Start(int listenPort) 2 { 3 try 4 { 5 this.acceptor = 6 New Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp); 7 this.acceptor.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), listenPort)); 8 this.acceptor.Listen(50); 9 this.acceptor.BeginAccept(new AsyncCallback(this.AcceptConnection), null); 10 } 11 catch (Exception e) 12 { 13 MessageBox.Show(e.Message); 14 return false ; 15 } 16 return true; 17 }
這個代碼很簡單,就是創建一個Socket對象,然后,綁定到本機(127.0.0.1)的listenPort(8888)端口
this.acceptor.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), listenPort));
然后開始監聽這個端口
this.acceptor.Listen(50);
參數50表示等待處理的連接隊列的最大值,這個有點難理解,說簡單點就是,當客戶端請求連接服務端(我們這個就是服務端)時, 服務端會將監聽到的請求都放到一個隊列里(50就是設置這個隊列的最大值的),然后服務端通過Accept方法,從這個隊列里取出來一個請求,然后依據這個請求,創建一個和客戶端的連接,並用這個連接和客戶端進行通訊。 也就是說如果在服務端,一直都不Accept的話,客戶端最多只能連接50次, 超過50次,服務器就會直接拒絕請求,因為等待隊列已經滿了 。
在這里引出了一個方法:Accept . 這正是我們下一步要用到的。看下一句
this.acceptor.BeginAccept(new AsyncCallback(this.AcceptConnection), null);
這里出現了一個BeginAccept方法
Socket里的方法有個規律,類似Begin***的方法,肯定有個End***方法和他對應,這是用於異步處理的。而沒有Begin和End前綴的***方法,則是同步處理用的。因此this.acceptor.Accept 這個方法就是用於同步接收的方法了,當然這里我們是不能使用同步接收的方法的,我們可不想,如果隊列里沒有連接的時候,程序堵在Accept這里不動。
BeginAccept的第一個參數,是異步處理的委托,這里我們設置成了AcceptConnection方法。 也就是一旦我們的服務端監聽到了連接,就會馬上調用AcceptConnection方法進行處理。
下面我們繼續進入AcceptConnection方法
1 private void AcceptConnection(IAsyncResult ar) 2 { 3 try 4 { 5 ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(Session.CreateAndExecute), this.acceptor.EndAccept(ar)); 6 } 7 catch (ObjectDisposedException) 8 { 9 return; 10 } 11 catch (Exception) 12 { 13 return; 14 } 15 try 16 { 17 this.acceptor.BeginAccept(new AsyncCallback(this.AcceptConnection), null); 18 } 19 catch (Exception) 20 { 21 } 22 }
忽略異常處理的部分(當然這里處理的相關粗糙),這段代碼,其實只有兩句
// 1: ThreadPool.UnsafeQueueUserWorkItem( new WaitCallback(Session.CreateAndExecute), this.acceptor.EndAccept(ar) ); // 2: this.acceptor.BeginAccept(new AsyncCallback(this.AcceptConnection), null);
第一句是從線程池里取出來一個線程,並在這個線程里執行Session.CreateAndExecute 方法。具體的說明,可以查MSDN。在這里只要明白,它就相當於在另外一個線程里執行這句
Session.CreateAndExecute(this.acceptor.EndAccept(ar));
就行了
這里this.acceptor.EndAccept(ar) 要注意一下 。
上面也提到了。Begin***,End***是成對出現的,在Proxy.Start方法里,出現了一個BeginAccept,這里自然要出現一個EndAccept和他對應,同時
this.acceptor.EndAccept(ar) 的返回值是一個新的Socket ,這個Socket就是前面提到的用於和客戶端進行通訊的Socket了。在這里直接將這個Socket作為參數傳遞給了 Session.CreateAndExecute 方法
第二句和Proxy.Start里一樣的,就是再繼續監聽有沒有其它請求,如果監聽到了,就再調用Proxy.AcceptConnection處理 。
這樣我們就可以不間斷的處理所有來自客戶端的請求了。
Net/Session.cs
好了 Proxy.AcceptConnection 講完了,下面自然要進入 Session.CreateAndExecute 方法了。 Session.CreateAndExecute 方法在 Net目錄下的Session.cs 里。
定義如下 :
public static void CreateAndExecute(object param)
{
ClientPipe clientPipe = new ClientPipe((Socket)param);
Session session = new Session(clientPipe, null);
session.Execute();
}
這是一個靜態方法,主要是用來創建一個Session類的實例。並執行Session實例的Execute方法,這里只有一個參數,這個參數就是從Proxy.AcceptConnection 方法里傳進來的用來和客戶端通訊的那個Socket ;
前面已經提到過,Session類是用來封裝代理服務器的一次會話,而代理服務器的一次會話表示,從客戶端讀請求,然后轉發至服務器,然后再讀取服務器的響應,然后再將響應轉發回客戶端,所以在代理的一次會話中,需要一個和客戶端通訊的SOCKET從客戶端讀請求,並將響應發回給客戶端,另外還需要一個和服務端通訊的SOCKET往服務端轉發請求,並讀取 服務端的響應 。
所以Session類有一個兩個參數的構造方法(第二句)
public Session(ClientPipe clientPipe, ServerPipe serverPipe)
前面也提到過,ClientPipe 和 ServerPipe 其實就是對於Socket的封裝。
所以ClientPipe有一個Socket類型參數的構造方法
ClientPipe clientPipe = new ClientPipe((Socket)param);
在這里第二個參數是 null 。 也就是ServerPipe是null,后面我們會看到,這個是在轉發請求到服務器時創建的(NEW出來的)。為什么要延遲到那里再NEW,其實很容易理解,因為到目前為止,我們還沒有開始讀取客戶端的信息,這樣我們就不知道客戶端究竟要將請求發送到哪里,自然就沒辦法建立一個到目標服務端的管道了。Fiddler創建一個這樣的構造方法,是因為ServerPipe的重用,但ServerPipe的重用我們給簡化掉了,不過這樣的構造形式仍然保留了下來,至於理由嘛,就是這樣看起來很對稱 :) 。
下面我們來看看Session的這個構造方法。
1 public Session(ClientPipe clientPipe, ServerPipe serverPipe) 2 { 3 this.Timers = new SessionTimers(); 4 this.Timers.ClientConnected = DateTime.Now; 5 this.Flags = new StringDictionary(); 6 if (clientPipe != null) 7 { 8 this.clientIP 9 = (clientPipe.Address == null) ? null : clientPipe.Address.ToString(); 10 11 this.clientPort = clientPipe.Port; 12 this.Flags["x-clientIP"] = this.clientIP; 13 this.Flags["x-clientport"] = this.clientPort.ToString(); 14 if (clientPipe.LocalProcessID != 0) 15 { 16 this._localProcessID = clientPipe.LocalProcessID; 17 this.Flags["x-ProcessInfo"] 18 = string.Format( 19 "{0}:{1}", 20 clientPipe.LocalProcessName, 21 this._localProcessID 22 ); 23 this._localProcessName = clientPipe.LocalProcessName; 24 } 25 } 26 27 this.Response = new ServerChatter(this); 28 this.Request = new ClientChatter(this); 29 this.Request.ClientPipe = clientPipe; 30 this.Response.ServerPipe = serverPipe; 31 }
這里開始就實例化了一個SessionTimers。
this.Timers = new SessionTimers();
這個類在Fiddler里是用來記錄一次會話過程中的各個階段的時間點的,主要是用來分析會話的過程中各個階段所花費的時間。在這里我們保留下來以備后用,但是對這次例子用處不大。可以忽略掉。
下面又實例化了一個StringDictionary類型的Flags對象,
this.Flags = new StringDictionary();
用來存儲Session過程中的一些標志信息,例如客戶端的進程信息,是否設置了斷點,斷點類型等等。這個標志信息是很重要的,他可以使我們方便的在不同的方法甚至類中傳遞一些信息.
下面一句是判斷clientPipe是否為空。如果不為空(這里不為空,我們在Session.CreateAndExecute方法里已經構造了一個實例,並傳遞過來了). 就讀取一些信息,寫到 this.Flags 里。
這里稍稍再詳細一點的對ClientPipe做個說明,剛才講過了,ClientPipe其實就是對負責和客戶端通訊的那個Socket的一個封裝,所以除了提供基本的通訊功能外,又進一步對一些客戶端的信息進行了封裝,例如,客戶端的IP,端口,進程名和進程ID等等。
再往下四句
this.Response = new ServerChatter(this); this.Request = new ClientChatter(this); this.Request.ClientPipe = clientPipe; this.Response.ServerPipe = serverPipe;
不知道還記不記得最開始的那段分析,我們提到了。ClientChatter,ServerChatter和ClientPipe,ServerPipe的區別,ClientChatter,ServerChatter是較高層次的封裝,例如ClientChatter類就提供了對於客戶端請求頭的封裝,你要想獲取請求的頭信息,可以這樣獲取 ClientChatter的實例.Headers ,Headers是一個HTTPRequestHeaders類型的對象,后面我們會詳細的講講這個類,這里先點出來一下。當然在封裝這些信息之前,需要先從客戶端讀取原始的HTTP頭和內容信息,這個就要通過ClientChatter調用ClientPipe完成了。
所以總結一下就是, ClientPipe負責從客戶端讀取原始請求信息,並簡單的封裝一下客戶端的相關信息,而ClientChatter會對這些原始請求進行進一步的封裝。 以方便后續的調用。 ServerChatter和ServerPipe是同樣的道理 。
知道了這些,上面那四句話,自然就容易理解了。
創建一個ServerChatter類型的Response對象,用來和服務端進行通訊和獲取相關的信息。再創建一個 ClientChatter 類型的Request對象,用來和客戶端進行通訊並獲取相關的信息,可以看到Request有一個ClientPipe屬性,我們將在Session.CreateAndExecute里創建的clientPipe對象賦值給了它,而這個對象就是對和客戶端通訊的Socket的一個封裝
好了,看完了Session的這個構造方法,我們重新回到Session.CreateAndExecute方法。現在只剩下最后一句了。
session.Execute();
這個就不用講了吧,基本上是個人類都能看明白了,就是調用Session類的Execute方法。
所以下面我們進入Session.Execute方法 。
當然在進入Execute方法之前,
我們需要先回顧一下剛才所分析出來的東西:
session 這個對象里現在已經有了哪些東西?
session:Session -- Request:ClientChatter -- ClientPipe:ClientPipe = new ClientPile(和客戶端通訊的Socket) -- Response:ServerChatter -- ServerPipe:ServerPipe = null -- Flags:StringDictionary
好的,有了這些儲備后,我們就可以正式進入Session.Execute方法了 。
Execute里面代碼相對較多,為使思路清晰,我們去掉一些細節部分,只保留主干部分,如下所示:
1 internal void Execute() 2 { 3 if (!this.ObtainRequest()){return;} // 獲取請求信息 4 if (this.State < SessionStates.ReadingResponse) 5 { 6 if (!this.Response.ResendRequest()) // 將包裝后的請求重新發到目標服務器 7 { 8 this.CloseSessionPipes(true); 9 this.State = SessionStates.Aborted; 10 return; 11 } 12 13 Intercepter.UpdateSession(this); 14 15 if (!this.Response.ReadResponse ()) // 讀取從目標服務器返回的信息 16 { 17 if (this.State != SessionStates.Aborted) 18 { 19 this.Request.FailSession(0x1f8, 20 "Receive Failure", "ReadResponse() failed: The server did not return a response for this request." 21 ); 22 } 23 this.CloseSessionPipes(true); 24 this.State = SessionStates.Aborted; 25 } 26 this.ResponseBodyBytes = this.Response.TakeEntity(); 27 if (this.Response.ServerPipe != null) 28 { 29 this.Response.ServerPipe.End(); 30 } 31 32 if (this.ReturnResponse()) // 將從目標服務器讀取的信息返回給客戶端 33 { 34 this.State = SessionStates.Done; 35 } 36 else 37 { 38 this.State = SessionStates.Aborted; 39 } 40 if (this.Request != null && this.Request.ClientPipe != null) 41 { 42 this.Request.ClientPipe.End(); 43 } 44 this.Response.ReleaseServerPipe(); 45 } 46 }
看一下上面有注釋的四句,是不是感覺有點熟悉,是的,這正是前面講過的,代理服務器一次會話的四個步驟。
this.ObtainRequest() // 獲取請求信息 this.Response.ResendRequest() // 將請求報文重新包裝后轉發給目標服務器 this.Response.ReadResponse () // 讀取從目標服務器返回的信息 this.ReturnResponse() // 將從目標服務器讀取的信息返回給客戶端
