You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在发送前打印完整请求文本?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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 12:55:18