Java 基礎(十六)網路程式設計

diamond_lin發表於2017-11-17

寫了這麼久 Java 程式碼,對網路程式設計的瞭解還停留在簡單使用網路請求框架的階段。
說起網路程式設計的知識點,好像大部分的東西也都知道,但是好像就知道一個專有名詞的意思。比如說:
“URL 是什麼?”
“URL 就是統一資源定位器”
“嗯?完了?”
“它就是一個專有名詞啊”
“...”

再比如說:
“你瞭解 http 嗎?”
“哦,這個呀,我知道,超文字傳輸協議呀”
“嗯?還有嗎?”
“...”
“那 https 呢?”
“這就是一個安全的超文字傳輸協議呀”
"..."

網路程式設計嘛,說白了就是和伺服器的一次通話/互動資源,說起來其實很簡單,用起來好像也挺簡單的。
以下是使用 PostMan 自動生成的一次網路請求程式碼,和我們專案中的程式碼基本上差不多。

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
  .url("http://gank.io/api/history/content/2/1")
  .get()
  .addHeader("cache-control", "no-cache")
  .addHeader("postman-token", "c9b415ce-4a35-097c-1a53-b12d526d8d97")
  .build();

Response response = client.newCall(request).execute();複製程式碼

這個過程真的很簡單了,因為這特麼是已經封裝好的OkHttp,那麼問題來了,這些引數到底都表示什麼意思?引數都是以什麼樣的格式上傳給伺服器的?伺服器怎麼響應的?伺服器響應的資料是什麼格式?快取、Token、效能優化、異常怎麼處理?
今天,就和大家一起來探討這些問題。
在探討這些問題之前,得先做一些準備工作,首先我們來學習以下幾個知識點。

  • Internet 地址
  • URL 和 URI
  • HTTP
  • URLConnection
  • HttpURLConnection

Internet 地址

本來標題是想用 IP 的,查了一下發現IP 是Internet Protocol(網路協議)的縮寫,這個話題太大了,我們簡單瞭解一下 Internet 地址即可。

我們將所有連線到 Internet 的裝置看做一個節點(node),計算機節點稱為主機(host)。每個節點或主機都由至少一個唯一的數來標識,這稱為 Internet 地址,也就是我們所說的 IP 地址。

IPv4和 IPv6

就是4個位元組長度的ip 地址和6個位元組長度的 ip 地址。
IPv6目前還沒在世界範圍內推廣,大家記住因為 IPv4地址緊張不夠用才引入 IPv6的概念即可。
比如 61.135.169.121就是百度首頁伺服器的 ip 地址。

域名解析 DNS

剛剛我說了,61.135.169.121是百度伺服器的地址,但是我們訪問百度的時候用的是 www.baidu.com,這個網址叫域名,通常訪問的時候使用這個域名進行訪問,訪問的過程中會去 DNS 伺服器查詢域名“www.baidu.com”所對應的 IP 地址“61.135.169.121”,然後通過 IP 地址訪問伺服器。

InetAddress

java.net.InetAddress 類是 Java 對 IP地址(包括 v4和 v6)的高層表示。大多數其他網路都要用到這個類,包括 Socket、ServerSocket、URP、DatagramSocket、DatagramPacket 等。一般來說,它包括一個主機名和一個 IP 地址。
具體 api 就不細講了,反正和 IP地址相關的問題,都可以來找這個類。
子類實現有 Inet4Address 和 Inet6Address。
大多數情況下,我們不用考慮 v4還是 v6,因為 v6還沒普及,從程式碼層面來說,用 v4還是 v6,Java 已經幫我們處理好了,作為應用層(OSI 模型的最高層)的我們不需要關心這些。

URL 和 URI

在上面我們了街道如何通過主機名和 IP 地址確定主機在 Internet 的地址(其實就是呼叫 InetAddress 的 api)。這裡我們繼續學習如何確定資源的地址。

