本文將會講到5G和HTTP。曾經在深入淺出經典面試題:從瀏覽器中輸入URL到頁面加載發生了什么 - Part 3 提到為什么有些RPC框架不選用HTTP,而5G會采用HTTP。
您可以從本文里獲取到一些概念:5G用HTTP作為reference point interface的實現,HTTP/2,RESTful API/HATEOAS/OpenAPI等最佳實踐和標准,這些都是一些常見但是又容易忽略的知識點。
本文參考了一些文章,見文章末尾的鏈接列表。
HTTP的優點和缺點
我們大家知道HTTP協議包含的信息太多,太繁重,導致消息體會很大,但是其中有一些消息根本用不上,這也是為什么HTTP/1.1消息效率不高的原因,所以一些RPC框架舍棄它,例如dubbo定義自己的協議等,如果大家定義過協議,例如類似TCP協議,就能明白協議定義的重要性。如果要效率高,消息短,那就會太底層,如TCP,如果要想易於理解,例如HTTP,那就得長一些。
5G和HTTP
5G明年試商用,在5G里采用HTTP協議,確實有意思。可以參看TS 29.501協議 5G System;Principles and Guidelines for Services Definition,Stage 3。先看看下圖:

在通信領域,由原來的Diameter,AAA等轉變為HTTP,的確是一個大變化,但是開發的效率將會大大提高。
那么5G將會應用到什么HTTP相關技術呢?
- HTTP/2/0 (協議下載https://http2.github.io/http2-spec/)
- JSON
- HATEOAS
- RESTful
- OpenAPI
HTTP/2.0
還是先看看HTTP/2吧。談到HTTP/2,最先想到Google的SPDY,它是HTTP/2的前身。為什么Google要做SPDY呢?原因很簡單,HTTP的效率不高。自從有了SPDY后,加載時間減少64%(http://dev.chromium.org/spdy/spdy-whitepaper),原話這么說的, In lab tests, we have compared the performance of these applications over HTTP and SPDY, and have observed up to 64% reductions in page load times in SPDY.
SPDY並不用於取代HTTP,它只是修改了HTTP的請求與應答在網絡上傳輸的方式;這意味着只需增加一個SPDY傳輸層,現有的所有服務端應用均不用做任何修改。 當使用SPDY的方式傳輸,HTTP請求會被處理、標記簡化和壓縮。比如,每一個SPDY端點會持續跟蹤每一個在之前的請求中已經發送的HTTP報文頭部,從而避免重復發送還未改變的頭部。而還未發送的報文的數據部分將在被壓縮后被發送。
HTTP/2主要特性包括:
二進制協議
HTTP/1.1 版的頭信息肯定是文本(ASCII編碼),數據體可以是文本,也可以是二進制。HTTP/2 則是一個徹底的二進制協議,頭信息和數據體都是二進制,並且統稱為"幀"(frame):頭信息幀和數據幀。
二進制協議的一個好處是,可以定義額外的幀。HTTP/2 定義了近十種幀,為將來的高級應用打好了基礎。如果使用文本實現這種功能,解析數據將會變得非常麻煩,二進制解析則方便得多。
多工
HTTP/2 復用TCP連接,在一個連接里,客戶端和瀏覽器都可以同時發送多個請求或回應,而且不用按照順序一一對應,這樣就避免了"隊頭堵塞"。
舉例來說,在一個TCP連接里面,服務器同時收到了A請求和B請求,於是先回應A請求,結果發現處理過程非常耗時,於是就發送A請求已經處理好的部分, 接着回應B請求,完成后,再發送A請求剩下的部分。
這樣雙向的、實時的通信,就叫做多工(Multiplexing)。
數據流
因為 HTTP/2 的數據包是不按順序發送的,同一個連接里面連續的數據包,可能屬於不同的回應。因此,必須要對數據包做標記,指出它屬於哪個回應。
HTTP/2 將每個請求或回應的所有數據包,稱為一個數據流(stream)。每個數據流都有一個獨一無二的編號。數據包發送的時候,都必須標記數據流ID,用來區分它屬於哪個數據流。另外還規定,客戶端發出的數據流,ID一律為奇數,服務器發出的,ID為偶數。
數據流發送到一半的時候,客戶端和服務器都可以發送信號(RST_STREAM幀),取消這個數據流。1.1版取消數據流的唯一方法,就是關閉TCP連接。這就是說,HTTP/2 可以取消某一次請求,同時保證TCP連接還打開着,可以被其他請求使用。
客戶端還可以指定數據流的優先級。優先級越高,服務器就會越早回應。
頭信息壓縮
HTTP 協議不帶有狀態,每次請求都必須附上所有信息。所以,請求的很多字段都是重復的,比如Cookie和User Agent,一模一樣的內容,每次請求都必須附帶,這會浪費很多帶寬,也影響速度。
HTTP/2 對這一點做了優化,引入了頭信息壓縮機制(header compression)。一方面,頭信息使用gzip或compress壓縮后再發送;另一方面,客戶端和服務器同時維護一張頭信息表,所有字段都會存入這個表,生成一個索引號,以后就不發送同樣字段了,只發送索引號,這樣就提高速度了。
服務器推送
HTTP/2 允許服務器未經請求,主動向客戶端發送資源,這叫做服務器推送(server push)。
常見場景是客戶端請求一個網頁,這個網頁里面包含很多靜態資源。正常情況下,客戶端必須收到網頁后,解析HTML源碼,發現有靜態資源,再發出靜態資源請求。其實,服務器可以預期到客戶端請求網頁后,很可能會再請求靜態資源,所以就主動把這些靜態資源隨着網頁一起發給客戶端了。
我給自己挖個坑,后面專門出一篇文章寫HTTP/2.
HATEOAS 約束
HATEOAS(Hypermedia as the engine of application state)是 REST 架構風格中最復雜的約束,也是構建成熟 REST 服務的核心。它的重要性在於打破了客戶端和服務器之間嚴格的契約,使得客戶端可以更加智能和自適應,而 REST 服務本身的演化和更新也變得更加容易。
在介紹 HATEOAS 之前,先介紹一下 Richardson 提出的 REST 成熟度模型。該模型把 REST 服務按照成熟度划分成 4 個層次:(這個可以參考Richardson的成熟度模型,見后文鏈接)
- 第一個層次(Level 0)的 Web 服務只是使用 HTTP 作為傳輸方式,實際上只是遠程方法調用(RPC)的一種具體形式。SOAP 和 XML-RPC 都屬於此類。
- 第二個層次(Level 1)的 Web 服務引入了資源的概念。每個資源有對應的標識符和表達。
- 第三個層次(Level 2)的 Web 服務使用不同的 HTTP 方法來進行不同的操作,並且使用 HTTP 狀態碼來表示不同的結果。如 HTTP GET 方法來獲取資源,HTTP DELETE 方法來刪除資源。
- 第四個層次(Level 3)的 Web 服務使用 HATEOAS。在資源的表達中包含了鏈接信息。客戶端可以根據鏈接來發現可以執行的動作。
從上述 REST 成熟度模型中可以看到,使用 HATEOAS 的 REST 服務是成熟度最高的,也是推薦的做法。對於不使用 HATEOAS 的 REST 服務,客戶端和服務器的實現之間是緊密耦合的。客戶端需要根據服務器提供的相關文檔來了解所暴露的資源和對應的操作。當服務器發生了變化時,如修改了資源的 URI,客戶端也需要進行相應的修改。而使用 HATEOAS 的 REST 服務中,客戶端可以通過服務器提供的資源的表達來智能地發現可以執行的操作。當服務器發生了變化時,客戶端並不需要做出修改,因為資源的 URI 和其他信息都是動態發現的。
所以我們可以看到HATEOAS可以降低客戶端和服務器之間的耦合。
我們看看在Spring官網上的例子。
下面是一個類 Customer.
class Customer { String name; }
一個傳統的例子是:
{
"name" : "Alice" }
如果變成HATEOAS風格的,可以是下面這樣:
{
"name": "Alice", "links": [ { "rel": "self", "href": "http://localhost:8080/customer/1" } ] }
我們可以看到,不僅有了name,還多了一個links. links下的rel的值是self,意思就是說指向當前資源的鏈接。
關於ref的值,可以參考下表:
| rel 屬性值 | 描述 |
|---|---|
| self | 指向當前資源本身的鏈接的 rel 屬性。每個資源的表達中都應該包含此關系的鏈接。 |
| edit | 指向一個可以編輯當前資源的鏈接。 |
| item | 如果當前資源表示的是一個集合,則用來指向該集合中的單個資源。 |
| collection | 如果當前資源包含在某個集合中,則用來指向包含該資源的集合。 |
| related | 指向一個與當前資源相關的資源。 |
| search | 指向一個可以搜索當前資源及其相關資源的鏈接。 |
| first、last、previous、next | 這幾個 rel 屬性值都有集合中的遍歷相關,分別用來指向集合中的第一個、最后一個、上一個和下一個資源。 |
根據以上,我們可以清楚的看出根據rel不同的類型有不同的用處,這樣客戶端可以智能的進行不同的操作,達到解耦的目的。
OpenAPI
其實RESTful API都是和OpenAPI相關的,為什么會把OpenAPI單獨拿出來說?原理很簡單,那是因為現在很多API的定義,包括一些大廠的,都做的不是很好。RESTful API設計的最佳實踐文檔就在這里,但是大部分人還是沒有去遵守。關於RESTful API文檔,建議去參考微軟的文章( https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design)。那么OpenAPI是干什么的?說白了就是為了RESTful API,定義了一個標准,讓我們和機器不用再去查看源代碼、文檔,甚至不用像我前面文件里抓包那樣,去了解API的定義。
最典型的例子還是Swagger。Swagger的Editor等產品是支持OpenAPI的,總的來說,Open API的那些標准不是太難,因為現成的例子供參考。關鍵是如果利用這些將自己的產品變得更加標准,這是很重要的策略和思路。我原來在這個上面花了很多時間引入到項目里,我覺得是值的,一個是讓產品規范了,有質的保證,二是讓自己和同事的思維提高了。
總的來說,這篇文章簡單介紹了5G和HTTP的關系,以及HTTP里用到RESTful API,HTTP/2等技術,這和以前通信領域是不一樣的。
參考文章:
- https://www.ibm.com/developerworks/cn/java/j-lo-SpringHATEOAS/
- https://spring.io/understanding/HATEOAS
- https://http2.github.io/http2-spec/
- https://martinfowler.com/articles/richardsonMaturityModel.html
- http://www.ruanyifeng.com/blog/2016/08/http.html
- https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design
- https://www.openapis.org/
- http://dev.chromium.org/spdy/spdy-whitepaper
- https://www.etsi.org/deliver/etsi_ts/129500_129599/129501/15.00.01_60/ts_129501v150001p.pdf
