Java 架構師眼中的 HTTP 協議

HTTP 協議的內容比較多,本文咱們將分六部分來介紹。javascript

HTTP 協議的基本內容

什麼是 HTTP 協議

首先咱們來看協議是什麼?協議是指計算機通訊網絡中兩臺計算機之間進行通訊所必須共同遵照有規則的文本格式。一但有了協議,就可使不少公司分工起來,有些公司作 Server 端,如 Tomcat,而有些公司就能夠作瀏覽器了。這樣你們只要一套約定,彼此的通信就會相互兼容。css

接下來咱們看什麼是 HTTP?HTTP 是基於 TCP/IP 的應用層通訊協議,它是客戶端和服務器之間相互通訊的標準。它規定了如何在互聯網上請求和傳輸內容。經過應用層協議,個人意思是,它只是一個規範了主機(客戶端和服務器)如何通訊的抽象層,而且它自己依賴於 TCP/IP 來獲取客戶端和服務器之間的請求和響應。默認的 TCP 端口是80端口,固然,使用其餘端口也是能夠的。然而,HTTPS 使用的端口是443端口。html

HTTP 協議的簡單歷史

http 協議歷史

根據上圖,咱們可將 HTTP 協議的發展歷程分爲五個階段。java

第一階段,1996年以前。初版的 HTTP 文檔是1991年提出來的 HTTP/0.9,其主要特色有:(1)它僅有一個 GET 方法。(2)沒有 header 數據塊。(3)必須以HTML格式響應。git

第二階段,HTTP/1.0 - 1996。HTML 格式響應,HTTP/1.0 可以處理其餘的響應格式,例如:圖像、視頻文件、純文本或其餘任何的內容類型(Content-Type 來區分)。它增長了更多的方法(即 POST 和 HEAD),請求/響應的格式也發生了改變,請求和響應中均加入了 HTTP 頭信息,響應數據還增長了狀態碼標識,還介紹了字符集的支持、多部分發送、權限、緩存、內容編碼等不少內容。HTTP/1.0 的主要缺點之一是,你不能在每一個鏈接中發送多個請求。也就是說,每當客戶端要向服務器端請求東西時,它都會打開一個新的 TCP 鏈接,而且在這個單獨請求完成後,該鏈接就會被關閉。每一次鏈接裏面都包含了著名的三次握手協議。因而有些 HTTP/1.0 的實現試圖經過引入一個新的頭信息 Connection: keep-alive,來解決這個問題。web

第三個階段,HTTP/1.1 - 1999。HTTP/1.0 發佈以後,隨着 HTTP 開始普及以後,它的缺點也開始展示。時隔三年,HTTP/1.1 便在1999年問世,它在以前的基礎上作了不少的改進。主要內容包含:面試

  • 新增的 HTTP 方法有 PUT、PATCH、HEAD、OPTIONS、DELETE。
  • 主機名標識。在 HTTP/1.0 中,Host 頭信息不是必須項,但 HTTP/1.1 中要求必需要有 Host 頭信息。
  • 持久性鏈接。正如前面所說,在 HTTP/1.0 中每一個鏈接只有一個請求,且在這個請求完成後該鏈接就會被關閉,從而會致使嚴重的性能降低及延遲問題。HTTP/1.1 引入了對持久性鏈接的支持,例如:默認狀況下鏈接不會被關閉,在多個連續的請求下它會保存鏈接的打開狀態。想要關閉這些鏈接,須要將 Connection: close 加入到請求的頭信息中。客戶端一般會在最後一次請求中發送這個頭信息用來安全的關閉鏈接。
  • 管道機制。HTTP/1.1 也引入了對管道機制的支持,客戶端能夠向服務器發送多個請求,而無需等待來自同一鏈接上的服務器響應,而且當收到請求時服務器必須以相同的順序來響應。但你可能會問客戶端是怎麼知道第一個響應下載完成和下一個響應內容開始的?要解決這個問題,必需要有 Content-Length 頭信息,客戶端能夠用它來肯定響應結束,而後開始等待下一個響應。

第四個階段,SPDY - 2009。Google 走在前面,它開始試驗一種可替換的協議來減小網頁的延遲,使得網頁加載更快、提高 Web 安全性。2009年,他們稱這種協議爲 SPDY。SPDY 的功能包含多路複用、壓縮、優先級、安全等。2015年,谷歌不想存在兩個相互競爭的標準,所以他們決定把它合併到 HTTP 中成爲 HTTP/2,同時放棄 SPDY。spring

第五個階段,HTTP/2 - 2015。HTTP/2 是專爲低延遲傳輸的內容而設計。關鍵特徵或與 HTTP / 1.1 舊版本的差別,以下。sql

  • 二進制協議。HTTP/2 傾向於使用二進制協議來減小 HTTP/1.x 中的延遲。二進制協議更容易解析,而不具備像 HTTP/1.x 中那樣對人的可讀性。HTTP/2 中的數據塊是幀和流。
    幀和流:

HTTP 消息是由一個或多個幀組成的。有一個叫作 HEADERS 的幀存放元數據,真正的數據是放在 DATA 幀中的,幀類型定義在the HTTP/2 specs(HTTP/2規範),如 HEADERS、DATA、RST_STREAM、SETTINGS、PRIORITY 等。每一個 HTTP/2 請求和響應都被賦予一個惟一的流 ID 且放入了幀中。幀就是一塊二進制數據。一系列幀的集合就稱爲流。每一個幀都有一個流 id,用於標識它屬於哪個流,每個幀都有相同的頭。同時,除了流標識是惟一的,值得一提的是,客戶端發起的任何請求都使用奇數和服務器的響應是偶數的流 id。除了 HEADERS 和 DATA, 另一個值得說一說幀類型是 RST_STREAM,它是一個特殊的幀類型,用於停止流,如客戶端發送這兒幀來告訴服務器我再也不須要這個流了。在 HTTP/1.1 中只有一種方式來實現服務器中止發送響應給客戶端,那就是關閉鏈接引發延遲增長,由於後續的請求就須要打開一個新的鏈接。 在 HTTP/2 中,客戶端可使用 RST_FRAME 來中止接收指定的流而不關閉鏈接且還能夠在此鏈接中接收其它流。apache

  • 多路複用。因爲 HTTP/2 如今是一個二進制協議,且是使用幀和流來實現請求和響應,一旦 TCP 鏈接打開了,全部的流都經過這一鏈接來進行異步的發送而不須要打開額外的鏈接。反過來,服務器的響應也是異步的方式,如響應是無序的、客戶端使用流 id 來標識屬於流的包。這就解決了存在於 HTTP/1.x 中 head-of-line 阻塞問題,如客戶端將沒必要耗時等待請求,而其餘請求將被處理。以下圖所示。

http2.0 Multiplexing

  • HPACK 頭部壓縮。它是一個單獨的用於明確優化發送 Header RFC 的一部分。它的本質是,當咱們同一個客戶端不斷的訪問服務器時,在 header 中發送不少冗餘的數據,有時 cookie 就增大 header,且消耗帶寬和增長了延遲。爲了解決這個問題, HTTP/2 引入了頭部壓縮。與請求和響應不一樣,header 不是使用 gzip 或 compress 等壓縮格式,它有不一樣的機制,它使用了霍夫曼編碼和在客戶端和服務器維護的頭部表來消除重複的 headers(如 User Agent),在後續的請求中就只使用頭部表中引用。它與 HTTP/1.1 中的同樣,不過增長了僞 header,如 :method、:scheme、:host 和:path。

  • 服務器推送。在服務器端,Server Push 是 HTTTP/2 的另一個重要功能,咱們知道,客戶端是經過請求來獲取資源的,它能夠經過推送資源給客戶端而不需客戶端主動請求。例如,瀏覽器載入了一個頁面,瀏覽器解析頁面時發現了須要從服務器端載入的內容,接着它就發送一個請求來獲取這些內容。Server Push容許服務器推送數據來減小客戶端請求。它是如何實現的呢,服務器在一個新的流中發送一個特殊的幀 PUSH_PROMISE,來通知客戶端:「嘿,我要把這個資源發給你!你就不要請求了。」

  • 請求優先級。客戶端能夠在一個打開的流中在流的 HEADERS 幀中放入優先級信息。在任什麼時候間,客戶端均可以發送一個 PRIORITY 的幀來改變流的優先級。若是沒有優先級信息,服務器就會異步的處理請求,好比無序處理。若是流被賦予了優先級,它就會基於這個優先級來處理,由服務器決定須要多少資源來處理該請求。

  • 安全。你們對 HTTP/2 是否強制使用安全鏈接(經過 TLS)進行了充分的討論。最後的決定是不強制使用。然而,大多數廠商表示,他們將只支持基於 TLS 的 HTTP/2。因此,儘管 HTTP/2 規範不須要加密,但它已經成爲默認的強制執行的。在這種狀況下,基於 TLS 實現的 HTTP/2 須要的 TLS 版本最低要求是1.2。 所以必須有最低限度的密鑰長度、臨時密鑰等。

經過開發者工具咱們看一下Google的請求協議。

enter image description here

而咱們大多數的網站的協議的版本都是 HTTP 1.1。

enter image description here

HTTP 協議的具體內容

而咱們平時老生常談的 HTTP 的協議大都是指的是 HTTP 1.1 協議的內容,接下去咱們一塊兒看一下 HTTP 1.1 協議的結構。以下圖所示。enter image description here

接下來,我將經過四部分大概介紹一下 HTTP 協議的基本內容。

1.URL & URI

enter image description here

schema://host[:port#]/path/.../[;url-params][?query-string][#anchor] 

URL(Uniform Resource Locator)主要包括如下幾部分。

  • scheme:指定低層使用的協議,通常是 HTTP,若是強調安全的話能夠是 HTTPS。
  • host:HTTP 服務器的 IP 地址或者域名。
  • port:HTTP 服務器的默認端口是80,這種狀況下端口號能夠省略。若是使用了別的端口,必須指明。
  • path:訪問資源的路徑。
  • url-params:URL 的參數。
  • query-string:發送給 HTTP 服務器的數據。
  • anchor:錨。

URI,在 Java 的 Servlet 中指的是 resource path 部分。

2.請求方法 Method

