Java 架构师眼中的 HTTP 协议
本文來自作者 張振華?在 GitChat 上分享 「Java 架構(gòu)師眼中的 HTTP 協(xié)議」,「閱讀原文」查看交流實(shí)錄。
編輯 | 哈比
HTTP 協(xié)議的基本內(nèi)容
· 什么是 HTTP 協(xié)議?
協(xié)議,是指計(jì)算機(jī)通信網(wǎng)絡(luò)中兩臺計(jì)算機(jī)之間進(jìn)行通信所必須共同遵守有規(guī)則的文本格式。
一旦有了協(xié)議,就可以使很多公司分工起來,有些公司做 Server 端,如 Tomcat,而有些公司就可以做瀏覽器了。這樣大家只要一套約定,彼此的通訊就會相互兼容。
什么是 HTTP?
HTTP 是基于 TCP/IP 的應(yīng)用層通信協(xié)議,它是客戶端和服務(wù)器之間相互通信的標(biāo)準(zhǔn)。它規(guī)定了如何在互聯(lián)網(wǎng)上請求和傳輸內(nèi)容。
通過應(yīng)用層協(xié)議,它只是一個規(guī)范了主機(jī)(客戶端和服務(wù)器)如何通信的抽象層,并且它本身依賴于 TCP/IP 來獲取客戶端和服務(wù)器之間的請求和響應(yīng)。
默認(rèn)的 TCP 端口是 80 端口,當(dāng)然,使用其他端口也是可以的。然而,HTTPS 使用的端口是 443 端口。
· HTTP 協(xié)議的簡單歷史
根據(jù)上圖,我們可將 HTTP 協(xié)議的發(fā)展歷程分為五個階段。
第一階段,1996 年之前。
第一版的 HTTP 文檔是 1991 年提出來的 HTTP/0.9,其主要特點(diǎn)有:
(1)它僅有一個 GET 方法。
(2)沒有 header 數(shù)據(jù)塊。
(3)必須以 HTML 格式響應(yīng)。
第二階段,HTTP/1.0 - 1996。
HTML 格式響應(yīng),HTTP/1.0 能夠處理其他的響應(yīng)格式,例如:圖像、視頻文件、純文本或其他任何的內(nèi)容類型(Content-Type 來區(qū)分)。
它增加了更多的方法(即 POST 和 HEAD),請求 / 響應(yīng)的格式也發(fā)生了改變,請求和響應(yīng)中均加入了 HTTP 頭信息。
響應(yīng)數(shù)據(jù)還增加了狀態(tài)碼標(biāo)識,還介紹了字符集的支持、多部分發(fā)送、權(quán)限、緩存、內(nèi)容編碼等很多內(nèi)容。
HTTP/1.0 的主要缺點(diǎn)之一是,你不能在每個連接中發(fā)送多個請求。
也就是說,每當(dāng)客戶端要向服務(wù)器端請求東西時,它都會打開一個新的 TCP 連接,并且在這個單獨(dú)請求完成后,該連接就會被關(guān)閉。
每一次連接里面都包含了著名的三次握手協(xié)議。
于是有些 HTTP/1.0 的實(shí)現(xiàn)試圖通過引入一個新的頭信息 Connection: keep-alive,來解決這個問題。
第三個階段,HTTP/1.1 - 1999。
HTTP/1.0 發(fā)布之后,隨著 HTTP 開始普及之后,它的缺點(diǎn)也開始展現(xiàn)。
時隔三年,HTTP/1.1 便在 1999 年問世,它在之前的基礎(chǔ)上做了很多的改進(jìn)。主要內(nèi)容包含:
新增的 HTTP 方法有 PUT、PATCH、HEAD、OPTIONS、DELETE。
主機(jī)名標(biāo)識。在 HTTP/1.0 中,Host 頭信息不是必須項(xiàng),但 HTTP/1.1 中要求必須要有 Host 頭信息。
持久性連接。正如前面所說,在 HTTP/1.0 中每個連接只有一個請求,且在這個請求完成后該連接就會被關(guān)閉,從而會導(dǎo)致嚴(yán)重的性能下降及延遲問題。HTTP/1.1 引入了對持久性連接的支持,例如:默認(rèn)情況下連接不會被關(guān)閉,在多個連續(xù)的請求下它會保存連接的打開狀態(tài)。想要關(guān)閉這些連接,需要將 Connection: close 加入到請求的頭信息中。客戶端通常會在最后一次請求中發(fā)送這個頭信息用來安全的關(guān)閉連接。
管道機(jī)制。HTTP/1.1 也引入了對管道機(jī)制的支持,客戶端可以向服務(wù)器發(fā)送多個請求,而無需等待來自同一連接上的服務(wù)器響應(yīng),并且當(dāng)收到請求時服務(wù)器必須以相同的順序來響應(yīng)。但你可能會問客戶端是怎么知道第一個響應(yīng)下載完成和下一個響應(yīng)內(nèi)容開始的?要解決這個問題,必須要有 Content-Length 頭信息,客戶端可以用它來確定響應(yīng)結(jié)束,然后開始等待下一個響應(yīng)。
第四個階段,SPDY - 2009。
Google 走在前面,它開始試驗(yàn)一種可替換的協(xié)議來減少網(wǎng)頁的延遲,使得網(wǎng)頁加載更快、提升 Web 安全性。
2009 年,他們稱這種協(xié)議為 SPDY。SPDY 的功能包含多路復(fù)用、壓縮、優(yōu)先級、安全等。
2015 年,谷歌不想存在兩個相互競爭的標(biāo)準(zhǔn),因此他們決定把它合并到 HTTP 中成為 HTTP/2,同時放棄 SPDY。
第五個階段,HTTP/2 - 2015。
HTTP/2 是專為低延遲傳輸?shù)膬?nèi)容而設(shè)計(jì)。關(guān)鍵特征或與 HTTP / 1.1 舊版本的差異如下。
(1)二進(jìn)制協(xié)議。
HTTP/2 傾向于使用二進(jìn)制協(xié)議來減少 HTTP/1.x 中的延遲。二進(jìn)制協(xié)議更容易解析,而不具有像 HTTP/1.x 中那樣對人的可讀性。
HTTP/2 中的數(shù)據(jù)塊是幀和流。
HTTP 消息是由一個或多個幀組成的。有一個叫做 HEADERS 的幀存放元數(shù)據(jù),真正的數(shù)據(jù)是放在 DATA 幀中的。
幀類型定義在 the HTTP/2 specs(HTTP/2 規(guī)范),如 HEADERS、DATA、RST_STREAM、SETTINGS、PRIORITY 等。
每個 HTTP/2 請求和響應(yīng)都被賦予一個唯一的流 ID 且放入了幀中。幀就是一塊二進(jìn)制數(shù)據(jù)。
一系列幀的集合就稱為流。每個幀都有一個流 id,用于標(biāo)識它屬于哪一個流,每一個幀都有相同的頭。
同時,除了流標(biāo)識是唯一的,值得一提的是,客戶端發(fā)起的任何請求都使用奇數(shù)和服務(wù)器的響應(yīng)是偶數(shù)的流 id。
除了 HEADERS 和 DATA, 另外一個值得說一說幀類型是 RST_STREAM,它是一個特殊的幀類型,用于中止流,如客戶端發(fā)送這兒幀來告訴服務(wù)器我不再需要這個流了。
在 HTTP/1.1 中只有一種方式來實(shí)現(xiàn)服務(wù)器停止發(fā)送響應(yīng)給客戶端,那就是關(guān)閉連接引起延遲增加,因?yàn)楹罄m(xù)的請求就需要打開一個新的連接。
在 HTTP/2 中,客戶端可以使用 RST_FRAME 來停止接收指定的流而不關(guān)閉連接且還可以在此連接中接收其它流。
(2)多路復(fù)用。
由于 HTTP/2 現(xiàn)在是一個二進(jìn)制協(xié)議,且是使用幀和流來實(shí)現(xiàn)請求和響應(yīng)。
一旦 TCP 連接打開了,所有的流都通過這一連接來進(jìn)行異步的發(fā)送而不需要打開額外的連接。
反過來,服務(wù)器的響應(yīng)也是異步的方式,如響應(yīng)是無序的、客戶端使用流 id 來標(biāo)識屬于流的包。
這就解決了存在于 HTTP/1.x 中 head-of-line 阻塞問題,如客戶端將不必耗時等待請求,而其他請求將被處理。如下圖所示:
(3)HPACK 頭部壓縮。
它是一個單獨(dú)的用于明確優(yōu)化發(fā)送 Header RFC 的一部分。
它的本質(zhì)是,當(dāng)我們同一個客戶端不斷的訪問服務(wù)器時,在 header 中發(fā)送很多冗余的數(shù)據(jù),有時 cookie 就增大 header,且消耗帶寬和增加了延遲。
為了解決這個問題, HTTP/2 引入了頭部壓縮。與請求和響應(yīng)不同,header 不是使用 gzip 或 compress 等壓縮格式,它有不同的機(jī)制。
它使用了霍夫曼編碼和在客戶端和服務(wù)器維護(hù)的頭部表來消除重復(fù)的 headers(如 User Agent),在后續(xù)的請求中就只使用頭部表中引用。
它與 HTTP/1.1 中的一樣,不過增加了偽 header,如 :method、:scheme、:host 和:path。
(4)服務(wù)器推送。
在服務(wù)器端,Server Push 是 HTTTP/2 的另外一個重要功能。
我們知道,客戶端是通過請求來獲取資源的,它可以通過推送資源給客戶端而不需客戶端主動請求。
例如,瀏覽器載入了一個頁面,瀏覽器解析頁面時發(fā)現(xiàn)了需要從服務(wù)器端載入的內(nèi)容,接著它就發(fā)送一個請求來獲取這些內(nèi)容。
Server Push 允許服務(wù)器推送數(shù)據(jù)來減少客戶端請求。
它是如何實(shí)現(xiàn)的呢,服務(wù)器在一個新的流中發(fā)送一個特殊的幀 PUSH_PROMISE,來通知客戶端:“嘿,我要把這個資源發(fā)給你 ! 你就不要請求了。”
(5)請求優(yōu)先級。
客戶端可以在一個打開的流中在流的 HEADERS 幀中放入優(yōu)先級信息。在任何時間,客戶端都可以發(fā)送一個 PRIORITY 的幀來改變流的優(yōu)先級。
如果沒有優(yōu)先級信息,服務(wù)器就會異步的處理請求,比如無序處理。
如果流被賦予了優(yōu)先級,它就會基于這個優(yōu)先級來處理,由服務(wù)器決定需要多少資源來處理該請求。
(6)安全。
大家對 HTTP/2 是否強(qiáng)制使用安全連接(通過 TLS)進(jìn)行了充分的討論。最后的決定是不強(qiáng)制使用。
然而,大多數(shù)廠商表示,他們將只支持基于 TLS 的 HTTP/2。所以,盡管 HTTP/2 規(guī)范不需要加密,但它已經(jīng)成為默認(rèn)的強(qiáng)制執(zhí)行的。
在這種情況下,基于 TLS 實(shí)現(xiàn)的 HTTP/2 需要的 TLS 版本最低要求是 1.2。 因此必須有最低限度的密鑰長度、臨時密鑰等。
通過開發(fā)者工具我們看一下 Google 的請求協(xié)議。
而我們大多數(shù)的網(wǎng)站的協(xié)議的版本都是 HTTP 1.1。
HTTP 協(xié)議的具體內(nèi)容
而我們平時老生常談的 HTTP 的協(xié)議大都是指的是 HTTP 1.1 協(xié)議的內(nèi)容,接下去我們一起看一下 HTTP 1.1 協(xié)議的結(jié)構(gòu)。如下圖所示。
接下來,我將通過四部分大概介紹一下 HTTP 協(xié)議的基本內(nèi)容。
1.URL & URI
schema://host[:port#]/path/.../[;url-params][?query-string][#anchor]
URL(Uniform Resource Locator)主要包括以下幾部分。
scheme:指定低層使用的協(xié)議,一般是 HTTP,如果強(qiáng)調(diào)安全的話可以是 HTTPS。
host:HTTP 服務(wù)器的 IP 地址或者域名。
port:HTTP 服務(wù)器的默認(rèn)端口是 80,這種情況下端口號可以省略。如果使用了別的端口,必須指明。
path:訪問資源的路徑。
url-params:URL 的參數(shù)。
query-string:發(fā)送給 HTTP 服務(wù)器的數(shù)據(jù)。
anchor:錨。
URI,在 Java 的 Servlet 中指的是 resource path 部分。
2. 請求方法 Method
主要包括以下幾種請求方法。
GET:向指定的資源發(fā)出 “顯示” 請求。使用 GET 方法應(yīng)該只用在讀取數(shù)據(jù),而不應(yīng)當(dāng)被用于產(chǎn)生 “副作用” 的操作中,例如在 Web Application 中。其中一個原因是 GET 可能會被網(wǎng)絡(luò)蜘蛛等隨意訪問。
POST:向指定資源提交數(shù)據(jù),請求服務(wù)器進(jìn)行處理(例如提交表單或者上傳文件)。數(shù)據(jù)被包含在請求本文中。這個請求可能會創(chuàng)建新的資源或修改現(xiàn)有資源,或二者皆有。
PUT:向指定資源位置上傳其最新內(nèi)容。
DELETE:請求服務(wù)器刪除 Request-URI 所標(biāo)識的資源。
OPTIONS:這個方法可使服務(wù)器傳回該資源所支持的所有 HTTP 請求方法。用 “*” 來代替資源名稱,向 Web 服務(wù)器發(fā)送 OPTIONS 請求,可以測試服務(wù)器功能是否正常運(yùn)作。
HEAD:與 GET 方法一樣,都是向服務(wù)器發(fā)出指定資源的請求。只不過服務(wù)器將不傳回資源的本文部分。它的好處在于,使用這個方法可以在不必傳輸全部內(nèi)容的情況下,就可以獲取其中 “關(guān)于該資源的信息”(元信息或稱元數(shù)據(jù))。
TRACE:回顯服務(wù)器收到的請求,主要用于測試或診斷。
CONNECT:HTTP/1.1 協(xié)議中預(yù)留給能夠?qū)⑦B接改為渠道方式的代理服務(wù)器。通常用于 SSL 加密服務(wù)器的鏈接(經(jīng)由非加密的 HTTP 代理服務(wù)器)。
Method 名稱是區(qū)分大小寫的。
當(dāng)某個請求所針對的資源不支持對應(yīng)的請求方法的時候,服務(wù)器應(yīng)當(dāng)返回狀態(tài)碼 405(Method Not Allowed),當(dāng)服務(wù)器不認(rèn)識或者不支持對應(yīng)的請求方法的時候,應(yīng)當(dāng)返回狀態(tài)碼 501(Not Implemented)。
3.HTTP 之狀態(tài)碼
狀態(tài)代碼有三位數(shù)字組成,第一個數(shù)字定義了響應(yīng)的類別,共分五種類別:
1xx:指示信息—表示請求已接收,繼續(xù)處理。
2xx:成功—表示請求已被成功接收、理解、接受。
3xx:重定向—要完成請求必須進(jìn)行更進(jìn)一步的操作。
4xx:客戶端錯誤—請求有語法錯誤或請求無法實(shí)現(xiàn)。
5xx:服務(wù)器端錯誤—服務(wù)器未能實(shí)現(xiàn)合法的請求。
常見狀態(tài)碼有:
200 OK ? ? ? ? ? ? ? ? ? ? ? ?// 客戶端請求成功 400 Bad Request ? ? ? ? ? ? ? // 客戶端請求有語法錯誤,不能被服務(wù)器所理解 401 Unauthorized ? ? ? ? ? ? ?// 請求未經(jīng)授權(quán),這個狀態(tài)代碼必須和 WWW-Authenticate 報(bào)頭域一起使用 403 Forbidden ? ? ? ? ? ? ? ? // 服務(wù)器收到請求,但是拒絕提供服務(wù) 404 Not Found ? ? ? ? ? ? ? ? // 請求資源不存在,eg:輸入了錯誤的 URL 500 Internal Server Error ? ? // 服務(wù)器發(fā)生不可預(yù)期的錯誤 503 Server Unavailable ? ? ? ?// 服務(wù)器當(dāng)前不能處理客戶端的請求,一段時間后可能恢復(fù)正常
4. 請求體 & 響應(yīng)體
請求體 & 響應(yīng)體,這個沒有特殊規(guī)定,需要配合不同的 Content-Type 來使用。
唯一需要注意的是 multipart/form-data、application/x-www-from-urlencoded、raw、binary 的區(qū)別。
(1)multipart/form-data
它將表單的數(shù)據(jù)組織成 Key-Value 形式,用分隔符 boundary(boundary 可任意設(shè)置)處理成一條消息。
由于有 boundary 隔離,所以當(dāng)即上傳文件,又有參數(shù)的時候,必須要用這種 ?content-type 類型。如下圖所示:
(2)x-www-form-urlencoded
即 application/x-www-from-urlencoded,將表單內(nèi)的數(shù)據(jù)轉(zhuǎn)換為 Key-Value。這種和 Get 方法把參數(shù)放在 URL 后面一樣的想過,這種不能文件上傳。
(3)raw
可以上傳任意格式的 “文本”,可以上傳 Text、JSON、XML、HTML 等。
(4)binary
即 Content-Type:application/octet-stream,只可以上傳二進(jìn)制數(shù)據(jù)流,通常用來上傳文件。由于沒有鍵值,所以一次只能上傳一個文件。
(5)Header
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 指定請求和響應(yīng)遵循的緩存機(jī)制;
Connection 允許客戶端和服務(wù)器指定與請求 / 響應(yīng)連接有關(guān)的選項(xiàng);
Date 提供日期和時間標(biāo)志,說明報(bào)文是什么時間創(chuàng)建的;
Pragma 頭域用來包含實(shí)現(xiàn)特定的指令,最常用的是 Pragma:no-cache;
Trailer,如果報(bào)文采用了分塊傳輸編碼 (chunked transfer encoding) 方式,就可以用這個首部列出位于報(bào)文拖掛(trailer)部分的首部集合;
Transfer-Encoding 告知接收端為了保證報(bào)文的可靠傳輸,對報(bào)文采用了什么編碼方式;
Upgrade 給出了發(fā)送端可能想要 “升級” 使用的新版本和協(xié)議;
Via 顯示了報(bào)文經(jīng)過的中間節(jié)點(diǎn)(代理,網(wǎng)嘎 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 主要描述了消息體的結(jié)構(gòu)、大小、編碼等信息,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。我們在上面已經(jīng)介紹了 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 扮演的角色其實(shí)就是一個 Request 消息的調(diào)節(jié)器。
需要注意的是若一個 Headers 名稱不在上面列表中,則默認(rèn)當(dāng)做 Entity Headers 的字段。
前綴為 Accept 的 Headers 定義了客戶端可以接受的媒介類型、語言和字符集等。
From、Host、Referer 和 User-Agent 詳細(xì)定義了客戶端如何初始化 Request。
前綴為 If 的 Headers 規(guī)定了服務(wù)器只能返回符合這些描述的資源,若不符合,則會返回 304 Not Modified。
Request Body,若 Request-Line 中的 Method 為 GET,請求中不包含消息體,若為 POST,則會包含消息體。
一個具體的 Request 消息實(shí)例,如下。
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 前面已經(jīng)討論過了 Reason-Phrase 是對 status code 的具體描述
一個最常見的 Response 響應(yīng)為:
HTTP/1.1 200 OK
Response Headers 的定義如下。
response-header = Accept-Ranges
? ? ? ? ? ? ? | Age
? ? ? ? ? ? ? | ETag
? ? ? ? ? ? ? | Location
? ? ? ? ? ? ? | Proxy-Authenticate
? ? ? ? ? ? ? | Retry-After
? ? ? ? ? ? ? | Server
? ? ? ? ? ? ? | Vary
? ? ? ? ? ? ? | WWW-Authenticate
其中,Age 表示消息自 server 生成到現(xiàn)在的時長,單位是秒;ETag 是對 Entity 進(jìn)行 MD5 hash 運(yùn)算的值,用來檢測更改;Location 是被重定向的 URL;Server 表示服務(wù)器標(biāo)識。
Http 更加詳細(xì)的介紹,請參考?http://www.runoob.com/http/http-status-codes.html。
架構(gòu)師關(guān)注 HTTP 協(xié)議的重點(diǎn)
HTTP 協(xié)議內(nèi)容其實(shí)也挺多的,架構(gòu)師其實(shí)也應(yīng)該有重點(diǎn),哪些是我們必須重點(diǎn)關(guān)注的,心里要清楚。
· 采用哪個 HTTP 協(xié)議版本及其 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 項(xiàng)目的配置方法。
只需要在 properties 里面選擇如下配置即可。
server.ssl.enabled=true server.ssl.****// 等等 server.http2.enabled=true server.tomcat.protocol-header-https-value=https
需要注意的是一般 HTTP 2 的使用都要伴隨著證書。詳細(xì)的配置直接參考 ServerProperties 類里面的具體描述即可。
· HTTPS 配置注意事項(xiàng)
一般很少針對單個項(xiàng)目去設(shè)置的,一般都是通過開源的云如 ali、asw 里面的 SLB 配置 HTTP2 和證書,在網(wǎng)關(guān)那層統(tǒng)一做掉。
客戶端一般是各個瀏覽器或者 App 的瀏覽器內(nèi)核庫來支持的。其實(shí)也很少需要開發(fā)來關(guān)心具體如何按照 HTTP2 來實(shí)現(xiàn)一些代碼邏輯。
· 緩存機(jī)制 HTTP 緩存
1. 如何緩存
降低網(wǎng)絡(luò)上發(fā)送 HTTP 請求的次數(shù),這里采用 “過期” 機(jī)制。
HTTP 服務(wù)器通過兩種實(shí)體頭(Entity-Header)來實(shí)現(xiàn) “過期” 機(jī)制:Expires 頭和 Cache-Control 頭的 max-age 子項(xiàng)。
Expires/Cache-Control 控制瀏覽器是否直接從瀏覽器緩存取數(shù)據(jù)還是重新發(fā)請求到服務(wù)器取數(shù)據(jù)。
只是 Cache-Control 比 Expires 可以控制的多一些,而且 Cache-Control 會重寫 Expires 的規(guī)則。
降低網(wǎng)絡(luò)上完整回復(fù) HTTP 請求包的次數(shù),這里采用 “確證” 機(jī)制。
HTTP 服務(wù)器通過兩種方式實(shí)現(xiàn) “確證” 機(jī)制:ETag 以及 Last-Modified。
2. 相關(guān)的 Header
主要包括以下幾個。
Cache-Control
常用的值有:
(1)max-age(單位為 s)指定設(shè)置緩存最大的有效時間,定義的是時間長短。
當(dāng)瀏覽器向服務(wù)器發(fā)送請求后,在 max-age 這段時間里瀏覽器就不會再向服務(wù)器發(fā)送請求了。
(2)s-maxage(單位為 s)同 max-age,只用于共享緩存(比如 CDN 緩存),也就是說 max-age 用于普通緩存,而 s-maxage 用于代理緩存。
如果存在 s-maxage,則會覆蓋掉 max-age 和 Expires header。
(3)public 指定響應(yīng)會被緩存,并且在多用戶間共享。如果沒有指定 public 還是 private,則默認(rèn)為 public。
(4)private 響應(yīng)只作為私有的緩存,不能在用戶間共享。如果要求 HTTP 認(rèn)證,響應(yīng)會自動設(shè)置為 private。
(5)no-cache 指定不緩存響應(yīng),表明資源不進(jìn)行緩存,比如,設(shè)置了 no-cache 之后并不代表瀏覽器不緩存,而是在緩存前要向服務(wù)器確認(rèn)資源是否被更改。
因此有的時候只設(shè)置 no-cache 防止緩存還是不夠保險,還可以加上 private 指令,將過期時間設(shè)為過去的時間。
(6)no-store 表示絕對禁止緩存。一看就知道,如果用了這個命令,當(dāng)然就是不會進(jìn)行緩存啦!每次請求資源都要從服務(wù)器重新獲取。
(7)must-revalidate 指定如果頁面是過期的,則去服務(wù)器進(jìn)行獲取。這個指令并不常用,就不做過多的討論了。
Expires
緩存過期時間,用來指定資源到期的時間,是服務(wù)器端的具體時間點(diǎn)。
也就是說,Expires=max-age + 請求時間,需要和 Last-modified 結(jié)合使用。
但在上面我們提到過 cache-control 的優(yōu)先級更高。
Expires 是 Web 服務(wù)器響應(yīng)消息頭字段,在響應(yīng) HTTP 請求時告訴瀏覽器在過期時間前瀏覽器可以直接從瀏覽器緩存取數(shù)據(jù),而無需再次請求。
Last-modified
服務(wù)器端文件的最后修改時間,需要和 cache-control 共同使用,是檢查服務(wù)器端資源是否更新的一種方式。
當(dāng)瀏覽器再次進(jìn)行請求時,會向服務(wù)器傳送 If-Modified-Since 報(bào)頭,詢問 Last-Modified 時間點(diǎn)之后資源是否被修改過。
如果沒有修改,則返回碼為 304,使用緩存;
如果修改過,則再次去服務(wù)器請求資源,返回碼和首次請求相同為 200,資源為服務(wù)器最新資源。
Etag
根據(jù)實(shí)體內(nèi)容生成一段 hash 字符串,標(biāo)識資源的狀態(tài),由服務(wù)端產(chǎn)生。瀏覽器會將這串字符串傳回服務(wù)器,驗(yàn)證資源是否已經(jīng)修改。
為什么要使用 Etag 呢 ?Etag 主要為了解決 Last-Modified 無法解決的一些問題。
一些文件也許會周期性的更改,但是它的內(nèi)容并不改變(僅僅改變的修改時間),這個時候我們并不希望客戶端認(rèn)為這個文件被修改了,而重新 Get。
某些文件修改非常頻繁,比如在秒以下的時間內(nèi)進(jìn)行修改(比方說 1s 內(nèi)修改了 N 次),If-Modified-Since 能檢查到的粒度是 s 級的,這種修改無法判斷(或者說 UNIX 記錄 MTIME 只能精確到秒)。
某些服務(wù)器不能精確的得到文件的最后修改時間。
緩存過程如下圖所示:
· Session 與 Cookie 必知必會
很好的解決了 HTTP 通訊中狀態(tài)問題,但其本身也存在一些問題,比如:
客戶端存儲,可能會被修改或刪除。
發(fā)送請求時,Cookie 會被一起發(fā)送到服務(wù)器,當(dāng) Cookie 數(shù)據(jù)量較大時也會帶來額外的請求數(shù)據(jù)量。
客戶端對 Cookie 數(shù)量及大小有一定的限制,Session 解決了 Cookie 的一些缺點(diǎn)。Session 同樣是為了記錄用戶狀態(tài),對于每個用戶來說都會有相應(yīng)的一個狀態(tài)值保存在服務(wù)器中,而只在客戶端記錄一個 sessionID 用于區(qū)分是哪個用戶的 Session。
與 Cookie 相比,Session 有一定的優(yōu)勢,如:
Session 值存儲在服務(wù)器,相對來說更安全。
客戶端發(fā)送給服務(wù)器的只有一個 sessionID,數(shù)據(jù)量更小。Session 同樣需要在客戶端存儲一個 sessionID。可以這個值存儲在 Cookie,每次發(fā)送請求時通過 Cookie 請求頭將其發(fā)送到服務(wù)器;也可以不使用 Cookie,而將 sessionID 作為一個額外的請求參數(shù),通過 URL 或請求體發(fā)送到服務(wù)器。
基于 Cookie 實(shí)現(xiàn) Session 的實(shí)現(xiàn)原理如下圖:
由上可見,基于 Cookie 實(shí)現(xiàn) Session 時,其本質(zhì)上還是在客戶端保存一個 Cookie 值。
這個值就是 sessionID,sessionID 的名稱也可按需要設(shè)置,為保存安全,其值也可能會在服務(wù)器端做加密處理。
服務(wù)器在收到 sessionID 后,就可以對其解密及查找對應(yīng)的用戶信息等。
· 協(xié)議格式如何統(tǒng)一(見文章后面內(nèi)容)
Spring 對 HTTP 協(xié)議的支持
我為什么想提一下這個呢,我看到太多的開發(fā)者遇到 HTTP 協(xié)議都喜歡自定義變量,自定義類,其實(shí)完全沒有必要。且看下面的分析。
· Spring MVC Web
在 spring-web**.jar 里面我們可以找到如下幾個類:
需要我們重點(diǎn)關(guān)注的有 HttpStatus、MediaType 等。
Spring web bind 中對應(yīng)的注解,如下圖所示:
上面是一些主要的(需要注意的是 @RequestParam、@PostMapping、@**Mapping 與我們上面的 HttpMethod 相對應(yīng)。
而里面還有 Headers、Consumes 和 produces 等參數(shù)來確定一些 Mapping 的條件),下面的可以根據(jù)需要查看源碼里面有哪些注解。
· Spring Data Rest
Spring Data Rest 是基于 Spring Data repositories,分析實(shí)體之間的關(guān)系。為我們生成 Hypermedia API(HATEOAS) 風(fēng)格的 Http Restful API 接口。
Spring Data REST 通過構(gòu)建在 Spring Data repositories 之上,自動將其導(dǎo)出為 REST 資源的 api,減少了大量重復(fù)代碼和無聊的樣板代碼。
它利用超媒體來允許客戶端查找存儲庫暴露的功能,并將這些資源自動集成到相關(guān)的超媒體功能中。
Spring Data REST 本身就是一個 Spring MVC 應(yīng)用程序,它的設(shè)計(jì)方式應(yīng)該是盡可能少的集成到現(xiàn)有的 Spring MVC 應(yīng)用程序中。
現(xiàn)有的(或?qū)淼?#xff09;服務(wù)層可以與 Spring Data REST 一起運(yùn)行,只有較小的考慮。
· Spring RestTemplate 的實(shí)際使用
Spring RestTemplate 是 Spring 提供的用于訪問 Rest 服務(wù)的客戶端,RestTemplate 提供了多種便捷訪問遠(yuǎn)程 HTTP 服務(wù)的方法,能夠大大提高客戶端的編寫效率。
簡單例子,如下。
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));
更詳盡例子,如下。
/** * 發(fā)送一個 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")); }/** * 發(fā)送一個 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); }
我們在實(shí)際工作中會覆蓋默認(rèn)的 RestTemplate。
/*** 替代默認(rèn)的 SimpleClientHttpRequestFactory* 設(shè)置超時時間重試次數(shù)* 還可以設(shè)置一些攔截器以便監(jiān)控** @return*/ @Bean public RestTemplate restTemplate() {// 生成一個設(shè)置了連接超時時間、請求超時時間、異常重試次數(shù) 3 次 ? ?RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build(); // 實(shí)際工作中,這個地方還會加上 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 做了很好的支持。
我們可以重點(diǎn)看一下 Pageable 和 Page,以及 PageImpl 和 PageRequest 對分頁和排序做了很好的封裝,及其返回的 JSON 格式也做了很好的約定。
· Spring Cloud 中的知識點(diǎn)
Spring Cloud 的微服務(wù)管理都是基于 HTTP 協(xié)議的 Rest 風(fēng)格的 API 來管理的,所以我們詳細(xì)了解 HTTP 協(xié)議還是非常有必要的。
JSON API
我們都知道了約定的好處。如果你和你的團(tuán)隊(duì)曾經(jīng)爭論過使用什么方式構(gòu)建合理 JSON 響應(yīng)格式, 那么 JSON API 就是你的 anti-bikeshedding 武器。
通過遵循共同的約定,可以提高開發(fā)效率,利用更普遍的工具,可以是你更加專注于開發(fā)重點(diǎn):你的程序。
點(diǎn)擊?http://jsonapi.org 訪問 JSON API 官方。
(1)JSON API 介紹
JSON API 是數(shù)據(jù)交互規(guī)范,用以定義客戶端如何獲取與修改資源,以及服務(wù)器如何響應(yīng)對應(yīng)請求。
JSON API 設(shè)計(jì)用來最小化請求的數(shù)量,以及客戶端與服務(wù)器間傳輸?shù)臄?shù)據(jù)量。在高效實(shí)現(xiàn)的同時,無需犧牲可讀性、靈活性和可發(fā)現(xiàn)性。
JSON API 需要使用 JSON API 媒體類型(application/vnd.api+json)進(jìn)行數(shù)據(jù)交互。
JSON API 服務(wù)器支持通過 GET 方法獲取資源。而且必須獨(dú)立實(shí)現(xiàn) HTTP POST,PUT 和 DELETE 方法的請求響應(yīng),以支持資源的創(chuàng)建、更新和刪除。
JSON API 服務(wù)器也可以選擇性支持 HTTP PATCH 方法 [RFC5789] 和 JSON Patch 格式 [RFC6902],進(jìn)行資源修改。
JSON Patch 支持是可行的,因?yàn)槔碚撋蟻碚f,JSON API 通過單一 JSON 文檔,反映域下的所有資源,并將 JSON 文檔作為資源操作介質(zhì)。
在文檔頂層,依據(jù)資源類型分組。每個資源都通過文檔下的唯一路徑辨識。
(2)規(guī)則約定
文檔中的關(guān)鍵字 MUST、MUST NOT、REQUIRED、SHALL、SHALL NOT、SHOULD、 SHOULD NOT、RECOMMENDED、MAY 和 OPTIONAL。依據(jù) RFC 2119 [RFC2119] 規(guī)范解釋。
(3)內(nèi)容約定
客戶端職責(zé),即客戶端必須在包含 Content-Type: application/vnd.api+json 頭并且不包含媒體類型參數(shù)的請求文檔中發(fā)送所有 JSON API 數(shù)據(jù)。
在 Accept 頭中包含 JSON API 媒體類型并且不包含媒體類型參數(shù)的客戶端必須在 Accept 頭中指定媒體類型至少一次。
客戶端必須忽略任何從響應(yīng)文檔的 Content-Type 頭中獲取的 application/vnd.api+json 媒體類型參數(shù)。
服務(wù)器職責(zé),即服務(wù)器必須在包含 Content-Type: application/vnd.api+json 頭并且不包含媒體類型參數(shù)的請求文檔中發(fā)送所有 JSON API 數(shù)據(jù)。
如果接收到一個用任何媒體類型參數(shù)指定 Content-Type: application/vnd.api+json 頭的請求,服務(wù)器必須返回一個 415 Unsupported Media Type 狀態(tài)碼響應(yīng)。
如果接收到一個在 Accept 頭中包含任何 JSON API 媒體類型并且所有實(shí)體都以媒體類型參數(shù)更改的請求,服務(wù)器必須返回一個 406 Not Acceptable 狀態(tài)碼響應(yīng)。
(4)文檔結(jié)構(gòu)
我們將描述 JSON API 文檔結(jié)構(gòu),通過媒體類型?application/vnd.api+json 標(biāo)示。
JSON API 文檔使用 JavaScript 對象(JSON)[RFC4627] 定義。
盡管同種媒體類型用以請求和響應(yīng)文檔,但某些特性只適用于其中一種。差異在下面呈現(xiàn)。
除非另有說明,根據(jù)本規(guī)范定義的對象都不應(yīng)該包含任何其他鍵。
客戶端和服務(wù)器實(shí)現(xiàn)必須忽略本規(guī)范未指定的鍵。
(5)Top Level
JSON 對象必須位于每個 JSON API 文檔的根級。這個對象定義文檔的 “top level”。
文檔必須包含以下至少一種 top-level 鍵。
data: 文檔的”primary data”。
errors: 錯誤對象列表。
meta: 包含非標(biāo)準(zhǔn)元信息的元對象。
data 鍵和 errors 鍵不能再一個文檔中同時存在。
文檔可能包含以下任何 top-level 鍵。
jsonapi: 描述服務(wù)器實(shí)現(xiàn)的對象。
links: 與 primary data 相關(guān)的鏈接對象。
include: 與 primary data 或其他資源相關(guān)的資源對象(included resources)列。
如果文檔不包含 top-level data 鍵,included 鍵也不應(yīng)該出現(xiàn)。
文檔的 top-level 鏈接對象可能包含以下鍵。
self: 生成當(dāng)前響應(yīng)文檔的鏈接。
related: 當(dāng) primary data 代表資源關(guān)系時,表示相關(guān)資源鏈接。
Primary data 的分頁鏈接。
文檔中的 “primary data” 代表一個請求所要求的資源或資源集合。
Primary data 必須是以下列舉的一種。
如果請求要求單一資源,應(yīng)該是一個單一資源對象,或一個單一資源標(biāo)識符,或 null。
如果請求要求資源集合,應(yīng)該是一個資源對象列表,或一個空列表 ([])。
例如,以下 primary data 表示一個單一資源對象。
{"data": {"type": "articles", ? ?"id": "1", ? ?"attributes": { ? ? ?// ... this article's attributes ? ?}, ? ?"relationships": { ? ? ?// ... this article's relationships ? ?}} }
以下 primary data 表示一個指向同樣資源的單一資源標(biāo)識符。
{ ? ?"data": { ? ? ?"type": "articles", ? ? ?"id": "1" ? ?} }
即使只包含一個元素或?yàn)榭?#xff0c;資源的一個邏輯集合也必須表示為一個列表。
資源對象,即 JSON API 文檔中的 “Resuorce objects”,代表資源。
一個資源對象必須至少包含以下 top-level 鍵。
id
`type’
例外,當(dāng)資源對象來自客戶端并且代表一個將要在服務(wù)器創(chuàng)建的新資源時,id 鍵不是必須的。
此外,資源對象可能包含以下 top-level 鍵。
‘a(chǎn)ttribute’: 屬性對象代表資源的某些數(shù)據(jù)。
relationshiops: 關(guān)聯(lián)對象描述該資源與其他 JSON API 資源之間的關(guān)系。
links: 鏈接資源包含與資源相關(guān)的鏈接。
meta: 元數(shù)據(jù)資源包含與資源對象相關(guān)的非標(biāo)準(zhǔn)元信息,這些信息不能被作為屬性或關(guān)聯(lián)對象表示。
一篇文獻(xiàn)(即一個 “文獻(xiàn)” 類型的資源)在文檔中這樣表示:
{ ?"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" }}} }
標(biāo)識符,即每個資源對象包含一個 id 鍵和一個 type 鍵。id 鍵和 typ e 鍵的值必須是字符串類型。
對于每一個既定 API,每個資源對象的 type 和 id 對必須定義一個單獨(dú)且唯一的資源(由一個或多個但行為表現(xiàn)為一個服務(wù)器的服務(wù)器控制的 URI 集合構(gòu)成一個 API)。
type 鍵用于描述共享相同屬性和關(guān)聯(lián)的資源對象。type 鍵的值必須與遵循鍵名稱相同的約束條件。
字段,即資源對象的屬性和關(guān)聯(lián)被統(tǒng)稱為 “fields”。
一個資源對象的所有字段必須與 type 和 id 在同一命名空間中。即一個資源不能擁有名字相同的屬性與關(guān)聯(lián),也不能擁有被命名為 type 或 id 的屬性和關(guān)聯(lián)。
屬性,即 attribute,鍵的值必須是一個對象(一個 “attributes object”)。屬性對象的鍵(“attributes”)代表與資源對象中定義的與其有關(guān)的信息。
屬性可以包含任何合法 JSON 值。JSON 對象和列表涉及的復(fù)雜數(shù)據(jù)結(jié)構(gòu)可以作為屬性的值。
但是一個組成或被包含于屬性中的對象不能包含 relationships 或 links 鍵,因?yàn)檫@些鍵為此規(guī)范未來的用途所預(yù)留。
雖然一些 has-one 關(guān)系的外鍵(例如 author_id)被在內(nèi)部與其他將要在資源對象中表達(dá)的信息一起儲存,但是這些鍵不能作為屬性出現(xiàn)。
關(guān)聯(lián),即 relationships,鍵的值必須是一個對象(“relationships object”)。
關(guān)聯(lián)對象(“relationships”)的鍵表示在資源對象中定義的與其相關(guān)的其他資源對象。
關(guān)聯(lián)可以是單對象關(guān)聯(lián)或多對象關(guān)聯(lián)。
一個 “relationship object” 必須包含以下至少一種鍵:
links: 一個鏈接對象至少包含以下一種鍵:
self: 指向關(guān)聯(lián)本身的鏈接(“relationship link”)。此鏈接允許客戶端直接修改關(guān)聯(lián)。例如,通過一個 articale 的關(guān)聯(lián) URL 移除一個 author 將會解除一個人與 article 的關(guān)系,而不需要刪除這個 people 資源本身。獲取成功后,這個鏈接將返回一個相關(guān)資源之間的連接,將其作為 primary data(見獲取關(guān)聯(lián))。
related: 相關(guān)資源鏈接。
data: 資源連接。
meta: 包含關(guān)于此關(guān)聯(lián)的非標(biāo)準(zhǔn)元信息的元對象。
更多介紹見 官方文檔。
· Yahoo elide 對 JSON API 的支持
點(diǎn)擊 http://elide.io/pages/guide/01-start.html,訪問 Yahoo elide 官方網(wǎng)站。
1. elide 介紹
elide 通過 Spring Data Jpa 的 Entity,加上自定義的 @Include(rootLevel = true) 注解,來完成 JSON API 標(biāo)準(zhǔn)的輸出。
使用方法如下圖所示:
效果如下:
生產(chǎn)環(huán)境中 HTTP 協(xié)議有哪些架構(gòu)
· RestTemplate 的重試和監(jiān)控
代碼如下。
@SpringBootApplication public class DubbingApiApplication {/** ? * 使用全局的 RestTemplate ? * 設(shè)置了連接超時時間、請求超時時間、異常重試次數(shù) 3 次 ? * 并且會記錄所有請求的詳細(xì)日志 ? * ? * @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);} }
· 請求格式和返回結(jié)果的格式約定。
(1)實(shí)現(xiàn) ResponseBodyAdvice 對 controller 返回的 Result 進(jìn)行統(tǒng)一的包裝,如下代碼。
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
Result
ElideWrapHandler.process(httpServletRequest, body);
? ? ?}
}
(2)異常統(tǒng)一格式的處理,返回固定的 error 級別的格式結(jié)果,如下代碼。
/*** 框架級別的通用異常處理*/ @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 緩存,這里就不給大家貼代碼了,大家可以思考一下。
微服務(wù)中 HTTP 與 RPC 的權(quán)衡
· HTTP 與 RPC 比較
實(shí)際工作中建議兩者都用,API 對外,Ali Dubbo 的 RPC 對內(nèi)部使用,這樣兩個的優(yōu)點(diǎn)都能使用到。
總結(jié):面試中起到的關(guān)鍵作用是什么?
如果面試中問到你這個問題,主要的考驗(yàn)的點(diǎn)有:
思路是否清晰。
實(shí)戰(zhàn)解決那些問題如緩存可能會讓你說的非常細(xì)。
知識是否全面,及其是否針對一個問題了解的足夠多。
基本上面的東西如果你都能提到,面試官的印象基本上是非常好的,加分會加很多。
近期熱文
《這么糟糕的代碼,真的是我以前寫的嗎?》
《一條挨踢老狗的 2017 年終總結(jié)》
《OpenVPN 的穿墻遠(yuǎn)程連接旅程》
《前端跨域問題各種解決方案》
《程序員跳槽時,如何高效地準(zhǔn)備面試?》
「閱讀原文」看交流實(shí)錄,你想知道的都在這里
總結(jié)
以上是生活随笔為你收集整理的Java 架构师眼中的 HTTP 协议的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国第一个口罩
- 下一篇: Java: 将课程表解析成每周课表