记一次解决 Feign 提交批量添加请求收到 400 报错的经历

在尝试使用OpenFeign实现批量POST请求时,遇到了400错误。问题在于Content-Length头的值,经过一系列尝试和调试,发现不是不能添加此头,而是需要特定值,最终发现换行符问题导致。通过将换行符从' '改为' ',问题成功解决。

 上周在实现用 OpenFeign 提交批量添加请求时,遇到一个奇怪的问题,本来 Post 请求的 body 体,因为是批量请求,内容就比较多,准备起来就比较麻烦,仔细检查了很多次,提交时还是各种失败,最终终于按照官方文档在 Postman 中提交成功了,本以为万事大吉了,于是开始写 Java 代码, 没想到用 Java 代码用几乎一样的 body 体提交,居然总是失败,收到一个 400 的报错,反复查看那个报错,也看不出什么有用的信息,在网上搜索答案也没有什么提示,甚至考虑是不是 feign 的 client 有什么问题? 中间还尝试了用 ApacheHttpClient 替换默认的 DefaultHttpClient 等,都是一样的问题,卡住了一整天……

第二天恢复精力后,中间有空的时候,考虑是不是自己手拼的 body 体不标准,不确定哪里是不是有什么遗漏, 因为之前就因为少个换行啊什么的出现过一些提交报错,所以专门为此写了生成 body 体的 Java 类,用 TDD 的方式,一点点组装出 Get 的 body 体,想着先从批量 Get 请求开始,稳扎稳打地实现代码。

当天下午,把 Get 请求的 body 体拼接好,执行后居然成功了,非常高兴,以为是之前太粗心,心想还是要稳稳地用单元测试来保证代码的准确啊!

于是继续完成 Post 的 body 体的拼接,以为这样就应该没问题了,没想到,同样的错误又出现了,批量 Get 请求可以,而 Post 请求就失败。 但是为什么 Postman 可以呢? 仔细比对 Postman 和 Java 中的两个 body 体,也实在看不出有啥不同,甚至用 Java 中的 body 体放到 Postman 中提交也是可以成功的。又陷入了僵局,百思不得其解,只好先忙其他的工作。

因为这个工作有一定的时间限制,原来的代码是在 for 循环中一条一条地提交插入数据, 考虑未来运行期的性能问题,想要用批量插入来实现,这样一次提交就可以完成原来要十几个请求才能完成的插入数据功能。可是现在遇到这样的问题,几乎想要放弃了,暂时先用原来的 for 循环解决业务需求。

然后回头想了一下,既然 Postman 都提交成功了,那感觉胜利就近在眼前了,实在是不想放弃。

于是周六休息,放空了一下大脑,没有再想这个事,今天周日中午睡了一觉,看了会儿书后,重振旗鼓,再来尝试解决这个问题。

因为有之前打下的坚实基础, TDD 驱动出来的代码,过程中也重构得很整洁,再次开始,只需要执行之前写好的单元测试,就可以重现问题了,这次使用 debug 一点点深入到 feign 的源码内部,感谢 IDEA Intellij 的强大功能, 一层层进入到 RequestTemplate.java 这个文件, 发现 header 里面多了一项: Content-Length,因为我在提交的请求头中并没有加这一项,因此这个 header 引起了我的注意。

反复调试了几次,发现这个 header 是在 feign 解析请求模板时拼接进来的,当时看到的数字是 729,看到代码里有一些转编码的操作,怀疑是不是编码问题算错了长度?于是把请求的 body 在 Java 里写了一个临时的单元测试,验证了一下长度,果然也是 729 的长度, 把同样的 body 放在 Postman 中提交是可以成功的,但是加上 Content-Length:729 后,就失败了,跟 Java 里遇到的问题一模一样, 哈哈…… 虽然失败了,但很高兴,毕竟是在 Postman 中重现了问题。这样我就在想,是不是这个 multipart/mixed 的 POST 请求不能添加 Content-Length 这个 Header? 目前在 Postman 上测试的情况就是不加这个 header 就可以提交成功,但加上后不管输入什么数字就都是失败。