主要包括如下幾種請求方法。

  • GET:向指定的資源發出「顯示」請求。使用 GET 方法應該只用在讀取數據,而不該當被用於產生「反作用」的操做中,例如在 Web Application 中。其中一個緣由是 GET 可能會被網絡蜘蛛等隨意訪問。
  • POST:向指定資源提交數據,請求服務器進行處理(例如提交表單或者上傳文件)。數據被包含在請求本文中。這個請求可能會建立新的資源或修改現有資源,或兩者皆有。
  • PUT:向指定資源位置上傳其最新內容。
  • DELETE:請求服務器刪除 Request-URI 所標識的資源。
  • OPTIONS:這個方法可以使服務器傳回該資源所支持的全部 HTTP 請求方法。用「*」來代替資源名稱,向 Web 服務器發送 OPTIONS 請求,能夠測試服務器功能是否正常運做。
  • HEAD:與 GET 方法同樣,都是向服務器發出指定資源的請求。只不過服務器將不傳回資源的本文部分。它的好處在於,使用這個方法能夠在沒必要傳輸所有內容的狀況下,就能夠獲取其中「關於該資源的信息」(元信息或稱元數據)。
  • TRACE:回顯服務器收到的請求,主要用於測試或診斷。
  • CONNECT:HTTP/1.1 協議中預留給可以將鏈接改成渠道方式的代理服務器。一般用於 SSL 加密服務器的連接(經由非加密的 HTTP 代理服務器)。

Method 名稱是區分大小寫的。當某個請求所針對的資源不支持對應的請求方法的時候,服務器應當返回狀態碼 405(Method Not Allowed),當服務器不認識或者不支持對應的請求方法的時候,應當返回狀態碼 501(Not Implemented)。

3.HTTP 之狀態碼

狀態代碼有三位數字組成,第一個數字定義了響應的類別,共分五種類別:

  • 1xx:指示信息--表示請求已接收,繼續處理。
  • 2xx:成功--表示請求已被成功接收、理解、接受。
  • 3xx:重定向--要完成請求必須進行更進一步的操做。
  • 4xx:客戶端錯誤--請求有語法錯誤或請求沒法實現。
  • 5xx:服務器端錯誤--服務器未能實現合法的請求。

常見狀態碼有:

200 OK //客戶端請求成功 400 Bad Request //客戶端請求有語法錯誤,不能被服務器所理解 401 Unauthorized //請求未經受權,這個狀態代碼必須和WWW-Authenticate報頭域一塊兒使用 403 Forbidden //服務器收到請求,可是拒絕提供服務 404 Not Found //請求資源不存在,eg:輸入了錯誤的URL 500 Internal Server Error //服務器發生不可預期的錯誤 503 Server Unavailable //服務器當前不能處理客戶端的請求,一段時間後可能恢復正常 

4.請求體&響應體

請求體&響應體,這個沒有特殊規定,須要配合不一樣的 Content-Type 來使用。

惟一須要注意的是 multipart/form-data、application/x-www-from-urlencoded、raw、binary 的區別。

(1)multipart/form-data

它將表單的數據組織成 Key-Value 形式,用分隔符 boundary(boundary 可任意設置)處理成一條消息。因爲有 boundary 隔離,因此立即上傳文件,又有參數的時候,必需要用這種 content-type 類型。以下圖所示。

enter image description here

(2)x-www-form-urlencoded

即 application/x-www-from-urlencoded,將表單內的數據轉換爲 Key-Value。這種和 Get 方法把參數放在 URL 後面同樣的想過,這種不能文件上傳。

enter image description here

(3)raw

能夠上傳任意格式的「文本」,能夠上傳 Text、JSON、XML、HTML 等。

enter image description here

(4)binary

即 Content-Type:application/octet-stream,只能夠上傳二進制數據流,一般用來上傳文件。因爲沒有鍵值,因此一次只能上傳一個文件。

(5)Header

enter image description here

HTTP 消息的 Headers 共分爲三種,分別是 General Headers、Entity Headers、Request/Response Headers。

  • General Headers

我把被 Request 和 Response 共享的 Headers 成爲General Headers,具體有:

general-header = Cache-Control | Connection | Date | Pragma | Trailer | Transfer-Encoding | Upgrade | Via | Warning 

其中,Cache-Control 指定請求和響應遵循的緩存機制;Connection 容許客戶端和服務器指定與請求/響應鏈接有關的選項;Date 提供日期和時間標誌,說明報文是什麼時間建立的;Pragma 頭域用來包含實現特定的指令,最經常使用的是 Pragma:no-cache;Trailer,若是報文采用了分塊傳輸編碼(chunked transfer encoding) 方式,就能夠用這個首部列出位於報文拖掛(trailer)部分的首部集合;Transfer-Encoding 告知接收端爲了保證報文的可靠傳輸,對報文采用了什麼編碼方式;Upgrade 給出了發送端可能想要「升級」使用的新版本和協議;Via 顯示了報文通過的中間節點(代理,網嘎un)。

  • Entity Headers

Entity Headers 主要用來描述消息體(message body)的一些元信息,具體有:

entity-header  = Allow                   
               | Content-Encoding | Content-Language | Content-Length | Content-Location | Content-MD5 | Content-Range | Content-Type | Expires | Last-Modified 

其中,以 Content 爲前綴的 Headers 主要描述了消息體的結構、大小、編碼等信息,Expires 描述了 Entity 的過時時間,Last-Modified 描述了消息的最後修改時間。

  • Request/Response Headers

