基於SSL(TLS)的HTTPS網頁下載——如何編寫健壯的可靠的網頁下載

一隻會鏟史的貓發表於2021-06-09

原始碼下載地址
案例開發環境:VS2010
本案例未使用openssl庫,內部提供了sslite.dll庫進行TLS會話,該庫提供了ISSLSession介面用於建立SSL會話。下載的是網易(www.163.com)的主頁。程式執行後會列印SSL會話的加密套件名稱和Http響應頭,並在C盤根目錄下輸出“TestSSLHttp.html”和“TestSSLHttp_body.html”兩個檔案。前者是伺服器響應的原始檔案即包含了響應頭,後者是響應資料檔案(本案例中為主頁HTML)。

HTTP協議很簡單,寫個簡單的socket程式通過GET命令就能把網頁給down下來。但接收大的網路資源就複雜多了。何時解析、如何解析完整的HTTP響應頭,就是個頭疼問題。因為你不能指望一次recv就能接收完所有響應資料,也不能指望伺服器先傳送完HTTP響應頭,然後再傳送響應資料(有可能是兩者一併傳送的)。只有把HTTP響應頭徹底解析了,我們才能知道後續接收的Body資料有多大,何時才能接收完畢。

比如通過響應頭的"Content-Length"欄位,才能知道後續Body的大小。這個大小可能超過了你之前開闢的接收資料快取區大小。當然你可以在得知Body大小後,重新開闢一個與"Content-Length"一樣大小的快取區。但這樣做顯然是不明智的,比如你get的是一部4K高清藍光小電影,藍光電影不一定能get到,藍屏電腦倒有可能get到。。。。。。

遇到伺服器明確給出"Content-Length"欄位,是一件值得額手稱慶的大喜事,但不是每個IT民工都這麼幸運。如果遇到的是不靠譜的伺服器,傳送的是"Transfer-Encoding: chunked",那你就必須鍛鍊自己真正的解析和組織能力了。這些分塊傳輸的資料,顯然不會以你接收的節奏到達你的緩衝區,比如先接收到一個block塊大小,然後是一個完整的塊資料,很有可能你會接收到多個塊或者不完整的塊,這就需要你站在巨集觀的角度把他們拼接起來。

如果你遇到的是甩的一米的伺服器,它不僅給你的是chunked,而且還增加了"Content-Encoding: gzip",那麼你就需要拼接後進行解壓,當然你也可能遇到的是"deflate"壓縮。
附:我寫過web伺服器,所以也知道伺服器的心理。。。。。。
HttpServer:一款Windows平臺下基於IOCP模型的高併發輕量級web伺服器

題外話:我一直困惑的是HTTP協議為何不是對分塊資料單獨gzip壓縮然後傳輸,而只能是整體gzip壓縮後再分塊傳輸。這個對大資源傳輸很關鍵,比如上面的4K高清藍光小電影,顯然不能通過gzip+chunked方式傳輸,土豪伺服器例外。

當然你也可以用開源的llhttp來解析收到的http資料,從而避免上述可能會遇到的各種坑。最新版本的nodejs中就使用llhttp代替之前的的http-parser,據說解析效率有大幅提升。為此我下載了nodejs原始碼,並編譯了一把,這是一個快樂的過程,因為你可以看到v8引擎,openssl,zlib等各種開源庫。。。。,不過llhttp只負責解析,不負責快取,因此你還是需要在解析的過程中,進行資料快取。
關於V8引擎的使用參見文章
V8引擎靜態庫及其呼叫方法

以下是sslite庫提供的介面,SSLConnect是建立連線,SSLHandShake是SSL握手,握手成功後即可呼叫SSLSend和SSLRecv進行資料接收和傳送,非常簡單。如果接收資料很多,SSLRecv會通過回撥函式將資料拋給呼叫層。

以下是部分原始碼截圖,註釋很多,就不一一解釋了。

相關文章