于是,想了一下似乎没什么办法绕过 feign 的功能,翻了下 OpenFeign feign-core 的源代码,就是在 RequestTemplate.java 这个文件中看到自动添加 Content-Length 这一项的代码,在 feign 的 Github Issue 中也没找到类似的问题,考虑是不是给 feign 开源社区提个 Issue ?于是发了这个 Issue 1810, 然后又想,既然已经找到具体的代码了,为什么不帮忙修改一下提一个 pull request 呢? 于是 Fork 了代码,下来后看到人家的代码也是有配套的单元测试的,很容易测试,不一会儿就把代码修改并测试通过了,提交了 pull request 1811,蛮有成就感地去吃晚饭了。

吃完饭,又在想等人家审核代码、合并发布也要等不知道多少天,我能不能有个临时的解决方案先绕过它呢?是不是我传个什么特殊的值, feign 就不会再加这个 header 了呢? 一些想法都需要验证,好在用 Postman 来推敲问题就相对快捷了, 填 0 ,就直接返回了一个 --batch_xxxx--, 数据库也没有插入任何数据,似乎是直接结束了请求, 填 null 直接报错,非法的值, 填一个很大的数字 1000,就是一直在发送,点取消才能停止,按字符串长度 729 提交,就跟之前的报错一样,然后一点点添加长度,730 往上升,780 往下降,最后在 750 的时候, 提交成功了!

这说明什么? 说明不是不能加 Content-Length 这个值,而是一个特定的值就能成功,可是明明字符串长度是 729,为什么会在 750 时提交成功呢? 再次 debug 到 RequestTemplate.java 中,仔细查看了计算长度的代码,甚至将相同的代码摘出来,单独测试了一下,源代码中虽然是计算的 byte 的长度,但是使用 UTF-8 的编码得到的长度也是 729,难道说 Postman 计算长度时加了别的什么东西?729 和 750 相差了 21,其他 header 似乎也没有什么内容恰好是 21 个字符的, 正在没什么头绪的时候,突然注意到 body 的内容正好是 22 行,很接近 21 这个数字,突然想到,是不是换行符的问题? 22 行内容正好是 21 个换行符。

没错,肯定是这个问题!

于是换了一下换行符,好在之前的代码很整洁,还专门抽了一个常量: String EOL = "\n"; ,所以很简单,改成 "\r\n" 就成了,一试,果然通了!

高兴之余,想到之前提的 pull request 不是错了,赶紧上去关闭了 Issue 和 Pull request。 然后回到项目上的代码,把自己之前写的单元测试改好,测试全通过,代码整理了一下,提交了代码。

回想了一下这个过程,其实好多次都已经接近真相了,可就是没有触及答案,就好像在黑暗中摸索,几次手已经划过了电灯开关附近,但偏偏就是没摸到开关。反思了一下,一方面是自己的经验不足,对 feign 还比较陌生,另一方面,对于 HTTP 请求的细节根本没有深入研究过,对于 multipart/mixed 的请求也是头一次接触,否则可能早就找到问题原因解决了。 好在这样的一次经历最后有了一个圆满的结局,同时也学到了一些新知识。

