HTTP STATUS CODE: 521的解决办法

本文解析了如何通过分析浏览器请求响应,破解JavaScript加密的Cookie,实现自动化下载受保护的PDF文件。通过Wireshark抓包,理解动态脚本生成过程,最终用Java代码模拟浏览器行为。

一、遇到的问题

在使用Java程序访问一个PDF文件的URL时,发现无法下载该PDF文件,加上各种header也不行,然后看程序的response.getStatusLine(),发现是一个以前从未见到过的错误:HTTP/1.1 521 。
乍一看是500系列的,对面的服务崩了?但是复制url到浏览器地址栏,发现还是可以正常浏览这个PDF的。
于是清除浏览器缓存,重新访问这个URL,用大鲨鱼(wireshark)监控数据包发现这个URL刷新一次,但是被请求了两次,都被大鲨鱼监控到了,第一次的状态是521,第二次才是200。
所以弄懂第一次请时返回的数据是什么,第二次请求时浏览器的请求头以及参数是什么,然后用程序模拟浏览器的行为,就可以用代码获得数据了。
大鲨鱼监控的截图如下
可以看No.那一列,第7行是第一次请求URL的数据包,第14行是第一次请求回应的数据包。第16行是第二次请求该URL(http://ip/doc/.../abc.pdf),第82行就是第二次回应的数据包。

二、分析请求响应报文

下图是第一次请求以及回应的HTTP Stream,红色部分是请求内容,蓝紫色(就叫蓝色吧)部分是响应内容。
请求没有什么特别的,简单的get方法,请求头也没什么陌生的。
但是响应内容就有意思的多了。这个响应分两部分,一个是响应头,发现Set-Cookie,一个是响应体,从script标签开始到一大串…结束,有用的部分自然是script标签里面的东西。
第一次的请求内容和响应内容
复制这个内容到IDEA,格式化之后发现了非常反人类的代码,图片如下:
格式化之后的脚本代码
可以看出这个是定义了三个变量x,y,z还有一个函数 f然后循环。。。然后。。。总之很头疼。不过谷歌浏览器可以帮助我们查看这段脚本到底是干什么的。
创建一个html文件,把第一次请求时回应的内容复制到文件里即可。把eval函数换成console.log,用浏览器打开html文件即可查看脚本输出。
将eval替换成console.log
输出结果如下:
又是一段脚本代码
发现又是一段js代码,如法炮制,再一次用idea格式化这个新的脚本代码。结果如下:
依旧抽象但可以分析的结果
发现这段代码的大意是设置一个名称为__jsl_clearance的cookie,它的值是:
1543922503.114|0|+{一段代码的结果}+;Expires=Tue, 04-Dec-18 12:21:43 GMT;Path=/;
此时先不着急看这段代码的输出结果,先看大鲨鱼(wireshark)监控到的第二次请求情况:
第二次请求pdf的URL时,请求和响应内容
这此请求很容易理解,请求时有cookie,响应的结果也确实是pdf文件,长度为61805 字节,通过浏览器下载PDF文件,查看文件属性也确实是61805字节。
所以如果能够获得完整的cookie,那么通过get方法请求pdf的URL,即可用代码下载这个PDF文件。
这次请求的cookie有两个键值对,一个是__jsluid,它的值通过第一次请求时的响应头: set-cookie获取,另一个是__jsl_clearance,它只需运行那段神秘的脚本获得对应的值,再加上前缀:1543922503.114|0|即可。
大鲨鱼已经给出那段脚本运行的结果:9NisYvdLKC%2BNMZgGcD08dy2YGFU%3D。
所以完整的cookie为:
__jsluid=923e768304c90e8bd92e2113f9163ca6; __jsl_clearance=1543922503.114|0|9NisYvdLKC%2BNMZgGcD08dy2YGFU%3D
此时再用console.log的方法查看这段脚本代码生成的结果,直接提取那段生成字符串:9NisYvdLKC%2BNMZgGcD08dy2YGFU%3D的脚本代码,看输出即可:
脚本以及运行结果
此段脚本代码的输出与大鲨鱼捕获的内容一模一样。
至此全套的访问流程已经梳理清楚。
总结一下:
通过wireshark抓包工具监控浏览器请求URL时的数据报文,发现一次刷新请求,实际上确是请求了两次,分析两次请求时的请求头、响应头、响应体即可弄清浏览器是如何请求到数据的。
浏览器在第一次请求时,会收到响应头:set-cookie,获得关键cookie中的一个:__jsluid。
通过运行第一次请求时得到的脚本(毕竟是一个用script标签修饰的脚本,浏览器会自动运行),该脚本生成新的脚本代码,新代码为浏览器提供另一个关键cookie:__jsl_clearance,以及控制浏览器带着这两个cookie再次访问该URL,就可以获取真实的PDF文件。

三、代码实现

用到的依赖包:

<dependency>
    <groupId>com.eclipsesource.j2v8</groupId>
    <artifactId>j2v8_win32_x86_64</artifactId>
    <version>4.6.0</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.6</version>
</dependency>

具体代码如下:

public class CrackJavaScript {
    private static final Logger lg = Logger.getLogger(CrackJavaScript.class);

    /**
     * 传入pdf文件的URL, 返回下载好的PDF文件
     *
     * @param pdfUrl        pdf文件的URL地址
     * @return              pdf文件对象
     */
    public static File getPDFFile(String pdfUrl) {
        String fileName = Utils.MD5(pdfUrl);
        File file = new File("pdfFile/" + fileName + ".pdf");
        try {
            HttpClient client = HttpClients.createDefault();
            HttpGet get = new HttpGet(pdfUrl);
            setHeader(get);
            HttpResponse response = client.execute(get);

            String __jsluid = getJsluid(response);
            String body = getResponseBodyAsString(response);
            String __jsl_clearance = getJslClearance(body);
            get = new HttpGet(pdfUrl);
            get.setHeader("cookie", __jsluid + "; " + __jsl_clearance);
            setHeader(get);
            response = client.execute(get);
            output(response, file);
        } catch (Exception e) {
            lg.error(e.getMessage(), e);
        }
        return file;
    }

    /**
     * 给HttpGet设置一些必要的header
     *
     * @param get           通过get方法访问pdf资源
     */
    private static void setHeader(HttpGet get) {
        get.setHeader("Upgrade-Insecure-Requests", "1");
        get.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36");
    }

    /**
     * 将HttpResponse输出到文件, 即将pdf输入流写到硬盘.
     *
     * @param response      http响应
     * @param file          落地文件
     * @throws IOException  IO异常
     */
    private static void output(HttpResponse response, File file) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        fileOutputStream.write(getResponseBodyAsBytes(response));
        fileOutputStream.flush();
        fileOutputStream.close();
    }

    /**
     * 通过破解动态JavaScript脚本,
     * 获取cookie名为 __jsl_clearance的值
     *
     * @param body          相应内容(一般为第一次请求获取到的动态js字符串)
     * @return              cookie名为 __jsl_clearance的值
     */
    private static String getJslClearance(String body) {
        //V8:谷歌开源的运行JavaScript脚本的库. 参数:globalAlias=window, 表示window为全局别名,
        // 告诉V8在运行JavaScript代码时, 不要从代码里找window的定义.
        V8 runtime = V8.createV8Runtime("window");
        //将第一次请求pdf资源时获取到的字符串提取成V8可执行的JavaScript代码
        body = body.trim()
                .replace("<script>", "")
                .replace("</script>", "")
                .replace("eval(y.replace(/\\b\\w+\\b/g, function(y){return x[f(y,z)-1]||(\"_\"+y)}))",
                        "y.replace(/\\b\\w+\\b/g, function(y){return x[f(y,z)-1]||(\"_\"+y)})");
        //用V8执行该段代码获取新的动态JavaScript脚本
        String result = runtime.executeStringScript(body);

        //获取 jsl_clearance 的第一段, 格式形如: 1543915851.312|0|
        String startStr = "document.cookie='";
        int i1 = result.indexOf(startStr) + startStr.length();
        int i2 = result.indexOf("|0|");
        String  cookie1 = result.substring(i1, i2 + 3);
        /*
        获取 jsl_clearance 的第二段,格式形如: DW2jqgJO5Bo45yYRKLlFbnqQuD0%3D。
        主要原理是: 新的动态JavaScript脚本是为浏览器设置cookie, 且cookie名为__jsl_clearance
        其中第一段值(格式形如:1543915851.312|0|)已经明文写好, 用字符串处理方法即可获取.
        第二段则是一段JavaScript函数, 需要有V8运行返回,
        该函数代码需要通过一些字符串定位, 提取出来, 交给V8运行.
         */
        startStr = "|0|'+(function(){";
        int i3 = result.indexOf(startStr) + startStr.length();
        int i4 = result.indexOf("})()+';Expires");
        String code = result.substring(i3, i4).replace(";return", ";");
        String cookie2 = runtime.executeStringScript(code);

        /*
        拼接两段字符串, 返回jsl_clearance的完整的值.
        格式形如: 1543915851.312|0|DW2jqgJO5Bo45yYRKLlFbnqQuD0%3D
        */
        return cookie1 + cookie2;
    }

    /**
     * 将HTTP响应体转换为字符串返回
     *
     * @param response      HTTP响应
     * @return              响应体的字符串形式
     * @throws IOException  IO异常
     */
    private static String getResponseBodyAsString(HttpResponse response) throws IOException {
        return IOUtils.readStreamAsString(response.getEntity().getContent(), "UTF-8");
    }

    /**
     * 将HTTP响应体转换为byte数组返回
     *
     * @param response      HTTP响应
     * @return              响应体的byte数组形式
     * @throws IOException  IO异常
     */
    private static byte[] getResponseBodyAsBytes(HttpResponse response) throws IOException {
        return IOUtils.readStreamAsByteArray(response.getEntity().getContent());
    }

    /**
     * 通过响应头的set-cookie
     * 获取cookie名称为__jsluid的值
     * @param response      HttpResponse
     * @return              __jsluid的值
     */
    private static String getJsluid(HttpResponse response) {
        Header header = response.getFirstHeader("set-cookie");
        String[] split = header.getValue().split(";");
        for (String s : split) {
            if (s.contains("__jsluid")) {
                return s.trim();
            }
        }
        return "";
    }
}