Request-Line 是 Request 消息體的第一部分,其具體定義以下:

Request-Line = Method SP URI SP HTTP-Version CRLF
Method = "OPTIONS" | "HEAD" | "GET" | "POST" | "PUT" | "DELETE" | "TRACE" 

其中 SP 表明字段的分隔符,HTTP-Version 通常就是"http/1.1",後面緊接着是一個換行。

在 Request-Line 後面緊跟着的就是 Headers。咱們在上面已經介紹了 General Headers 和 Entity Headers,下面即是 Request Headers的定義。

request-header = Accept                   
               | Accept-Charset | Accept-Encoding | Accept-Language | Authorization | Expect | From | Host | If-Match | If-Modified-Since | If-None-Match | If-Range | If-Unmodified-Since | Max-Forwards | Proxy-Authorization | Range | Referer | TE | User-Agent 

Request Headers 扮演的角色其實就是一個 Request 消息的調節器。須要注意的是若一個 Headers 名稱不在上面列表中,則默認當作 Entity Headers 的字段。前綴爲 Accept 的 Headers 定義了客戶端能夠接受的媒介類型、語言和字符集等。From、Host、Referer 和 User-Agent 詳細定義了客戶端如何初始化 Request。前綴爲 If 的 Headers 規定了服務器只能返回符合這些描述的資源,若不符合,則會返回 304 Not Modified。

Request Body,若 Request-Line 中的 Method 爲 GET,請求中不包含消息體,若爲 POST,則會包含消息體。

一個具體的 Request 消息實例,以下。

GET /articles/http-basics HTTP/1.1 Host: www.articles.com Connection: keep-alive Cache-Control: no-cache Pragma: no-cache Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 
  • Response 消息體

Response 消息格式和 Request 相似,也分爲三部分,即 Response-Line、Response Headers、Response Body。

Response-Line 具體定義以下:

Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
HTTP-Version字段值通常爲HTTP/1.1
Status-Code前面已經討論過了
Reason-Phrase 是對status code的具體描述

一個最多見的 Response 響應爲:

HTTP/1.1 200 OK 

Response Headers的定義以下。

response-header = Accept-Ranges
                | Age | ETag | Location | Proxy-Authenticate | Retry-After | Server | Vary | WWW-Authenticate 

其中,Age 表示消息自 server 生成到如今的時長,單位是秒;ETag 是對 Entity 進行 MD5 hash 運算的值,用來檢測更改;Location 是被重定向的 URL;Server 表示服務器標識。

Http 更加詳細的介紹,請參考這裏 。

架構師關注 HTTP 協議的重點

HTTP 協議內容其實也挺多的,架構師其實也應該有重點,哪些是咱們必須重點關注的,內心要清楚。

採用哪一個 HTTP 協議版本及其 Java 裏面如何配置

1.Tomcat 的原始配置在 server.xml 裏面。

<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol" maxThreads="150" SSLEnabled="true" > <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" /> <SSLHostConfig> <Certificate certificateKeyFile="conf/localhost-rsa-key.pem" certificateFile="conf/localhost-rsa-cert.pem" certificateChainFile="conf/localhost-rsa-chain.pem" type="RSA" /> </SSLHostConfig> </Connector> 

2.Spring boot2.0 項目的配置方法。

只須要在 properties 裏面選擇以下配置便可。

server.ssl.enabled=true server.ssl.****//等等 server.http2.enabled=true server.tomcat.protocol-header-https-value=https 

須要注意的是通常 HTTP 2的使用都要伴隨着證書。詳細的配置直接參考 ServerProperties 類裏面的具體描述便可。

HTTPS 配置注意事項

通常不多針對單個項目去設置的,通常都是經過開源的雲如 ali、asw裏面的 SLB 配置 HTTP2和證書,在網關那層統一作掉。客戶端通常是各個瀏覽器或者 App 的瀏覽器內核庫來支持的。其實也不多須要開發來關心具體如何按照 HTTP2 來實現一些代碼邏輯。

緩存機制 HTTP 緩存

1.如何緩存

下降網絡上發送 HTTP 請求的次數,這裏採用「過時」機制。

HTTP 服務器經過兩種實體頭(Entity-Header)來實現「過時」機制:Expires 頭和 Cache-Control 頭的 max-age 子項。

Expires/Cache-Control 控制瀏覽器是否直接從瀏覽器緩存取數據仍是從新發請求到服務器取數據。只是 Cache-Control 比 Expires 能夠控制的多一些,並且 Cache-Control 會重寫 Expires 的規則。

下降網絡上完整回覆 HTTP 請求包的次數,這裏採用「確證」機制。

HTTP服務器經過兩種方式實現「確證」機制:ETag 以及 Last-Modified。

2.相關的 Header

主要包括如下幾個。

  • Cache-Control

經常使用的值有:

