【生产问题记录】一次简单的 Http 请求异常处理 (请求的 url 太长, Nginx 直接返回 400, 导致请求服务异常)

这篇具有很好参考价值的文章主要介绍了【生产问题记录】一次简单的 Http 请求异常处理 (请求的 url 太长, Nginx 直接返回 400, 导致请求服务异常)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1 结论

按照惯例直接说结论。

后台服务 A 有一个 Http 接口, 代码如下:

@RequestMapping(value = "/user", method = RequestMethod.GET)
public List<UserInfoVo> getUserInfoByUserIds(@RequestParam(value = "userIds") List<String> userIds) {
    // ...
}

没错, 一个 Get 请求, 入参是一个 List

同时有另一个后台服务 B, 里面有段逻辑会通过 RestTemplate 调用服务 A 的这个接口, 代码如下:

public List<UserInfoVo> batchGetUserInfo(Collection<String> userIds) {

    String url = String.format("%s/user?userIds=%s", remoteHost, StringUtils.join(userIds, ","));
    String returnResult = restTemplate.getForObject(url, String.class);
    // ...
}

在服务 B 中, 通过 batchGetUserInfo 方法请求服务 A 时, 传入了一个长度为 122 的 List, List 中每一项是一个 32 位的 UUID。
结果导致调用服务 A 的 url 长度太长, Nginx 认为这时一个异常的请求格式, 直接返回状态码 400, 结构导致服务 B 逻辑异常

2 过程

2.1 反馈

下午, 突然收到用户反馈: 进入某个页面后, 直接白屏。

2.2 定位到直接原因

直接通过 Nginx 请求日志, 发现用户反馈的操作时间段内, 有一个接口一直返回 400 的错误。
根据客户端反馈这个错误的确会导致页面白屏。

知道了直接原因了, 但是没有解决, 还是需要定位到根本原因。

2.3 Arthas 排查

通过错误的 url, 定位到对应的代码, 然后通过 Kibana 查看日志, 发现只有一个简单的异常提示, 没输出任何堆栈信息。

因为是一个查询接口, 所以本地通过拼接参数, 尝试请求这个接口, 发现是逻辑正常的, 应该是数据问题, 这就尴尬了。

通过分析代码逻辑, 看不出什么异常的。
在没有日志, 复现不出反馈情况, 代码逻辑分析不出异常时, 决定通过 Arthas 协助排查了。

在生产环境中, 启动了一个预发版本, 通过 url 模拟用户请求。

同时启动 Arthas, watch 对应的接口

watch com.aaa.bbb.TestController testMethod "{params,returnObj,throwExp}" -x 4

定位到以下异常:

org.springframework.web.client.HttpClientErrorException: 400 
	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:700) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:653) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:287) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at com.aaa.bbb..service.impl.RemoteServiceImpl.batchGetUserInfo(RemoteServiceImpl.java:206) ~[classes/:?]
	......

根据堆栈信息定位到代码

public List<StudentVo> batchGetUserInfo(Collection<String> userIds) {

    String url = String.format("%s/user?userIds=%s", remoteHost, StringUtils.join(userIds, ","));
    String returnResult = restTemplate.getForObject(url, String.class);
    // ...
}

看逻辑没多大的异常, 唯一比较惊讶的就是 Get 请求, 入参确实是一个 List, 不过 Http 本身就支持这样操作。

继续通过 Arthas, watch 对应的接口, 不过这次 watch 上面的代码:

watch com.aaa.bbb..service.impl.RemoteServiceImpl batchGetUserInfo "{params,returnObj,throwExp}" -x 4

继续通过 url 模拟用户请求。

发现堆栈信息同样的 HttpClientErrorException: 400 异常, 但是通过打印的参数列表发现, 入参竟然是 123 个的 String。

第一时间感觉到: 参数太多, 拼接的 url 太长, 导致请求失败。
但是转念一下, Get 请求 url 的长度限制是浏览器的行为, Http 协议没有对传输的数据大小进行限制
现在是 2 个后台服务的 Http 请求, 没有经过任什么浏览器, 理论上是这个长度无限制的。

