Form表單的POST提交,調用該類接口最長用的方式就是HttpClient,如果使用Feign,如何實現呢?
首先,看下Http中已Form的形式做Post提交的定義:
-----------------------------------華麗的分割線------------------------------------------
POST提交
HTTP 協議是以 ASCII 碼傳輸,建立在 TCP/IP 協議之上的應用層規范。規范把 HTTP 請求分為三個部分:狀態行、請求頭、消息主體。類似於下面這樣:
<method> <request-URL> <version> <headers> <entity-body>
協議規定 POST 提交的數據必須放在消息主體(entity-body)中,但協議並沒有規定數據必須使用什么編碼方式。實際上,開發者完全可以自己決定消息主體的格式,只要最后發送的 HTTP 請求滿足上面的格式就可以。
但是,數據發送出去,還要服務端解析成功才有意義。一般服務端語言如 php、python 等,以及它們的 framework,都內置了自動解析常見數據格式的功能。服務端通常是根據請求頭(headers)中的 Content-Type 字段來獲知請求中的消息主體是用何種方式編碼,再對主體進行解析。所以說到 POST 提交數據方案,包含了 Content-Type 和消息主體編碼方式兩部分。下面就正式開始介紹它們。
application/x-www-form-urlencoded <---Form表單提交
這應該是最常見的 POST 提交數據的方式了。瀏覽器的原生 form 表單,如果不設置 enctype 屬性,那么最終就會以 application/x-www-form-urlencoded 方式提交數據。請求類似於下面這樣(無關的請求頭在本文中都省略掉了):
POST http://www.example.com HTTP/1.1 Content-Type: application/x-www-form-urlencoded;charset=utf-8 title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3
首先,Content-Type 被指定為 application/x-www-form-urlencoded;
其次,提交的數據按照 key1=val1&key2=val2 的方式進行編碼,key 和 val 都進行了 URL 轉碼。
大部分服務端語言都對這種方式有很好的支持。例如 PHP 中,$_POST[‘title’] 可以獲取到 title 的值,$_POST[‘sub’] 可以得到 sub 數組。
很多時候,我們用 Ajax 提交數據時,也是使用這種方式。例如 JQuery 和 QWrap 的 Ajax,Content-Type 默認值都是「application/x-www-form-urlencoded;charset=utf-8」。
multipart/form-data
這又是一個常見的 POST 數據提交的方式。我們使用表單上傳文件時,必須讓 form 的 enctyped 等於這個值。
這種方式一般用來上傳文件,各大服務端語言對它也有着良好的支持。
上面提到的這兩種 POST 數據的方式,都是瀏覽器原生支持的,而且現階段原生 form 表單也只支持這兩種方式。
但是隨着越來越多的 Web 站點,尤其是 WebApp,全部使用 Ajax 進行數據交互之后,我們完全可以定義新的數據提交方式,給開發帶來更多便利。
application/json
application/json 這個 Content-Type 作為響應頭大家肯定不陌生。實際上,現在越來越多的人把它作為請求頭,用來告訴服務端消息主體是序列化后的 JSON 字符串。由於 JSON 規范的流行,除了低版本 IE 之外的各大瀏覽器都原生支持 JSON.stringify,服務端語言也都有處理 JSON 的函數,使用 JSON 不會遇上什么麻煩。
POST http://www.example.com HTTP/1.1
Content-Type: application/json;charset=utf-8
{"title":"test","sub":[1,2,3]}
text/xml
它是一種使用 HTTP 作為傳輸協議,XML 作為編碼方式的遠程調用規范。
XML-RPC 協議簡單、功能夠用,各種語言的實現都有。它的使用也很廣泛,如 WordPress 的 XML-RPC Api,搜索引擎的 ping 服務等等。JavaScript 中,也有現成的庫支持以這種方式進行數據交互,能很好的支持已有的 XML-RPC 服務。不過,我個人覺得 XML 結構還是過於臃腫,一般場景用 JSON 會更靈活方便。
-----------------------------------華麗的分割線------------------------------------------
通過上面對POST提交的簡單介紹,對Post提交Form表單已經有了認識:
1、Content-Type 被指定為 application/x-www-form-urlencoded;
2、提交的數據按照 key1=val1&key2=val2 的方式進行編碼,key 和 val 都進行了 URL 轉碼
Feign代碼實現樣例1
@FeignClient(name = "authentication", url = "${feign.url.authentication}") public interface AuthenticationService { @RequestMapping(value = "/authentication/idcard_internal_apply", method = RequestMethod.POST, consumes = "application/x-www-form-urlencoded;charset=UTF-8") RealNameResponse verify(String entityBody); }
測試調用:
@RunWith(SpringJUnit4ClassRunner.class) // SpringJUnit支持,由此引入Spring-Test框架支持 @SpringBootTest(classes = MasApplication.class) public class NewMyTest { @Autowired private AuthenticationService authenticationService; @Test public void testRealName() throws Exception { Map<String, String> reqParms = new HashMap<>(); reqParms.put("merchantNo", "245888"); reqParms.put("idNo", "310123198901233456"); reqParms.put("realName", "李大庄"); reqParms.put("merchantOrderNo", DateUtil.format(new Date(), DateUtil.longFormat)); reqParms.put("reqSource", "ZF952_API"); //===========拼裝數據為:k=v&k=v 格式=========== StringBuffer v=new StringBuffer(); for(Map.Entry<String, String> entries: reqParms.entrySet()){ v.append(entries.getKey()).append("=").append(entries.getValue()).append("&"); } System.out.println("---------------"+v.substring(0,v.length()-1)); RealNameResponse response = authenticationService.verify(v.substring(0,v.length()-1)); System.out.println("RESPONSE--------------->"+response); } }
很明顯代碼有點挫,先構建了Map對象,然后遍歷拼裝k=v&k=v格式數據,是否可以直接支持Map數據的傳參呢,當然可以
Feign代碼實現樣例2
/** * Description: Form表單提交 * * @author: lishaohua * @date: 2018/5/28 9:50 */ @ConfigurationProperties("feign") public class FeignFormConfiguration { private Integer connectionTimeout = 5000; private Integer readTimeout = 10000; private Integer retry = 0; private Long period = 100L; private Long maxPeriod = 1000L; @Autowired private ObjectFactory<HttpMessageConverters> messageConverters; @Bean @Scope("prototype") public Encoder feignFormEncoder() { Encoder encoder = new FormEncoder(new SpringEncoder(this.messageConverters)); return encoder; } public Request.Options feignOptions() { return new Request.Options(connectionTimeout, readTimeout); } public Retryer feignRetryer() { if (retry > 0) { // the first call is also one attempt try return new Retryer.Default(period, maxPeriod, retry + 1); } else { return Retryer.NEVER_RETRY; } } public Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } public Integer getConnectionTimeout() { return connectionTimeout; } public void setConnectionTimeout(Integer connectionTimeout) { this.connectionTimeout = connectionTimeout; } public Integer getReadTimeout() { return readTimeout; } public void setReadTimeout(Integer readTimeout) { this.readTimeout = readTimeout; } public Integer getRetry() { return retry; } public void setRetry(Integer retry) { this.retry = retry; } public Long getPeriod() { return period; } public void setPeriod(Long period) { this.period = period; } public Long getMaxPeriod() { return maxPeriod; } public void setMaxPeriod(Long maxPeriod) { this.maxPeriod = maxPeriod; } }
@FeignClient(name = "authentication", url = "${feign.url.authentication}", configuration = FeignFormConfiguration.class) public interface AuthenticationService { @RequestMapping(value = "/authentication/idcard_internal_apply", method = RequestMethod.POST, consumes = "application/x-www-form-urlencoded;charset=UTF-8") RealNameResponse verify(Map<String,?> entityBody); }
測試
@RunWith(SpringJUnit4ClassRunner.class) // SpringJUnit支持,由此引入Spring-Test框架支持 @SpringBootTest(classes = MasApplication.class) public class NewMyTest { @Autowired private AuthenticationService authenticationService; @Test public void testRealName() throws Exception { Map<String, String> reqParms = new HashMap<>(); reqParms.put("merchantNo", "245888"); reqParms.put("idNo", "310123198901233456"); reqParms.put("realName", "李大庄"); reqParms.put("merchantOrderNo", DateUtil.format(new Date(), DateUtil.longFormat)); reqParms.put("reqSource", "ZF952_API"); RealNameResponse response = authenticationService.verify(reqParms); System.out.println("RESPONSE--------------->"+response); } }
為了更好的理解POST提交后各個參數的含義,建議使用大白鯊抓包。。。。
參考文章: