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

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

@FeignClient(name = "${serviceName}")
public interface HotService
{
    @RequestMapping(value = "/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 = "/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

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



可以看到请求确实把值传递出去了,但是却不是以“键=值”的形式,只有“值”,所以服务端在获取指定参数时值就是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 hot(MultiValueMap params);
}
注意到上面提到的consumes参数去掉了,用了MultiValueMap就没必要再设置了
相应的,调用方式也要改变:
LinkedMultiValueMap multiValueMap = new LinkedMultiValueMap<>();
multiValueMap.add("terms", terms);
Map result = hotService.hot(multiValueMap);

经过测试,完美解决问题。

你可能感兴趣的:(Spring Cloud Feign使用ApacheHttpClient提交POST请求的问题)