http响应Header:Content-Encoding=br怎么办?

来由

调用一个外部接口,偶然遇到响应的header里面content-encoding为br(如下图),一般而言,这个值是常见的gzip等,起初并未在意。
此接口的response header

后来解析接口response body的时候,自然而然的使用String content = org.apache.http.util.EntityUtils.toString(response.getEntity(), CHARSET)转String,然而如看官所愿,大意了,怎么都是乱···码!!!

第一时间以为是编码方式(很正常的吧^^),好吧,仔细看响应,content-type简单的text/html,居然没有指定charset,懵逼了,没有编码方式。。。国际惯例utf-8都不能解析?于是其他的接口(如下图)跟这个接口比对,终于发现了该接口指定了一个content-encoding=br,太特殊了。
其他接口的response header
于是Bing一下,找到了 Content-Encoding的说明,原来值br表示是采用了 Brotli 算法的编码方式。

Brotli算法

Brotli是一种全新的数据格式,可以提供比Zopfli高20-26%的压缩比。据谷歌研究,Brotli压缩速度同zlib的Deflate实现大致相同,而在Canterbury语料库上的压缩密度比LZMA和bzip2略大。 链接:Google开源Brotli压缩算法 微软使用了一种基于谷歌提供的C代码的实现,向.NET Core 2.1添加了Brotli压缩支持。由于Brotli得到了许多Web浏览器和Web服务器的广泛支持,所以.NET Core提供对这项技术的支持是非常有用的。

什么是 Brotli 压缩算法
Brotli最初发布于2015年,用于网络字体的离线压缩。Google软件工程师在2015年9月发布了包含通用无损数据压缩的Brotli增强版本,特别侧重于HTTP压缩。其中的编码器被部分改写以提高压缩比,编码器和解码器都提高了速度,流式API已被改进,增加更多压缩质量级别。新版本还展现了跨平台的性能改进,以及减少解码所需的内存。

与常见的通用压缩算法不同,Brotli使用一个预定义的120千字节字典。该字典包含超过13000个常用单词、短语和其他子字符串,这些来自一个文本和HTML文档的大型语料库。预定义的算法可以提升较小文件的压缩密度。

使用brotli替换deflate来对文本文件压缩通常可以增加20%的压缩密度,而压缩与解压缩速度则大致不变。使用Brotli进行流压缩的内容编码类型已被提议使用“br”。

解决

首先引入解码相关的依赖

<dependency>
    <groupId>org.brotli</groupId>
    <artifactId>dec</artifactId>
    <version>0.1.2</version>
</dependency>

解码代码

import org.apache.commons.io.IOUtils;
import org.brotli.dec.BrotliInputStream;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class BrotliUtil {

    public static String toString(InputStream is) {
        try {
            BrotliInputStream stream = new BrotliInputStream(is);
            BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
            StringBuilder result = new StringBuilder();
            String str = null;
            while ((str = reader.readLine()) != null) {
                result.append(str);
            }
            return result.toString();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            IOUtils.closeQuietly(is);
        }
    }
}

RFC

参考
[1]: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Encoding
[2]: https://zh.wikipedia.org/wiki/Brotli