参考文章:
[1]: http://blog.51cto.com/12925223/2309945
[2]: https://github.com/jhao104/memory-notes/blob/master/Python/Python爬虫—破解JS加密的Cookie.md

<think>首先,用户在询问关于 HTTP probe 失败且状态码为503的错误。错误信息是:"HTTP probe failed with statuscode: 503"。我需要解释这个错误含义并提供解决方法。 根据系统指令: - 所有行内数学表达式必须使用 $...$ 格式。 - 独立公式使用 $$...$$ 格式并单独成段。 - LaTeX 语法要正确。 - 使用中文回答。 - 生成相关问题。 - 在引用的段落末尾添加引用标识。 用户提到了站内引用 [1],但我需要基于用户的问题和我的回答来参考它。用户说:“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答。” 然而,这是用户的消息,所以我会基于此进行回应。 用户提供的引用 [1] 描述了健康检查状态: - UP:所有依赖正常 (HTTP 200) - DOWN:关键依赖故障 (HTTP 503) - OUT_OF_SERVICE:维护模式 (HTTP 503) - UNKNOWN:未知状态 错误 "HTTP probe failed with statuscode: 503" 表明健康检查返回了503状态码,这对应于 DOWN 或 OUT_OF_SERVICE 状态。 关键点: - **含义**:HTTP 503 状态码意味着“服务不可用”。在健康检查的上下文中,这表明服务暂时无法处理请求,可能是因为服务器过载、维护或关键依赖项失败。 - 根据引用,503 状态码可能表示: - DOWN:关键依赖故障 - OUT_OF_SERVICE:维护模式 - 所以,错误消息表示健康检查失败,因为服务返回了503状态码。 **解决方法**: - 检查服务日志:查看后端服务的日志以确定为什么返回503错误。 - 关键依赖项:确保所有依赖的服务(如数据库、外部API)正常运行。 - 服务器负载:如果服务器过载,可能需要扩展资源或优化性能。 - 维护模式:如果服务处于维护状态,检查配置或等待维护结束。 - 配置问题:验证健康检查配置是否正确,包括端点URL。 - 重试机制:实现重试逻辑来处理瞬时故障。 - 监控和警报:设置监控工具来检测类似问题。 结构化回答: - 解释含义。 - 提供解决方法。 - 使用中文。 - 生成相关问题。 - 添加引用标识(引用 [1])。 回答结构: 1. **含义解释**:简洁地描述503错误的含义。 2. **原因分析**:基于引用和一般知识,讨论可能的原因。 3. **解决方法**:列出步骤或建议。 4. **相关问题**:按照指令生成相关问题。 确保格式: - 行内数学表达式:如果有变量等,使用 $...$,但在这个上下文中可能不需要数学表达式。如果需要,例如讨论HTTP状态码的数量,但保持简洁。 - 独立公式:如果需要公式,单独段落。 - 中文回答。 生成相关问题: - 指令是“生成相关问题”,所以在回答结束时添加一个部分。 添加引用: - 在引用的地方自然地添加 [^1]。用户提供了引用 [1],所以我应该在描述含义时引用它。 最终结构: - 开头解释含义。 - 然后讨论原因和解决办法。 - 结束生成相关问题。 相关问题想法: 1. 如何配置健康检查端点? 2. 如何区分DOWN和OUT_OF_SERVICE状态? 3. 处理HTTP 503错误的最佳实践? 4. 健康检查在微服务架构中的作用? </think>### HTTP probe failed with statuscode: 503 错误的含义及解决方法 #### 错误含义 HTTP probe 失败并返回状态码 503 表示健康检查未能通过,服务当前不可用。具体来说: - HTTP 503 状态码对应服务器端的“服务不可用”错误,这通常意味着服务暂时无法处理请求。 - 根据健康检查标准(如 Spring Boot Actuator 或其他监控工具),503 状态码常用于表示两种状态: - **DOWN**: 关键依赖项故障(例如数据库、API 服务不可达),导致服务整体不可用。 - **OUT_OF_SERVICE**: 服务处于维护模式,可能是人为配置或计划内停机。 - 这会导致健康检查失败,影响服务的可用性监控和自动恢复机制[^1]。简单来说,503 错误表示服务无法正常响应请求,需要立即排查。 #### 可能原因 1. **关键依赖故障**:服务依赖的外部资源(如数据库、消息队列或第三方 API)出现问题。 2. **服务器过载**:高并发请求导致资源耗尽(如 CPU、内存不足)。 3. **维护模式**:服务被手动设置为停机状态,用于维护或升级。 4. **配置错误**:健康检查端点的配置不当,例如 URL 错误或超时设置不合理。 5. **瞬时故障**:网络问题或短暂的服务中断,但可能自动恢复。 6. **应用逻辑问题**:后端代码异常(如未处理的错误),导致服务拒绝请求。 #### 解决方法 以下是逐步的排查和解决建议。基于最佳实践,优先检查日志和依赖项。 1. **检查服务日志**: - 查看后端服务的日志文件(如应用日志、系统日志),搜索错误堆栈或 503 响应记录。 - 例如,使用命令 `tail -f /var/log/your-app.log` 实时监控日志。 - 日志通常会指明具体原因,如数据库连接失败或内存溢出。 2. **验证关键依赖项**: - 确保所有依赖服务正常运行: - 测试数据库连接:使用工具如 `telnet db-host 3306`(MySQL)或 `ping api-service`。 - 检查外部 API:通过 `curl -I https://external-api.com/health` 验证其健康状态。 - 如果依赖项失败,修复它们(如重启数据库或优化 API 调用)。 3. **优化服务器资源**: - 监控资源使用:使用工具如 `top`(Linux)或 `docker stats`(容器环境),检查 CPU、内存使用率。 - 如果过载: - 垂直扩展:增加服务器规格(如 RAM、CPU)。 - 水平扩展:添加更多实例,使用负载均衡器分发流量。 - 优化应用:减少资源消耗(如缓存常用数据、减少线程阻塞)。 4. **检查维护模式设置**: - 如果服务被设为维护模式(OUT_OF_SERVICE): - 查看配置:在应用配置文件中(如 `application.properties`),检查是否有类似 `management.health.out-of-service=enabled` 的设置。 - 禁用维护模式:通过管理接口或配置更改,恢复服务状态。 5. **调整健康检查配置**: - 确保健康检查端点正确:默认路径通常是 `/actuator/health`(Spring Boot),验证 URL 是否正确。 - 设置超时和重试: - 延长健康检查超时时间(如从 5s 到 10s),避免瞬时故障误报。 - 在 probe 配置中添加重试逻辑(例如 Kubernetes 中的 `livenessProbe` 设置 `failureThreshold: 3`)。 - 示例 Kubernetes 配置片段: ```yaml livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 3 ``` 6. **处理瞬时故障**: - 实现重试机制:在客户端代码中添加指数退避重试(如使用库 `retry4j`)。 - 使用熔断模式:例如 Hystrix 或 Resilience4j,在依赖故障时隔离服务,防止级联失败。 7. **监控和警报**: - 设置警报系统:使用 Prometheus + Grafana 或 Datadog 监控健康状态,配置警报规则(如当 503 错误率超过 5% 时通知)。 - 常规测试:定期运行健康检查(如 `curl -X GET http://your-service/actuator/health`),确保服务稳定。 如果问题持续,建议查阅具体框架的文档(如 Spring Boot Actuator 或 Kubernetes probes),因为实现细节可能因平台而异。通常在修复后,重新启动服务即可恢复健康状态。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值