(1)max-age(單位爲 s)指定設置緩存最大的有效時間,定義的是時間長短。當瀏覽器向服務器發送請求後,在 max-age 這段時間裏瀏覽器就不會再向服務器發送請求了。 (2)s-maxage(單位爲 s)同 max-age,只用於共享緩存(好比 CDN 緩存),也就是說 max-age 用於普通緩存,而 s-maxage 用於代理緩存。若是存在 s-maxage,則會覆蓋掉 max-age 和 Expires header。 (3)public 指定響應會被緩存,而且在多用戶間共享。若是沒有指定 public 仍是 private,則默認爲 public。 (4)private 響應只做爲私有的緩存,不能在用戶間共享。若是要求 HTTP 認證,響應會自動設置爲 private。 (5)no-cache 指定不緩存響應,代表資源不進行緩存,好比,設置了 no-cache 以後並不表明瀏覽器不緩存,而是在緩存前要向服務器確認資源是否被更改。所以有的時候只設置 no-cache 防止緩存仍是不夠保險,還能夠加上 private 指令,將過時時間設爲過去的時間。 (6)no-store 表示絕對禁止緩存。一看就知道,若是用了這個命令,固然就是不會進行緩存啦!每次請求資源都要從服務器從新獲取。 (7)must-revalidate 指定若是頁面是過時的,則去服務器進行獲取。這個指令並不經常使用,就不作過多的討論了。

  • Expires

緩存過時時間,用來指定資源到期的時間,是服務器端的具體時間點。也就是說,Expires=max-age + 請求時間,須要和 Last-modified 結合使用。但在上面咱們提到過 cache-control 的優先級更高。Expires 是 Web 服務器響應消息頭字段,在響應 HTTP 請求時告訴瀏覽器在過時時間前瀏覽器能夠直接從瀏覽器緩存取數據,而無需再次請求。

  • Last-modified

服務器端文件的最後修改時間,須要和 cache-control 共同使用,是檢查服務器端資源是否更新的一種方式。當瀏覽器再次進行請求時,會向服務器傳送 If-Modified-Since 報頭,詢問 Last-Modified 時間點以後資源是否被修改過。若是沒有修改,則返回碼爲304,使用緩存;若是修改過,則再次去服務器請求資源,返回碼和首次請求相同爲200,資源爲服務器最新資源。

  • Etag

根據實體內容生成一段 hash 字符串,標識資源的狀態,由服務端產生。瀏覽器會將這串字符串傳回服務器,驗證資源是否已經修改。

爲何要使用 Etag 呢?Etag 主要爲了解決 Last-Modified 沒法解決的一些問題。

一些文件也許會週期性的更改,可是它的內容並不改變(僅僅改變的修改時間),這個時候咱們並不但願客戶端認爲這個文件被修改了,而從新 Get。

某些文件修改很是頻繁,好比在秒如下的時間內進行修改(比方說1s內修改了 N 次),If-Modified-Since 能檢查到的粒度是 s 級的,這種修改沒法判斷(或者說 UNIX 記錄 MTIME 只能精確到秒)。

某些服務器不能精確的獲得文件的最後修改時間。

緩存過程以下圖所示。
enter image description here

Session 與 Cookie 必知必會

很好的解決了 HTTP 通信中狀態問題,但其自己也存在一些問題,好比:

  • 客戶端存儲,可能會被修改或刪除。
  • 發送請求時,Cookie 會被一塊兒發送到服務器,當 Cookie 數據量較大時也會帶來額外的請求數據量。
  • 客戶端對 Cookie 數量及大小有必定的限制,Session 解決了 Cookie 的一些缺點。Session 一樣是爲了記錄用戶狀態,對於每一個用戶來講都會有相應的一個狀態值保存在服務器中,而只在客戶端記錄一個 sessionID 用於區分是哪一個用戶的 Session。

與 Cookie 相比,Session有必定的優點,如:

  • Session 值存儲在服務器,相對來講更安全。
  • 客戶端發送給服務器的只有一個 sessionID,數據量更小。Session一樣須要在客戶端存儲一個 sessionID。能夠這個值存儲在 Cookie,每次發送請求時經過 Cookie 請求頭將其發送到服務器;也能夠不使用 Cookie,而將 sessionID 做爲一個額外的請求參數,經過 URL 或請求體發送到服務器。

基於 Cookie 實現 Session 的實現原理以下圖的示。

enter image description here

由上可見,基於 Cookie 實現 Session 時,其本質上仍是在客戶端保存一個 Cookie 值。這個值就是 sessionID,sessionID 的名稱也可按須要設置,爲保存安全,其值也可能會在服務器端作加密處理。服務器在收到 sessionID 後,就能夠對其解密及查找對應的用戶信息等。

協議格式如何統一(見文章後面內容)

Spring 對 HTTP 協議的支持

我爲何想提一下這個呢,我看到太多的開發者遇到 HTTP 協議都喜歡自定義變量,自定義類,其實徹底沒有必要。且看下面的分析。

Spring MVC Web

在 spring-web**.jar 裏面咱們能夠找到以下幾個類:

enter image description here

須要咱們重點關注的有 HttpStatus、MediaType等。

enter image description here

Spring web bind中對應的註解,以下圖所示。

enter image description here

上面是一些主要的(須要注意的是 @RequestParam、@PostMapping、@****Mapping 與咱們上面的 HttpMethod 相對應。而裏面還有 Headers、Consumes 和 produces 等參數來肯定一些 Mapping 的條件),下面的能夠根據須要查看源碼裏面有哪些註解。

enter image description here

Spring Data Rest