HTML 是一個超文字標記語言,因為它提供了一種方法,可以知道 URL 標識的其他文件的連結。URL 可以唯一地標識一個資源在 Internet 上的位置。URL 是最常見的 URI(統一資源識別符號)。URI 可以由資源的網路位置來標識資源,也可以由資源的名字、編號或其他特性來標識。

URL 類是 Java 程式在網路上定位和獲取資料最簡單的方法。你不需要考慮所使用協議的細節,也不用擔心如何與吳福氣通訊。只要把 URL 告訴 Java,它就會為你獲得資料。

URI 和 URL 的區別

統一資源標誌符URI就是在某一規則下能把一個資源獨一無二地標識出來。拿人做例子,假設這個世界上所有人的名字都不能重複,那麼名字就是URI的一個例項,通過名字這個字串就可以標識出唯一的一個人。
現實當中名字當然是會重複的,所以身份證號才是URI,通過身份證號能讓我們能且僅能確定一個人。
那統一資源定位符URL是什麼呢。也拿人做例子然後跟HTTP的URL做類比,就可以有:動物住址協議://地球/中國/浙江省/杭州市/西湖區/某大學/14號宿舍樓/525號寢/張三.人可以看到,這個字串同樣標識出了唯一的一個人,起到了URI的作用,所以URL是URI的子集。
URL是以描述人的位置來唯一確定一個人的。在上文我們用身份證號也可以唯一確定一個人。對於這個在杭州的張三,我們也可以用:身份證號:123456789來標識他。所以不論是用定位的方式還是用編號的方式,我們都可以唯一確定一個人,都是URI的一種實現,而URL就是用定位的方式實現的URI。回到Web上,假設所有的Html文件都有唯一的編號,記作html:xxxxx,xxxxx是一串數字,即Html文件的身份證號碼,這個能唯一標識一個Html文件,那麼這個號碼就是一個URI。而URL則通過描述是哪個主機上哪個路徑上的檔案來唯一確定一個資源,也就是定位的方式來實現的URI。對於現在網址我更傾向於叫它URL,畢竟它提供了資源的位置資訊,如果有一天網址通過號碼來標識變成了http://741236985.html,那感覺叫成URI更為合適,不過這樣子的話還得想辦法找到這個資源咯…

以上,摘抄自知乎。接下來,我們來看看 URL 和 URI 的 api 吧

URI

以下,是從URI 原始碼裡面摘選出來的九個重要欄位

private transient String scheme;//方案
private transient String fragment;//特定於方案的部分
private transient String authority;//授權
private transient String userInfo;//使用者資訊
private transient String host;//主機
private transient int port = -1;//埠
private transient String path;//路徑
private transient String query;//查詢
private volatile transient String schemeSpecificPart;//片段複製程式碼

可能大家還是不太理解 URI 是啥,其實就是將一個String地址分解成特定的屬性。看看下圖就能明白了~

URL

直接看圖吧~~

URL 的 api 基本和 URI 差不多,大多都是構造方法和各個欄位的get、set 方法。但是有個方法我們需要注意一下。

  • public URLConnection openConnection()

URL 可以根據我們傳的引數直接建立一個通訊連結,我們來簡單看看是怎麼建立的。

public URLConnection openConnection() throws java.io.IOException {
    return handler.openConnection(this);
}複製程式碼

在 URL 類裡面找到 handler 的建立程式碼如下:

