脑残式网络编程入门(三):HTTP协议必知必会的一些知识
為什么80%的碼農都做不了架構師?>>> ??
本文原作者:“竹千代”,原文由“玉剛說”寫作平臺提供寫作贊助,原文版權歸“玉剛說”微信公眾號所有,即時通訊網收錄時有改動。
?
1、前言
無論是即時通訊應用還是傳統的信息系統,Http協議都是我們最常打交道的網絡應用層協議之一,它的重要性可能不需要再強調(有鑒于此,即時通訊網整理了大量的有關http協議的文章,如有必要可從本文的相關鏈接處查閱)。但是實際上很多人(包括我自己),雖然每天都會跟http的代碼打交道,但對http了解的并不夠深入。本文就我自己的學習心得,分享一下我認為需要知道的http常見的相關知識點。
HTTP協議所處的TCP/IP協議層級:
(▲ 上圖來自《計算機網絡通訊協議關系圖(中文珍藏版)[附件下載]》,您可下載此圖的完整清晰版)
(本文同步發布于:http://www.52im.net/thread-1751-1-1.html)
2、系列文章
本文是系列文章中的第3篇,本系列大綱如下:
?
- 《腦殘式網絡編程入門(一):跟著動畫來學TCP三次握手和四次揮手》
- 《腦殘式網絡編程入門(二):我們在讀寫Socket時,究竟在讀寫什么?》
- 《腦殘式網絡編程入門(三):HTTP協議必知必會的一些知識》(本文)
?
3、Http協議報文相關知識
?
3.1基礎概念
首先我們來點基礎的,看看http報文具體的格式。http報文可以分為請求報文和響應報文,格式大同小異。
主要分為三個部分:
?
- 1)起始行;
- 2)首部;
- 3)主體。
請求報文格式:
| 1 2 3 4 | <method> <request-url> <version> <headers> ? <entity-body> |
響應報文格式:
| 1 2 3 4 | <version> <status> <reason-phrase> <headers> ? <entity-body> |
從請求報文格式和響應報文格式可以看出,兩者主要在起始行上有差異。
這里稍微解釋一下各個標簽:
| 1 2 3 4 5 | <method> 指請求方法,常用的主要是Get、 Post、Head 還有其他一些我們這里就不說了,有興趣的可以自己查閱一下 <version> 指協議版本,現在通常都是Http/1.1了 <request-url> 請求地址 <status> 指響應狀態碼, 我們熟悉的200、404等等 <reason-phrase> 原因短語,200 OK 、404 Not Found 這種后面的描述就是原因短語,通常不必太關注。 |
?
3.2method
我們知道請求方法最常用的有Get 和Post兩種,面試時也常常會問到這兩者有什么區別,通常什么情況下使用。這里我們來簡單說一說。
兩個方法之間在傳輸形式上有一些區別,通過Get方法發起請求時,會將請求參數拼接在request-url尾部,格式是url?param1=xxx?m2=xxx&[…]。
我們需要知道,這樣傳輸參數會使得參數都暴露在地址欄中。并且由于url是ASCII編碼的,所以參數中如果有Unicode編碼的字符,例如漢字,都會編碼之后傳輸。另外值得注意的是,雖然http協議并沒有對url長度做限制,但是一些瀏覽器和服務器可能會有限制,所以通過GET方法發起的請求參數不能夠太長。而通過POST方法發起的請求是將參數放在請求體中的,所以不會有GET參數的這些問題。
另外一點差別就是方法本身的語義上的。GET方法通常是指從服務器獲取某個URL資源,其行為可以看作是一個讀操作,對同一個URL進行多次GET并不會對服務器產生什么影響。而POST方法通常是對某個URL進行添加、修改,例如一個表單提交,通常會往服務器插入一條記錄。多次POST請求可能導致服務器的數據庫中添加了多條記錄。所以從語義上來講,兩者也是不能混為一談的。
?
3.3狀態碼
常見的狀態碼主要有:
?
- 200 OK??請求成功,實體包含請求的資源
- 301 Moved Permanent 請求的URL被移除了,通常會在Location首部中包含新的URL用于重定向。
- 304 Not Modified? ? 條件請求進行再驗證,資源未改變。
- 404 Not Found? ?? ? 資源不存在
- 206 Partial Content 成功執行一個部分請求。這個在用于斷點續傳時會涉及到。
?
3.4header
在請求報文和響應報文中都可以攜帶一些信息,通過與其他部分配合,能夠實現各種強大的功能。這些信息位于起始行之下與請求實體之間,以鍵值對的形式,稱之為首部。每條首部以回車換行符結尾,最后一個首部額外多一個換行,與實體分隔開。
這里我們重點關注一下:
| 01 02 03 04 05 06 07 08 09 10 | Date? Cache-Control? Last-Modified? Etag? Expires? If-Modified-Since?? If-None-Match? If-Unmodified-Since? If-Range? If-Match |
Http的首部還有很多,但限于篇幅我們不一一討論。這些首部都是Http緩存會涉及到的,在下文中我們會來說說各自的作用。
?
3.5實體
請求發送的資源,或是響應返回的資源。
?
3.6更多資料
因為篇幅原因,本文只是對http的相關知識做出簡要介紹,如果需要詳細了解,則建議閱下以下文章:
?
- 《網絡編程懶人入門(七):深入淺出,全面理解HTTP協議》
- 《從HTTP/0.9到HTTP/2:一文讀懂HTTP協議的歷史演變和設計思路》
?
4、Http緩存相關的知識
當我們發起一個http請求后,服務器返回所請求的資源,這時我們可以將該資源的副本存儲在本地,這樣當再次對該url資源發起請求時,我們能快速的從本地存儲設備中獲取到該url資源,這就是所謂的緩存。緩存既可以節約不必要的網絡帶寬,又能迅速對http請求做出響應。
先擺出幾個概念:
?
- 新鮮度檢測;
- 再驗證;
- 再驗證命中。
我們知道,有些url所對應的資源并不是一成不變的,服務器中該url的資源可能在一定時間之后會被修改。這時本地緩存中的資源將與服務器一側的資源有差異。
既然在一定時間之后可能資源會改變,那么在某個時間之前我們可以認為這個資源沒有改變,從而放心大膽的使用緩存資源,當請求時間超過來該時間,我們認為這個緩存資源可能不再與服務器端一致了。所以當我們發起一個請求時,我們需要先對緩存的資源進行判斷,看看究竟我們是否可以直接使用該緩存資源,這個就叫做新鮮度檢測。即每個資源就像一個食品一樣,擁有一個過期時間,我們吃之前需要先看看有沒有過期。
如果發現該緩存資源已經超過了一定的時間,我們再次發起請求時不會直接將緩存資源返回,而是先去服務器查看該資源是否已經改變,這個就叫做再驗證。如果服務器發現對應的url資源并沒有發生變化,則會返回304 Not Modified,并且不再返回對應的實體。這稱之為再驗證命中。相反如果再驗證未命中,則返回200 OK,并將改變后的url資源返回,此時緩存可以更新以待之后請求。
我們看看具體的實現方式。
新鮮度檢測:
我們需要通過檢測資源是否超過一定的時間,來判斷緩存資源是否新鮮可用。那么這個一定的時間怎么決定呢?其實是由服務器通過在響應報文中增加Cache-Control:max-age,或是Expire這兩個首部來實現的。值得注意的是Cache-Control是http1.1的協議規范,通常是接相對的時間,即多少秒以后,需要結合last-modified這個首部計算出絕對時間。而Expire是http1.0的規范,后面接一個絕對時間。
再驗證:
如果通過新鮮度檢測發現需要請求服務器進行再驗證,那么我們至少需要告訴服務器,我們已經緩存了一個什么樣的資源了,然后服務器來判斷這個緩存資源到底是不是與當前的資源一致。邏輯是這樣沒錯。那怎么告訴服務器我當前已經有一個備用的緩存資源了呢?我們可以采用一種稱之為條件請求的方式實現再驗證。
Http定義了5個首部用于條件請求:??
?
- If-Modified-Since
- If-None-Match
- If-Unmodified-Since
- If-Range
- If-Match
If-Modified-Since 可以結合Last-Modified這個服務器返回的響應首部使用,當我們發起條件請求時,將Last-Modified首部的值作為If-Modified-Since首部的值傳遞到服務器,意思是查詢服務器的資源自從我們上一次緩存之后是否有修改。
If-None-Match 需要結合另一個Etag的服務器返回的響應首部使用。Etag首部實際上可以認為是服務器對文檔資源定義的一個版本號。有時候一個文檔被修改了,可能所做的修改極為微小,并不需要所有的緩存都重新下載數據。或者說某一個文檔的修改周期極為頻繁,以至于以秒為時間粒度的判斷已經無法滿足需求。這個時候可能就需要Etag這個首部來表明這個文檔的版號了。發起條件請求時可將緩存時保存下來的Etag的值作為If-None-Match首部的值發送至服務器,如果服務器的資源的Etag與當前條件請求的Etag一致,表明這次再驗證命中。??
其他三個與斷點續傳涉及到的相關知識有關,本文暫時不討論。待我之后寫一篇文章來講講斷點續傳。
5、以Android端的OkHttp庫為例來講解http緩存的客戶端具體實現
緩存的Http理論知識大致就是這么些。我們從OkHttp的源碼來看看(iOS端可以讀一讀著名的AFNetworking庫的代碼),這些知名的開源庫是如何利用Http協議實現緩存的。這里我們假設讀者對OkHttp的請求執行流程有了大致的了解,并且只討論緩存相關的部分。對于OkHttp代碼不熟悉的同學,建議先看看相關代碼或是其他文章。
我們知道OkHttp的請求在發送到服務器之前會經過一系列的Interceptor,其中有一個CacheInterceptor即是我們需要分析的代碼。
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | final InternalCache cache; ? @Override public Response intercept(Chain chain) throws IOException { ????Response cacheCandidate = cache != null ????????? cache.get(chain.request()) ????????: null; ? ????long now = System.currentTimeMillis(); ? ????CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); ????Request networkRequest = strategy.networkRequest; ????Response cacheResponse = strategy.cacheResponse; ? ????...... ? ?} |
方法首先通過InternalCache 獲取到對應請求的緩存。這里我們不展開討論這個類的具體實現,只需要知道,如果之前緩存了該請求url的資源,那么通過request對象可以查找到這個緩存響應。
將獲取到的緩存響應,當前時間戳和請求傳入CacheStrategy,然后通過執行get方法執行一些邏輯最終可以獲取到strategy.networkRequest,strategy.cacheResponse。如果通過CacheStrategy的判斷之后,我們發現這次請求無法直接使用緩存數據,需要向服務器發起請求,那么我們就通過CacheStrategy為我們構造的networkRequest來發起這次請求。我們先來看看CacheStrategy做了哪些事情。
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | CacheStrategy.Factory.java ? public Factory(long nowMillis, Request request, Response cacheResponse) { ??????this.nowMillis = nowMillis; ??????this.request = request; ??????this.cacheResponse = cacheResponse; ? ??????if (cacheResponse != null) { ????????this.sentRequestMillis = cacheResponse.sentRequestAtMillis(); ????????this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis(); ????????Headers headers = cacheResponse.headers(); ????????for (int i = 0, size = headers.size(); i < size; i++) { ??????????String fieldName = headers.name(i); ??????????String value = headers.value(i); ??????????if ("Date".equalsIgnoreCase(fieldName)) { ????????????servedDate = HttpDate.parse(value); ????????????servedDateString = value; ??????????} else if ("Expires".equalsIgnoreCase(fieldName)) { ????????????expires = HttpDate.parse(value); ??????????} else if ("Last-Modified".equalsIgnoreCase(fieldName)) { ????????????lastModified = HttpDate.parse(value); ????????????lastModifiedString = value; ??????????} else if ("ETag".equalsIgnoreCase(fieldName)) { ????????????etag = value; ??????????} else if ("Age".equalsIgnoreCase(fieldName)) { ????????????ageSeconds = HttpHeaders.parseSeconds(value, -1); ??????????} ????????} ??????} ????} |
CacheStrategy.Factory的構造方法首先保存了傳入的參數,并將緩存響應的相關首部解析保存下來。之后調用的get方法如下
| 01 02 03 04 05 06 07 08 09 10 | public CacheStrategy get() { ??CacheStrategy candidate = getCandidate(); ? ??if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) { ????// We're forbidden from using the network and the cache is insufficient. ????return new CacheStrategy(null, null); ??} ? ??return candidate; } |
get方法很簡單,主要邏輯在getCandidate中,這里的邏輯是如果返回的candidate所持有的networkRequest不為空,表示我們這次請求需要發到服務器,此時如果請求的cacheControl要求本次請求只使用緩存數據。那么這次請求恐怕只能以失敗告終了,這點我們等會兒回到CacheInterceptor中可以看到。接著我們看看主要getCandidate的主要邏輯。
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | private CacheStrategy getCandidate() { ??// No cached response. ??if (cacheResponse == null) { ????return new CacheStrategy(request, null); ??} ? ??// Drop the cached response if it's missing a required handshake. ??if (request.isHttps() && cacheResponse.handshake() == null) { ????return new CacheStrategy(request, null); ??} ? ??// If this response shouldn't have been stored, it should never be used ??// as a response source. This check should be redundant as long as the ??// persistence store is well-behaved and the rules are constant. ??if (!isCacheable(cacheResponse, request)) { ????return new CacheStrategy(request, null); ??} ? ??CacheControl requestCaching = request.cacheControl(); ??if (requestCaching.noCache() || hasConditions(request)) { ????return new CacheStrategy(request, null); ??} ????...... } |
上面這段代碼主要列出四種情況下需要忽略緩存,直接想服務器發起請求的情況:
?
- 1)緩存本身不存在;
- 2)請求是采用https 并且緩存沒有進行握手的數據;
- 3)緩存本身不應該不保存下來。可能是緩存本身實現有問題,把一些不應該緩存的數據保留了下來;
- 4)如果請求本身添加了 Cache-Control: No-Cache,或是一些條件請求首部,說明請求不希望使用緩存數據。
這些情況下直接構造一個包含networkRequest,但是cacheResponse為空的CacheStrategy對象返回。
?
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | private CacheStrategy getCandidate() { ??...... ? ??CacheControl responseCaching = cacheResponse.cacheControl(); ??if (responseCaching.immutable()) { ????return new CacheStrategy(null, cacheResponse); ??} ? ??long ageMillis = cacheResponseAge(); ??long freshMillis = computeFreshnessLifetime(); ? ??if (requestCaching.maxAgeSeconds() != -1) { ????freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds())); ??} ? ??long minFreshMillis = 0; ??if (requestCaching.minFreshSeconds() != -1) { ????minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds()); ??} ? ??long maxStaleMillis = 0; ??if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) { ????maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds()); ??} ? ??if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { ????Response.Builder builder = cacheResponse.newBuilder(); ????if (ageMillis + minFreshMillis >= freshMillis) { ??????builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\""); ????} ????long oneDayMillis = 24 * 60 * 60 * 1000L; ????if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) { ??????builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\""); ????} ????return new CacheStrategy(null, builder.build()); ??} ? ????......???? } |
如果緩存響應的Cache-Control首部包含immutable,那么說明該資源不會改變。客戶端可以直接使用緩存結果。值得注意的是immutable并不屬于http協議的一部分,而是由facebook提出的擴展屬性。
之后分別計算ageMills、freshMills、minFreshMills、maxStaleMills這四個值。?
如果響應緩存沒有通過Cache-Control:No-Cache 來禁止客戶端使用緩存,并且:
ageMillis + minFreshMillis < freshMillis + maxStaleMillis
這個不等式成立,那么我們進入條件代碼塊之后最終會返回networkRequest為空,并且使用當前緩存值構造的CacheStrtegy。
這個不等式究竟是什么含義呢?我們看看這四個值分別代表什么:
?
- ageMills 指這個緩存資源自響應報文在源服務器中產生或者過期驗證的那一刻起,到現在為止所經過的時間。用食品的保質期來比喻的話,好比當前時間距離生產日期已經過去了多久了。
- freshMills 表示這個資源在多少時間內是新鮮的。也就是假設保質期18個月,那么這個18個月就是freshMills。
- minFreshMills 表示我希望這個緩存至少在多久之后依然是新鮮的。好比我是一個比較講究的人,如果某個食品只有一個月就過期了,雖然并沒有真的過期,但我依然覺得食品不新鮮從而不想再吃了。
- maxStaleMills 好比我是一個不那么講究的人,即使食品已經過期了,只要不是過期很久了,比如2個月,那我覺得問題不大,還可以吃。
minFreshMills 和maxStatleMills都是由請求首部取出的,請求可以根據自己的需要,通過設置:
Cache-Control:min-fresh=xxx、Cache-Control:max-statle=xxx
來控制緩存,以達到對緩存使用嚴格性的收緊與放松。
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | private CacheStrategy getCandidate() { ????...... ? ??// Find a condition to add to the request. If the condition is satisfied, the response body ??// will not be transmitted. ??String conditionName; ??String conditionValue; ??if (etag != null) { ????conditionName = "If-None-Match"; ????conditionValue = etag; ??} else if (lastModified != null) { ????conditionName = "If-Modified-Since"; ????conditionValue = lastModifiedString; ??} else if (servedDate != null) { ????conditionName = "If-Modified-Since"; ????conditionValue = servedDateString; ??} else { ????return new CacheStrategy(request, null); // No condition! Make a regular request. ??} ? ??Headers.Builder conditionalRequestHeaders = request.headers().newBuilder(); ??Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue); ? ??Request conditionalRequest = request.newBuilder() ??????.headers(conditionalRequestHeaders.build()) ??????.build(); ??return new CacheStrategy(conditionalRequest, cacheResponse); } |
如果之前的條件不滿足,說明我們的緩存響應已經過期了,這時我們需要通過一個條件請求對服務器進行再驗證操作。接下來的代碼比較清晰來,就是通過從緩存響應中取出的Last-Modified,Etag,Date首部構造一個條件請求并返回。
接下來我們返回CacheInterceptor。
| 01 02 03 04 05 06 07 08 09 10 11 12 | // If we're forbidden from using the network and the cache is insufficient, fail. if (networkRequest == null && cacheResponse == null) { ??return new Response.Builder() ??????.request(chain.request()) ??????.protocol(Protocol.HTTP_1_1) ??????.code(504) ??????.message("Unsatisfiable Request (only-if-cached)") ??????.body(Util.EMPTY_RESPONSE) ??????.sentRequestAtMillis(-1L) ??????.receivedResponseAtMillis(System.currentTimeMillis()) ??????.build(); } |
可以看到,如果我們返回的networkRequest和cacheResponse都為空,說明我們即沒有可用的緩存,同時請求通過Cache-Controlnly-if-cached只允許我們使用當前的緩存數據。這個時候我們只能返回一個504的響應。接著往下看,
| 1 2 3 4 5 6 | // If we don't need the network, we're done. if (networkRequest == null) { ??return cacheResponse.newBuilder() ??????.cacheResponse(stripBody(cacheResponse)) ??????.build(); } |
如果networkRequest為空,說明我們不需要進行再驗證了,直接將cacheResponse作為請求結果返回。
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | Response networkResponse = null; ????try { ??????networkResponse = chain.proceed(networkRequest); ????} finally { ??????// If we're crashing on I/O or otherwise, don't leak the cache body. ??????if (networkResponse == null && cacheCandidate != null) { ????????closeQuietly(cacheCandidate.body()); ??????} ????} ? ????// If we have a cache response too, then we're doing a conditional get. ????if (cacheResponse != null) { ??????if (networkResponse.code() == HTTP_NOT_MODIFIED) { ????????Response response = cacheResponse.newBuilder() ????????????.headers(combine(cacheResponse.headers(), networkResponse.headers())) ????????????.sentRequestAtMillis(networkResponse.sentRequestAtMillis()) ????????????.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) ????????????.cacheResponse(stripBody(cacheResponse)) ????????????.networkResponse(stripBody(networkResponse)) ????????????.build(); ????????networkResponse.body().close(); ? ????????// Update the cache after combining headers but before stripping the ????????// Content-Encoding header (as performed by initContentStream()). ????????cache.trackConditionalCacheHit(); ????????cache.update(cacheResponse, response); ????????return response; ??????} else { ????????closeQuietly(cacheResponse.body()); ??????} ????} ? ? ?????Response response = networkResponse.newBuilder() ????????.cacheResponse(stripBody(cacheResponse)) ????????.networkResponse(stripBody(networkResponse)) ????????.build(); ? ????if (cache != null) { ??????if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { ????????// Offer this request to the cache. ????????CacheRequest cacheRequest = cache.put(response); ????????return cacheWritingResponse(cacheRequest, response); ??????} ? ??????if (HttpMethod.invalidatesCache(networkRequest.method())) { ????????try { ??????????cache.remove(networkRequest); ????????} catch (IOException ignored) { ??????????// The cache cannot be written. ????????} ??????} ????} ? ????return response; |
如果networkRequest存在不為空,說明這次請求是需要發到服務器的。此時有兩種情況,一種cacheResponse不存在,說明我們沒有一個可用的緩存,這次請求只是一個普通的請求。如果cacheResponse存在,說明我們有一個可能過期了的緩存,此時networkRequest是一個用來進行再驗證的條件請求。
不管哪種情況,我們都需要通過networkResponse=chain.proceed(networkRequest)獲取到服務器的一個響應。不同的只是如果有緩存數據,那么在獲取到再驗證的響應之后,需要cache.update(cacheResponse, response)去更新當前緩存中的數據。如果沒有緩存數據,那么判斷此次請求是否可以被緩存。在滿足緩存的條件下,將響應緩存下來,并返回。
OkHttp緩存大致的流程就是這樣,我們從中看出,整個流程是遵循了Http的緩存流程的。
最后我們總結一下緩存的流程:
?
- 1)從接收到的請求中,解析出Url和各個首部;
- 2)查詢本地是否有緩存副本可以使用;
- 3)如果有緩存,則進行新鮮度檢測,如果緩存足夠新鮮,則使用緩存作為響應返回,如果不夠新鮮了,則構造條件請求,發往服務器再驗證。如果沒有緩存,就直接將請求發往服務器;
- 4)把從服務器返回的響應,更新或是新增到緩存中。
?
6、有必要了解一下廣泛使用的OAuth認證授權協議
?
6.1什么是OAuth?
OAUTH協議為用戶資源的授權提供了一個安全的、開放而又簡易的標準。與以往的授權方式不同之處是OAUTH的授權不會使第三方觸及到用戶的帳號信息(如用戶名與密碼),即第三方無需使用用戶的用戶名與密碼就可以申請獲得該用戶資源的授權,因此OAUTH是安全的。oAuth是Open Authorization的簡寫。
基本上現在主流的第3方登陸接口都是使用或者類似于OAuth的實現原理,比如:QQ開放給第3方的登陸API、微信登陸API、新浪微博賬號登陸API等。
?
6.2OAuth的優點
?
- 1)簡單:不管是OAUTH服務提供者還是應用開發者,都很易于理解與使用;
- 2)安全:沒有涉及到用戶密鑰等信息,更安全更靈活;
- 3)開放:任何服務提供商都可以實現OAUTH,任何軟件開發商都可以使用OAUTH。
?
6.3OAuth授權流程原理
OAuth定義的幾個角色:
?
OAuth授權流程基本流程如下:
?
授權流程以有道云筆記為例(如下圖):
?
從上圖可以看出,一個典型的OAuth授權的流程主要分為6步:
?
- 1)客戶端向用戶申請授權;
- 2)用戶同意授權;
- 3)客戶端通過獲取的授權,向認證服務器申請Access Token;
- 4)認證服務器通過授權認證后,下發Access Token;
- 5)客戶端通過獲取的到Access Token向資源服務器發起請求;
- 6)資源服務器核對Access Token后下發請求資源。
?
6.4更多資料
?
- 《IM開發基礎知識補課(四):正確理解HTTP短連接中的Cookie、Session和Token》
- 《小白必讀:閑話HTTP短連接中的Session和Token》
- 《IM開發基礎知識補課:正確理解前置HTTP SSO單點登陸接口的原理》
?
7、必須要掌握的Https知識
?
7.1HTTPS基礎
蘋果已從去年開始強制新上線的APP必須使用HTTPS(詳見《蘋果即將強制實施 ATS,你的APP準備好切換到HTTPS了嗎?》),谷歌的Chrome瀏覽器也已宣布不支持https的網絡將被標記不“不安全”,做為開發者,我們能感覺到HTTPS越來越被重視,所以了解https也是必須的。
簡單的說:Http + 加密 + 認證 + 完整性保護 = Https。
傳統的Http協議是一種應用層的傳輸協議,Http直接與TCP協議通信。
其本身存在一些缺點:
?
- Http協議使用明文傳輸,容易遭到竊聽;
- Http對于通信雙方都沒有進行身份驗證,通信的雙方無法確認對方是否是偽裝的客戶端或者服務端;
- Http對于傳輸內容的完整性沒有確認的辦法,往往容易在傳輸過程中被劫持篡改。
因此,在一些需要保證安全性的場景下,比如涉及到銀行賬戶的請求時,Http無法抵御這些攻擊。??Https則可以通過增加的SSL\TLS,支持對于通信內容的加密,以及對通信雙方的身份進行驗證。
?
7.2Https的加密原理
近代密碼學中加密的方式主要有兩類:
?
- 1)對稱秘鑰加密;
- 2)非對稱秘鑰加密。
對稱秘鑰加密是指加密與解密過程使用同一把秘鑰。這種方式的優點是處理速度快,但是如何安全的從一方將秘鑰傳遞到通信的另一方是一個問題。
非對稱秘鑰加密是指加密與解密使用兩把不同的秘鑰。這兩把秘鑰,一把叫公開秘鑰,可以隨意對外公開。一把叫私有秘鑰,只用于本身持有。得到公開秘鑰的客戶端可以使用公開秘鑰對傳輸內容進行加密,而只有私有秘鑰持有者本身可以對公開秘鑰加密的內容進行解密。這種方式克服了秘鑰交換的問題,但是相對于對稱秘鑰加密的方式,處理速度較慢。
SSL\TLS的加密方式則是結合了兩種加密方式的優點。首先采用非對稱秘鑰加密,將一個對稱秘鑰使用公開秘鑰加密后傳輸到對方。對方使用私有秘鑰解密,得到傳輸的對稱秘鑰。之后雙方再使用對稱秘鑰進行通信。這樣即解決了對稱秘鑰加密的秘鑰傳輸問題,又利用了對稱秘鑰的高效率來進行通信內容的加密與解密。
安全方面的文章,可以詳細閱讀以下幾篇:
?
- 《即時通訊安全篇(一):正確地理解和使用Android端加密算法》
- 《即時通訊安全篇(二):探討組合加密算法在IM中的應用》
- 《即時通訊安全篇(三):常用加解密算法與通訊安全講解》
- 《即時通訊安全篇(四):實例分析Android中密鑰硬編碼的風險》
- 《即時通訊安全篇(五):對稱加密技術在Android平臺上的應用實踐》
- 《即時通訊安全篇(六):非對稱加密技術的原理與應用實踐》
- 《傳輸層安全協議SSL/TLS的Java平臺實現簡介和Demo演示》
- 《理論聯系實際:一套典型的IM通信協議設計詳解(含安全層設計)》
- 《微信新一代通信安全解決方案:基于TLS1.3的MMTLS詳解》
?
7.3Https的認證
SSL\TLS采用的混合加密的方式還是存在一個問題,即怎么樣確保用于加密的公開秘鑰確實是所期望的服務器所分發的呢?也許在收到公開秘鑰時,這個公開秘鑰已經被別人篡改了。因此,我們還需要對這個秘鑰進行認證的能力,以確保我們通信的對方是我們所期望的對象。
目前的做法是使用由數字證書認證機構頒發的公開秘鑰證書。服務器的運營人員可以向認證機構提出公開秘鑰申請。認證機構在審核之后,會將公開秘鑰與共鑰證書綁定。服務器就可以將這個共鑰證書下發給客戶端,客戶端在收到證書后,使用認證機構的公開秘鑰進行驗證。一旦驗證成功,即可知道這個秘鑰是可以信任的秘鑰。
?
7.4Https小結
Https的通信流程:
?
- 1)Client發起請求;
- 2)Server端響應請求,并在之后將證書發送至Client;
- 3)Client使用認證機構的共鑰認證證書,并從證書中取出Server端共鑰;
- 4)Client使用共鑰加密一個隨機秘鑰,并傳到Server;
- 5)Server使用私鑰解密出隨機秘鑰;
- 6)通信雙方使用隨機秘鑰最為對稱秘鑰進行加密解密。
?
附錄:相關資料匯總
[1] 更多網絡編程資料匯總:
《TCP/IP詳解?-?第11章·UDP:用戶數據報協議》
《TCP/IP詳解?-?第17章·TCP:傳輸控制協議》
《TCP/IP詳解?-?第18章·TCP連接的建立與終止》
《TCP/IP詳解?-?第21章·TCP的超時與重傳》
《技術往事:改變世界的TCP/IP協議(珍貴多圖、手機慎點)》
《通俗易懂-深入理解TCP協議(上):理論基礎》
《通俗易懂-深入理解TCP協議(下):RTT、滑動窗口、擁塞處理》
《理論經典:TCP協議的3次握手與4次揮手過程詳解》
《理論聯系實際:Wireshark抓包分析TCP 3次握手、4次揮手過程》
《計算機網絡通訊協議關系圖(中文珍藏版)》
《UDP中一個包的大小最大能多大?》
《P2P技術詳解(一):NAT詳解——詳細原理、P2P簡介》
《P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解》
《P2P技術詳解(三):P2P技術之STUN、TURN、ICE詳解》
《通俗易懂:快速理解P2P技術中的NAT穿透原理》
《高性能網絡編程(一):單臺服務器并發TCP連接數到底可以有多少》
《高性能網絡編程(二):上一個10年,著名的C10K并發連接問題》
《高性能網絡編程(三):下一個10年,是時候考慮C10M并發問題了》
《高性能網絡編程(四):從C10K到C10M高性能網絡應用的理論探索》
《不為人知的網絡編程(一):淺析TCP協議中的疑難雜癥(上篇)》
《不為人知的網絡編程(二):淺析TCP協議中的疑難雜癥(下篇)》
《不為人知的網絡編程(三):關閉TCP連接時為什么會TIME_WAIT、CLOSE_WAIT》
《不為人知的網絡編程(四):深入研究分析TCP的異常關閉》
《不為人知的網絡編程(五):UDP的連接性和負載均衡》
《不為人知的網絡編程(六):深入地理解UDP協議并用好它》
《不為人知的網絡編程(七):如何讓不可靠的UDP變的可靠?》
《網絡編程懶人入門(一):快速理解網絡通信協議(上篇)》
《網絡編程懶人入門(二):快速理解網絡通信協議(下篇)》
《網絡編程懶人入門(三):快速理解TCP協議一篇就夠》
《網絡編程懶人入門(四):快速理解TCP和UDP的差異》
《網絡編程懶人入門(五):快速理解為什么說UDP有時比TCP更有優勢》
《網絡編程懶人入門(六):史上最通俗的集線器、交換機、路由器功能原理入門》
《網絡編程懶人入門(七):深入淺出,全面理解HTTP協議》
《網絡編程懶人入門(八):手把手教你寫基于TCP的Socket長連接》
《技術掃盲:新一代基于UDP的低延時網絡傳輸層協議——QUIC詳解》
《讓互聯網更快:新一代QUIC協議在騰訊的技術實踐分享》
《現代移動端網絡短連接的優化手段總結:請求速度、弱網適應、安全保障》
《聊聊iOS中網絡編程長連接的那些事》
《移動端IM開發者必讀(一):通俗易懂,理解移動網絡的“弱”和“慢”》
《移動端IM開發者必讀(二):史上最全移動弱網絡優化方法總結》
《IPv6技術詳解:基本概念、應用現狀、技術實踐(上篇)》
《IPv6技術詳解:基本概念、應用現狀、技術實踐(下篇)》
《從HTTP/0.9到HTTP/2:一文讀懂HTTP協議的歷史演變和設計思路》
《腦殘式網絡編程入門(一):跟著動畫來學TCP三次握手和四次揮手》
《腦殘式網絡編程入門(二):我們在讀寫Socket時,究竟在讀寫什么?》
《腦殘式網絡編程入門(三):HTTP協議必知必會的一些知識》
>>?更多同類文章 ……
[2] Web端即時通訊資料匯總:
《新手入門貼:史上最全Web端即時通訊技術原理詳解》
《Web端即時通訊技術盤點:短輪詢、Comet、Websocket、SSE》
《SSE技術詳解:一種全新的HTML5服務器推送事件技術》
《Comet技術詳解:基于HTTP長連接的Web端實時通信技術》
《新手快速入門:WebSocket簡明教程》
《WebSocket詳解(一):初步認識WebSocket技術》
《WebSocket詳解(二):技術原理、代碼演示和應用案例》
《WebSocket詳解(三):深入WebSocket通信協議細節》
《WebSocket詳解(四):刨根問底HTTP與WebSocket的關系(上篇)》
《WebSocket詳解(五):刨根問底HTTP與WebSocket的關系(下篇)》
《WebSocket詳解(六):刨根問底WebSocket與Socket的關系》
《socket.io實現消息推送的一點實踐及思路》
《LinkedIn的Web端即時通訊實踐:實現單機幾十萬條長連接》
《Web端即時通訊技術的發展與WebSocket、Socket.io的技術實踐》
《Web端即時通訊安全:跨站點WebSocket劫持漏洞詳解(含示例代碼)》
《開源框架Pomelo實踐:搭建Web端高性能分布式IM聊天服務器》
《使用WebSocket和SSE技術實現Web端消息推送》
《詳解Web端通信方式的演進:從Ajax、JSONP 到 SSE、Websocket》
《MobileIMSDK-Web的網絡層框架為何使用的是Socket.io而不是Netty?》
《理論聯系實際:從零理解WebSocket的通信原理、協議格式、安全性》
《微信小程序中如何使用WebSocket實現長連接(含完整源碼)》
>>?更多同類文章 ……
(本文同步發布于:http://www.52im.net/thread-1751-1-1.html)
轉載于:https://my.oschina.net/jb2011/blog/1844292
總結
以上是生活随笔為你收集整理的脑残式网络编程入门(三):HTTP协议必知必会的一些知识的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android rxjava2的disp
- 下一篇: 邮件服务器软件EwoMail 1.05