HTTP代理實現請求報文的攔截與篡改3--代碼分析開始


返回目錄 

  下面我們就來分析一下源代碼,詳細的代碼可以看源碼,在這里為了使實現的思路更加的清晰,我們只對部分關鍵的代碼進行說明         

  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***方法和他對應,這是用於異步處理的。而沒有BeginEnd前綴的***方法,則是同步處理用的。因此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 。 也就是ServerPipenull,后面我們會看到,這個是在轉發請求到服務器時創建的(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,ServerChatterClientPipe,ServerPipe的區別,ClientChatter,ServerChatter是較高層次的封裝,例如ClientChatter類就提供了對於客戶端請求頭的封裝,你要想獲取請求的頭信息,可以這樣獲取 ClientChatter的實例.Headers ,Headers是一個HTTPRequestHeaders類型的對象,后面我們會詳細的講講這個類,這里先點出來一下。當然在封裝這些信息之前,需要先從客戶端讀取原始的HTTP頭和內容信息,這個就要通過ClientChatter調用ClientPipe完成了。

  所以總結一下就是, ClientPipe負責從客戶端讀取原始請求信息,並簡單的封裝一下客戶端的相關信息,而ClientChatter會對這些原始請求進行進一步的封裝。 以方便后續的調用。  ServerChatterServerPipe是同樣的道理 。    

 

知道了這些,上面那四句話,自然就容易理解了。 

  創建一個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() // 將從目標服務器讀取的信息返回給客戶端 

 返回目錄


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM