跳到主要內容

Rust - 給我一塊小餅乾;淺談 reqwest 裡的 Cookies 操作

緣起

這陣子利用 Rust 開發了一個爬蟲程式,上網抓取一些網路資料,無意間發現對方的系統竟然會記錄且驗證 Session ID。在架構微服務化、API 無狀態化的風潮下,這樣的實作方式已經很少看到了(至少在我寫爬蟲、撈資料的這近十年間)。為了解決這個問題,我重啟了我對 Cookies 塵封已久的記憶,尋找處理 Session ID 對應的解決方案。

在Rust語言裡有許多 HTTP/HTTPS Client 的 Crate,reqwest [1][2] 應該是比較常見的一個套件。它是一個基於 hyper [3][4] Crate 的高階封裝實作,簡化了 Rust 在 HTTP/HTTPS 上的操作。同時,它支援了各種常見的 HTTP 方法(GET、POST、PUT、DELETE等)、使用者定義的標頭、Query String、POST Form 及 JSON Body 等操作,以及對應的 Response 處理。

看似功能完整、內容豐富的 reqwest Crate,在原生的功能實作中,竟然沒有 Cookies 操作的對應模組及功能?!這當下可讓我吃驚了,怎麼會這樣?提供 Cookies 的操作,這不是一個 HTTP/HTTPS Client 所應該具備的標配功能嗎?難道說真的是太少人使用,所以乾脆就不提供了?

面對這樣的狀況,那可真是令人苦惱啊!我可不想為了這件事,還要自已刻一個 Cookies 的操作處理器。

本文開始

關於這個問題,我在網路上花了不少的時間做了研究。其實要處理 Cookies 不外乎有兩個途徑,第一是使用者自己客製化、以手動的方式處理;顯然這個對我們來說,並不是一個最好的選項(尤其對於一個「站在巨人的肩膀上 [5][6]」哲學信仰者的我來說…);第二種方法則是尋找是否有現成的套件可以使用,以有效縮短我們的開發時間。我相信這個應該會是大部分開發者所傾向使用的一種開發方式。 

雖然在 reqwest 的套件裡,並沒有直接支援 Cookies 的操作功能,但是它還是為這個部分保留了一些彈性的空間。在 reqwest 裡頭宣告了幾個與 Cookie 操作相關的 Trait,例如 Cookie、CookieStore 等等。尋著這些線索,我們在網路上找到了一些參考資料;當然,也有相對應的實作可以參考使用,也就是接下來我們要介紹的這個套件- reqwest_cookie_store[7]。 那就先讓我們從一段簡短的程式範例,來開始說明如何透過使用 reqwest_cookie_store crate 對 Cookies 進行操作吧!

一段簡短的程式範例

下面的這段程式,簡單的示範了怎麼在 Rust 程式中,使用 reqwest_cookie_store crate 建立 Cookie Store:

 
use reqwest_cookie_store::{CookieStore, CookieStoreMutex};
use std::sync::Arc;

fn main() {
// 建立 Cookie Store
let cookie_store = CookieStore::new(None);
let cookie_store = CookieStoreMutex::new(cookie_store);
let cookie_store = Arc::new(cookie_store);
}
 

看到這段程式碼,恐怕腦海裡馬上就浮現出許多的問題。一個 Cookie Store,為什麼要套嵌三層?而 `Arc` 又是什麼?為什麼要這麼作?對初學 Rust 的人來說,可能會造成很大的困擾。雖然看似繁複,但這一切都是為了做出好更有效地記憶體管理策略所做的必要犧牲。

這段程式碼的設計,實際上是為了實現 Rust 特有的所有權模型和記憶體安全性而做出的選擇。在 Rust 中,所有權(Ownership)系統確保每一塊記憶體都有明確的所有者(而且同一時間只能有一個),當所有者離開作用域時,記憶體會自動釋放,避免了常見的記憶體洩漏問題。然而,這也帶來了額外的複雜性,特別是在處理多層套嵌結構時。

三層套嵌的設計,通常是為了達成某些特定目標。例如,`Arc`(原子引用計數,Atomic Reference Counting)允許多個所有者共享同一個資源,並確保資源在最後一個引用消失後才會釋放。這在多執行緒的環境中特別有用,可以避免資料競爭和非同步程式中的不安全行為。這麼做的結果是,雖然程式碼看起來繁瑣且難以理解,但它在背後解決了複雜的記憶體管理問題,並保證了程式的安全性和效率。

對初學者來說,面對這樣的設計可能感到不知所措,但隨著對 Rust 更深入的理解,這種設計理念和其背後的原因將變得更加清晰。最終,這種複雜性將轉化為強大的記憶體管理能力,讓開發者能夠編寫出更安全、更高效的程式碼。

建立起 Cookie Store 之後,我們透過以下的代碼建立起 HTTP/HTTPS Client,並把剛剛完成的 Cookie Store 給設置進去:


use reqwest::{ClientBuilder, Request, Result};

use reqwest_cookie_store::{CookieStore, CookieStoreMutex};

use std::sync::Arc;


#[tokio::main]

async fn main() -> Result<()> {

   // 建立 Cookie Store

   let cookie_store = CookieStore::new(None);

   let cookie_store = CookieStoreMutex::new(cookie_store);

   let cookie_store = Arc::new(cookie_store);


   // 建立 Client 

let client = ClientBuilder::new()

       .cookie_store(true)

       .cookie_provider(Arc::clone(&cookie_store))

       .build()

       .unwrap();


   // 建立 Request (GET)

   let url = "https://www.google.com/";

   let req = Request::new(reqwest::Method::GET, url.parse().unwrap());


   // 呼叫並取回結果

   let _resp = client.execute(req).await?;


   Ok(())

}

 

您或許注意到了這一段程式碼與前面的程式片段有些許的不同。這裡主要的原因是因為 reqwest 在執行呼叫的時候,大部分我們都會採用非同步的方式執行呼叫。我們暫時不在這邊討論撰寫非同步程式的細節,總之您先知道這些差異是來自於這個部分。

通常到這裡,只要在接下來的操作中使用同一個 client,那這些連續呼叫中所使用到的 Cookies,就會儲存在我們的 Cookie Store 之中。如何取出使用中的Cookie呢?參考以下的程式碼:

 
...(略)
 
#[tokio::main]
async fn main() -> Result<()> {
 
...(略)
 
// 呼叫並取回結果
let _resp = client.execute(req).await?;

// 取出 Cookie Store 裡的 Cookies
{
let store = cookie_store.lock().unwrap();
for cookie in store.iter_any() {
let (k, v) = cookie.name_value();
println!("{k}={v}");
}
}

Ok(())
}

同時得到以下的輸出結果:

 
NID=517=uy-Uv_L5KZ5SGd8RzTcw-tgt_7UPHB8doRt_...(略)
AEC=AVYB7coV8VPItjSPsj71B3wOsFKCf1bimi6XKyro_...(略)
 

That is, happy scraping.

結論

在這篇文章中,我們深入探討了如何在 Rust 中使用 reqwest_cookie_store 這個 crate 來管理 HTTP 請求中的 Cookies。從建立 Cookie Store、設置 HTTP Client,到最終取出並列印 Cookies,我們一步步地走過整個流程。

reqwest_cookie_store 提供了方便且強大的工具,讓我們在 Rust 應用程式中輕鬆地處理 Cookies。當然,透過理解程式碼背後的設計原理,我們可以更深入地掌握 Rust 的記憶體管理機制和非同步程式設計。

當然,希望可以透過這篇文章的分享,讓同是學習 Rust 中的你/妳,可以更快的上手,利用 Rust 建立起應用程式。

參考資料

[1] reqwest Docs (https://docs.rs/reqwest/latest/reqwest/)

[2] reqwest GitHub (https://github.com/seanmonstar/reqwest)

[3] hyper Docs (https://docs.rs/hyper/latest/hyper/)

[4] hyper official web site (https://hyper.rs/)

[5] Issac Newton - "If I have seen further it is by standing on the shoulders of giants."

[6] 遠見-重點是肩膀,不是巨人 (https://www.gvm.com.tw/article/46684)

[7] reqwest_cookie_store Docs (https://docs.rs/reqwest_cookie_store/latest/reqwest_cookie_store/)

留言

這個網誌中的熱門文章

青山瀑布、老梅外拍

班上的第二次外拍,不過今天的狀況不大好,不太能抓到感覺,很多景都是看人家拍就跟著拍(除了那隻不知名的小蟲除外);到了老梅更慘,沒有減光鏡,只能夠拍出海浪拍打岩岸的畫面,平常看到人家拍老梅那種細細柔柔的味道,一點都沒辦法呈現。 不過我不覺得這次外拍是失敗的,至少我大概知道使用減光鏡能怎樣表現,也拍到了那張岩石上奇特的小蟲。 Ps. 文華大哥謝謝你啦!教我擺個葉子,讓畫面更有感覺。 :) Fig. 1 蠻有詩意的畫面,不過我覺得流水的型狀還不夠漂亮就是了。 Fig. 2 老師評語:在不是楓紅的季節,擺張綠色的葉子,清楚的五爪在畫面上顯的相當有張力。不過Fig1改採直幅構圖,感覺應該會更好。 Fig. 3 老師評語:只有黑與白,顏色比較單調。不過曝光控調的還不錯。 Fig. 4 流水澎湃你感覺到了嗎? 老師評語:同上一張,顏色單調;可以考慮用些植物做為前景來搭配。 Fig. 5 老師評語:綠葉沒有透光的感覺,看起來暗暗的;加上後方白色的瀑布,主體不明顯。 Fig. 6 老師評語:光影變化、曝光控制還不錯,下方的石頭可以考慮裁切掉,或著留更多成為前景。 Fig. 7 我很喜歡這張照片,不錯的光影變化,充滿生命力的感覺。 老師評語:特別的光影變化,還有生動昆蟲。雖然昆蟲的細節因顏色不明顯,不過和特別的光影變化搭配,加上石頭的曲線,乃是相當不錯。 Fig. 8 老師評語:沒有用減光鏡,所以石頭上有輕微的反光,顏色不夠生動。不過構圖上,使用石槽將視線由左下往右上引導至浪花,感覺還不錯。 Fig. 9 老梅奇特的岩岸地形。 老師評語:利用直幅構圖,表現石槽及延伸感。 Fig. 10 海水拍打在岩岸上,濺起陣陣的水花。 老師評語:快門速度要快不快、要慢不慢,可以再斟酌一下。 Fig. 11 老師評語:拍人物時沒有打閃光,導致眼睛有陰影而無神。可以透過閃光燈補光,製造眼神光。

[潛水器材] 防寒衣

新一年度的潛水季準備到來,預訂的潛水訓練班也即將開課。為了充實個人的基本學能,花了些時間在網路及書本上,研讀並整理相關的潛水常識。就先談談潛水活動所常使用到的個人裝備--防寒衣開始吧! 空氣的熱傳導係數為0.025 W/(m·K),而水的熱傳係數為0.6 W/(m·K)。依此推算,水的熱傳導速度,約比空氣快24倍 [1][2] 。 正由於水對熱的傳導速度,要比在空氣中來得快。當潛水員入水之後,人體與水接觸並加上水流的作用,尤其是在清澈的淡水,或是冰冷的環境下,身體的熱量很快就會被帶走。長時間的體溫流失,最終將會造成人體失溫。防寒衣的主要功用,就是在人體與水之間,建立起一層隔離層。透過隔離層的建立,可以有效的將水與人體隔絕,不再接觸並減少體溫流失。 目前市面上主要的防寒衣有以下三類: 濕式防寒衣 (Wet Suit) 主要的材質為Neoprene / Polychloroprene [3] 。當潛水員穿著以Neoprene製作的防寒衣下水時,衣料中的空隙將會被水分子填滿,同時這些水分子將被潛水員的體溫加熱。由於衣料中的水分子被鎖住在空隙中不再流動,因此可以將熱量保持在身體的四週,並大幅的減少體溫的散失,進而達到防寒保溫的作用。 如上所說,濕式防寒衣必須將熱量保留在身體的四周,因此應避免冷水由防寒衣的縫隙進出。選購防漢寒衣時,應特別注意剪裁是否合身,活動是否方便等。 半乾式防寒衣 (Semi-Wet Suit) 你也可以稱為半濕式防寒衣,一般材質亦使用Neoprene。但與濕式防寒衣不同的是,半乾式防寒衣在軀幹與四肢連接處,採用水密設計,可以阻擋冷水進入防寒衣內,以達到防寒效果。不過,由於半乾式防寒衣並不是完全將身體密封於防寒衣內,水仍然有機會從皮膚與防寒衣間的細縫滲入;而且位於亞熱帶地區的台灣,使用半乾式防寒衣的機率並不高,因此購買前應多加考慮。 乾式防寒衣 (Dry Suit) 乾式防寒衣通常以防水材質製作,常用於冰冷海域及極地低溫環境下潛水。由於乾式防寒衣應用的範圍,較多於專業/技術潛水領域,一般休閒潛水較少使用,故不在此多做討論。 除了防止體溫的流失,防寒衣另有一個很重要的功能,就是保護潛水員不受水中生物的螫咬。水底世界中有各式各樣的生物,為了在這充滿競爭及挑戰的環境中生存,牠們各自發展出不同的生存策略,而帶有毒螫...

小景

小景 , a photo by 我是歐嚕嚕 (I'm Olulu...) on Flickr. Information: Camera: LOMO LC-A Film: Perutz Primera 200