有个接口是这样的getXxByIds(String Ids)
id 用','分隔,运行一段时间报了 400。
报错内容
feign.FeignException$BadRequest: [400] during [POST] to [http...
复制代码
问题重现
来模拟下问题
服务接口
@Slf4j
@RestController
public class SkyController {
@GetMapping("/sky")
public String sky (@RequestParam(required = false) String name) {
log.info(name);
return name;
}
@PostMapping("/deliver")
public String deliver (String packageBox) {
log.info(packageBox);
return packageBox;
}
@PostMapping("/feignPost")
public String feignPost(@RequestParam(name = "name") String name){
log.info(name);
return name;
}
@PostMapping("/feignBody")
public String feignBody(@RequestBody String name){
log.info(name);
return name;
}
}
复制代码
另建一个服务 创建 feignClinet, 示例服务加入了网关,使用了服务名称,可以直接使用 url
// 网关 服务名称
@FeignClient(value = "paw-dogs-sky-service")
public interface SkyFeignClient {
@PostMapping("/deliver")
String deliver (@RequestParam(name = "packageBox") String packageBox);
@PostMapping("/feignPost")
String feignPost(@RequestParam(name = "name") String name);
@PostMapping("/feignBody")
String feignBody(@RequestBody String name);
}
复制代码
编写测试类
@Slf4j
@SpringBootTest
class SkyFeignClientTest {
@Autowired
SkyFeignClient skyFeignClient;
@Test
void deliver () {
String param = RandomUtil.randomString(10*1024);
log.info(param);
String result = skyFeignClient.deliver(param);
log.info(result);
}
@Test
void feignPost () {
String param = RandomUtil.randomString(10*1024);
log.info(param);
String result = skyFeignClient.feignPost(param);
log.info(result);
}
@Test
void feignBody () {
String param = RandomUtil.randomString(1*1024);
log.info(param);
String result = skyFeignClient.feignBody(param);
log.info(result);
}
}
复制代码
运行测试发现 param 较大时 get、post form-url 请求失败,requestBody 方式请求成功。
运行 post form-url 请求 会发现 post 请求发送的也是 url 拼接的方式
用 postman 直接访问服务测试
通过拼接的长 url 访问失败
通过 form-url 方式访问成功
问题出现在 url 的长度限制
url 长度限制
浏览器 url 长度限制
服务器长度限制 如 tomcat 限制, nginx url 限制
SpringBoot 项目长度限制 max-http-header-size
默认 8k,更改提供服务 sky 的配置 100*1024 (100k) ,重试服务正常调用。
DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8)
server:
port: 8080
max-http-header-size: 102400
复制代码
源码分析
从异常类 SynchronousMethodHandler 入手 debug 跟踪
构建请求的类 RequestTemplate
对应 query 请求 用有序链表存放参数 Map<String, QueryTemplate> queries = new LinkedHashMap<>()
形如 key-->value packageBox--> packageBox={packageBox}
SpringMvcContract
对注解进行处理
类上的注解processAnnotationOnClass
方法上的注解processAnnotationOnMethod
参数上的注解 processAnnotationsOnParameter
AnnotatedParameterProcessor
的实现类 RequestParamParameterProcessor
对 query 参数进行封装
public boolean processArgument(AnnotatedParameterContext context,
Annotation annotation, Method method) {
int parameterIndex = context.getParameterIndex();
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
MethodMetadata data = context.getMethodMetadata();
if (Map.class.isAssignableFrom(parameterType)) {
checkState(data.queryMapIndex() == null,
"Query map can only be present once.");
data.queryMapIndex(parameterIndex);
return true;
}
RequestParam requestParam = ANNOTATION.cast(annotation);
String name = requestParam.value();
checkState(emptyToNull(name) != null,
"RequestParam.value() was empty on parameter %s", parameterIndex);
context.setParameterName(name);
// 对参数进行封装
Collection<String> query = context.setTemplateParameter(name,
data.template().queries().get(name));
data.template().query(name, query);
return true;
}
复制代码
SynchronousMethodHandler
invoke 方法 executeAndDecode(template, options)
执行 http 请求并解码。
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
// 构建请求
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
// 执行http请求
response = client.execute(request, options);
...
}
复制代码
构建 Request 请求,通过 client 访问 http 服务。实现的 client 有Default
LoadBalancerFeignClient
FeignBlockingLoadBalancerClient
解决
1. 调大服务提供者的header参数(微服务较多 不太适用)
2. 改为requestBody调用服务
复制代码
总结
feign 通过解析接口类、方法、参数上的注解,通过RequestTemplate
@RequestParam 以 Url 拼接的方式,构建了Request
请求,通过 Client 访问 http 服务。对较长参数改为 RequestBody 方式调用服务。
评论