#先了解一下HTTP 協議
史前時期
HTTP 協議在我們的生活中隨處可見,打開手機或者電腦,只要你上網,不論是用 iPhone、Android、Windows 還是 Mac,不論是用瀏覽器還是 App,不論是看新聞、短視頻還是聽音樂、玩游戲,后面總會有 HTTP 在默默為你服務。
據 NetCraft 公司統計,目前全球至少有 16 億個網站、2 億多個獨立域名,而這個龐大網絡世界的底層運轉機制就是 HTTP。
那么,在享受如此便捷舒適的網絡生活時,你有沒有想過,HTTP 協議是怎么來的?它最開始是什么樣子的?又是如何一步一步發展到今天,幾乎“統治”了整個互聯網世界的呢?
20 世紀 60 年代,美國國防部高等研究計划署(ARPA)建立了 ARPA 網,它有四個分布在各地的節點,被認為是如今互聯網的“始祖”。
然后在 70 年代,基於對 ARPA 網的實踐和思考,研究人員發明出了著名的 TCP/IP 協議。由於具有良好的分層結構和穩定的性能,TCP/IP 協議迅速戰勝其他競爭對手流行起來,並在 80 年代中期進入了 UNIX 系統內核,促使更多的計算機接入了互聯網。
創世紀
1989 年,任職於歐洲核子研究中心(CERN)的蒂姆·伯納斯 - 李(Tim Berners-Lee)發表了一篇論文,提出了在互聯網上構建超鏈接文檔系統的構想。這篇論文中他確立了三項關鍵技術。
- URI:即統一資源標識符,作為互聯網上資源的唯一身份;
- HTML:即超文本標記語言,描述超文本文檔;
- HTTP:即超文本傳輸協議,用來傳輸超文本。
所以在這一年,我們的HTTP誕生了。
HTTP/0.9
20 世紀 90 年代初期的互聯網世界非常簡陋,計算機處理能力低,存儲容量小,網速很慢,還是一片“信息荒漠”。網絡上絕大多數的資源都是純文本,很多通信協議也都使用純文本,所以 HTTP 的設計也不可避免地受到了時代的限制。
這一時期的 HTTP 被定義為 0.9 版,結構比較簡單,為了便於服務器和客戶端處理,它也采用了純文本格式。蒂姆·伯納斯 - 李最初設想的系統里的文檔都是只讀的,所以只允許用“GET”動作從服務器上獲取 HTML 文檔,並且在響應請求之后立即關閉連接,功能非常有限。
HTTP/0.9 雖然很簡單,但它作為一個“原型”,充分驗證了 Web 服務的可行性,而“簡單”也正是它的優點,蘊含了進化和擴展的可能性,因為:
“把簡單的系統變復雜”,要比“把復雜的系統變簡單”容易得多。
HTTP/1.0
1993 年,NCSA(美國國家超級計算應用中心)開發出了 Mosaic,是第一個可以圖文混排的瀏覽器,隨后又在 1995 年開發出了服務器軟件 Apache,簡化了 HTTP 服務器的搭建工作。
同一時期,計算機多媒體技術也有了新的發展:1992 年發明了 JPEG 圖像格式,1995 年發明了 MP3 音樂格式。
這些新軟件新技術一經推出立刻就吸引了廣大網民的熱情,更的多的人開始使用互聯網,研究 HTTP 並提出改進意見,甚至實驗性地往協議里添加各種特性,從用戶需求的角度促進了 HTTP 的發展。
於是在這些已有實踐的基礎上,經過一系列的草案,HTTP/1.0 版本在 1996 年正式發布。它在多方面增強了 0.9 版,形式上已經和我們現在的 HTTP 差別不大了,例如:
- 增加了 HEAD、POST 等新方法
- 增加了響應狀態碼,標記可能的錯誤原因
- 引入了協議版本號概念
- 引入了 HTTP Header(頭部)的概念,讓 HTTP 處理請求和響應更加靈活
- 傳輸的數據不再僅限於文本
HTTP/1.1
1995 年,網景的 Netscape Navigator 和微軟的 Internet Explorer 開始了著名的“瀏覽器大戰”,都希望在互聯網上占據主導地位。於是在“瀏覽器大戰”結束之后的 1999 年,HTTP/1.1 發布了 RFC 文檔,編號為 2616,正式確立了延續十余年的傳奇。
HTTP/1.1 主要的變更點有:
- 增加了 PUT、DELETE 等新的方法;
- 增加了緩存管理和控制;
- 明確了連接管理,允許持久連接;
- 允許響應數據分塊(chunked),利於傳輸大文件;
- 強制要求 Host 頭,讓互聯網主機托管成為可能。
HTTP/2
HTTP/1.1 發布之后,整個互聯網世界呈現出了爆發式的增長,度過了十多年的“快樂時光”,更涌現出了 Facebook、Twitter、淘寶、京東等互聯網新貴。
這期間也出現了一些對 HTTP 不滿的意見,主要就是連接慢,無法跟上迅猛發展的互聯網,但 HTTP/1.1 標准一直“巋然不動”,無奈之下人們只好發明各式各樣的“小花招”來緩解這些問題,比如以前常見的切圖、JS 合並等網頁優化手段。
終於有一天,搜索巨頭 Google 忍不住了,首先開發了自己的瀏覽器 Chrome,然后推出了新的 SPDY 協議,並在 Chrome 里應用於自家的服務器,如同十多年前的網景與微軟一樣,從實際的用戶方來“倒逼”HTTP 協議的變革,這也開啟了第二次的“瀏覽器大戰”。
歷史再次重演,不過這次的勝利者是 Google,Chrome 目前的全球的占有率超過了 60%。Google 借此順勢把 SPDY 推上了標准的寶座,互聯網標准化組織以 SPDY 為基礎開始制定新版本的 HTTP 協議,最終在 2015 年發布了 HTTP/2,RFC 編號 7540。
SPDY(讀作“SPeeDY”)是Google開發的基於TCP的會話層協議,用以最小化網絡延遲,提升網絡速度,優化用戶的網絡使用體驗。
SPDY並不是一種用於替代HTTP的協議,而是對HTTP協議的增強。新協議的功能包括數據流的多路復用、請求優先級以及HTTP報頭壓縮。
谷歌表示,引入SPDY協議后,在實驗室測試中頁面加載速度比原先快64%。
HTTP/2 的制定充分考慮了現今互聯網的現狀:寬帶、移動、不安全,在高度兼容 HTTP/1.1 的同時在性能改善方面做了很大努力,主要的特點有:
- 二進制協議,不再是純文本
- 可發起多個請求,廢棄了 1.1 里的管道
- 使用專用算法壓縮頭部,減少數據傳輸量
- 允許服務器主動向客戶端推送數據
- 增強了安全性,“事實上”要求加密通信
雖然 HTTP/2 到今天已經五歲,也衍生出了 gRPC 等新協議,但由於 HTTP/1.1 實在是太過經典和強勢,目前它的普及率還比較低,大多數網站使用的仍然還是 20 年前的 HTTP/1.1。
HTTP/3
在 HTTP/2 還處於草案之時,Google 又發明了一個新的協議,叫做 QUIC,而且還是相同的“套路”,繼續在 Chrome 和自家服務器里試驗着“玩”,依托它的龐大用戶量和數據量,持續地推動 QUIC 協議成為互聯網上的“既成事實”。
2018 年,互聯網標准化組織 IETF 提議將“HTTP over QUIC”更名為“HTTP/3”並獲得批准,HTTP/3 正式進入了標准化制訂階段,也許兩三年后就會正式發布,到時候我們很可能會跳過 HTTP/2 直接進入 HTTP/3。
QUIC(Quick UDP Internet Connection)是谷歌制定的一種基於UDP的低時延的互聯網傳輸層協議。
在2016年11月國際互聯網工程任務組(IETF)召開了第一次QUIC工作組會議,受到了業界的廣泛關注。
這也意味着QUIC開始了它的標准化過程,成為新一代傳輸層協議
了解這么多,那到底HTTP是什么呢?
你可能會不假思索、脫口而出:“HTTP 就是超文本傳輸協議,也就是 HyperText Transfer Protocol。”
回答的也沒錯,但是太過簡單。更准確的回答應該是“HTTP 是一個在計算機世界里專門在兩點之間傳輸文字、圖片、音頻、視頻等超文本數據的約定和規范”
#關於HttpClient
簡介
官網這樣說
超文本傳輸協議(HTTP)可能是當今Internet上使用的最重要的協議。
Web服務,支持網絡的設備和網絡計算的增長繼續將HTTP協議的作用擴展到用戶驅動的Web瀏覽器之外,同時增加了需要HTTP支持的應用程序的數量。
盡管java.net軟件包提供了用於通過HTTP訪問資源的基本功能,但它並未提供許多應用程序所需的全部靈活性或功能。
HttpClient試圖通過提供高效,最新且功能豐富的程序包來實現此空白,以實現最新HTTP標准和建議的客戶端。
HttpClient是為擴展而設計的,同時提供了對基本HTTP協議的強大支持,
對於構建HTTP感知的客戶端應用程序(例如Web瀏覽器,Web服務客戶端或利用或擴展HTTP協議進行分布式通信的系統)的任何人來說,HttpClient都可能會感興趣。
URLConnection
,增加了易用性和靈活性,它不僅是客戶端發送 HTTP 請求變得容易,而且也方便了開發人員測試接口(基於 HTTP 協議的),即提高了開發的效率,也方便提高代碼的健壯性。因此熟練掌握 HttpClient 是很重要的必修內容,掌握 HttpClient 后,相信對於 HTTP 協議的了解會更加深入。
- 基於標准、純凈的 Java 語言。實現了 HTTP 1.0 和 HTTP 1.1
- 以可擴展的面向對象的結構實現了 HTTP 全部的方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, and TRACE)。
- 支持 HTTPS 協議。
- 通過 HTTP 代理建立透明的連接。
- 利用 CONNECT 方法通過 HTTP 代理建立隧道的 HTTPS 連接。
- Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO/Kerberos 認證方案。
- 插件式的自定義認證方案。
- 便攜可靠的套接字工廠使它更容易的使用第三方解決方案。
- 連接管理器支持多線程應用。支持設置最大連接數,同時支持設置每個主機的最大連接數,發現並關閉過期的連接。
- 自動處理 Set-Cookie 中的 Cookie。
- 插件式的自定義 Cookie 策略。
- Request 的輸出流可以避免流中內容直接緩沖到 Socket 服務器。
- Response 的輸入流可以有效的從 Socket 服務器直接讀取相應內容。
- 在 HTTP 1.0 和 HTTP 1.1 中利用 KeepAlive 保持持久連接。
- 直接獲取服務器發送的 response code 和 headers。
- 設置連接超時的能力。
- 實驗性的支持 HTTP 1.1 response caching。
- 源代碼基於 Apache License 可免費獲取。
- 創建
HttpClient
對象 - 創建請求方法的實例,並指定請求 URL。如果需要發送 GET 請求,創建
HttpGet
對象;如果需要發送 POST 請求,創建HttpPost
對象 -
如果需要發送請求參數,可調用
HttpGet
、HttpPost
共同的setParams(HttpParams params)
方法來添加請求參數;對於HttpPost
對象而言,也可調用setEntity(HttpEntity entity)
方法來設置請求參數 - 調用
HttpClient
對象的execute(HttpUriRequest request)
發送請求,該方法返回一個HttpResponse
- 調用
HttpResponse
的getAllHeaders()
、getHeaders(String name)
等方法可獲取服務器的響應頭 - 調用
HttpResponse
的getEntity()
方法可獲取HttpEntity
對象,該對象包裝了服務器的響應內容。程序可通過該對象獲取服務器的響應內容 - 釋放連接。無論執行方法是否成功,都必須釋放連接
使用用例
pom配置
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.12</version> </dependency>
創建Get請求
@Test
void testGet() {
// 創建 HttpClient 客戶端
CloseableHttpClient httpClient = HttpClients.createDefault();
// 創建 HttpGet 請求
HttpGet httpGet = new HttpGet("http://192.168.1.250:15005/dsm-ubm/base-role-info/list");
// 設置長連接
httpGet.setHeader("Connection", "keep-alive");
// 設置認證信息
httpGet.setHeader("Authorization", "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiY3JlYXRlZCI6MTU4ODA2NjM1NTMxNiwiZXhwIjoxNTg4NjcxMTU1fQ.mfroxGQMf_QbHGViEBhQ0hzHoxdNM0TwpGWT64t3LPUl8Sn_ZSBFFKUAt0aKkywM3Lq8245LSXu6BYOptVwYZg");
// 設置代理(模擬瀏覽器版本)
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
CloseableHttpResponse httpResponse = null;
try {
// 請求並獲得響應結果
httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
//打印結果
System.out.println(EntityUtils.toString(httpEntity));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (httpResponse != null) {
try {
httpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
keep-alive說明
keep-alive:從HTTP/1.1起,瀏覽器默認都開啟了Keep-Alive,保持連接特性,客戶端和服務器都能選擇隨時關閉連接,則請求頭中為connection:close。
簡單地說,當一個網頁打開完成后,客戶端和服務器之間用於傳輸HTTP數據的TCP連接不會關閉,如果客戶端再次訪問這個服務器上的網頁,會繼續使用這一條已經建立的TCP連接。
但是Keep-Alive不會永久保持連接,它有一個保持時間,可以在不同的服務器軟件(如Apache)中設定這個時間。
創建Post請求,content-type=application/json
@Test void testPost() { // 創建 HttpClient 客戶端 CloseableHttpClient httpClient = HttpClients.createDefault(); // 創建 HttpPost 請求 HttpPost httpPost = new HttpPost("http://192.168.1.250:15005/dsm-ubm/api/getInAreaPatientList"); // 設置長連接 httpPost.setHeader("Connection", "keep-alive"); // 設置認證信息 httpPost.setHeader("Authorization", "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiY3JlYXRlZCI6MTU4ODA2NjM1NTMxNiwiZXhwIjoxNTg4NjcxMTU1fQ.mfroxGQMf_QbHGViEBhQ0hzHoxdNM0TwpGWT64t3LPUl8Sn_ZSBFFKUAt0aKkywM3Lq8245LSXu6BYOptVwYZg"); // 設置代理(模擬瀏覽器版本) httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"); // 創建 HttpPost 參數 JSONObject jsonObject = new JSONObject(); jsonObject.put("FromTime", ""); jsonObject.put("Ward", "4008"); CloseableHttpResponse httpResponse = null; try { StringEntity entity = new StringEntity(jsonObject.toJSONString(), "utf-8"); entity.setContentType("application/json"); httpPost.setEntity(entity); httpResponse = httpClient.execute(httpPost); HttpEntity httpEntity = httpResponse.getEntity(); //打印結果 System.out.println(EntityUtils.toString(httpEntity)); } catch (IOException e) { e.printStackTrace(); } finally { try { if (httpResponse != null) { httpResponse.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (httpClient != null) { httpClient.close(); } } catch (IOException e) { e.printStackTrace(); } } }
創建Post請求,content-type=application/x-www-form-urlencoded
@Test void testPost() { // 創建 HttpClient 客戶端 CloseableHttpClient httpClient = HttpClients.createDefault(); // 創建 HttpPost 請求 HttpPost httpPost = new HttpPost("http://localhost:8080/hello"); // 設置長連接 httpPost.setHeader("Connection", "keep-alive"); // 設置認證信息 httpPost.setHeader("Authorization", "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiY3JlYXRlZCI6MTU4ODA2NjM1NTMxNiwiZXhwIjoxNTg4NjcxMTU1fQ.mfroxGQMf_QbHGViEBhQ0hzHoxdNM0TwpGWT64t3LPUl8Sn_ZSBFFKUAt0aKkywM3Lq8245LSXu6BYOptVwYZg"); // 設置代理(模擬瀏覽器版本) httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"); // 創建 HttpPost 參數 List<BasicNameValuePair> params = new ArrayList<>(); params.add(new BasicNameValuePair("message", "鷓鴣哨")); CloseableHttpResponse httpResponse = null; try { httpPost.setEntity(new UrlEncodedFormEntity(params, "utf-8")); httpResponse = httpClient.execute(httpPost); HttpEntity httpEntity = httpResponse.getEntity(); //打印結果 System.out.println(EntityUtils.toString(httpEntity)); } catch (IOException e) { e.printStackTrace(); } finally { try { if (httpResponse != null) { httpResponse.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (httpClient != null) { httpClient.close(); } } catch (IOException e) { e.printStackTrace(); } } }
application/json與application/x-www-form-urlencoded區別
application/json和application/x-www-form-urlencoded都是表單數據發送時的編碼類型。默認地,表單數據會編碼為application/x-www-form-urlencoded。就是說,在發送到服務器之前,所有字符都會進行編碼。
application/json,隨着json規范的越來越流行,並且瀏覽器支持程度原來越好,許多開發人員將application/json作為請求content-type,告訴服務器請求的主體內容是json格式的字符串,服務器端會對json字符串進行解析,這種方式的好處就是前端人員不需要關心數據結構的復雜度,只要是標准的json格式就能提交成功,需要封裝成對象的話,可以加上@RequestBody注解
application/x-www-form-urlencoded是Jquery的Ajax請求默認方式,這種方式的好處就是瀏覽器都支持,在請求發送過程中會對數據進行序列化處理,以鍵值對形式,數據拼接方式為key=value的方式,后台如果使用對象接收的話,可以自動封裝成對象
@RequestParam
- 用來處理Content-Type: 為 application/x-www-form-urlencoded編碼的內容。(Http協議中,如果不指定Content-Type,則默認傳遞的參數就是application/x-www-form-urlencoded類型)。
- 在Content-Type: application/x-www-form-urlencoded的請求中, get 方式中queryString的值,和post方式中 body data的值都會被Servlet接受到並轉化到Request.getParameter()參數集中,所以@RequestParam可以獲取的到。
@RequestBody
- 處理HttpEntity傳遞過來的數據,一般用來處理非Content-Type: application/x-www-form-urlencoded編碼格式的數據。
- GET請求中,因為沒有HttpEntity,所以@RequestBody並不適用。
- POST請求中,通過HttpEntity傳遞的參數,必須要在請求頭中聲明數據的類型Content-Type,SpringMVC通過使用HandlerAdapter 配置的HttpMessageConverters來解析HttpEntity中的數據,然后綁定到相應的bean上。
@ResponseBody 和 @RequestBody 區別
@ResponseBody是作用在方法上的,@ResponseBody 表示該方法的返回結果直接寫入 HTTP response body 中,一般在異步獲取數據時使用【也就是AJAX】,在使用 @RequestMapping后,返回值通常解析為跳轉路徑,但是加上 @ResponseBody 后返回結果不會被解析為跳轉路徑,而是直接寫入 HTTP response body 中。 比如異步獲取 json 數據,加上 @ResponseBody 后,會直接返回 json 數據。
@RequestBody 用於讀取Request請求的body部分數據,使系統默認的HttpMessageConverter進行解析,然后把相應的數據綁定要返回的對象上,再把HttpMessageConverter返回的對象數據綁定到Controller方法的參數上。
#關於RestTemplate
介紹
RestTemplate 是從 Spring3.0 開始支持的一個 HTTP 請求工具,它提供了常見的REST請求方案的模版,例如 GET 請求、POST 請求、PUT 請求、DELETE 請求以及一些通用的請求執行方法 exchange 以及 execute。RestTemplate 繼承自 InterceptingHttpAccessor 並且實現了 RestOperations 接口,其中 RestOperations 接口定義了基本的 RESTful 操作,這些操作在 RestTemplate 中都得到了實現。是比httpClient更優雅的Restful URL訪問。
- RestTemplate是Spring提供的用於訪問Rest服務的客戶端,
- RestTemplate提供了多種便捷訪問遠程Http服務的方法,能夠大大提高客戶端的編寫效率。
- 調用RestTemplate的默認構造函數,RestTemplate對象在底層通過使用java.net包下的實現創建HTTP 請求,
- 可以通過使用ClientHttpRequestFactory指定不同的HTTP請求方式。
- ClientHttpRequestFactory接口主要提供了三種實現方式
- 1、SimpleClientHttpRequestFactory方式,此處生成SimpleBufferingClientHttpRequest,使用HttpURLConnection創建底層的Http請求連接
- 2、HttpComponentsClientHttpRequestFactory方式,此處生成HttpComponentsClientHttpRequest,使用http client來實現網絡請求
- 3、OkHttp3ClientHttpRequestFactory方式,此處生成OkHttp3ClientHttpRequest,使用okhttp來實現網絡請求
優點
- 並沒有重寫底層的HTTP請求技術,而是提供配置,可選用OkHttp/HttpClient等
- 在OkHttp/HttpClient之上,封裝了請求操作,可以定義Convertor來實現對象到請求body的轉換方法,以及返回body到對象的轉換方法。
配置
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
//單位為ms
factory.setReadTimeout(5000);
//單位為ms
factory.setConnectTimeout(5000);
return factory;
}
@Primary
@Bean
public ClientHttpRequestFactory httpComponentsClientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
//單位為ms
factory.setReadTimeout(6000);
//單位為ms
factory.setConnectTimeout(6000);
return factory;
}
/*@Bean
public ClientHttpRequestFactory okHttp3ClientHttpRequestFactory() {
OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory();
//單位為ms
factory.setReadTimeout(7000);
//單位為ms
factory.setConnectTimeout(7000);
return factory;
}*/
}
使用
@RunWith(SpringRunner.class) @SpringBootTest public class TestGet { @Autowired private RestTemplate restTemplate; @Test public void testGet() { String url = "http://localhost:8080/welcome?message={1}"; //可以使用map來封裝請求參數 Map<String, String> map = new HashMap<>(); map.put("1", "world"); String jsonResult = restTemplate.getForObject(url, String.class, map); System.out.println("result:" + jsonResult); } }
@RunWith(SpringRunner.class) @SpringBootTest public class TestPost { @Autowired private RestTemplate restTemplate; @Test public void testPost() { RequestObj requestObj = RequestObj.builder().id(1) .age(20) .name("鷓鴣哨").build(); String url = "http://localhost:8080/test"; //發起請求 String jsonResult = restTemplate.postForObject(url, requestObj, String.class); System.out.println("result:" + jsonResult); } }
#關於Feign
介紹
Feign被廣泛應用在Spring Cloud 的解決方案中,是學習基於Spring Cloud 微服務架構不可或缺的重要組件。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
<!-- feign底層采用的http請求方式 不加則默認使用JDK的HttpURLConnection -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
</dependencies>
Feign配置
@Configuration public class FeignConfig { @Bean Logger.Level feignLoggerLevel() { //記錄請求和響應的標頭,正文和元數據 return Logger.Level.FULL; } /** * 如果遠程接口由於各種問題沒有在響應中設置content-type, * 導致FeignClient接收的時候contentType為null,HttpMessageConverterExtractor將其設置為MediaType.APPLICATION_OCTET_STREAM * 此時MessageConverter需要增加MediaType.APPLICATION_OCTET_STREAM支持 */ @Bean public Decoder feignDecoder() { MappingJackson2HttpMessageConverter hmc = new MappingJackson2HttpMessageConverter(customObjectMapper()); List<MediaType> unModifiedMediaTypeList = hmc.getSupportedMediaTypes(); List<MediaType> mediaTypeList = new ArrayList<>(unModifiedMediaTypeList.size() + 1); mediaTypeList.addAll(unModifiedMediaTypeList); mediaTypeList.add(MediaType.APPLICATION_OCTET_STREAM); hmc.setSupportedMediaTypes(mediaTypeList); ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(hmc); return new ResponseEntityDecoder(new SpringDecoder(objectFactory)); }
@Bean public ObjectMapper customObjectMapper() {
//解決LocalDate、LocalDateTime反序列化問題
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
return new ObjectMapper(); } }
啟用feign客戶端
@SpringBootApplication @EnableFeignClients public class DemoHttpApplication { public static void main(String[] args) { SpringApplication.run(DemoHttpApplication.class, args); } }
定義feign客戶端
@FeignClient(name = "test-service", path = "/", url = "http://localhost:8080") public interface TestClient { @GetMapping("/welcome") String welcome(@RequestParam String message); @PostMapping("/test") String test(@RequestBody RequestObj param); }
測試調用
@RunWith(SpringRunner.class) @SpringBootTest public class FeignTest { @Autowired private TestClient testClient; @Test public void testGet() { System.out.println("result:" + testClient.welcome("world")); } @Test public void testPost() { RequestObj requestObj = RequestObj.builder().id(1) .age(20) .name("鷓鴣哨").build(); System.out.println("result:" + testClient.test(requestObj)); } }