2.4 Nginx 直接返回 400 错误码

请求 url 感觉没什么问题?
既然这样, 会不会是结果响应方处理有什么异常吗?

同样通过 Kibana 查看日志, 发现对应的接口, 没有当前用户的请求日志。也就是说, 对应的请求没有到达服务 A。

不是被调用方的问题, 那么会不会是 RestTemplate 这个框架内部做了限制呢?

restTemplate.getForObject 出发, 进入到源码, 发现内部也是没有对 url 长度做限制的, 同时定位到抛出异常的位置如下

public class DefaultResponseErrorHandler implements ResponseErrorHandler {

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
		    // 从响应里面获取到状态码
		    HttpStatus statusCode = getHttpStatusCode(response);
		    switch (statusCode.series()) {
			      // 状态码 4xx
			      case CLIENT_ERROR:
				        throw new HttpClientErrorException(statusCode, response.getStatusText(),
				            response.getHeaders(), getResponseBody(response), getCharset(response));
			      // 状态码 5xx						
			      case SERVER_ERROR:
				        throw new HttpServerErrorException(statusCode, response.getStatusText(), 
				            response.getHeaders(), getResponseBody(response), getCharset(response));
			      default:
				        throw new RestClientException("Unknown status code [" + statusCode + "]");
		    }
	  }
}

抛出异常的结果是根据请求返回的状态码来决定的。 也就是服务 B 有发起请求, 同时收到了一个 400 的错误码, restTemplate 将其封装为一个 HttpClientErrorException。

调用方有发起请求, 被调用方没有请求日志, 2 者之间通过通过 Http 请求, 那么有问题的的地方应该就是 2 者中间的 Nginx 了。

2.5 验证

public List<StudentVo> batchGetUserInfo(Collection<String> userIds) {

    String url = String.format("%s/user?userIds=%s", remoteHost, StringUtils.join(userIds, ","));
    String returnResult = restTemplate.getForObject(url, String.class);
    // ...
}

将上面的 remoteHost 替换为一个具体的 ip 地址, 直接请求对应的容器, 绕过 Nginx。
重新部署, 通过 url 模拟用户请求, 正常响应。

3 总结

服务 A 的请求先经过 Nginx, 再由 Nginx 转发到 B。
而异常的用户的请求到了 Nginx, Nginx 直接返回了 400, 从而导致用户请求异常。

通过查询资料, Nginx 报 400 的场景如下

  1. request_uri 过长超过 nginx 配置大小
  2. cookie 或者 header 过大超过 nginx 配置大小
  3. 空 HOST 头
  4. content_length 和 body 长度不一致

我遇到的情况就是第一种。
Nginx 处理时认为客户端请求格式错误, 于是直接返回 400, 不会向 upstream server (也就是下游服务) 转发请求, 因而 upstream server 对这些错误请求其实完全是无感知的。

至此结束。

碎碎念:
其实对 Http 响应码有一点了解, 结合上面获取到的请求参数太多和堆栈的信息的 400, 基本可以推导出问题了, 不用像我一样, 一步步猜测验证。
而本身通过这次, 对 Http 的响应码和 Nginx 也算是多了一点了解。文章来源地址https://www.toymoban.com/news/detail-794995.html

