Spring Cloud Feign使用ApacheHttpCl
类似GET请求的方式,指定method为POST
@FeignClient(name = "${serviceName}")
public interface HotService
{
@RequestMapping(value = "/term/hot", method = RequestMethod.POST)
public Map<String, Object> hot(String terms);
}
请求报错:java.lang.IllegalArgumentException: MIME type may not contain reserved characters
找到这段异常信息所在位置
org.apache.http.entity.ContentType
public static ContentType create(final String mimeType, final Charset charset) {
final String type = Args.notBlank(mimeType, "MIME type").toLowerCase(Locale.ROOT);
Args.check(valid(type), "MIME type may not contain reserved characters");
return new ContentType(type, charset);
}
private static boolean valid(final String s) {
for (int i = 0; i < s.length(); i++) {
final char ch = s.charAt(i);
if (ch == '"' || ch == ',' || ch == ';') {
return false;
}
}
return true;
}
可以看出来是检测Content-Type不合法报的异常,但在代码中我们并没有指定Content-Type,所以应该是使用了默认值。
feign.httpclient.ApacheHttpClient
private ContentType getContentType(Request request) {
ContentType contentType = ContentType.DEFAULT_TEXT;
Iterator var3 = request.headers().entrySet().iterator();
while(var3.hasNext()) {
Entry<String, Collection<String>> entry = (Entry)var3.next();
if(((String)entry.getKey()).equalsIgnoreCase("Content-Type")) {
Collection values = (Collection)entry.getValue();
if(values != null && !values.isEmpty()) {
contentType = ContentType.create((String)((Collection)entry.getValue()).iterator().next(), request.charset());
break;
}
}
}
return contentType;
}
在ApacheHttpClient中看到默认设置的Content-Type是ContentType.DEFAULT_TEXT,即text/plain; charset=ISO-8859-1,其中包含分号,也就导致了上面提到的异常。所以POST请求时我们需要指定Content-Type,修改下代码:
@FeignClient(name = "${serviceName}")
public interface HotService
{
@RequestMapping(value = "/term/hot", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public Map<String, Object> hot(String terms);
}
通过给@RequestMapping设置consumes参数,我们指定提交内容类型为表单数据。
再次请求,请求成功发出,但是在服务端接受参数时,对应的值是null,这里我们考虑开启Feign日志,查看传递的参数是什么。
新建一个类
@Configuration
public class FeignConfiguration
{
@Bean
public Logger.Level feignLoggerLevel() {
return feign.Logger.Level.FULL;
}
}
在原先的@FeignClient注解中添加参数configuration,变成
@FeignClient(name = "${serviceName}", configuration = FeignConfiguration.class)
同时指定service类的日志级别为DEBUG:
logging.level.com.cs.search.service.HotService: DEBUG
请求之后看到下面的日志:
可以看到请求确实把值传递出去了,但是却不是以“键=值”的形式,只有“值”,所以服务端在获取指定参数时值就是null。看来service中请求时并不是如我们所想的那样把值绑定到了方法的参数上去,顺着这个想下去,既然请求时直接把值传递出去了,那如果我们的传递的值本身就是“键=值”这样的形式呢?
我们把原先的请求方式
hotService.hot(terms)
替换成
hotService.hot("terms="+terms);
这次请求之后,Feign的日志如下:
变成了我们预想的样子,当然服务端也正确的接受了。
但是,这种方法相当于手动构建body内容,某种意义上相当于是个GET请求,有没有更好的解决方法?考虑到现在问题的关键在于,我们传了一个String类型的参数,系统似乎没有使用正确的HttpMessageConverters来构建请求参数。Spring中有一个类型是MultiValueMap,这种类型的参数,Spring会将其解析成application/x-www-form-urlencoded,因此我们将参数替换成这种类型尝试一下。
@FeignClient(name = "${serviceName}")
public interface HotService
{
@RequestMapping(value = "/term/hot", method = RequestMethod.POST)
public Map<String, Object> hot(MultiValueMap params);
}
注意到上面提到的consumes参数去掉了,用了MultiValueMap就没必要再设置了
相应的,调用方式也要改变:
LinkedMultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
multiValueMap.add("terms", terms);
Map<String, Object> result = hotService.hot(multiValueMap);
经过测试,完美解决问题。