Spring Data Rest 是基於 Spring Data repositories,分析實體之間的關係。爲咱們生成Hypermedia API(HATEOAS)風格的Http Restful API接口。

Spring Data REST經過構建在Spring Data repositories之上,自動將其導出爲REST資源的api,減小了大量重複代碼和無聊的樣板代碼。它利用超媒體來容許客戶端查找存儲庫暴露的功能,並將這些資源自動集成到相關的超媒體功能中。

Spring Data REST自己就是一個Spring MVC應用程序,它的設計方式應該是儘量少的集成到現有的Spring MVC應用程序中。現有的(或未來的)服務層能夠與Spring Data REST一塊兒運行,只有較小的考慮。

Spring RestTemplate 的實際使用

Spring RestTemplate 是 Spring 提供的用於訪問 Rest 服務的客戶端,RestTemplate 提供了多種便捷訪問遠程 HTTP 服務的方法,可以大大提升客戶端的編寫效率。

簡單例子,以下。

RestTemplate restTemplate = new RestTemplate(); String fooResourceUrl = "http://localhost:8080/spring-rest/foos"; ResponseEntity<String> response = restTemplate.getForEntity(fooResourceUrl + "/1", String.class); assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); 

更詳盡例子,以下。

/** * 發送一個get請求,並接受封裝成map */ @Test public void restTemplateMap() { RestTemplate restTemplate = new RestTemplate(); Map map=restTemplate.getForObject("https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=ACCESS_TOKEN",Map.class); System.out.println(map.get("errmsg")); } /** * 發送一個get請求,並接受封裝成string */ @Test public void restTemplateString() { RestTemplate restTemplate = new RestTemplate(); String str=restTemplate.getForObject("https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=ACCESS_TOKEN",String.class); System.out.println(str); } /** * 添加消息頭 */ @Test public void httpHeaders() { final String uri = "https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=ACCESS_TOKEN"; RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); HttpEntity<String> entity = new HttpEntity<String>("parameters", headers); ResponseEntity<String> result = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class); System.out.println(result); } 

咱們在實際工做中會覆蓋默認的 RestTemplate。