<think>嗯,用户遇到了Feign远程调用时参数过多导致400错误的问题。我需要先回忆一下Feign的相关知识,以及常见的错误原因。400错误通常表示客户端请求有问题,比如参数不匹配或者格式错误。参数过多可能导致URL过长,超过服务器限制,或者参数传递方式不正确。 首先,用户提到参数过多,可能是在GET请求中把参数放在URL里,导致URL过长。根据HTTP协议,URL长度是有限制的,不同浏览器和服务器有不同的限制,但通常超过一定长度就会报错。所以需要检查是否使用的是GET方法,如果是,可能需要改用POST,或者调整参数传递方式。 然后,想到Feign的默认配置可能对参数处理有限制。比如,Feign默认可能将多个参数作为查询参数拼接在URL中,这会导致URL过长。解决方案可能是将参数封装成对象,作为请求体传输,但GET方法通常不支持请求体,所以可能需要改用POST方法,或者在Feign的配置中允许GET请求携带请求体,不过这可能不符合RESTful规范。 另外,检查用户的Feign接口定义是否正确。如果参数是复杂对象,是否使用了@RequestBody注解,或者在GET请求中错误地使用了该注解,导致参数解析失败。需要确保在POST请求中使用@RequestBody,而GET请求使用@RequestParam或将其封装成对象,Feign会自动处理。 还可能涉及到Feign的编码器配置。如果参数是对象,需要确保有正确的编码器将对象转换为查询参数或表单数据。可能需要配置Feign使用不同的编码器,比如SpringEncoder或者自定义的编码器来处理多参数的情况。 此外,引用中的内容提到Feign在传参时可能压缩数据,导致数据丢失。虽然用户的问题是关于参数过多,但也需要考虑参数是否被正确编码,没有丢失或损坏。可能需要检查参数的编码方式,确保特殊字符被正确转义,避免服务器无法解析。 还有,用户提到的解决方案中,使用拦截器或者修改Feign的配置来处理请求头或参数。比如,在异步调用中保存请求属性,或者在Feign配置中增加拦截器来添加必要的头信息,确保参数正确传递。 另外,分页参数过多的情况,可以考虑将分页信息封装成对象,减少单个参数的数量,或者使用POST方法来传递参数,避免URL过长。同时,检查服务提供方的接口是否支持接收这些参数,确保双方接口定义一致。 最后,总结可能的解决方案:改用POST方法、封装参数对象、调整Feign配置、检查编码器、使用拦截器处理请求属性、分页参数优化等。需要逐步指导用户检查接口定义、参数传递方式,并提供具体的配置示例,比如如何修改Feign接口和使用@SpringQueryMap注解,或者配置日志以调试请求详情。</think>### Feign远程调用参数过多导致400错误的解决方案 #### 1. **问题定位与原因分析** - **400错误本质**:客户端请求参数与服务器接收规则不匹配,常见于参数类型错误、参数缺失或参数过多导致URL过长[^1]。 - **Feign参数传递机制**:默认情况下,Feign会将GET请求参数拼接在URL中,若参数过多可能超出服务器允许的URL长度限制(如Tomcat默认8KB)[^2]。 - **参数压缩与解析**:当参数为复杂对象时,Feign可能因编码器配置问题导致参数解析失败[^3]。 --- #### 2. **具体解决方案** ##### **方案一:改用POST方法传递参数** 将GET请求改为POST,通过请求体传递参数,避免URL长度限制: ```java @FeignClient(name = "target-service") public interface OrderFeignClient { @PostMapping("/order/list") // 修改为POST Result getOrderList(@RequestBody OrderQueryParams params); // 封装参数为对象 } ``` **参数对象示例**: ```java @Data public class OrderQueryParams { private Integer page; private Integer size; private String keyword; // 其他字段... } ``` ##### **方案二:GET请求参数优化** 若必须使用GET,需通过以下方式处理: 1. **封装参数为Map**: ```java @GetMapping("/order/list") Result getOrderList(@SpringQueryMap Map<String, Object> params); ``` 2. **使用`@SpringQueryMap`注解**(Spring Cloud OpenFeign支持): ```java @GetMapping("/order/list") Result getOrderList(@SpringQueryMap OrderQueryParams params); ``` ##### **方案三:调整服务器配置** 在服务提供方增加URL长度限制(以Tomcat为例): ```properties # application.properties server.tomcat.max-http-post-size=20971520 # 20MB server.tomcat.max-http-header-size=20971520 ``` ##### **方案四:分页参数简化** 若为分页查询,建议使用专用分页参数封装: ```java public class PageParam { private Integer current = 1; private Integer size = 10; } ``` --- #### 3. **辅助调试手段** ##### **开启Feign详细日志** ```yaml # application.yml logging: level: feign.client: DEBUG ``` 通过日志观察实际发送的请求URL和参数是否符合预期[^2]。 ##### **使用拦截器统一处理参数** ```java public class FeignParamInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { template.query("fixedParam", "value"); // 添加固定参数 } } ``` 注册拦截器: ```java @Configuration public class FeignConfig { @Bean public FeignParamInterceptor paramInterceptor() { return new FeignParamInterceptor(); } } ``` --- #### 4. **注意事项** 1. **编码规范**:确保参数对象字段命名与接收方一致[^1]。 2. **特殊字符处理**:对参数值进行URL编码: ```java String encodedValue = URLEncoder.encode(rawValue, StandardCharsets.UTF_8); ``` 3. **版本兼容性**:`@SpringQueryMap`需要Spring Cloud 2020.0.0及以上版本[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值