如何在发送前打印完整请求文本?reqwest与Cloudflare问题排查
问题
使用reqwest向Cloudflare防护的API发送POST请求时,只要请求格式稍有偏差(比如请求头顺序不对)就会被拦截。将请求通过BurpSuite代理时能正常通过,但直接发送就会收到Cloudflare拦截页面,推测是代理调整了请求头顺序。
需要解决两个问题:
- 不使用代理时,如何在发送前打印完整的请求文本?
- 如何确保后续请求不再被Cloudflare拦截?
相关代码:
// this works: // let proxy = reqwest::Proxy::https("127.0.0.1:8080")?; // let client = reqwest::Client::builder().proxy(proxy).build()?; // this doesn't work! let client = reqwest::Client::builder().build()?; let mut headers = reqwest::header::HeaderMap::new(); headers.insert("User-Agent", "some text".parse().unwrap()); headers.insert(reqwest::header::ACCEPT, "application/json".parse().unwrap()); let data = "{\"message\": \"Hello\", \"options\": {}}"; let request = client .post("https://myAPI.com/api/v1/") .headers(headers) .body(data); let response = request.send().await?.text().await?; println!("{}", response);
解决方案
一、打印完整请求文本
reqwest没有直接提供打印完整请求的接口,可通过以下两种方式实现:
1. 借助日志中间件输出
使用reqwest-middleware和tracing生态,通过日志打印请求详情:
- 添加依赖到
Cargo.toml:reqwest = { version = "0.11", features = ["json"] } reqwest-middleware = "0.2" reqwest-tracing = "0.2" tracing-subscriber = "0.3" tokio = { version = "1.0", features = ["full"] } - 示例代码:
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use reqwest_tracing::TracingMiddleware; use tracing_subscriber::fmt; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // 初始化日志订阅器,设置DEBUG级别输出请求细节 fmt::init(); let client = ClientBuilder::new(reqwest::Client::new()) .with(TracingMiddleware::default()) .build(); let mut headers = reqwest::header::HeaderMap::new(); headers.insert("User-Agent", "some text".parse().unwrap()); headers.insert(reqwest::header::ACCEPT, "application/json".parse().unwrap()); headers.insert(reqwest::header::CONTENT_TYPE, "application/json".parse().unwrap()); let data = "{\"message\": \"Hello\", \"options\": {}}"; let request = client .post("https://myAPI.com/api/v1/") .headers(headers) .body(data); let response = request.send().await?.text().await?; println!("{}", response); Ok(()) } - 运行时设置环境变量
RUST_LOG=debug,控制台会输出完整的请求头、请求行和请求体内容。
2. 手动拼接请求字符串
自行构建符合HTTP协议格式的请求文本,适合简单场景:
use reqwest::Request; fn print_request(request: &Request) -> Result<(), Box<dyn std::error::Error>> { // 构建请求行 let request_line = format!("{} {} HTTP/1.1\r\n", request.method(), request.url().path()); // 拼接所有请求头 let mut headers_str = String::new(); for (name, value) in request.headers() { headers_str.push_str(&format!("{}: {}\r\n", name, value.to_str()?)); } // 处理请求体(仅支持非流式body) let body_str = match request.body() { Some(body) => { if let Some(bytes) = body.as_bytes() { format!("\r\n{}", String::from_utf8_lossy(bytes)) } else { "\r\n[流式请求体无法显示]".to_string() } }, None => "\r\n".to_string() }; // 打印完整请求 println!("{}{}{}", request_line, headers_str, body_str); Ok(()) } // 使用方式:先构建请求,打印后再执行 // let request = client.post(...).build()?; // print_request(&request)?; // let response = client.execute(request).await?;
二、避免Cloudflare拦截的核心方案
结合你的推测,问题大概率出在请求头顺序或格式不符合浏览器标准,可通过以下方式解决:
1. 按浏览器标准顺序添加请求头
HeaderMap是无序哈希表,会打乱添加顺序,改用链式调用header()方法按浏览器习惯顺序添加:
let client = reqwest::Client::builder().build()?; // 按浏览器典型顺序添加头:User-Agent → Accept → Content-Type let request = client .post("https://myAPI.com/api/v1/") .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36") .header("Accept", "application/json") .header("Content-Type", "application/json") .body("{\"message\": \"Hello\", \"options\": {}}");
浏览器默认会自动添加Host等必要头,无需手动设置。
2. 使用默认头保持顺序
通过ClientBuilder::default_headers()设置的头会保持添加顺序,适合复用固定头的场景:
let mut headers = reqwest::header::HeaderMap::new(); // 按顺序添加 headers.insert("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36".parse().unwrap()); headers.insert(reqwest::header::ACCEPT, "application/json".parse().unwrap()); headers.insert(reqwest::header::CONTENT_TYPE, "application/json".parse().unwrap()); let client = reqwest::Client::builder() .default_headers(headers) .build()?; let request = client .post("https://myAPI.com/api/v1/") .body("{\"message\": \"Hello\", \"options\": {}}");
3. 补充必要请求头
你的代码中缺少Content-Type: application/json,这是JSON POST请求的标准头,必须补充。同时建议使用真实浏览器的User-Agent,避免被识别为爬虫。
4. 完全复刻浏览器请求
用BurpSuite或浏览器开发者工具捕获正常访问API的请求,完全照搬所有请求头的内容和顺序,包括Accept-Encoding、Connection等细节头,能最大程度规避Cloudflare的校验。
内容的提问来源于stack exchange,提问作者Gudarzi
相关产品推荐
相关产品推荐

