Spring|Spring Cloud Feign使用ApacheHttpClient提交POST请求的问题

类似GET请求的方式,指定method为POST

@FeignClient(name = "${serviceName}") public interface HotService { @RequestMapping(value = "https://www.it610.com/term/hot", method = RequestMethod.POST) public Map 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> 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 = "https://www.it610.com/term/hot", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) public Map 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

请求之后看到下面的日志:

Spring|Spring Cloud Feign使用ApacheHttpClient提交POST请求的问题
文章图片

可以看到请求确实把值传递出去了,但是却不是以“键=值”的形式,只有“值”,所以服务端在获取指定参数时值就是null。看来service中请求时并不是如我们所想的那样把值绑定到了方法的参数上去,顺着这个想下去,既然请求时直接把值传递出去了,那如果我们的传递的值本身就是“键=值”这样的形式呢?
我们把原先的请求方式 hotService.hot(terms) 替换成 hotService.hot("terms="+terms);

这次请求之后,Feign的日志如下:

Spring|Spring Cloud Feign使用ApacheHttpClient提交POST请求的问题
文章图片

变成了我们预想的样子,当然服务端也正确的接受了。
但是,这种方法相当于手动构建body内容,某种意义上相当于是个GET请求,有没有更好的解决方法?考虑到现在问题的关键在于,我们传了一个String类型的参数,系统似乎没有使用正确的HttpMessageConverters来构建请求参数。Spring中有一个类型是MultiValueMap,这种类型的参数,Spring会将其解析成application/x-www-form-urlencoded,因此我们将参数替换成这种类型尝试一下。
@FeignClient(name = "${serviceName}") public interface HotService { @RequestMapping(value = "https://www.it610.com/term/hot", method = RequestMethod.POST) public Map hot(MultiValueMap params); } 注意到上面提到的consumes参数去掉了,用了MultiValueMap就没必要再设置了 相应的,调用方式也要改变: LinkedMultiValueMap multiValueMap = new LinkedMultiValueMap<>(); multiValueMap.add("terms", terms); Map result = hotService.hot(multiValueMap);

【Spring|Spring Cloud Feign使用ApacheHttpClient提交POST请求的问题】经过测试,完美解决问题。

    推荐阅读