Feign多种类型的POST
三种携带请求体的方式
前文Feign高级功能说了,Feign有三种方式实现请求体,分别是
- 使用
@Body
注解
- 使用一个不加注解的参数
- 使用若干个添加
@Param
但未在@ReqeustLine、@Heads...
等地方使用的参数
POST的三种常见ContentType
而http的post有三种类型,分别是:
下面按照这三种ContentType
分别讨论一下。
1.发送application/json
格式的请求体
发送此类post请求,需要添加头部@Heads("Content-Type:application/json")
使用@Body
发送
注意使用@Body
的方式构建请求体,别忘了注解中的值左右两遍的大括号需要转义。
1 2 3 4 5 6 7
| interface TestJson {
@RequestLine(value = "POST /test") @Body("%7B\"user_name\":\"{userName}\"%7D") @Headers("Content-Type:application/json") Response getItemCoupon(@Param("userName") String userName); }
|
使用不加注解的参数
1 2 3 4 5
| interface TestJson2 { @RequestLine(value = "POST /test") @Headers("Content-Type:application/json") Response getItemCoupon(TestParam body); }
|
1 2 3 4 5
| TestJson2 target = Feign.builder() .encoder(new JacksonEncoder()) .target(TestJson2.class, "http://localhost:8081");
target.getItemCoupon(new TestParam("username", "password"));
|
接口中有一个没有注解的参数TestParam
,build时需要配置JacksonEncoder
来处理它,将TestParam
序列化成JSON字符串作为请求体。
使用没有使用的@Param
参数
1 2 3 4 5
| interface TestJson3 { @RequestLine(value = "POST /test") @Headers("Content-Type:application/json") Response getItemCoupon(@Param("userName") String userName, @Param("password") String passWord); }
|
1 2 3 4 5
| TestJson2 target = Feign.builder() .encoder(new JacksonEncoder()) .target(TestJson2.class, "http://localhost:8081");
target.getItemCoupon("username","password");
|
请看接口中有两个参数,但没有被使用,那么这两个参数就会组成一个Map
,然后被配置的JacksonEncoder
转成json字符串作为请求体。
结果报文
2.发送x-www-form-urlencoded
格式的post请求
使用@Body
发送
1 2 3 4 5 6
| interface TestWwwFormUrlencoded { @RequestLine(value = "POST /test") @Headers("Content-Type:application/x-www-form-urlencoded") @Body("name={name}&age={age}") Response getItemCoupon(@Param("name") String name, @Param("age") Integer age); }
|
1 2 3 4
| TestWwwFormUrlencoded target = Feign.builder() .target(TestWwwFormUrlencoded.class, "http://localhost:8081"); Response xiao = target.getItemCoupon(UriUtils.encode("小明"), 18);
|
这样的用法要注意两点,1.需要自己手工处理url编码,2.需要自己拼接参数,且数量有限。
虽然能写任意字符串拼接到body中,但是很麻烦不推荐这种用法。
使用不加注解的参数
1 2 3 4 5
| interface TestWwwFormUrlencoded2 { @RequestLine(value = "POST /test") @Headers("Content-Type:application/x-www-form-urlencoded") Response getItemCoupon(String param); }
|
如果这样的话,传参需要自己拼接参数,还要自己url转码,很麻烦不推荐。
使用map来接收参数,并配合自定义encoder处理
1 2 3 4 5
| interface TestWwwFormUrlencoded3 { @RequestLine(value = "POST /test") @Headers("Content-Type:application/x-www-form-urlencoded") Response getItemCoupon(Map<String, Object> params); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static class WwwEncode implements Encoder {
@Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { Map<String, Object> map = (Map<String, Object>) object; List<String> list = new ArrayList<>(); map.forEach((k, v) -> { list.add(UriUtils.encode(k) + "=" + UriUtils.encode(String.valueOf(v))); }); String body = String.join("&", list); template.body(Request.Body.encoded(body.getBytes(), StandardCharsets.UTF_8)); } }
|
这样比第一种方法方便点,但掉用时传递的参数是map,在不确定参数数目是很好用,但不利于识别,也不推荐。
使用没有使用的@Param
参数
1 2 3 4 5
| interface TestWwwFormUrlencoded4 { @RequestLine(value = "POST /test") @Headers("Content-Type:application/x-www-form-urlencoded") Response getItemCoupon(@Param("name") String userName, @Param("age") String passWord); }
|
1 2 3 4 5 6 7 8 9 10 11 12
| public static class WwwEncode implements Encoder { @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { Map<String, Object> map = (Map<String, Object>) object; List<String> list = new ArrayList<>(); map.forEach((k, v) -> { list.add(UriUtils.encode(k) + "=" + UriUtils.encode(String.valueOf(v))); }); String body = String.join("&", list); template.body(Request.Body.encoded(body.getBytes(), StandardCharsets.UTF_8)); } }
|
Feign会把参数放到一个map里,传入encoder
中。
同样需要encoder,,这种方法在确定参数数量的情况下比较好用,并且有参数名的提示,比上面那种方法用着舒服点,但要求固定的参数数量,多数情况下都是这样的。
并且可以同时使用@QueryMap
注解,将多余的注解拼在url上,虽然不太好,但也是可以的。
结果报文
这种格式主要用于上传文件,但Feign上传文件还是挺麻烦的,这里不讲上传文件的方法。如果服务端必须要求是form-data
格式的请求,Feign也能实现,下面讲讲feign使用form-data
发送post请求。
因为上面讲到的x-www-form-urlencoded
形式的请求需要把字符进行url编码,这样体积一下就大了很多,而且不能用法发文件,所以form-data
格式就被发明出来了。它使用一个约定好的符号进行分隔参数。报文如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| POST /test HTTP/1.1 Host: localhost:8081 Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW Host: localhost:8081 Content-Length: 266
Content-Disposition: form-data; name="name"
abc
Content-Disposition: form-data; name="age"
18
|
上面的报文可以看出请求体部分是由boundary开始,由参数值结束。
boundary在请求头中约定好,且用于参数分隔的boundary要比请求头中定义的多了两个--
。
最后收尾的boundary的尾部再多了两个–-
所以真正的请求体格式是
1 2 3 4 5 6 7 8 9 10 11
| 设 bound = 111
--111 Content-Disposition: form-data; name="name"
abc --111 Content-Disposition: form-data; name="age"
18 --111--
|
使用@Body
发送
- 处理不了,在Body注解中拼接请求体太麻烦了,所以不采用这种方式。
使用不加注解的参数
1 2 3 4
| interface TestFormData { @RequestLine(value = "POST /test") Response getItemCoupon(Map<String, Object> map); }
|
定义自定义encoder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public static class FormDataencodedPost implements Encoder { @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { String boundary = "--------------" + System.currentTimeMillis(); Map<String, Object> map = (Map<String, Object>) object; StringBuilder sb = new StringBuilder(); map.forEach((k, v) -> { sb.append("--").append(boundary).append("\r\n"); sb.append("Content-Disposition: form-data; name=\"").append(k).append("\"").append("\r\n"); sb.append("\r\n"); sb.append(v).append("\r\n"); }); sb.append("--").append(boundary).append("--\r\n"); template.body(Request.Body.encoded(sb.toString().getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8));
template.removeHeader("Content-Type"); template.header("Content-Type", "multipart/form-data; boundary=" + boundary); } }
|
1 2 3 4 5 6 7 8 9
| TestFormData target = Feign.builder() .encoder(new FormDataencodedPost()) .target(TestFormData.class, "http://localhost:8081");
HashMap<String, Object> params = new HashMap<>(); params.put("name", "老王"); params.put("age", 12);
target.getItemCoupon(params);
|
- 参数是map,所以此处
encoder
中拿到的参数就是map,然后遍历map,将参数和值拼接成上面讲的form-data
所需的形式。
- boundary使用的是
-----
+时间戳
的形式,也可以用其他随机生成的方式,for循环里面拼接boundary时,在每个boundary前面添加两个-
,然后注意\r\n
别遗漏了。
- 参数循环拼接完成后在最后面在添加一个收尾的boundary,此boundary的前后各添加两个
-
。
- 还要注意的一点时,先清空
Content-Type
再添加Content-Type
,讲boundary设进去。
使用没有使用的@Param
参数
使用这个其实也是讲参数封装成map进行encoder
和上面是一样的这里就不写了。
结果报文
form-data
的拼接方式比较复杂,如果发送完后台接收不到数据,请仔细核对报文格式是否正确。可以打开WireShark
抓包,再用其他工具进行正常的post,仔细比对两者报文的差异。
总结
以上就是使用Feign发送三种类型的POST请求的方法,业务上需要的场景一般就都能满足了,案例中用到的encoder
写的比较简单,真实使用时请注意参数校验,处理好异常情况,或写更通用的encoder
。