auth_url = "https://reg.usps.com/entreg/json/AuthenticateAction" headers = { "Connection": "keep-alive", "Accept": "*/*", "Content-Type": "application/json; charset=UTF-8", # 必须与发送的数据格式一致 # "Cache-Control": "max-age=0", "Sec-ch-ua": '"Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"', "Sec-ch-ua-mobile": "?0", "Sec-ch-ua-platform": '"Linux"', "Sec-Fetch-Site": "same-origin", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36", "Cookie": "TLTSID=4e5396f3d77f16638b0600e0ed96ae55; reg-entreg=ffffffff3b22206b45525d5f4f58455e445a4a4212d3; NSC_d.tt1_443=ffffffff2198c58945525d5f4f58455e445a4a42378b; _gcl_au=1.1.135541022.1743605114; mab_usps=32; tmab_usps=83; _ga=GA1.1.833753698.1743605115; mdLogger=false; uosLNeRegXVyDVaY=9601b61b4d76be0655ec7fdadb356a63824bc47ced4cb400cc7f6d29776a3b3812948fb9608fcc55cfe37a7de06b57a9794eed8da5769a25ed6e0fb945154a87; ak_bmsc=AE74815066F721963DEC64EB4825AEEB~000000000000000000000000000000~YAAQaz0xF2b8APqVAQAAvx89+hvetxBy/OcP2fW++jr4hxJfdiCTHIfTETkA1x3jq3rNH2Twt+U86tZ3oYZQ9gbSrDkuMiWu+ZMdKambDmGbsZPyU0dmcbQzObzctjGn3YaicaOqvF9SXHSxn21zyNzS3KhpHMY726pSeqXCFfpGN7kBQChCpLQrJq4MJACHfGKaowk770KsjdomiduOUpu/+7JLE4jslLXBklJnzhnD+dLBGtwgq3WXFwVu//xTlbhdApBUIUTrB31OoY7lyr7TVbV4M+HzPRhQEM5zct2pq+berrFg/lmOvIS4cPaKWAJCPbxEQZKxhOJm8KK8zBxc897pSMWvfBU=; EntRegName=Michale||Li; EntRegPrefs=lang=en; o59a9A4Gx=A4mV9vaVAQAAVWeASDnX-Xyy27X1sj1uIXfXU4Ucr1HD01-Y4xAdIMV89lVtASWA9mSuckX5wH8AAEB3AAAAAA|1|0|fc6d67bc1f76bd944e0f366634fbea6d656afa09; _ga_QM3XHZ2B95=GS1.1.1743660064.4.1.1743662154.0.0.0; JSESSIONID=0000A_4rSxwbnR_ZR3V57cje37b:1cn707vkr; bTNsZJBqwVewZTem=c5de2cbbf18e9f14c97e03096b3ff128e749780ff7b14176d1e3497a7b6d05bff13ef4aaed59ccc834cbc1899b6ad001b5c550333aa122d427a8c6e9776a13b8; bm_sv=354475CF54520C6DE195ACE1614F74E0~YAAQkPnLF3vZofmVAQAAcipd+huRXahY/A16X+oE3MEcB9DavjXYoCj2fb0ra8M8QRPX97zesM+S5ebR4ReIe6dfLVfQHTyf4whGMYpEfU0Q8PX3FkLvkbjoe4RfuCtxmwV0uWmeyR1gTZ8HHNCoVj5hLgBwBCzu3vifMyiiRDi5YxmPHM+ZdkTFtqt2QXeEvKYcD7XQuGHoIZEOQNaVgch6/2cwPjxX1A+I4X363w7B5NyrXyi6Re2CRC+P5AA=~1; _ga_3NXP3C8S9V=GS1.1.1743660064.4.1.1743662168.0.0.0; kampyleUserSession=1743662170111; kampyleUserSessionsCount=11; kampyleSessionPageCounter=1; _ga_7TB0KSCYX9=GS1.1.1743662169.6.1.1743662229.0.0.0; _ga_CSLL4ZEK4L=GS1.1.1743660064.4.1.1743662229.0.0.0", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Language": "zh-CN,zh;q=0.9", "x-requested-with": "XMLHttpRequest", } session = requests.Session() try: # 发送禁止重定向的 POST 请求 resp = session.post( auth_url, headers=headers, data=data, proxies=proxies, # allow_redirects=False, # 核心控制参数 # timeout=10 # 建议添加超时 ) # print(resp.history[0].text) # 打印关键调试信息 print("=" * 30) print(f"状态码: {resp.status_code}") print(f"响应: {resp.headers}") print(f"Location 头: {resp.headers.get('Location', '无')}") # 解析响应体(JSON 或原始文本) try: response_data = resp.json() print("JSON 响应:", response_data) except requests.exceptions.JSONDecodeError: pass print("原始文本响应:", resp.text[0:100]) print("=" * 30) except requests.exceptions.RequestException as e: print(f"请求异常: {str(e)}") 原始文本响应: <!DOCTYPE html PUBLIC "" ""> <html><head><META http-equiv="Content-Type" content="text/html; char
04-04
<think>嗯,我现在需要分析用户提供的这段代码,看看为什么在访问美国邮政服务(USPS)的认证接口时,返回的响应体是HTML内容而不是预期的JSON数据。首先,用户使用的URL是auth_url = "https://reg.usps.com/entreg/json/AuthenticateAction",看起来是用于认证的API端点。代码中设置了headers,包括Content-Type为application/json,这可能意味着他们在发送JSON数据。用户使用了requests库的Session对象发送POST请求,但响应却返回了HTML,这可能意味着认证失败或者请求被重定向到了登录页面。 首先,我需要检查请求是否正确构造。用户提供的headers看起来比较完整,包括User-Agent和一些安全相关的头信息,还有较长的Cookie。可能存在的问题是Cookie的有效性。如果Cookie过期或无效,服务器可能会拒绝请求,转而返回登录页面的HTML。此外,代码中注释掉了allow_redirects=False,这意味着允许重定向。如果认证失败,服务器可能会重定向到登录页面,导致响应内容为HTML。需要确认是否应该禁止重定向,以便查看原始响应。 另一个可能性是发送的数据格式是否正确。用户提到data变量,但在代码中没有提供data的内容。如果data不是正确的JSON格式,或者缺少必要的字段(比如用户名、密码),服务器可能无法处理请求,从而返回错误页面。此外,检查请求的URL是否正确,可能接口已经更新,或者需要特定的参数。 还要考虑反爬虫机制。USPS可能有检测自动化请求的措施,比如验证请求头中的某些字段,检查User-Agent是否真实,或者使用CAPTCHA。用户的User-Agent看起来是Chrome在Linux上的,但有些网站可能会对非浏览器环境或可疑的请求头进行拦截。此外,大量的Cookie可能存在问题,如果某些Cookie失效或需要更新,可能导致认证失败。 另外,响应体的开头有,这是字节顺序标记(BOM),常见于UTF-8编码的文件。可能服务器返回的响应编码不是预期的UTF-8,导致解析时出现乱码。可以尝试检查响应的编码设置,或者手动指定编码格式。 建议用户逐步排查问题:首先确认data是否正确,确保发送的是有效的JSON数据;然后检查Cookie是否有效,可能需要重新登录获取新的Cookie;接着尝试禁止重定向,查看初始响应是否包含错误信息;最后,检查是否有反爬机制,考虑添加更真实的请求头或使用其他反反爬措施。</think>以下是针对你提供的代码和问题的分析及解决步骤: --- ### **一、现象解析** 你收到包含`<!DOCTYPE html`的HTML响应,说明: 1. **认证失败**:服务器未接受你的请求参数,返回了登录页面 2. **重定向触发**:响应状态码可能为`3xx`,但代码未正确处理重定向 3. **反爬机制**:USPS可能检测到自动化请求特征(如非常规Header组合) --- ### **二、关键问题排查点** #### **1. Cookie失效** - 🔍 观察点:Cookie中包含`JSESSIONID`和`TLTSID`等会话标识 - ❗ 问题:长Cookie字符串可能包含过期值(如`_ga`等统计跟踪ID) - ✅ 建议: - 删除所有Cookie,重新通过浏览器登录获取新Cookie - 优先保留关键会话Cookie: ```python headers["Cookie"] = "TLTSID=...; JSESSIONID=...;" ``` #### **2. 重定向处理** - 🔍 当前代码:`allow_redirects=False`被注释 - ❗ 问题:若认证失败,服务器返回`302`跳转到登录页 - ✅ 修复: ```python resp = session.post(..., allow_redirects=False) # 禁止自动重定向 ``` - 通过检查`resp.status_code`和`Location`头确认跳转逻辑 #### **3. 请求数据验证** - 🔍 缺失信息:代码中未展示`data`变量内容 - ✅ 需确保请求体为合法JSON且包含必要字段: ```python data = json.dumps({ "username": "encryptedValue", # 观察浏览器实际加密方式 "password": "encryptedValue", "token": "..." # 可能需要动态获取 }) ``` #### **4. Headers优化** - ❗ 风险点:`Accept-Encoding`包含`zstd`可能引发解码问题 - ✅ 修改建议: ```python "Accept-Encoding": "gzip, deflate, br" # 移除zstd # 添加动态生成的X-CSRF-Token ``` --- ### **三、调试建议** #### **步骤1:模拟浏览器请求** 通过Chrome开发者工具: 1. 正常登录USPS网站 2. 在Network标签中捕获`AuthenticateAction`请求 3. 对比以下关键差异: - 请求头顺序 - Cookie更新机制 - 加密参数(常见于密码字段) #### **步骤2:响应分析代码改进** ```python # 添加响应编码强制处理(针对BOM字符) resp.encoding = "utf-8-sig" if resp.text.startswith("\ufeff") else "utf-8" print("完整响应文本:", resp.text) ``` --- ### **四、反爬对抗策略** | 检测维度 | 应对方法 | |----------------|---------------------------------| | TLS指纹 | 使用`curl_cffi`库模拟浏览器指纹 | | 请求头完整性 | 保持与浏览器完全一致的Header顺序 | | 行为分析 | 添加随机操作延迟(0.5-2秒) | --- ### **五、完整修复示例** ```python # 精简后的有效Headers essential_headers = { "Content-Type": "application/json; charset=UTF-8", "User-Agent": headers["User-Agent"], # 保留核心UA "Cookie": "TLTSID=...; JSESSIONID=...", # 动态更新的会话Cookie "x-requested-with": "XMLHttpRequest" } # 加密后的有效请求体(需通过逆向分析获取) data = { "credentials": { "username": {"value": "ENCRYPTED_DATA"}, "password": {"value": "ENCRYPTED_DATA"} } } resp = session.post( auth_url, headers=essential_headers, json=data, # 自动序列化为JSON allow_redirects=False, timeout=15 ) ``` --- 若问题仍未解决,建议通过浏览器抓包获取完整的加密参数传输流程,重点检查是否有动态token或客户端加密逻辑。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值