if (handler == null) {
    try {
            if (protocol.equals("file")) {
                handler = (URLStreamHandler)Class.forName("sun.net.www.protocol.file.Handler").newInstance();
            } else if (protocol.equals("ftp")) {
                handler = (URLStreamHandler)Class.forName("sun.net.www.protocol.ftp.Handler").newInstance();
            } else if (protocol.equals("jar")) {
                handler = (URLStreamHandler)Class.forName("sun.net.www.protocol.jar.Handler").newInstance();
            } else if (protocol.equals("http")) {
                handler = (URLStreamHandler)Class.forName("com.android.okhttp.HttpHandler").newInstance();
            } else if (protocol.equals("https")) {
                handler = (URLStreamHandler)Class.forName("com.android.okhttp.HttpsHandler").newInstance();
            }
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }複製程式碼

這裡根據 protocol 欄位,建立了不同的 handler,根據邏輯我們可以看到建立了 HttpHandler,使用 HttpHandler 的 openConnection()方法建立了一個sun.net.www.protocol.http.HttpURLConnection 物件。

HTTP

超文字傳輸協議(Hypertext Transfer Protocol,HTTP)是一個標準,定義了客戶端如何與伺服器對話,以及資料如何從伺服器傳回客戶端。儘管通常認為 HTTP是一種傳輸 HTML 檔案以及檔案中內嵌圖片的方法,但實際上 HTTP 是一個資料格式。它可以用來傳輸 TIFF 圖片、Word 文件、exe 檔案等。這裡我們將深入後臺,瞭解當我們子安位址列輸入http://www.google.com並按下Enter鍵時到底發生了什麼。

HTTP 協議

HTTP 是客戶端和伺服器之間通訊的標準協議。HTTP 指定客戶端與伺服器如何建立連線、客戶端如何從伺服器請求資料,伺服器如何響應請求,以及最後如何關閉連線。HTTP 連線使用 TCP/IP 來傳輸資料。對於從客戶端到伺服器的每一個請求,都有4個步驟:

1.預設情況下,客戶端在埠80開啟與伺服器的一個 TCP 連線,URL 中還可以指定其他埠。
2.客戶端向伺服器發生訊息,請求指定路徑上的資源。這個請求包括一個首部,可選的(取決於請求的性質)還可以有一個空行,後面是這個請求的資料。
3.伺服器向客戶端發生響應,響應以響應碼開頭,後面是包含資料的首部、一個空行以及所請求的文件或錯誤訊息。
4.伺服器關閉連線。

這是基本 HTTP1.0過程,在 HTTP1.1以及以後的版本中,可以通過一個 TCP 連線連續傳送多個請求和響應。也就是說,在第一步和第四步直接,第二步和第三步可以反覆多次。另外,在 HTTP1.1中,請求和響應可以分為多個塊傳送。這樣有更好的擴充套件性。

HTTP Request

每個請求和響應都有同樣的基本形式:一個首部行、一個包含元素資料的 HTTP 首部、一個空行,然後是一個訊息體。

格式如下:

  • 請求行,分為三部分:請求方法、請求地址、協議版本

例如 GET/image/logo.gif HTTP/1.1,表示從/image目錄下請求 logo.gif這個檔案

  • 請求頭,用於傳遞一些附加資訊

常見通用請求 Header

名稱 作用
Content-Type 請求體的型別,如:text/plain,application/json
Accept 說明接收的型別,可以多個值,用","隔開
Content-Length 請求體長度
Content-Encoding 請求體的編碼格式,如 gzip、deflate
Accept-Encoding 告知對方我方接受的 Content-Encoding
Catche-Control 取值一般為 no-catche、max-age=xx(xx 為整數,表示自願快取 xx 秒)
  • 空行 請求頭結束的識別符號
  • 可選訊息體

也就是三個部分,分別是:請求行、請求頭、請求正文

請求方法:

HTTP/1.1協議中一共定義了八種方法來表面 Request-URI指定的資源的不同操作方式:OPTIONS、HEAD、GET、POST、PUT、DELETE、TRACE、CONNECT

這八種方法裡面,現在我們一般都只有 GET 和 POST 方法。

GET 和 POST 的區別:
1.GET 提交的資料會放在 URL 之後,以?分割 URL 和傳輸資料,引數之間以&相連。
2.GET 提交的資料大小有限制,最多隻能有1024位元組,而 POST 方法提交的資料沒有限制
3.GET 方式需要使用 Requ.QueryString來取得變數的值,而 POST 方式通過 Request.Form來獲取值
4.Get 方式提交資料,會帶來安全問題,比如一個登陸頁面,通過 GET 方式提交資料時,使用者名稱和密碼將出現在 URL 上,如果頁面可以被快取或者其他人可以訪問這臺機器,就可以從歷史記錄獲取該使用者的賬號和密碼。

HTTP Response

當客戶端向伺服器發生一個請求,伺服器以一個狀態作為相應,相應的內容包括:訊息協議的版本、成功或者錯誤編碼、伺服器資訊、實體元資訊以及必要的實體內容。根據響應類別,伺服器響應可以包含實體內容,但不是所有的響應都應有實體內容。

響應訊息的結構也分為三部分

  • 響應狀態行
    主要包含 HTTP 版本資訊(一般是1.1)和狀態碼,狀態碼對於資訊如下所示:
狀態碼 對應資訊
1xx 提示資訊-表示請求已接受,繼續處理
2xx 用於表示請求已經被成功接收、處理、
3xx 用於表示自願被永久轉到其他 URL,也就是重定向
4xx 客戶端的錯誤-請求有語法錯誤或無法實現
5xx 伺服器端錯誤-伺服器未能實現合法的請求
  • 響應頭

響應頭同樣可用於傳遞一些附加資訊。
常見響應 Header

名詞 作用
Date 伺服器日期
Last-Modified 最後被修改時間
Transfer-Encoding 取值一般為 chunked,一般出現在響應體長度不能確定的情況下
Set-cookie 設定 Cookie
Location 重定向到另一個 URL
Server 後臺伺服器
  • 響應體

響應體也就是我們需要的內容,一般在相應頭中會用 Content-Length來明確相應體的長度,便於瀏覽器接受,對於大資料量的正文訊息,也會使用 chunked 的編碼方式。

HTTPS

簡介

HTTPS(Hypertext Transfer Protocol over Secure Socket Layer),是以安全為目標的 http 通道,簡單的講就算 HTTP 的安全版。即HTTP 下加入 SSL 層,HTTPS 的安全基礎是 SSL,因此加密的詳細內容就需要 SSL。

HTTPS 和 HTTP 的區別

1.https 需要到 ca 申請證照,一般免費證照很少,需要交費。
2.http 是超文字傳輸協議,資訊是明文傳輸;https 則是具有安全性的 ssl 加密傳輸協議。
3.http 和 https 室友完全不同的連線方式,用的埠也不一樣,前者是80,後者是443.
4.http 的連結很簡單,無狀態的。https 協議是由ssl+http 協議構建的可進行加密傳輸、身份認證的網路協議,比 http 協議安全。

https 的作用

它的主要作用可以分為兩種:一種是建立一個資訊保安通道,來保證資料傳輸安全;另一種是確認網站的真實性。

1.一般意義上的 https,就是伺服器有一個證照。主要目的是保證伺服器就是他聲稱的伺服器,這個跟第一點一樣;服務端和客戶端之間的所有通訊,都是加密的。
2.具體講,是客戶端產生的一個對稱的祕鑰,通過伺服器的證照來交換祕鑰,即一般意義上的握手過程。
3.接下來所有的資訊往來都是加密的。即使第三方擷取,也沒有任何意義,因為他沒有祕鑰,當然篡改也就沒有什麼意義了。
4.少許對客戶端有妖氣的情況下,會要求客戶端也必須有一個證照。

這裡的客戶端證照,其實就類似表示個人資訊的時候,除了使用者名稱/密碼,還有一個 CA 認證過的身份。因為個人證照一般來說是別人無法模擬的,所有這樣能夠更深的確認自己的身份。少數個人銀行的專業版是這種做法,比如 U 盾。

SSL 就不講了吧,涉及的東西太多了。

URLConnection

URLConnection 是一個抽象類,表示指向 URL 指定資源的活動連線。URLConnection 有了兩個不同但相關的用途。首先,與 URL 類相比,它對與伺服器(特別是 HTTP 伺服器)的互動提供了更多的控制。URLConnection 可以檢查伺服器傳送的首部,並相應地做出響應。它可以設定客戶端請求中使用的首部欄位。最後,URLConnection 可以用 POST、PUT 和其他 HTTP 請求方法向伺服器發生資料。

開啟 URLConnection

直接使用URLConnection 類的程式遵循以下基本步驟:

1.構造一個 URL 物件
2.呼叫這個 URL 物件的 openConnection()獲取一個對應該 URL 的 URLConnection 物件。(如 http 請求獲取的是 HttpURLConnection)
3.配置這個 URLConnection
4.讀取首部欄位
5.獲取輸入流並讀取資料
6.獲取輸出流並寫入資料
7.關閉連線

並不一定執行所有這些步驟。例如,如果某種 URL 的預設設定是可以接受的,那麼可能會跳過步驟3。如果只需要伺服器的資料,不關心任何元資訊,或者協議不提供任何元資訊,那麼可以跳過步驟4。如果只希望接受伺服器的資料,而不向伺服器發生資料,就可以跳過步驟6.依據不同協議,步驟5和6的順序可能會反過來或者交替出現。

URLConnection 類僅有一個建構函式為保護型別。

protected RULConnection(RUL url)複製程式碼

因此,除非派生 URLConnection 的子類來處理新的 URL 型別,否則要通過呼叫 URL 類的 openConnection()方法來建立這樣一個物件。

try{
    URL u = new URL("http://www.baidu.com");
    URLConnection uc = u.openConnection();
}catch(Exception e){
    System.out.println(e);
}複製程式碼

URLConnection類宣告為抽象類。不過,除了一個方法外,其餘方法都已經實現。你會發現覆蓋這個類的其他方法很方便,或者可能很有必要。必須由子類實現的一個方法是 connect(),他建立與伺服器的連線,因而依賴於服務型別(HTTP,FTP 等)。例如 sun.net.www.protocol.http.HttpURLConnection的 connect()方法會建立一個sun.net.www.http.HttpClient物件,由他負責連線伺服器。

第一次構造 URLConnection 時,它是未連線的。也就是說,本地和原創主機無法傳送和接收資料。沒有 sockey 連線這兩個主機。connect()方法在本地和遠端主機之間建立一個連線(一般使用 TCP socket,但也可能通過其他機制來建立),這樣就可以收發資料了,不過,對於 getInputStream()、getContent()、getHeaderFiled()和其他要求開啟連線的方法,如果連線尚未開啟,他們就會呼叫 connect()。因此,你很少需要直接呼叫 connect()。

讀取伺服器的資料

下面是使用 URLConnection 物件從一個 URL 獲取資料所需的最起碼的步驟:

1.構造一個 URL 物件。
2.呼叫這個 URL 物件的 openConnection()方法,獲取對應的 URLConnection。
3.呼叫這個 URLConnection 的 getInputStream()方法
4.使用通常的流 API 讀取輸入流。

getInputStream() 方法返回一個通用的 InputStream,可以讀取和解析伺服器傳送的資料。以下示例使用 URLConnection 下載一個 Web 頁面:

對於這個例子中這樣一個簡單的輸入流,URL 和 URLConnection 直接的區別並不明顯。
這兩個類的最大不同在於:

  • URLConnection 提供了對 HTTP 首部的訪問
  • URLConnection 可以配置傳送給伺服器的請求引數
  • URLConnection 除了讀取伺服器資料外,還可以向伺服器寫入資料

讀取首部

HTTP 伺服器在每個響應前面的首部中提供了大量資訊。例如,下面是一個伺服器返回的典型的 HTTP 首部:

bdpagetype →1
bdqid →0xef0ab4fd0001728d
bduserid →0
cache-control →private
connection →Keep-Alive
content-encoding →gzip
content-type →text/html; charset=utf-8
cxy_all →baidu+631df64ada7e1077f47313160f7a4090
date →Thu, 16 Nov 2017 08:20:15 GMT
expires →Thu, 16 Nov 2017 08:19:54 GMT
p3p →CP=" OTI DSP COR IVA OUR IND COM "
server →BWS/1.1
strict-transport-security →max-age=172800
transfer-encoding →chunked
vary →Accept-Encoding
x-powered-by →HPHP
x-ua-compatible →IE=Edge,chrome=1複製程式碼

這裡有大量資訊,haha,我很多都不認識,具體欄位可對照我上文列的那個響應頭欄位表。

URLConnection 有幾個獲取常用響應頭欄位的方法

  • getContentType() 獲取響應主體的 MIME 內容型別
  • getContentLength() 獲取內容中有多少位元組
  • getContentEncoding()獲取內容的編碼格式
  • getDate() 獲取內容返回時間
  • getLastModified() 獲取最近修改時間
  • getExpires() 表示文件過期時間,過期後需要重新下載
  • getHeaderField(String name)獲取首部的任意欄位

快取

Web 瀏覽器多年來一直在快取頁面和圖片。如果一個 logo 在網站的每一個頁面上重複出現,瀏覽器一般只會從遠端伺服器上載入一次,將它儲存在快取上,每次需要的時候會從快取重新載入,而不是每次遇到這個 logo 都請求遠端伺服器載入。一些 HTTP 首部(包括 Expires 和 Cache-control)可以控制快取。

預設情況下,一般認為使用 GET 通過 HTTP 訪問的頁面可以快取,也應當快取。使用 HTTPS 或 POST 訪問的頁面不應快取。不過 HTTP 首部可以對此作出調整:

  • Expires 首部(主要針對 HTTP1.0)指示可以快取這個資源表示,直到指定的世界為止。
  • Catche-control 首部(HTTP1.1)提供了細粒度的快取策略:

    • max-age=[seconds]:從現在直到快取項過期之前的秒數
    • s-maxage=[seconds]:從現在起,直到快取項在共享快取中過期之前的秒數。私有快取可以將快取項儲存更長時間
    • public:可以快取一個經過認證的響應。否則已認證的響應不能快取。
    • private:僅耽擱使用者快取可以儲存響應,而共享快取不應儲存。
    • no-catch:這個策略的作用與名字不太一致。快取項仍然可以快取,不過客戶端在每次訪問時要用一個 Etag 或 Last-modified 首部重新驗證響應的狀態。
    • no-store:不管怎樣都不快取。

      如果 Catch-control 和 Expires 首部都出現,Cache-control 會覆蓋 Expires。伺服器可以在一個首部中傳送多個 Cache-control 首部,只要它們沒有衝突

  • Last-modified 首部指示資源最後一次修改的日期。客戶端可以用一個 HEAD 請求來檢查這個日期,只要當本地快取的副本遭遇 Last-modified 日期時,它才會真正執行 GET 來獲取資源。

  • Etag 首部(HTTP1.1)是資源改變時這個資源的位移識別符號。客戶端可以使用一個 HEAD 請求來檢查這個識別符號,只有當本地快取的副本有一個不同的 Etag 時,它才會真正執行 GET 來獲取資源。

配置連線

URLConnection 類有7個保護的例項欄位,定義了客戶端如何向伺服器做出請求。這些欄位包括

protected URL url;
protected boolean doInpt = true;
protected boolean doOutput = false;
protected boolean allowUserInteraction = defaultAllowUserInteraction;
protected useCaches = defaultUserCachesl;
protected long ifModifiedSince = 0;
protected boolean connected = false;複製程式碼

例如,如果 doOutput 為 true,那麼除了通過 URLConnection 讀取資料外,還可以通過將資料寫入到伺服器。如果useCaches為 false,連線會染過所有本地快取,重新從伺服器下載檔案。

由於這些欄位是保護欄位,所以要讀寫這些欄位只能通過 get、set 方法。

只能在 URLConnection 連線之前修改這些欄位。對於設定欄位的方法,如果呼叫這些方法時連線已經開啟,大多數會丟擲一個 IllegaStateException 異常。一般情況下,只能在連線開啟之前設定 URLConnection 物件的屬性。

  • protected URL url

url 欄位制定了這個 URLConnection 連線的 URL。建構函式會在建立 URLConnection 時設定這個欄位,此後不能再改變。可以通過呼叫 getURL()方法獲取這個欄位的值。

  • protected boolean connected

如果連線已經開啟,boolean 欄位 connected 為 true,如果連線關閉,這個欄位則為 false。由於在建立一個新 URLConnection 物件時連線尚未開啟,所以初始值為 false。

  • protected boolean allowUserInteraction

有些 URLConnection 需要與使用者互動。例如,Web 瀏覽器可能需要使用者名稱和口令。不過,很多應用程式不能假定真實存在一個可以與它互動的使用者。不過好像我們 Android 開發用不上。

  • protected boolean doInput

URLConnection 可以用於讀取伺服器、寫入伺服器,或者同時用於讀/寫伺服器。如果 URLConnection 可以用來讀取,保護型別 boolean 欄位 doInput 就為 true,否則為 false。預設值是 true。

  • protected boolean doOutput

程式可以用 URLConnection 將輸出發回伺服器。例如,如果程式需要使用 POST 方法向伺服器發生資料,可以通過從 URLConnection 獲取輸出流來完成。如果 URLConnection 可以用於寫入,欄位 doOutput 就為 true,否則為 false。

  • protected boolean ifModifySince

許多客戶端會保留以前獲取的文件快取。如果使用者再次要求相同的文件,可以從快取中獲取。不過,在最後一次獲取這個文件之後,伺服器上的文件可能會改變。要判斷是否有變化,唯一的辦法就是詢問伺服器。客戶端可以在戶請求 HTTP 首部中包括一個If-MOdified_since。這個首部包括一個日期和時間。如果文件在這個時間之後有所修改,伺服器就傳送該文件,否則就不發生。一般情況下,這個世界是客戶端最後獲得文件的時間。

  • protected boolean useCaches

有些客戶端可以從本地快取獲取文件,而不是從伺服器獲取。applet 可以訪問瀏覽器的快取。獨立的應用程式可以用 java.net.ResponseCache類。如果有快取,useCaches 變數確定了是否可以使用快取。預設值為 true,表示將使用快取。

超時

public void setConnectionTimeout()

呼叫這個方法設定連線超時。

配置客戶端請求 HTTP 首部

HTTP 客戶端(如瀏覽器)向伺服器發生一個請求行和一個首部。

伺服器可以根據請求資訊向客戶端返回不同的資料,獲取和設定cookie,通過口令認證使用者等。通過再客戶端傳送和伺服器響應的首部中放置不同的欄位,就可以完成這些工作。

  • public void setRequestProperty(String name,String value)

setRequestProperty()方法指定的名和值為這個 URLConnection 的首部加一個欄位。這個方法只能在連線開啟之前使用。重複呼叫這個方法會覆蓋之前的欄位。如果要增加一對,需要使用 addRequestProperty()方法。

並沒有一個固定的合法首部列表。伺服器一般會忽略無法識別的首部。HTTP 確實對首部欄位的名和值有一些限制。例如,名不能包含空白符,值不能包含任何換行符。如果一個欄位包含換行符,會丟擲 IllegalArgumentException 異常。

向伺服器寫入資料

有時候需要向URLConnection 寫入資料,例如,使用 POST 向伺服器提交表單,或者使用 PUT 上傳檔案。getOutputStream()方法返回一個 OutputStream,可以用來寫入資料傳給伺服器:

public OutputStream getOutputStream()複製程式碼

由於 URLConnection 在預設情況下不允許輸出,所以在請求輸入流之前必須呼叫 setDooutput(true)。為一個 http URL 將 doOutput 設定為 true 時,請求方法將由GET 變為 POST。

猜測 MIME 媒體型別

  • public static String guessContentTypeFromName(String var0)

通過副檔名判斷,一般來說沒有問題

  • public static String guessContentTypeFromStream(InputStream var0)

通過嘗試檢視流中前幾個位元組來猜測內容型別,猜測結果通常沒有根據副檔名猜測靠譜。

HttpURLConnection

java.net.HttpURLConnection類是 URLConnection 的抽象子類。它提供了另外一些方法,在處理 http RUL 時尤其有幫助。具體的,它包含的方法可以獲得和設定請求方法、確定是否重定向、獲取響應碼和訊息,以及確定是否使用了代理伺服器。

請求方法

當客戶端聯絡一個伺服器時,它傳送的第一個內容是請求行。一般情況下,這一行以 GET 開頭,後面是客戶端希望獲取的資源路徑,以及 HTTO 版本。

通常情況下,預設請求方法是 GET,可以通過以下方法進行修改

  • public void setRequestMethod(String method)

請求的方法有 GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE,一般我們 Android 都只用 GET 和 POST,但是現在好像後臺都比較喜歡統一用 POST 了。

斷開與伺服器的連線

HTTP1.1支援持久連線,允許一個 TCP socket 傳送多個請求和響應。伺服器不會因為已經向客戶端傳送了最後一個位元組的資料就立即關閉連線。也就是說,在伺服器關閉連線之前,如果再連線同一個伺服器,它會重用 socket。手動呼叫 disconnect()方法會主動斷開連線,如果還有開啟的流,也會關閉。但是反過來關閉開啟的流,並不會關閉連線。

處理伺服器響應

前面我們介紹過伺服器響應的格式,這裡就不再贅述。

  • getResponseCode() 獲取響應狀態碼
  • getResponseMessage()獲取響應碼後面的文字字串

重定向

300一級的響應嗎都表示某種重定向,即請求的資源不再期望的位置上,但有可能會在其他位置找到。遇到這樣的響應時,大多數瀏覽器會自動從新位置載入文件。

預設情況下,HTTPURLConnection 會跟隨重定向,不過 HTTPURLConnection 有兩個靜態方法可以控制是否跟隨重定向。

  • public static boolean getFollowRedirects()
  • public static void steFollowRedirects(boolean follow)

注意,這裡是靜態方法,全域性修改。

結束語

可能這一章寫的比較凌亂,其實我也很絕望,很多東西我也是一邊學一邊記錄的,這已經是整理的第三版筆記了。

這一章學習的主要目的是為了對整個 HTTP請求有個大致的瞭解,很多知識點都是一筆帶過(還忽略了一些),同學們知道有這麼回事就行了,大概在十八、十九課的時候,我會和大家一起用我們學過的知識點,手擼一個 Volley 網路請求架構。

我提前透露一下這次手擼的 Volley會實現哪些需求以及用到了哪些知識點。

實現的需求

  • 支援請求 JSON 文字型別學,音訊,圖片型別,批量下載。上傳~
  • 請求各種資料時,呼叫層不用關心上傳引數的封裝
  • 獲取資料後,呼叫層不用關心 JSON 資料的解析
  • 回撥時,呼叫層只需要知道傳入的 JSON 的對應響應類
  • 回撥響應結果發生在主執行緒(執行緒切換)
  • 對下載,上傳擴充套件
  • 支援高併發請求,請求佇列一次獲取,可以設定最大併發數,設定先請求先執行

會用到的知識點

  • 泛型
  • 請求佇列
  • 阻塞佇列
  • 執行緒拒絕策略

用到的設計模式

  • 模板方法模式
  • 單例模式
  • 策略模式
  • 生產者消費者模式(不屬於23種基本的消費模式 haha)

相關文章