腦殘式網絡編程入門(三):HTTP協議必知必會的一些知識

本文原做者:「竹千代」,原文由「玉剛說」寫做平臺提供寫做贊助,原文版權歸「玉剛說」微信公衆號全部,即時通信網收錄時有改動。php

 

一、前言


不管是即時通信應用仍是傳統的信息系統,Http協議都是咱們最常打交道的網絡應用層協議之一,它的重要性可能不須要再強調(有鑑於此,即時通信網整理了大量的有關http協議的文章,若有必要可從本文的相關連接處查閱)。可是實際上不少人(包括我本身),雖然天天都會跟http的代碼打交道,但對http瞭解的並不夠深刻。本文就我本身的學習心得,分享一下我認爲須要知道的http常見的相關知識點。

HTTP協議所處的TCP/IP協議層級:
<ignore_js_op>腦殘式網絡編程入門(三):HTTP協議必知必會的一些知識_11.jpg
▲ 上圖來自《計算機網絡通信協議關係圖(中文珍藏版)[附件下載]》,您可下載此圖的完整清晰版html

二、系列文章


本文是系列文章中的第3篇,本系列大綱以下:

java

 

三、Http協議報文相關知識

 

3.1基礎概念


首先咱們來點基礎的,看看http報文具體的格式。http報文能夠分爲請求報文和響應報文,格式大同小異。

主要分爲三個部分:

git

  • 1)起始行;
  • 2)首部;
  • 3)主體。


請求報文格式:github

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請求後,服務器返回所請求的資源,這時咱們能夠將該資源的副本存儲在本地,這樣當再次對該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一致,代表此次再驗證命中。  

其餘三個與斷點續傳涉及到的相關知識有關,本文暫時不討論。待我以後寫一篇文章來說講斷點續傳。

五、以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)把從服務器返回的響應,更新或是新增到緩存中。

 

六、有必要了解一下普遍使用的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定義的幾個角色:
<ignore_js_op>腦殘式網絡編程入門(三):HTTP協議必知必會的一些知識_111.jpg 

OAuth受權流程基本流程以下:
<ignore_js_op>腦殘式網絡編程入門(三):HTTP協議必知必會的一些知識_1111111.jpg 

受權流程以有道雲筆記爲例(以下圖):
<ignore_js_op>腦殘式網絡編程入門(三):HTTP協議必知必會的一些知識_1111.jpeg 

從上圖能夠看出,一個典型的OAuth受權的流程主要分爲6步:

  • 1)客戶端向用戶申請受權;
  • 2)用戶贊成受權;
  • 3)客戶端經過獲取的受權,向認證服務器申請Access Token;
  • 4)認證服務器經過受權認證後,下發Access Token;
  • 5)客戶端經過獲取的到Access Token向資源服務器發起請求;
  • 6)資源服務器覈對Access Token後下發請求資源。

 

6.4更多資料

 

 

七、必需要掌握的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的加密方式則是結合了兩種加密方式的優勢。首先採用非對稱祕鑰加密,將一個對稱祕鑰使用公開祕鑰加密後傳輸到對方。對方使用私有祕鑰解密,獲得傳輸的對稱祕鑰。以後雙方再使用對稱祕鑰進行通訊。這樣即解決了對稱祕鑰加密的祕鑰傳輸問題,又利用了對稱祕鑰的高效率來進行通訊內容的加密與解密。

安全方面的文章,能夠詳細閱讀如下幾篇:

 

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實現長鏈接(含完整源碼)
>> 更多同類文章 ……

相關文章
相關標籤/搜索