服务使用之间如果使用 feign
相互调用的话,无论是 POST
或 GET
请求,如果携带的数据过长的话,会导致丢失部分数据或者报错。解决方法很简单。就是加大服务提供者的限制,如下: 修改 yml
或 properties
配置文件:
server:
port: 4450
# 增加请求头接受大小
max-http-header-size: 10485760
1. 问题排查过程
1.1 现象
调用说明:A服务调用B服务,前端提交到A服务的时候数据比较少,经过A加工后(数据多),调用B进行处理。
- 服务A调用服务B的时候,服务A 抛出异常,状态码是
400
,而B服务没有异常。 - 控制台信息
![6e8588d8660bb6d7551181bac7014e8e.png](https://img-blog.csdnimg.cn/img_convert/6e8588d8660bb6d7551181bac7014e8e.png)
1.2 问题排查
- 一开始以为是发送方做了数据控制,截断了数据的发送。于是乎觉得是
fegin
的HttpClient
的问题?使用Charles
调试数据请求,发现不是。这样就断定了是服务提供者的问题。 - 后面想起
spring boot
也是使用tomcat
容器部署的,tomcat
本身也存在数据请求的限制,找到了POST
相关配置如下:
# tomcat 配置
server.tomcat.max-http-form-post-size
# jetty 配置
server.jetty.max-http-form-post-size
还是解决不了。
2. 疑惑
2.1 为什么是是配置 max-http-header-size 就可以了。难道是 fegin
是把请求放到头部?需要需要重新分析请求的数据?
官方对此参数的说明
| Key Value | Default | Description | | --- | --- | --- | | server.max-http-header-size | 8KB | Maximum size of the HTTP message header. | 摘抄于 https:// docs.spring.io/spring-b oot/docs/current/reference/html/appendix-application-properties.html#server-properties
依据说明寻找 HTTP message header
规范
根据不同上下文,可将消息头分为:
- General headers: 同时适用于请求和响应消息,但与最终消息主体中传输的数据无关的消息头。
- Request headers: 包含更多有关要获取的资源或客户端本身信息的消息头。
- Response headers: 包含有关响应的补充信息,如其位置或服务器本身(名称和版本等)的消息头。
- Entity headers: 包含有关实体主体的更多信息,比如主体长(Content-Length)度或其MIME类型。
摘抄于 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers
并未找到相关头部具体的定义消息,但经过实际的代码试验,URL
就是头部的一部分,默认是 8KB
。超过后就会拒绝。(在头部设置超过 8KB
的数据也是同样的异常)。
控制台报错的错误
2.2 找出 fegin 拼接到url上的原因
通过 debug 找到关键方法:
//解析好了fegin 配置,准备调用远程方法
SynchronousMethodHandler#invoke(Object[] argv);
//执行远程调用和编码,
SynchronousMethodHandler#executeAndDecode(RequestTemplate template);
//这里转换成 Request 对象的时候,已经把参数拼接到 URL 上了,也就是说 POST 变成了GET 请求
SynchronousMethodHandler#targetRequest(RequestTemplate template)
也就是说,如果要彻底解决问题,需要更换底层相关实现。另外如果可以修改源代码,解决也很简单,只需要依据请求的方法来构建是否把数据放到 body 还是 url 上面即可。
2.3 使用 httpClient 代替默认实现
增加 maven
依赖:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-httpclient -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.8</version>
</dependency>
在yml文件中增加配置:
feign:
httpclient:
enabled: true
增加后启动依旧 POST
变 GET
请求。
以下是原因分析:
先回经过 ApacheHttpClient#toHttpUriRequest(Request request, Request.Options options);
方法,由于前面构建的 Request
对象就没有 body
信息。所以默认赋予一个空的Entity。如下图:
![4ec433179cfe8c9e1f5c4d96745249fd.png](https://img-blog.csdnimg.cn/img_convert/4ec433179cfe8c9e1f5c4d96745249fd.png)
其次调用了 RequestBuilder#build();
构建的时候,下图②处又将重新参数赋予到url上了,又重复出现一样的问题。如下图:
![068e39f77096928cd608f1e26f824e57.png](https://img-blog.csdnimg.cn/img_convert/068e39f77096928cd608f1e26f824e57.png)
再寻解决方案。。。
3. 思考
问:同样的数据,如果直接在使用前端的 Ajax 请求的话,会缺失数据吗?
如果正常使用 POST
请求,不会那么快就受到限制(默认2M限制),目前看到的是 Spring
的 fegin
用了 Get
请求,把参数写到url上,导致长度受限 8K
4. 参考文献
https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#server-properties
https://www.jianshu.com/p/11710629c226
HTTP Body 相关规范
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Messages https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/POST