到了这里,关于【生产问题记录】一次简单的 Http 请求异常处理 (请求的 url 太长, Nginx 直接返回 400, 导致请求服务异常)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 记录一次使用unordered_set插入数据异常的问题

    问题和 unordered_set 有关,相关代码如下: 这些代码作为so提供给 程序A 调用,但是程序执行过程中发现,defined_task_variable 插入值后,printSet()方法应该遍历defined_task_variable,打印出其内部数据的,但实际上其打印出来的都是(null),且只在arm平台出现,且必现,但x86一切正常 相

    2024年04月08日
    浏览(47)
  • 一次不规范HTTP请求引发的nginx响应400问题分析与解决

    最近分析数据偶然发现nginx log中有一批用户所有的HTTP POST log上报请求均返回400,没有任何200成功记录,由于只占整体请求的不到0.5%,所以之前也一直没有触发监控报警,而且很奇怪的是只对于log上报的POST接口会存在这种特定用户全部400的情况,而对于其他接口无论POST还是

    2024年02月14日
    浏览(53)
  • HTTP请求头大小写问题踩坑记录

    类似网关的需求做请求转发,大概是接收到客户端http请求后做简单处理,之后通过httpclient转发出去,其中请求头需要全部透传过去。但是直接全部无脑传过去会出现问题,比如因为处理过请求,所以长度已经变了,客户端的请求头content-length已经不再适用了,再比如如果客户

    2024年02月11日
    浏览(38)
  • 【KingSCADA】问题处理:记录KS历史报警查询异常

    哈喽,大家好!我是雷工。 本篇记录KingSCADA的历史报警应用中的一个问题,及处理过程。 最近客户遇到这么一个问题:当打开历史报警窗界面,自动加载的报警信息中有显示最近几天的报警信息,但当通过选择时间范围,通过时间段查询历史报警信息时查询不到,最近几天

    2024年02月11日
    浏览(102)
  • FreeSWITCH 1.10.10 简单图形化界面7-记录一次配置讯时网关的问题

    用户使用的是迅时HX4E。按照这篇文章配置后,能呼出,不能呼入。 直接呼入网关自己挂了,FreeSWITCH也没有收到任何呼入消息; 网关SIP日志,提示自己486 busy; 抓包发现讯时网关把invite 请求行IP是对的,但是消息发给网关自己了,挺纳闷的; 折腾两个小时用户发现把网线查到

    2024年02月09日
    浏览(43)
  • 记录一次生产环境Rancher故障

    目录 一、运行环境问题描述 二、问题分析 三、问题处理   一、运行环境问题描述   Rancher版本:2.4.17 操作系统:CentOS 7.2 Docker : 19.03.15   Rancher正常运行突然打不开WEB界面,排查Rancher运行发现无端口 80、443,如下图:  排查Rancher日志一直在报如下错误:

    2024年02月07日
    浏览(38)
  • 记录一次YAMLException异常

    ✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: 报错以及Bug ✨特色专栏: MySQL学习 🥭本文内容:记录一次YAMLException异常 📚个人知识库: [Leo知识库]https://gaoziman.gitee.io/blogs/),欢迎大家访问

    2024年02月05日
    浏览(52)
  • clickhouse一次异常排查记录

    clickhouse中报错 关闭了自启动,删了status,重启了clickhouse还是报错 1,排查定时执行的脚本日志(每小时第5分钟执行) INSERT INTO quality0529.previously_reported_urls (url) SELECT url FROM quality0529.hourly_data_view WHERE findUrlListLastTime = now() - INTERVAL 1 HOUR GROUP BY url 2,查看ck执行异常详细信息统计

    2024年02月11日
    浏览(47)
  • 一次完整的http请求过程

    浏览器输入一个URL回车后,会发生什么呢? 1、域名解析 :使用DNS协议进行域名解析 2、建立连接 :发起TCP三次握手 3、发起http请求 :建立TCP连接成功后,浏览器发起http请求 4、响应http请求 :服务端响应http请求,浏览器得到返回response 5、解析response :浏览器解析response,并

    2024年02月10日
    浏览(32)
  • 记一次 MySQL 主从同步异常的排查记录,百转千回

    你好,我是悟空。 这是悟空的第 183 篇原创文章 官网:www.passjava.cn 本文主要内容如下: 最近项目的测试环境遇到一个主备同步的问题: 备库的同步线程停止了,无法同步主库的数据更改。 备库报错如下: 完整的错误信息: 上面的报错信息是什么意思呢? 翻译一下就是主

    2023年04月17日
    浏览(80)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包