/** * 替代默認的SimpleClientHttpRequestFactory * 設置超時時間重試次數 * 還能夠設置一些攔截器以便監控 * * @return */ @Bean public RestTemplate restTemplate() { //生成一個設置了鏈接超時時間、請求超時時間、異常重試次數3次 RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build(); //實際工做中,這個地方還會加上filter來抓取每次restTemplate的日誌信息。 HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false)); HttpClient httpClient = builder.build(); ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); RestTemplate restTemplate = new RestTemplate(requestFactory); return restTemplate; } 

使用的地方會變成以下便可。

@Autowired public RestTemplate restTemplate; 

Spring Data Jpa

隨着使用 Spring Data Jpa 的人愈來愈多,它裏面也對 Spring Web 作了很好的支持。

enter image description here

咱們能夠重點看一下 Pageable 和 Page,以及 PageImpl 和 PageRequest 對分頁和排序作了很好的封裝,及其返回的 JSON 格式也作了很好的約定。

Spring Cloud 中的知識點

Spring Cloud 的微服務管理都是基於 HTTP 協議的 Rest 風格的 API 來管理的,因此咱們詳細瞭解 HTTP 協議仍是很是有必要的。

enter image description here

JSON API

咱們都知道了約定的好處。若是你和你的團隊曾經爭論過使用什麼方式構建合理 JSON 響應格式, 那麼 JSON API 就是你的 anti-bikeshedding 武器。

經過遵循共同的約定,能夠提升開發效率,利用更廣泛的工具,能夠是你更加專一於開發重點:你的程序。

點擊這裏,訪問 JSON API 官方。

(1)JSON API 介紹

JSON API 是數據交互規範,用以定義客戶端如何獲取與修改資源,以及服務器如何響應對應請求。

JSON API 設計用來最小化請求的數量,以及客戶端與服務器間傳輸的數據量。在高效實現的同時,無需犧牲可讀性、靈活性和可發現性。

JSON API 須要使用 JSON API 媒體類型(application/vnd.api+json)進行數據交互。

JSON API 服務器支持經過 GET 方法獲取資源。並且必須獨立實現 HTTP POST,PUT 和 DELETE 方法的請求響應,以支持資源的建立、更新和刪除。

JSON API 服務器也能夠選擇性支持 HTTP PATCH 方法 [RFC5789]和 JSON Patch 格式 [RFC6902],進行資源修改。JSON Patch 支持是可行的,由於理論上來講,JSON API 經過單一 JSON 文檔,反映域下的全部資源,並將 JSON 文檔做爲資源操做介質。在文檔頂層,依據資源類型分組。每一個資源都經過文檔下的惟一路徑辨識。

(2)規則約定

文檔中的關鍵字MUST、MUST NOT、REQUIRED、SHALL、SHALL NOT、SHOULD、 SHOULD NOT、RECOMMENDED、MAY 和 OPTIONAL。依據 RFC 2119 [RFC2119] 規範解釋。

(3)內容約定

客戶端職責,即客戶端必須在包含 Content-Type: application/vnd.api+json 頭而且不包含媒體類型參數的請求文檔中發送全部 JSON API 數據。在 Accept 頭中包含 JSON API 媒體類型而且不包含媒體類型參數的客戶端必須在 Accept 頭中指定媒體類型至少一次。

客戶端必須忽略任何從響應文檔的 Content-Type 頭中獲取的 application/vnd.api+json 媒體類型參數。

服務器職責,即服務器必須在包含 Content-Type: application/vnd.api+json 頭而且不包含媒體類型參數的請求文檔中發送全部 JSON API 數據。若是接收到一個用任何媒體類型參數指定 Content-Type: application/vnd.api+json 頭的請求,服務器必須返回一個 415 Unsupported Media Type 狀態碼響應。若是接收到一個在 Accept 頭中包含任何 JSON API 媒體類型而且全部實體都以媒體類型參數更改的請求,服務器必須返回一個 406 Not Acceptable狀態碼響應。

(4)文檔結構

咱們將描述 JSON API 文檔結構 ,經過媒體類型 application/vnd.api+json 標示。JSON API 文檔使用 JavaScript 對象(JSON)[RFC4627]定義。

儘管同種媒體類型用以請求和響應文檔,但某些特性只適用於其中一種。差別在下面呈現。

除非另有說明,根據本規範定義的對象都不該該包含任何其餘鍵。客戶端和服務器實現必須忽略本規範未指定的鍵。

(5)Top Level

JSON 對象必須位於每一個 JSON API 文檔的根級。這個對象定義文檔的「top level」。

文檔必須包含如下至少一種 top-level 鍵。

  • data: 文檔的」primary data」。
  • errors: 錯誤對象列表。
  • meta: 包含非標準元信息的元對象。

data 鍵和 errors 鍵不能再一個文檔中同時存在。

文檔可能包含如下任何 top-level 鍵。

  • jsonapi: 描述服務器實現的對象。
  • links: 與primary data相關的連接對象。
  • include: 與 primary data 或其餘資源相關的資源對象(included resources)列。

若是文檔不包含 top-level data 鍵,included 鍵也不該該出現。

文檔的 top-level 連接對象可能包含如下鍵。

  • self: 生成當前響應文檔的連接。
  • related: 當primary data表明資源關係時,表示相關資源連接。
  • Primary data的分頁連接。

文檔中的「primary data」表明一個請求所要求的資源或資源集合。

Primary data 必須是如下列舉的一種。

  • 若是請求要求單一資源,應該是一個單一資源對象,或一個單一資源標識符,或 null。
  • 若是請求要求資源集合,應該是一個資源對象列表,或一個空列表([])。

例如,如下 primary data 表示一個單一資源對象。

{
  "data": { "type": "articles", "id": "1", "attributes": { // ... this article's attributes }, "relationships": { // ... this article's relationships } } } 

如下 primary data 表示一個指向一樣資源的單一資源標識符。

{
  "data": { "type": "articles", "id": "1" } } 

即便只包含一個元素或爲空,資源的一個邏輯集合也必須表示爲一個列表。

資源對象,即 JSON API 文檔中的「Resuorce objects」,表明資源。

一個資源對象必須至少包含如下 top-level 鍵。

  • id
  • `type'

例外,當資源對象來自客戶端而且表明一個將要在服務器建立的新資源時,id鍵不是必須的。

此外,資源對象可能包含如下 top-level 鍵。

  • 'attribute': 屬性對象表明資源的某些數據。
  • relationshiops: 關聯對象描述該資源與其餘JSON API資源之間的關係。
  • links: 連接資源包含與資源相關的連接。
  • meta: 元數據資源包含與資源對象相關的非標準元信息,這些信息不能被做爲屬性或關聯對象表示。

一篇文獻(即一個「文獻」類型的資源)在文檔中這樣表示:

{
  "type": "articles", "id": "1", "attributes": { "title": "Rails is Omakase" }, "relationships": { "author": { "links": { "self": "/articles/1/relationships/author", "related": "/articles/1/author" }, "data": { "type": "people", "id": "9" } } } } 

標識符,即每一個資源對象包含一個 id 鍵和一個 type 鍵。id 鍵和 typ e鍵的值必須是字符串類型。

對於每個既定 API,每一個資源對象的 type 和 id 對必須定義一個單獨且惟一的資源(由一個或多個但行爲表現爲一個服務器的服務器控制的 URI 集合構成一個API)。

type 鍵用於描述共享相同屬性和關聯的資源對象。type鍵的值必須與遵循鍵名稱相同的約束條件。

字段,即資源對象的屬性和關聯被統稱爲「fields」。

一個資源對象的全部字段必須與 type 和 id 在同一命名空間中。即一個資源不能擁有名字相同的屬性與關聯,也不能擁有被命名爲 type 或 id 的屬性和關聯。

屬性,即 attribute,鍵的值必須是一個對象(一個「attributes object」)。屬性對象的鍵(「attributes」)表明與資源對象中定義的與其有關的信息。

屬性能夠包含任何合法 JSON 值。JSON 對象和列表涉及的複雜數據結構能夠做爲屬性的值。可是一個組成或被包含於屬性中的對象不能包含 relationships 或 links 鍵,由於這些鍵爲此規範將來的用途所預留。

雖然一些 has-one 關係的外鍵(例如author_id)被在內部與其餘將要在資源對象中表達的信息一塊兒儲存,可是這些鍵不能做爲屬性出現。

關聯,即relationships,鍵的值必須是一個對象(「relationships object」)。關聯對象(「relationships」)的鍵表示在資源對象中定義的與其相關的其餘資源對象。

關聯能夠是單對象關聯或多對象關聯。

一個「relationship object」必須包含如下至少一種鍵:

  • links: 一個連接對象至少包含如下一種鍵:
    • self: 指向關聯自己的連接(「relationship link」)。此連接容許客戶端直接修改關聯。例如,經過一個 articale 的關聯 URL 移除一個 author 將會解除一我的與 article 的關係,而不須要刪除這個 people 資源自己。獲取成功後,這個連接將返回一個相關資源之間的鏈接,將其做爲 primary data(見獲取關聯)。
    • related: 相關資源連接。
  • data: 資源鏈接。
  • meta: 包含關於此關聯的非標準元信息的元對象。

更多介紹見官方文檔

Yahoo elide 對 JSON API 的支持

點擊這裏,訪問 Yahoo elide官方網站。

1. elide介紹

elide 經過 Spring Data Jpa 的 Entity,加上自定義的 @Include(rootLevel = true) 註解,來完成 JSON API 標準的輸出。

使用方法以下圖所示。

enter image description here

效果以下:

enter image description here

生產環境中 HTTP 協議有哪些架構

RestTemplate 的重試和監控

代碼以下。

@SpringBootApplication public class DubbingApiApplication { /** * 使用全局的RestTemplate * 設置了鏈接超時時間、請求超時時間、異常重試次數3次 * 而且會記錄全部請求的詳細日誌 * * @return */ @Bean public RestTemplate restTemplate() { RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build(); HttpClentBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false)); HttpClient httpClient = builder.build(); ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); RestTemplate restTemplate = new RestTemplate(requestFactory); restTemplate.setInterceptors(Collections.singletonList(new LoggingRestTemplate())); restTemplate.setRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory()); return restTemplate; } public static void main(String[] args) { SpringApplication.run(DubbingApiApplication.class, args); } } 

請求格式和返回結果的格式約定。

1.實現 ResponseBodyAdvice 對 controller 返回的 Result 進行統一的包裝,以下代碼。

public class ElideResponseBodyAdvice implements ResponseBodyAdvice { @Autowired private ElideProperties elideProperties; /** * 配置註解能夠跳過去,類上,方法上都行 * * @param returnType * @param converterType * @return */ @Override public boolean supports(MethodParameter returnType, Class converterType) { if (converterType != null && converterType.isAssignableFrom(StringHttpMessageConverter.class)) { return false; } ElideSkippable elideSkippable = returnType.getMethodAnnotation(ElideSkippable.class); if (elideSkippable == null) { elideSkippable = returnType.getDeclaringClass().getAnnotation(ElideSkippable.class); } return !(elideSkippable != null && elideSkippable.value()); } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { HttpServletRequest httpServletRequest = ((ServletServerHttpRequest) request).getServletRequest(); //only process elide persistable return ResultElideWrapHandler.process(httpServletRequest, body); } } 

2.異常統一格式的處理,返回固定的 error 級別的格式結果,以下代碼。

/** * 框架級別的通用異常處理 */ @ControllerAdvice @Slf4j @Order(4) public class ExceptionAdvice { @ExceptionHandler({BindException.class}) @ResponseBody public JsonApiErrorDocument exception(BindException e, HttpServletResponse response) { log.warn(e.getMessage(), e); ErrorResponse errors = new ErrorResponse(Constant.ERROR_VALIDATION, response.getStatus(), e.getMessage()); errors.setMeta(e.getAllErrors()); return new JsonApiErrorDocument(errors); } } 

對緩存 Etag 的支持。

針對 HTTP 裏 Etag 的支持,也須要咱們框架層面去支持 Etag 緩存,這裏就不給你們貼代碼了,你們能夠思考一下。

微服務中 HTTP 與 RPC 的權衡

HTTP 與 RPC 比較

以下表所示。

比較項 HTTP RPC
微服務治理 Spring Cloud ali dubbo
耦合度 代碼無耦合 service jar依賴
api升級 徹底無影響 須要注意java Serializable的使用
周邊開源監控產品
開發效率 更快
語言要求 能夠不一個體系的語音 只能同一個體系如Java
性能 能夠 很是好
帶寬 http<http1.1<http2.0 更小帶寬

實際工做中建議二者都用,API 對外,Ali Dubbo的 RPC 對內部使用,這樣兩個的優勢都能使用到。

總結:面試中起到的關鍵做用是什麼

若是面試中問到你這個問題,主要的考驗的點有:

  • 思路是否清晰。
  • 實戰解決那些問題如緩存可能會讓你說的很是細。
  • 知識是否全面,及其是否針對一個問題了解的足夠多。

基本上面的東西若是你都能提到,面試官的印象基本上是很是好的,加分會加不少。

交流:歡迎你們留言,把本身碰到的 Java 數據結構問題一塊兒討論。也可加入 QQ 交流羣(羣1:240619787,羣2:559701472)。


本文首發於GitChat,未經受權不得轉載,轉載需與GitChat聯繫。

http://gitbook.cn/books/5a82b227e2f8ea02e7742197/index.html

相關文章
相關標籤/搜索