一区二区三区欧美日韩-一区二区三区欧美-一区二区三区免费在线视频-一区二区三区免费在线观看-久久精品店-久久精品第一页

歡迎您光臨深圳塔燈網(wǎng)絡(luò)科技有限公司!
電話圖標(biāo) 余先生:13699882642

網(wǎng)站百科

為您解碼網(wǎng)站建設(shè)的點(diǎn)點(diǎn)滴滴

第6章 網(wǎng)絡(luò)編程與網(wǎng)絡(luò)框架

發(fā)表日期:2018-02 文章編輯:小燈 瀏覽次數(shù):2911

6.1 公鑰密鑰加密原理

6.1.1 基礎(chǔ)知識(shí)

  • 密鑰:一般就是一個(gè)字符串或數(shù)字,在加密或者解密時(shí)傳遞給加密/解密算法。
  • 對(duì)稱加密算法:加密和解密都是使用的同一個(gè)密鑰。因此對(duì)稱加密算法要保證安全性的話,密鑰要做好保密,只能讓使用的人知道,不能對(duì)外公開(kāi)。
  • 非對(duì)稱加密算法:加密使用的密鑰和解密使用的密鑰是不同的。 公鑰密碼體制就是一種非對(duì)稱加密算法。

(1) 公鑰密碼體制

分為三個(gè)部分:公鑰、私鑰、加密/解密算法

加密解密過(guò)程如下:
加密:通過(guò)加密算法和公鑰對(duì)內(nèi)容(或者說(shuō)明文)進(jìn)行加密,得到密文。
解密:通過(guò)解密算法和私鑰對(duì)密文進(jìn)行解密,得到明文。

注意:由公鑰加密的內(nèi)容,只能由私鑰進(jìn)行解密。

公鑰密碼體制的公鑰和算法都是公開(kāi)的,私鑰是保密的。在實(shí)際的使用中,有需要的人會(huì)生成一對(duì)公鑰和私鑰,把公鑰發(fā)布出去給別人使用,自己保留私鑰。

(2) RSA加密算法

一種公鑰密碼體制,公鑰公開(kāi),私鑰保密,它的加密解密算法是公開(kāi)的。 RSA的這一對(duì)公鑰、私鑰都可以用來(lái)加密和解密,并且一方加密的內(nèi)容可以由并且只能由對(duì)方進(jìn)行解密。

(3) 簽名

就是在信息的后面再加上一段內(nèi)容,可以證明信息沒(méi)有被修改過(guò)。

一般是對(duì)信息做一個(gè)hash計(jì)算得到一個(gè)hash值(該過(guò)程不可逆),在把信息發(fā)送出去時(shí),把這個(gè)hash值加密后做為一個(gè)簽名和信息一起發(fā)出去。 接收方在收到信息后,會(huì)重新計(jì)算信息的hash值,并和信息所附帶的hash值(解密后)進(jìn)行對(duì)比,如果一致,就說(shuō)明信息的內(nèi)容沒(méi)有被修改過(guò),因?yàn)檫@里hash計(jì)算可以保證不同的內(nèi)容一定會(huì)得到不同的hash值,所以只要內(nèi)容一被修改,根據(jù)信息內(nèi)容計(jì)算的hash值就會(huì)變化。

當(dāng)然,不懷好意的人也可以修改信息內(nèi)容的同時(shí)也修改hash值,從而讓它們可以相匹配,為了防止這種情況,hash值一般都會(huì)加密后(也就是簽名)再和信息一起發(fā)送,以保證這個(gè)hash值不被修改。

6.1.2 基于RSA算法的加密通信的例子

“客戶”->“服務(wù)器”:你好
“服務(wù)器”->“客戶”:你好,我是服務(wù)器
“客戶”->“服務(wù)器”:向我證明你就是服務(wù)器
“服務(wù)器”->“客戶”:你好,我是服務(wù)器{你好,我是服務(wù)器}[私鑰|RSA]
“客戶”->“服務(wù)器”:{我們后面的通信過(guò)程,用對(duì)稱加密來(lái)進(jìn)行,這里是對(duì)稱加密算法和密鑰}[公鑰|RSA]
“服務(wù)器”->“客戶”:{OK,收到!}[密鑰|對(duì)稱加密算法]
“客戶”->“服務(wù)器”:{我的帳號(hào)是aaa,密碼是123,把我的余額的信息發(fā)給我看看}[密鑰|對(duì)稱加密算法]
“服務(wù)器”->“客戶”:{你的余額是100元}[密鑰|對(duì)稱加密算法]

總結(jié)一下,RSA加密算法在這個(gè)通信過(guò)程中所起到的作用主要有兩個(gè):
1. 因?yàn)樗借€只有“服務(wù)器”擁有,因此“客戶”可以通過(guò)判斷對(duì)方是否有私鑰來(lái)判斷對(duì)方是否是“服務(wù)器”。
2. 客戶端通過(guò)RSA的掩護(hù),安全的和服務(wù)器商量好一個(gè)對(duì)稱加密算法和密鑰來(lái)保證后面通信過(guò)程內(nèi)容的安全。

但是這里還留有一個(gè)問(wèn)題,“服務(wù)器”要對(duì)外發(fā)布公鑰,那“服務(wù)器”如何把公鑰發(fā)送給“客戶”呢?
我們可能會(huì)想到以下的兩個(gè)方法:
a) 把公鑰放到互聯(lián)網(wǎng)的某個(gè)地方的一個(gè)下載地址,事先給“客戶”去下載。
b) 每次和“客戶”開(kāi)始通信時(shí),“服務(wù)器”把公鑰發(fā)給“客戶”。

但是這個(gè)兩個(gè)方法都有一定的問(wèn)題,
對(duì)于a)方法,“客戶”無(wú)法確定這個(gè)下載地址是不是“服務(wù)器”發(fā)布的,你憑什么就相信這個(gè)
地址下載的東西就是“服務(wù)器”發(fā)布的而不是別人偽造的呢,萬(wàn)一下載到一個(gè)假的怎么辦?另外要所有的“客戶”都在通信前事先去下載公鑰也很不現(xiàn)實(shí)。
對(duì)于b)方法,也有問(wèn)題,因?yàn)槿魏稳硕伎梢宰约荷梢粚?duì)公鑰和私鑰,他只要向“客戶”發(fā)送他
自己的私鑰就可以冒充“服務(wù)器”了。示意如下:

“客戶”->“黑客”:你好//黑客截獲“客戶”發(fā)給“服務(wù)器”的消息
“黑客”->“客戶”:你好,我是服務(wù)器,這個(gè)是我的公鑰//黑客自己生成一對(duì)公鑰和私鑰,把
公鑰發(fā)給“客戶”,自己保留私鑰
“客戶”->“黑客”:向我證明你就是服務(wù)器
“黑客”->“客戶”:你好,我是服務(wù)器 {你好,我是服務(wù)器}[黑客自己的私鑰|RSA]//客戶收到

“黑客”用私鑰加密的信息后,是可以用“黑客”發(fā)給自己的公鑰解密的,從而會(huì)誤認(rèn)為“黑客”是“服務(wù)器”因此“黑客”只需要自己生成一對(duì)公鑰和私鑰,然后把公鑰發(fā)送給“客戶”,自己保留私鑰,這樣由于“客戶”可以用黑客的公鑰解密黑客的私鑰加密的內(nèi)容,“客戶”就會(huì)相信“黑客”是“服務(wù)器”,從而導(dǎo)致了安全問(wèn)題。這里問(wèn)題的根源就在于,大家都可以生成公鑰、私鑰對(duì),無(wú)法確認(rèn)公鑰對(duì)到底是誰(shuí)的。 如果能夠確定公鑰到底是誰(shuí)的,就不會(huì)有這個(gè)問(wèn)題了。例如,如果收到“黑客”冒充“服務(wù)器”發(fā)過(guò)來(lái)的公鑰,經(jīng)過(guò)某種檢查,如果能夠發(fā)現(xiàn)這個(gè)公鑰不是“服務(wù)器”的就好了。

6.1.3 數(shù)字證書(shū)

為了解決上述問(wèn)題,數(shù)字證書(shū)出現(xiàn)了,它可以解決我們上面的問(wèn)題。先大概看下什么是數(shù)字證書(shū),一個(gè)證書(shū)包含下面的具體內(nèi)容:

  • 證書(shū)的發(fā)布機(jī)構(gòu)
  • 證書(shū)的有效期
  • 證書(shū)所有者(Subject)
  • 公鑰
  • 指紋和指紋算法
  • 簽名算法

指紋和指紋算法
這個(gè)是用來(lái)保證證書(shū)的完整性的,也就是說(shuō)確保證書(shū)沒(méi)有被修改過(guò)。 其原理就是在發(fā)布證書(shū)時(shí),發(fā)布者根據(jù)指紋算法(一個(gè)hash算法)計(jì)算整個(gè)證書(shū)的hash值(指紋)并和證書(shū)放在一起,使用者在打開(kāi)證書(shū)時(shí),自己也根據(jù)指紋算法計(jì)算一下證書(shū)的hash值(指紋),如果和剛開(kāi)始的值對(duì)得上,就說(shuō)明證書(shū)沒(méi)有被修改過(guò),因?yàn)樽C書(shū)的內(nèi)容被修改后,根據(jù)證書(shū)的內(nèi)容計(jì)算的出的hash值(指紋)是會(huì)變化的。
注意,這個(gè)指紋會(huì)用"SecureTrust CA"這個(gè)證書(shū)機(jī)構(gòu)的私鑰用簽名算法加密后和證書(shū)放在一起

簽名算法
就是指的這個(gè)數(shù)字證書(shū)的數(shù)字簽名所使用的加密算法,這樣就可以使用證書(shū)發(fā)布機(jī)構(gòu)的證書(shū)里面的公鑰,根據(jù)這個(gè)算法對(duì)指紋進(jìn)行解密。指紋的加密結(jié)果就是數(shù)字簽名

數(shù)字證書(shū)可以保證數(shù)字證書(shū)里的公鑰確實(shí)是這個(gè)證書(shū)的所有者(Subject)的,或者證書(shū)可以用來(lái)確認(rèn)對(duì)方的身份。也就是說(shuō),我們拿到一個(gè)數(shù)字證書(shū),我們可以判斷出這個(gè)數(shù)字證書(shū)到底是誰(shuí)的。至于是如何判斷的,后面會(huì)在詳細(xì)討論數(shù)字證書(shū)時(shí)詳細(xì)解釋。現(xiàn)在把前面的通信過(guò)程使用數(shù)字證書(shū)修改為如下:

“客戶”->“服務(wù)器”:你好
“服務(wù)器”->“客戶”:你好,我是服務(wù)器,這里是我的數(shù)字證書(shū)//這里用證書(shū)代替了公鑰
“客戶”->“服務(wù)器”:向我證明你就是服務(wù)器
“服務(wù)器”->“客戶”:你好,我是服務(wù)器 {你好,我是服務(wù)器}[私鑰|RSA]

在每次發(fā)送信息時(shí),先對(duì)信息的內(nèi)容進(jìn)行一個(gè)hash計(jì)算得出一個(gè)hash值,將信息的內(nèi)容和這個(gè)hash值一起加密后發(fā)送。接收方在收到后進(jìn)行解密得到明文的內(nèi)容和hash值,然后接收方再自己對(duì)收到信息內(nèi)容做一次hash計(jì)算,與收到的hash值進(jìn)行對(duì)比看是否匹配,如果匹配就說(shuō)明信息在傳輸過(guò)程中沒(méi)有被修改過(guò)。如果不匹配說(shuō)明中途有人故意對(duì)加密數(shù)據(jù)進(jìn)行了修改,立刻中斷通話過(guò)程后做其它處理。

如何向證書(shū)的發(fā)布機(jī)構(gòu)去申請(qǐng)證書(shū)

舉個(gè)例子,假設(shè)我們公司"ABC Company"花了1000塊錢(qián),向一個(gè)證書(shū)發(fā)布機(jī)構(gòu)"SecureTrust CA"為我們自己的公司"ABC Company"申請(qǐng)了一張證書(shū),注意,這個(gè)證書(shū)發(fā)布機(jī)構(gòu)"SecureTrust CA"是一個(gè)大家公認(rèn)并被一些權(quán)威機(jī)構(gòu)接受的證書(shū)發(fā)布機(jī)構(gòu),我們的操作系統(tǒng)里面已經(jīng)安裝了"SecureTrust CA"的證書(shū)。"SecureTrust CA"在給我們發(fā)布證書(shū)時(shí),把Issuer,Public key,Subject,Valid from,Valid to等信息以明文的形式寫(xiě)到證書(shū)里面,然后用一個(gè)指紋算法計(jì)算出這些數(shù)字證書(shū)內(nèi)容的一個(gè)指紋,并把指紋和指紋算法用自己的私鑰進(jìn)行加密,然后和證書(shū)的內(nèi)容一起發(fā)布,同時(shí)"SecureTrust CA"還會(huì)給一個(gè)我們公司"ABC Company"的私鑰給到我們。
我們"ABC Company"申請(qǐng)到這個(gè)證書(shū)后,我們把證書(shū)投入使用,我們?cè)谕ㄐ胚^(guò)程開(kāi)始時(shí)會(huì)把證書(shū)發(fā)給對(duì)方。

對(duì)方如何檢查這個(gè)證書(shū)的確是合法的并且是我們"ABC Company"公司的證書(shū)呢?首先應(yīng)用程序(對(duì)方通信用的程序,例如IE、OUTLook等)讀取證書(shū)中的Issuer(發(fā)布機(jī)構(gòu))為"SecureTrust CA" ,然后會(huì)在操作系統(tǒng)中受信任的發(fā)布機(jī)構(gòu)的證書(shū)中去找"SecureTrust CA"的證書(shū),如果找不到,那說(shuō)明證書(shū)的發(fā)布機(jī)構(gòu)是個(gè)水貨發(fā)布機(jī)構(gòu),證書(shū)可能有問(wèn)題,程序會(huì)給出一個(gè)錯(cuò)誤信息。 如果在系統(tǒng)中找到了"SecureTrust CA"的證書(shū),那么應(yīng)用程序就會(huì)從證書(shū)中取出"SecureTrust CA"的公鑰,然后對(duì)我們"ABC Company"公司的證書(shū)里面的指紋和指紋算法用這個(gè)公鑰進(jìn)行解密,然后使用這個(gè)指紋算法計(jì)算"ABC Company"證書(shū)的指紋,將這個(gè)計(jì)算的指紋與放在證書(shū)中的指紋對(duì)比,如果一致,說(shuō)明"ABC Company"的證書(shū)肯定沒(méi)有被修改過(guò)并且證書(shū)是"SecureTrust CA" 發(fā)布的,證書(shū)中的公鑰肯定是"ABC Company"的。對(duì)方然后就可以放心的使用這個(gè)公鑰和我們"ABC Company"進(jìn)行通信了。

6.2 Http協(xié)議原理

6.2.1 基礎(chǔ)知識(shí)

1. TCP/IP協(xié)議族

  • IP協(xié)議:網(wǎng)絡(luò)層協(xié)議,保證了計(jì)算機(jī)之間可以發(fā)送和接收數(shù)據(jù)。
  • TCP協(xié)議:傳輸層協(xié)議,一種端到端的協(xié)議,建立一個(gè)虛擬鏈路用于發(fā)送和接收數(shù)據(jù),基于重發(fā)機(jī)制,提供可靠的通信連接。為了方便通信,將報(bào)文分割成多個(gè)報(bào)文段發(fā)送。
  • UDP協(xié)議:傳輸層協(xié)議,一種無(wú)連接的協(xié)議,每個(gè)數(shù)據(jù)報(bào)都是一個(gè)獨(dú)立的信息,包括完整的源地址或目的地址,它在網(wǎng)絡(luò)上以任何可能的路徑傳往目的地,因此能否到達(dá)目的地,到達(dá)目的地的時(shí)間以及內(nèi)容的正確性都是不能被保證的。

通信雙方一方作為服務(wù)器等待客戶提出請(qǐng)求并予以響應(yīng)。客戶則在需要服務(wù)時(shí)向服務(wù)器提出申請(qǐng)。服務(wù)器一般作為守護(hù)進(jìn)程始終運(yùn)行,監(jiān)聽(tīng)網(wǎng)絡(luò)端口,一旦有客戶請(qǐng)求,就會(huì)啟動(dòng)一個(gè)服務(wù)進(jìn)程來(lái)響應(yīng)該客戶,同時(shí)自己繼續(xù)監(jiān)聽(tīng)服務(wù)端口,使后來(lái)的客戶也能及時(shí)得到服務(wù)。一個(gè)socket(通常都是server socket)等待建立連接時(shí),另一個(gè)socket可以要求進(jìn)行連接,一旦這兩個(gè)socket連接起來(lái),它們就可以進(jìn)行雙向數(shù)據(jù)傳輸,雙方都可以進(jìn)行發(fā)送或接收操作。

2. TCP3次握手,4次揮手過(guò)程

(1) 建立連接協(xié)議(三次握手)

a)客戶端發(fā)送一個(gè)帶SYN標(biāo)志的TCP報(bào)文到服務(wù)器。(聽(tīng)得到嗎?)
b)服務(wù)端回應(yīng)客戶端的報(bào)文同時(shí)帶ACK(acknowledgement,確認(rèn))標(biāo)志和SYN(synchronize)標(biāo)志。它表示對(duì)剛才客戶端SYN報(bào)文的回應(yīng);同時(shí)又標(biāo)志SYN給客戶端,詢問(wèn)客戶端是否準(zhǔn)備好進(jìn)行數(shù)據(jù)通訊。(聽(tīng)得到,你能聽(tīng)到我嗎?)
c)客戶必須再次回應(yīng)服務(wù)端一個(gè)ACK報(bào)文。(聽(tīng)到了,我們可以說(shuō)話了)

為什么需要“三次握手”?
在謝希仁著《計(jì)算機(jī)網(wǎng)絡(luò)》第四版中講“三次握手”的目的是“為了防止已失效的連接請(qǐng)求報(bào)文段突然又傳送到了服務(wù)端,因而產(chǎn)生錯(cuò)誤”。“已失效的連接請(qǐng)求報(bào)文段”的產(chǎn)生在這樣一種情況下:client發(fā)出的第一個(gè)連接請(qǐng)求報(bào)文段并沒(méi)有丟失,而是在某個(gè)網(wǎng)絡(luò)結(jié)點(diǎn)長(zhǎng)時(shí)間的滯留了,以致延誤到連接釋放以后的某個(gè)時(shí)間才到達(dá)server。本來(lái)這是一個(gè)早已失效的報(bào)文段。但server收到此失效的連接請(qǐng)求報(bào)文段后,就誤認(rèn)為是client再次發(fā)出的一個(gè)新的連接請(qǐng)求。于是就向client發(fā)出確認(rèn)報(bào)文段,同意建立連接。假設(shè)不采用“三次握手”,那么只要server發(fā)出確認(rèn),新的連接就建立了。由于現(xiàn)在client并沒(méi)有發(fā)出建立連接的請(qǐng)求,因此不會(huì)理睬server的確認(rèn),也不會(huì)向server發(fā)送數(shù)據(jù)。但server卻以為新的運(yùn)輸連接已經(jīng)建立,并一直等待client發(fā)來(lái)數(shù)據(jù)。這樣,server的很多資源就白白浪費(fèi)掉了。采用“三次握手”的辦法可以防止上述現(xiàn)象發(fā)生。例如剛才那種情況,client不會(huì)向server的確認(rèn)發(fā)出確認(rèn)。server由于收不到確認(rèn),就知道client并沒(méi)有要求建立連接。”。 主要目的防止server端一直等待,浪費(fèi)資源。

(2) 連接終止協(xié)議(四次揮手)

由于TCP連接是全雙工的,因此每個(gè)方向都必須單獨(dú)進(jìn)行關(guān)閉。這原則是當(dāng)一方完成它的數(shù)據(jù)發(fā)送任務(wù)后就能發(fā)送一個(gè)FIN來(lái)終止這個(gè)方向的連接。收到一個(gè) FIN只意味著這一方向上沒(méi)有數(shù)據(jù)流動(dòng),一個(gè)TCP連接在收到一個(gè)FIN后仍能發(fā)送數(shù)據(jù)。首先進(jìn)行關(guān)閉的一方將執(zhí)行主動(dòng)關(guān)閉,而另一方執(zhí)行被動(dòng)關(guān)閉。
a) TCP客戶端發(fā)送一個(gè)FIN,用來(lái)關(guān)閉客戶到服務(wù)器的數(shù)據(jù)傳送(報(bào)文段4)。
b) 服務(wù)器收到這個(gè)FIN,它發(fā)回一個(gè)ACK,確認(rèn)序號(hào)為收到的序號(hào)加1(報(bào)文段5)。和SYN一樣,一個(gè)FIN將占用一個(gè)序號(hào)。
c) 服務(wù)器關(guān)閉客戶端的連接,發(fā)送一個(gè)FIN給客戶端(報(bào)文段6)。
d) 客戶段發(fā)回ACK報(bào)文確認(rèn),并將確認(rèn)序號(hào)設(shè)置為收到序號(hào)加1(報(bào)文段7)。

為什么需要“四次揮手”?
那可能有人會(huì)有疑問(wèn),在tcp連接握手時(shí)為何ACK是和SYN一起發(fā)送,這里ACK卻沒(méi)有和FIN一起發(fā)送呢。原因是因?yàn)閠cp是全雙工模式,接收到FIN時(shí)意味將沒(méi)有數(shù)據(jù)再發(fā)來(lái),但是還是可以繼續(xù)發(fā)送數(shù)據(jù)。

3. 請(qǐng)求報(bào)文

(1) 請(qǐng)求行

由3部分組成,分別為:請(qǐng)求方法、URL以及協(xié)議版本,之間由空格分隔

請(qǐng)求方法:GET、HEAD、PUT、POST等方法,但并不是所有的服務(wù)器都實(shí)現(xiàn)了所有的方法,部分方法即便支持,處于安全性的考慮也是不可用的
協(xié)議版本:常用HTTP/1.1

(2) 請(qǐng)求頭部

請(qǐng)求頭部為請(qǐng)求報(bào)文添加了一些附加信息,由“名/值”對(duì)組成,每行一對(duì),名和值之間使用冒號(hào)分隔

  • Host 接受請(qǐng)求的服務(wù)器地址,可以是IP:端口號(hào),也可以是域名
  • User-Agent 發(fā)送請(qǐng)求的應(yīng)用程序名稱
  • Accept-Charset 通知服務(wù)端可以發(fā)送的編碼格式
  • Accept-Encoding 通知服務(wù)端可以發(fā)送的數(shù)據(jù)壓縮格式
  • Accept-Language 通知服務(wù)端可以發(fā)送的語(yǔ)言
  • Range 正文的字節(jié)請(qǐng)求范圍,為斷點(diǎn)續(xù)傳和并行下載提供可能,返回狀態(tài)碼是206
  • Authorization 用于設(shè)置身份認(rèn)證信息
  • Cookie 已有的Cookie

請(qǐng)求頭部的最后會(huì)有一個(gè)空行,表示請(qǐng)求頭部結(jié)束,接下來(lái)為請(qǐng)求正文,這一行非常重要,必不可少

(3) 請(qǐng)求正文

可選部分,比如GET請(qǐng)求就沒(méi)有請(qǐng)求正文

4. 響應(yīng)報(bào)文

由3部分組成,分別為:協(xié)議版本,狀態(tài)碼,狀態(tài)碼描述,之間由空格分隔

狀態(tài)碼:為3位數(shù)字,2XX表示成功,3XX表示資源重定向,4XX表示客戶端請(qǐng)求出錯(cuò),5XX表示服務(wù)端出錯(cuò)
206狀態(tài)碼表示的是:客戶端通過(guò)發(fā)送范圍請(qǐng)求頭Range抓取到了資源的部分?jǐn)?shù)據(jù),得服務(wù)端提供支持

(1) 響應(yīng)頭部
  • Server 服務(wù)器應(yīng)用程序軟件的名稱和版本
  • Content-Type 響應(yīng)正文的類型。如:text/plain、application/json
  • Content-Length 響應(yīng)正文長(zhǎng)度
  • Content-Charset 響應(yīng)正文使用的編碼
  • Content-Language 響應(yīng)正文使用的語(yǔ)言
  • Content-Range 正文的字節(jié)位置范圍
  • Accept-Ranges bytes:表明服務(wù)器支持Range請(qǐng)求,單位是字節(jié);none:不支持
  • Set-Cookie 設(shè)置Cookie

正文的內(nèi)容可以用gzip等進(jìn)行壓縮,以提升傳輸速率

5. 通用首部

(1) Cache-Control

用于操作瀏覽器緩存的工作機(jī)制。取值如下:

  • max-age:表示緩存的新鮮時(shí)間,在此時(shí)間內(nèi)可以直接使用緩存。單位秒。
  • no-cache:不做緩存。
  • max-stale:可以接受過(guò)期多少秒的緩存。
(2) Connection

用于管理持久連接。目前大部分瀏覽器都是用http1.1協(xié)議,也就是說(shuō)默認(rèn)都會(huì)發(fā)起Keep-Alive的連接請(qǐng)求。所以是否能完成一個(gè)完整的Keep-Alive連接就看服務(wù)器設(shè)置情況。取值如下:

  • keep-alive:使客戶端到服務(wù)器端的連接持續(xù)有效,當(dāng)出現(xiàn)對(duì)服務(wù)器的后繼請(qǐng)求時(shí),避免了建立或者重新建立連接。
  • close:每個(gè)請(qǐng)求/應(yīng)答客戶和服務(wù)器都要新建一個(gè)連接,完成之后立即斷開(kāi)連接。
(3) Transfer-Encoding

在Http/1.1中,僅對(duì)分塊傳輸編碼有效。Transfer-Encoding: chunked 表示輸出的內(nèi)容長(zhǎng)度不能確定,普通的靜態(tài)頁(yè)面、圖片之類的基本上都用不到這個(gè),但動(dòng)態(tài)頁(yè)面就有可能會(huì)用到。一般使用Content-Length就夠了。

Http/1.1 200 OK .... Transfer-Encoding:chunked Connection:keep-alivecf0//16進(jìn)制,值為3312...3312字節(jié)分塊數(shù)據(jù)...392//16進(jìn)制,值為914...914字節(jié)分塊數(shù)據(jù)...0 
(4) Content-Encoding

請(qǐng)求體/響應(yīng)體的編碼格式,如gzip

6. HTTP Authentication

兩種常見(jiàn)的Authentication機(jī)制:HTTP Basic和Digest。(現(xiàn)在用的并不多,了解一下)

(1) Http Basic

最簡(jiǎn)單的Authentication協(xié)議。直接方式告訴服務(wù)器你的用戶名(username)和密碼(password)。

request頭部:

GET /secret HTTP/1.1 Authorization: Basic QWxpY2U6MTIzNDU2//由“Alice:123456”進(jìn)行Base64編碼以后得到的結(jié)果 ... 

response頭部:

HTTP/1.1 200 OK ... 

因?yàn)槲覀冚斎氲氖钦_的用戶名密碼,所以服務(wù)器會(huì)返回200,表示驗(yàn)證成功。如果我們用錯(cuò)誤的用戶的密碼來(lái)發(fā)送請(qǐng)求,則會(huì)得到類似如下含有401錯(cuò)誤的response頭部:

HTTP/1.1 401 Bad credentials WWW-Authenticate: Basic realm="Spring Security Application" ... 
(2) Http Digest

當(dāng)Alice初次訪問(wèn)服務(wù)器時(shí),并不攜帶密碼。此時(shí)服務(wù)器會(huì)告知Alice一個(gè)隨機(jī)生成的字符串(nonce)。然后Alice再將這個(gè)字符串與她的密碼123456結(jié)合在一起進(jìn)行MD5編碼,將編碼以后的結(jié)果發(fā)送給服務(wù)器作為驗(yàn)證信息。

因?yàn)閚once是“每次”(并不一定是每次)隨機(jī)生成的,所以Alice在不同的時(shí)間訪問(wèn)服務(wù)器,其編碼使用的nonce值應(yīng)該是不同的,如果攜帶的是相同的nonce編碼后的結(jié)果,服務(wù)器就認(rèn)為其不合法,將拒絕其訪問(wèn)。

curl和服務(wù)器通信過(guò)程:

curl -------- request1:GET ------->> Server curl <<------ response1:nonce ------- Server curl ---- request2:Digest Auth ---->> Server curl <<------- response2:OK --------Server 

request1頭部:

GET /secret HTTP/1.1 ... 

請(qǐng)求1中沒(méi)有包含任何用戶名和密碼信息

response1頭部:

HTTP/1.1 401 Full authentication is required to access this resource WWW-Authenticate: Digest realm="Contacts Realm via Digest Authentication", qop="auth",nonce="MTQwMTk3OTkwMDkxMzo3MjdjNDM2NTYzMTU2NTA2NWEzOWU2NzBlNzhmMjkwOA==" ... 

當(dāng)服務(wù)器接收到request1以后,認(rèn)為request1沒(méi)有任何的Authentication信息,所以返回401,并且告訴curl nonce的值是MTQwMTk3OTkwMDkxMzo3MjdjNDM2NTYzMTU2NTA2NWEzOWU2NzBlNzhmMjkwOA

request2頭部:

GET /secret HTTP/1.1 Authorization: Digest username="Alice", realm="Contacts Realm via Digest Authentication",nonce="MTQwMTk3OTkwMDkxMzo3MjdjNDM2NTYzMTU2NTA2NWEzOWU2NzBlNzhmMjkwOA==", uri="/secret", cnonce="MTQwMTk3", nc=00000001, qop="auth",response="fd5798940c32e51c128ecf88472151af"... 

curl接收到服務(wù)器的nonce值以后,就可以把如密碼等信息和nonce值放在一起然后進(jìn)行MD5編碼,得到一個(gè)response值,如前面紅色標(biāo)出所示,這樣服務(wù)器就可以通過(guò)這個(gè)值驗(yàn)證Alice的密碼是否正確。

response2頭部:

HTTP/1.1 200 OK ... 

當(dāng)我們完成Authentication以后,如果我們?cè)俅问褂脛偛诺膎once值,將會(huì)收到錯(cuò)誤信息。Digest Authentication比Basic安全,但是并不是真正的什么都不怕了,Digest Authentication這種容易方式容易收到Man in the Middle式攻擊。

7. 請(qǐng)求體的3種形式

據(jù)應(yīng)用場(chǎng)景的不同,HTTP請(qǐng)求的請(qǐng)求體有三種不同的形式。
第一種:
移動(dòng)開(kāi)發(fā)者常見(jiàn)的,請(qǐng)求體是任意類型,服務(wù)器不會(huì)解析請(qǐng)求體,請(qǐng)求體的處理需要自己解析,如 POST JSON時(shí)候就是這類。
第二種:
這里的格式要求就是URL中Query String的格式要求:多個(gè)鍵值對(duì)之間用&連接,鍵與值之前用=連接,且只能用ASCII字符,非ASCII字符需使用UrlEncode編碼。
第三種:
請(qǐng)求體被分成為多個(gè)部分,文件上傳時(shí)會(huì)被使用,這種格式最先應(yīng)該是被用于郵件傳輸中,每個(gè)字段/文件都被boundary(Content-Type中指定)分成單獨(dú)的段,每段以-- 加 boundary開(kāi)頭,然后是該段的描述頭,描述頭之后空一行接內(nèi)容,請(qǐng)求結(jié)束的標(biāo)制為boundary后面加--。(見(jiàn)下面詳細(xì)說(shuō)明)

8. http協(xié)議中的多部分對(duì)象(multipart/form-data)

默認(rèn)是application/x-www-form-urlencoded,但是在傳輸大型文件的時(shí)候效率比較低下。所以需要multipart/form-data。
報(bào)文的主體內(nèi)可以包含多部分對(duì)象,通常用來(lái)發(fā)送圖片、文件或表單等。

Connection: keep-alive Content-Length: 123 X-Requested-With: ShockwaveFlash/16.0.0.296 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36 Content-Type: multipart/form-data; boundary=Ij5ei4KM7KM7ae0KM7cH2ae0Ij5Ef1 Accept: */* Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.8 Range: bytes=0-1024 Cookie: bdshare_firstime=1409052493497--Ij5ei4KM7KM7ae0KM7cH2ae0Ij5Ef1 Content-Disposition: form-data; name="position"1425264476444 --Ij5ei4KM7KM7ae0KM7cH2ae0Ij5Ef1 Content-Disposition: form-data; name="pics"; filename="file1.txt" Content-Type: text/plain...(file1.txt的數(shù)據(jù))... ue_con_1425264252856 --Ij5ei4KM7KM7ae0KM7cH2ae0Ij5Ef1 Content-Disposition: form-data; name="cm"100672 --Ij5ei4KM7KM7ae0KM7cH2ae0Ij5Ef1-- 

a)在請(qǐng)求頭中Content-Type: multipart/form-data; boundary=Ij5ei4KM7KM7ae0KM7cH2ae0Ij5Ef1是必須的,boundary字符串可以隨意指定
b)上面有3個(gè)部分,分別用--boundary進(jìn)行分隔。Content-Disposition: form-data; name="參數(shù)的名稱" + "\r\n" + "\r\n" + 參數(shù)值
c)--boundary-- 作為結(jié)束

6.2.2Https

(1) Http的缺點(diǎn)

  • 通信使用明文,內(nèi)容可能會(huì)被竊聽(tīng) —— 加密通信線路
  • 不驗(yàn)證通信方,可能遭遇偽裝 —— 證書(shū)
  • 無(wú)法驗(yàn)證報(bào)文的完整性,可能已被篡改 —— 數(shù)字簽名

Http+加密+認(rèn)證+完整性保護(hù)=Https

Https就是身披SSL(Secure Socket Layer,安全套接層)協(xié)議這層外殼的Http。當(dāng)使用了SSL之后,Http先和SSL通信,SSL再和TCP通信。

SSL(secure sockets layer):安全套接層,它是在上世紀(jì)90年代中期,由網(wǎng)景公司設(shè)計(jì)的,為解決 HTTP 協(xié)議傳輸內(nèi)容會(huì)被偷窺(嗅探)和篡改等安全問(wèn)題而設(shè)計(jì)的,到了1999年,SSL成為互聯(lián)網(wǎng)上的標(biāo)準(zhǔn),名稱改為T(mén)LS(transport layer security):安全傳輸層協(xié)議,兩者可視為同一種東西的不同階段。

(2) Https的工作原理

HTTPS在傳輸數(shù)據(jù)之前需要客戶端(瀏覽器)與服務(wù)端(網(wǎng)站)之間進(jìn)行一次握手,在握手過(guò)程中將確立雙方加密傳輸數(shù)據(jù)的密碼信息。TLS/SSL協(xié)議不僅僅是一套加密傳輸?shù)膮f(xié)議,更是一件經(jīng)過(guò)藝術(shù)家精心設(shè)計(jì)的藝術(shù)品,TLS/SSL中使用了非對(duì)稱加密,對(duì)稱加密以及HASH算法。握手過(guò)程的具體描述如下:

  • 瀏覽器將自己支持的一套加密規(guī)則發(fā)送給網(wǎng)站。
  • 網(wǎng)站從中選出一組加密算法與HASH算法,并將自己的身份信息以證書(shū)的形式發(fā)回給瀏覽器。證書(shū)里面包含了網(wǎng)站地址,加密公鑰,以及證書(shū)的頒發(fā)機(jī)構(gòu)等信息。
  • 瀏覽器獲得網(wǎng)站證書(shū)之后瀏覽器要做以下工作:
    a) 驗(yàn)證證書(shū)的合法性(頒發(fā)證書(shū)的機(jī)構(gòu)是否合法,證書(shū)中包含的網(wǎng)站地址是否與正在訪問(wèn)的地址一致等),如果證書(shū)受信任,則瀏覽器欄里面會(huì)顯示一個(gè)小鎖頭,否則會(huì)給出證書(shū)不受信的提示。
    b) 如果證書(shū)受信任,或者是用戶接受了不受信的證書(shū),瀏覽器會(huì)生成一串隨機(jī)數(shù)的密碼,并用證書(shū)中提供的公鑰加密。
    c) 使用約定好的HASH算法計(jì)算握手消息,并使用生成的隨機(jī)數(shù)對(duì)消息進(jìn)行加密,最后將之前生成的所有信息發(fā)送給網(wǎng)站。
  • 網(wǎng)站接收瀏覽器發(fā)來(lái)的數(shù)據(jù)之后要做以下的操作:
    a) 使用自己的私鑰將信息解密取出密碼,使用密碼解密瀏覽器發(fā)來(lái)的握手消息,并驗(yàn)證HASH是否與瀏覽器發(fā)來(lái)的一致。
    b) 使用密碼加密一段握手消息,發(fā)送給瀏覽器。
  • 瀏覽器解密并計(jì)算握手消息的HASH,如果與服務(wù)端發(fā)來(lái)的HASH一致,此時(shí)握手過(guò)程結(jié)束,之后所有的通信數(shù)據(jù)將由之前瀏覽器生成的隨機(jī)密碼并利用對(duì)稱加密算法進(jìn)行加密。

這里瀏覽器與網(wǎng)站互相發(fā)送加密的握手消息并驗(yàn)證,目的是為了保證雙方都獲得了一致的密碼,并且可以正常的加密解密數(shù)據(jù),為后續(xù)真正數(shù)據(jù)的傳輸做一次測(cè)試。另外,HTTPS一般使用的加密與HASH算法如下:

  • 非對(duì)稱加密算法:RSA,DSA/DSS
  • 對(duì)稱加密算法:AES,RC4,3DES
  • HASH算法:MD5,SHA1,SHA256

HTTPS對(duì)應(yīng)的通信時(shí)序圖如下:

(3) 證書(shū)分類

SSL 證書(shū)大致分三類:

  • 認(rèn)可的證書(shū)頒發(fā)機(jī)構(gòu)(如: VeriSign), 或這些機(jī)構(gòu)的下屬機(jī)構(gòu)頒發(fā)的證書(shū).
  • 沒(méi)有得到認(rèn)可的證書(shū)頒發(fā)機(jī)構(gòu)頒發(fā)的證書(shū).
  • 自簽名證書(shū), 自己通過(guò)JDK自帶工具keytool去生成一個(gè)證書(shū),分為臨時(shí)性的(在開(kāi)發(fā)階段使用)或在發(fā)布的產(chǎn)品中永久性使用的兩種.

只有第一種, 也就是那些被安卓系統(tǒng)認(rèn)可的機(jī)構(gòu)頒發(fā)的證書(shū), 在使用過(guò)程中不會(huì)出現(xiàn)安全提示。對(duì)于向權(quán)威機(jī)構(gòu)(簡(jiǎn)稱CA,Certificate Authority)申請(qǐng)過(guò)證書(shū)的網(wǎng)絡(luò)地址,用OkHttp或者HttpsURLConnection都可以直接訪問(wèn) ,不需要做額外的事情 。但是申請(qǐng)需要$$ (每年要交 100 到 500 美元不等的費(fèi)用)。

CA機(jī)構(gòu)頒發(fā)的證書(shū)有3種類型:
域名型SSL證書(shū)(DV SSL):信任等級(jí)普通,只需驗(yàn)證網(wǎng)站的真實(shí)性便可頒發(fā)證書(shū)保護(hù)網(wǎng)站;
企業(yè)型SSL證書(shū)(OV SSL):信任等級(jí)強(qiáng),須要驗(yàn)證企業(yè)的身份,審核嚴(yán)格,安全性更高;
增強(qiáng)型SSL證書(shū)(EV SSL):信任等級(jí)最高,一般用于銀行證券等金融機(jī)構(gòu),審核嚴(yán)格,安全性最高,同時(shí)可以激活綠色網(wǎng)址欄。

(4) HTTPS協(xié)議和HTTP協(xié)議的區(qū)別:

  • https協(xié)議需要到ca申請(qǐng)證書(shū),一般免費(fèi)證書(shū)很少,需要交費(fèi)。
  • http是超文本傳輸協(xié)議,信息是明文傳輸,https 則是具有安全性的ssl加密傳輸協(xié)議。
  • http和https使用的是完全不同的連接方式用的端口也不一樣,前者是80,后者是443。
  • http的連接很簡(jiǎn)單,是無(wú)狀態(tài)的 。
  • HTTPS協(xié)議是由SSL+HTTP協(xié)議構(gòu)建的可進(jìn)行加密傳輸、身份認(rèn)證的網(wǎng)絡(luò)協(xié)議, 要比http協(xié)議安全。

6.3 Android SDK支持

6.3.1 InetAddress

(1) 簡(jiǎn)介
  • 代表IP地址,還有兩個(gè)子類,Inet4AddressInet6Address
  • 沒(méi)有構(gòu)造方法
(2) 方法
  • InetAddress.getByAddress(byte[] addr) 根據(jù)IP地址獲取InetAddress對(duì)象,如:new byte[]{127,0,0,1}
  • InetAddress.getByName(String host)根據(jù)主機(jī)名獲取InetAddress對(duì)象 www.baidu.com 沒(méi)有http://
  • InetAddress.getLocalHost()返回本機(jī)
  • getHostAddress() String 返回IP地址
  • getHostName() String 返回主機(jī)名
  • isReachable(int timeout) boolean 測(cè)試是否可以達(dá)到該地址,毫秒數(shù)

2. URLDecoder和URLEncoder

(1) 簡(jiǎn)介
  • URLDecoder和URLEncoder用于完成普通字符串和application/x-www-form-urlencoded MIME字符串之間的相互轉(zhuǎn)換
  • 若每個(gè)中文占2個(gè)字節(jié),每個(gè)字節(jié)轉(zhuǎn)換成2個(gè)十六進(jìn)制的數(shù)字,所以每個(gè)中文字符轉(zhuǎn)換成“%XX%XX”的形式
(2) 方法
  • URLEncoder.encode(String s, String enc) String
  • URLDecoder.decode(String s, String enc) String

6.3.2 Socket通信

1. Socket通信簡(jiǎn)介

Socket又稱套接字,是程序內(nèi)部提供的與外界通信的端口,即端口通信。通過(guò)建立socket連接,可為通信雙方的數(shù)據(jù)傳輸傳提供通道。主要特點(diǎn)有數(shù)據(jù)丟失率低,使用簡(jiǎn)單且易于移植。

(1) Socket的分類

在TCP/IP協(xié)議族當(dāng)中主要的Socket類型為流套接字(streamsocket)和數(shù)據(jù)報(bào)套接字(datagramsocket)。流套接字將TCP作為其端對(duì)端協(xié)議,提供了一個(gè)可信賴的字節(jié)流服務(wù)。數(shù)據(jù)報(bào)套接字使用UDP協(xié)議,提供數(shù)據(jù)打包發(fā)送服務(wù)。

2. Socket 基本通信模型

(1) Socket通信模型
Socket基本通信模型
(2) TCP通信模型
TCP通信模型
(3) UDP通信模型
UDP通信模型

4. Demo

首先添加權(quán)限:

<!--允許應(yīng)用程序改變網(wǎng)絡(luò)狀態(tài)--> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <!--允許應(yīng)用程序改變WIFI連接狀態(tài)--> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <!--允許應(yīng)用程序訪問(wèn)有關(guān)的網(wǎng)絡(luò)信息--> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><!--允許應(yīng)用程序訪問(wèn)WIFI網(wǎng)卡的網(wǎng)絡(luò)信息--> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <!--允許應(yīng)用程序完全使用網(wǎng)絡(luò)--> <uses-permission android:name="android.permission.INTERNET"/> 
(1) 使用TCP協(xié)議通信

客戶端實(shí)現(xiàn):

protected void connectServerWithTCPSocket() { Socket socket; try {// 創(chuàng)建一個(gè)Socket對(duì)象,并指定服務(wù)端的IP及端口號(hào) socket = new Socket("192.168.1.32", 1989); // 創(chuàng)建一個(gè)InputStream用戶讀取要發(fā)送的文件。 InputStream inputStream = new FileInputStream("e://a.txt"); // 獲取Socket的OutputStream對(duì)象用于發(fā)送數(shù)據(jù)。 OutputStream outputStream = socket.getOutputStream(); // 創(chuàng)建一個(gè)byte類型的buffer字節(jié)數(shù)組,用于存放讀取的本地文件 byte buffer[] = new byte[4 * 1024]; int temp = 0; // 循環(huán)讀取文件 while ((temp = inputStream.read(buffer)) != -1) { // 把數(shù)據(jù)寫(xiě)入到OuputStream對(duì)象中 outputStream.write(buffer, 0, temp); } // 發(fā)送讀取的數(shù)據(jù)到服務(wù)端 outputStream.flush(); /** 或創(chuàng)建一個(gè)報(bào)文,使用BufferedWriter寫(xiě)入**/ //String socketData = "[2143213;21343fjks;213]"; //BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( //socket.getOutputStream())); //writer.write(socketData.replace("\n", " ") + "\n"); //writer.flush(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } 

服務(wù)端實(shí)現(xiàn):

public void serverReceviedByTcp() { // 聲明一個(gè)ServerSocket對(duì)象 ServerSocket serverSocket = null; try { // 創(chuàng)建一個(gè)ServerSocket對(duì)象,并讓這個(gè)Socket在1989端口監(jiān)聽(tīng) serverSocket = new ServerSocket(1989); // 調(diào)用ServerSocket的accept()方法,接受客戶端所發(fā)送的請(qǐng)求, // 如果客戶端沒(méi)有發(fā)送數(shù)據(jù),那么該線程就阻塞,等到收到數(shù)據(jù),繼續(xù)執(zhí)行。 Socket socket = serverSocket.accept(); // 從Socket當(dāng)中得到InputStream對(duì)象,讀取客戶端發(fā)送的數(shù)據(jù) InputStream inputStream = socket.getInputStream(); byte buffer[] = new byte[1024 * 4]; int temp = 0; // 從InputStream當(dāng)中讀取客戶端所發(fā)送的數(shù)據(jù) while ((temp = inputStream.read(buffer)) != -1) { System.out.println(new String(buffer, 0, temp)); } serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } 

5. 方法

(1) ServerSocket

監(jiān)聽(tīng)來(lái)自于客戶端的Socket連接,如果沒(méi)有連接,它將一直處于等待狀態(tài)

  • ServerSocket(int port, int backlog, InetAddress bindAddr)在機(jī)器存在多IP的情況下,允許通過(guò)bindAddr來(lái)指定綁定到哪個(gè)IP;backlog是隊(duì)列中能接受的最大socket客戶端連接數(shù)(accept()之后將被取出)
  • ServerSocket() 創(chuàng)建非綁定服務(wù)器套接字
  • ServerSocket(int port) 創(chuàng)建綁定到特定端口的服務(wù)器套接字,等價(jià)于ServerSocket(port, 50, null)
  • accept() Socket 如果接收到一個(gè)客戶端Socket的連接請(qǐng)求,返回一個(gè)與客戶端Socket相對(duì)應(yīng)的Socket
  • close()
(2) Socket
  • Socket() 創(chuàng)建未連接套接字
  • Socket(String host, int port)
  • Socket(InetAddress address, int port)創(chuàng)建一個(gè)流套接字并將其連接到指定 IP 地址的指定端口號(hào),默認(rèn)使用本地主機(jī)默認(rèn)的IP和系統(tǒng)動(dòng)態(tài)分配的端口
  • Socket(InetAddress address, int port, InetAddress localAddr, int localPort)創(chuàng)建一個(gè)套接字并將其連接到指定遠(yuǎn)程地址上的指定遠(yuǎn)程端口,多IP時(shí)
  • getOutputStream() OutputStream 返回此套接字的輸出流
  • getInputStream() inputStream 返回此套接字的輸入流
  • connect(SocketAddress endpoint, int timeout) 將此套接字連接到服務(wù)器,并指定一個(gè)超時(shí)值
  • close()

6.3.3HttpURLConnection

1. URL

1)對(duì)象代表統(tǒng)一資源定位器,是指向互聯(lián)網(wǎng)“資源”的指針
2)通常由協(xié)議名、主機(jī)、端口、資源路徑組成
3)URL包含一個(gè)可打開(kāi)到達(dá)該資源的輸入流,可以將URL理解成為URI的特例

  • URL(String spec)
  • openConnection() URLConnection
  • getProtocol() String
  • getHost() String
  • getPort() int
  • getPath() String 獲取路徑部分,/search.html
  • getFile() String 獲取資源名稱,/search.html?keyword='你好'
  • getQuery() String 獲取查詢字符串,keyword='你好'
  • openStream() InputStream

2. URLConnection

抽象類,表示應(yīng)用程序和URL之間的通信連接,可以向URL發(fā)送請(qǐng)求,讀取URL指向的資源

  • setDoInput(boolean doinput) 發(fā)送POST請(qǐng)求,必須設(shè)置,設(shè)置為true
  • setDoOutput(boolean dooutput) 發(fā)送POST請(qǐng)求,必須設(shè)置,設(shè)置為true
  • setUseCaches(boolean usecaches)是否使用緩存
  • setRequestProperty(String key, String value) 設(shè)置普通的請(qǐng)求屬性
  • setConnectTimeout(int timeout)設(shè)置連接超時(shí)的時(shí)間
  • setReadTimeout(int timeoutMillis) 讀取輸入流的超時(shí)時(shí)間
  • connect() 抽象方法,建立實(shí)際的連接
  • getHeaderField(String key) String
  • getHeaderFields() Map<String,List<String>> 獲取所有的響應(yīng)頭;getHeaderField(String name)獲取指定的響應(yīng)頭
  • getOutputStream() OutputStream
  • getInputStream() InputStream
  • getContentLength() int

3. HttpURLConnection

(1) 簡(jiǎn)介

抽象類,是URLConnection的子類,增加了操作Http資源的便捷方法

  • 對(duì)象不能直接構(gòu)造,需要通過(guò)URL類中的openConnection()方法來(lái)獲得。
  • connect()函數(shù),實(shí)際上只是建立了一個(gè)與服務(wù)器的TCP連接,并沒(méi)有實(shí)際發(fā)送HTTP請(qǐng)求。HTTP請(qǐng)求實(shí)際上直到我們獲取服務(wù)器響應(yīng)數(shù)據(jù)(如調(diào)用getInputStream()、getResponseCode()等方法)時(shí)才正式發(fā)送出去。
  • 對(duì)HttpURLConnection對(duì)象的配置都需要在connect()方法執(zhí)行之前完成。
  • HttpURLConnection是基于HTTP協(xié)議的,其底層通過(guò)socket通信實(shí)現(xiàn)。如果不設(shè)置超時(shí)(timeout),在網(wǎng)絡(luò)異常的情況下,可能會(huì)導(dǎo)致程序僵死而不繼續(xù)往下執(zhí)行。
  • HTTP正文的內(nèi)容是通過(guò)OutputStream流寫(xiě)入的, 向流中寫(xiě)入的數(shù)據(jù)不會(huì)立即發(fā)送到網(wǎng)絡(luò),而是存在于內(nèi)存緩沖區(qū)中,待流關(guān)閉時(shí),根據(jù)寫(xiě)入的內(nèi)容生成HTTP正文。
  • 調(diào)用getInputStream()方法時(shí),返回一個(gè)輸入流,用于從中讀取服務(wù)器對(duì)于HTTP請(qǐng)求的返回信息。
  • 我們可以使用connect()方法手動(dòng)的發(fā)送一個(gè)HTTP請(qǐng)求,但是如果要獲取HTTP響應(yīng)的時(shí)候,請(qǐng)求就會(huì)自動(dòng)的發(fā)起,比如我們使用getInputStream()方法的時(shí)候,所以完全沒(méi)有必要調(diào)用connect()方法。
(2) 方法
  • setRequestMethod(String method)
  • setInstanceFollowRedirects(boolean followRedirects)
  • getResponseCode() int 獲取服務(wù)器的響應(yīng)碼
  • getResponseMessage() String 獲取響應(yīng)消息
  • getRequestMethod() String 獲取發(fā)送請(qǐng)求的方法,GET或POST
  • disconnect() 抽象方法
(3) 實(shí)現(xiàn)多線程下載

步驟:
①:創(chuàng)建URL對(duì)象
②:獲取URL對(duì)象指向資源的大小(getContentLength()方法)
③:在本地創(chuàng)建一個(gè)與網(wǎng)路資源相同大小的空文件
④:計(jì)算每條線程應(yīng)該下載網(wǎng)絡(luò)資源的哪個(gè)部分(從哪個(gè)字節(jié)開(kāi)始,到哪個(gè)字節(jié)結(jié)束)
⑤:依次創(chuàng)建,啟動(dòng)多條線程來(lái)下載網(wǎng)絡(luò)資源的指定部分

(4) 實(shí)例

使用GET方式訪問(wèn)HTTP

public static void main(String[] args) { try { // 1. 得到訪問(wèn)地址的URL URL url = new URL( "http://localhost:8080/Servlet/do_login.do?username=test&password=123456"); // 2. 得到網(wǎng)絡(luò)訪問(wèn)對(duì)象java.net.HttpURLConnection HttpURLConnection connection = (HttpURLConnection) url .openConnection(); /* 3. 設(shè)置請(qǐng)求參數(shù)(過(guò)期時(shí)間,輸入、輸出流、訪問(wèn)方式),以流的形式進(jìn)行連接 */ // 設(shè)置是否向HttpURLConnection輸出 connection.setDoOutput(false); // 設(shè)置是否從httpUrlConnection讀入 connection.setDoInput(true); // 設(shè)置請(qǐng)求方式 connection.setRequestMethod("GET"); // 設(shè)置是否使用緩存 connection.setUseCaches(true); // 設(shè)置此 HttpURLConnection 實(shí)例是否應(yīng)該自動(dòng)執(zhí)行 HTTP 重定向 connection.setInstanceFollowRedirects(true); // 設(shè)置超時(shí)時(shí)間 connection.setConnectTimeout(3000); // 連接 connection.connect(); // 4. 得到響應(yīng)狀態(tài)碼的返回值 responseCode int code = connection.getResponseCode(); // 5. 如果返回值正常,數(shù)據(jù)在網(wǎng)絡(luò)中是以流的形式得到服務(wù)端返回的數(shù)據(jù) String msg = ""; if (code == 200) { // 正常響應(yīng) // 從流中讀取響應(yīng)信息 BufferedReader reader = new BufferedReader( new InputStreamReader(connection.getInputStream())); String line = null;while ((line = reader.readLine()) != null) { // 循環(huán)從流中讀取 msg += line + "\n"; } reader.close(); // 關(guān)閉流 } // 6. 斷開(kāi)連接,釋放資源 connection.disconnect(); // 顯示響應(yīng)結(jié)果 System.out.println(msg); } catch (IOException e) { e.printStackTrace(); } } 

使用POST方式訪問(wèn)HTTP

public static void main(String[] args) { try { // 1. 獲取訪問(wèn)地址URL URL url = new URL("http://localhost:8080/Servlet/do_login.do"); // 2. 創(chuàng)建HttpURLConnection對(duì)象 HttpURLConnection connection = (HttpURLConnection) url .openConnection(); /* 3. 設(shè)置請(qǐng)求參數(shù)等 */ // 請(qǐng)求方式 connection.setRequestMethod("POST"); // 超時(shí)時(shí)間 connection.setConnectTimeout(3000); // 設(shè)置是否輸出 connection.setDoOutput(true); // 設(shè)置是否讀入 connection.setDoInput(true); // 設(shè)置是否使用緩存 connection.setUseCaches(false); // 設(shè)置此 HttpURLConnection 實(shí)例是否應(yīng)該自動(dòng)執(zhí)行 HTTP 重定向 connection.setInstanceFollowRedirects(true); // 設(shè)置使用標(biāo)準(zhǔn)編碼格式編碼參數(shù)的名-值對(duì) connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // 連接 connection.connect(); /* 4. 處理輸入輸出 */ // 寫(xiě)入?yún)?shù)到請(qǐng)求中 String params = "username=test&password=123456"; OutputStream out = connection.getOutputStream(); out.write(params.getBytes()); out.flush(); out.close(); // 從連接中讀取響應(yīng)信息 String msg = ""; int code = connection.getResponseCode(); if (code == 200) { BufferedReader reader = new BufferedReader( new InputStreamReader(connection.getInputStream())); String line;while ((line = reader.readLine()) != null) { msg += line + "\n"; } reader.close(); } // 5. 斷開(kāi)連接 connection.disconnect();// 處理結(jié)果 System.out.println(msg); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } 

4. HttpClient

(Android6.0之后的SDK中移除了對(duì)于HttpClient的支持,僅作了解)
在一般情況下,如果只是需要向Web站點(diǎn)的某個(gè)簡(jiǎn)單頁(yè)面提交請(qǐng)求并獲取服務(wù)器響應(yīng),HttpURLConnection完全可以勝任。但在絕大部分情況下,Web站點(diǎn)的網(wǎng)頁(yè)可能沒(méi)這么簡(jiǎn)單,這些頁(yè)面并不是通過(guò)一個(gè)簡(jiǎn)單的URL就可訪問(wèn)的,可能需要用戶登錄而且具有相應(yīng)的權(quán)限才可訪問(wèn)該頁(yè)面。在這種情況下,就需要涉及Session、Cookie的處理了,如果打算使用HttpURLConnection來(lái)處理這些細(xì)節(jié),當(dāng)然也是可能實(shí)現(xiàn)的,只是處理起來(lái)難度就大了。

為了更好地處理向Web站點(diǎn)請(qǐng)求,包括處理Session、Cookie等細(xì)節(jié)問(wèn)題,Apache開(kāi)源組織提供了一個(gè)HttpClient項(xiàng)目。HttpClient就是一個(gè)增強(qiáng)版的HttpURLConnection,HttpURLConnection可以做的事情HttpClient全部可以做;HttpURLConnection沒(méi)有提供的有些功能,HttpClient也提供了,但它只是關(guān)注于如何發(fā)送請(qǐng)求、接收響應(yīng),以及管理HTTP連接。

(1) HttpClient的使用

步驟:
①:創(chuàng)建HttpClient對(duì)象
②:需要GET請(qǐng)求,創(chuàng)建HttpCet對(duì)象;需要POST請(qǐng)求,創(chuàng)建HttpPost對(duì)象
③:需要發(fā)送請(qǐng)求參數(shù),調(diào)用HttpGet、HttpPost共同的setParams(HttpParams params),HttpPost對(duì)象還可以使用setEntity(HttpEntity entity)方法來(lái)設(shè)置請(qǐng)求參數(shù)。
④:調(diào)用HttpClient對(duì)象的execute(HttpUriRequest request) HttpResponse發(fā)送請(qǐng)求,執(zhí)行該方法返回一個(gè)HttpResponse。
⑤:調(diào)用HttpResponse的getAllHeaders()getHeader(String name)獲取響應(yīng)頭;調(diào)用HttpResponse的getEntity()獲取HttpEntity對(duì)象(包裝了服務(wù)器的響應(yīng)內(nèi)容)

(2) 使用GET方式訪問(wèn)HTTP
public static void main(String[] args) { // 1. 創(chuàng)建HttpClient對(duì)象 CloseableHttpClient httpClient = HttpClientBuilder.create().build(); // 2. 創(chuàng)建HttpGet對(duì)象 HttpGet httpGet = new HttpGet( "http://localhost:8080/Servlet/do_login.do?username=test&password=123456"); CloseableHttpResponse response = null; try { // 3. 執(zhí)行GET請(qǐng)求 response = httpClient.execute(httpGet); System.out.println(response.getStatusLine()); // 4. 獲取響應(yīng)實(shí)體 HttpEntity entity = response.getEntity(); // 5. 處理響應(yīng)實(shí)體 if (entity != null) { System.out.println("長(zhǎng)度:" + entity.getContentLength()); System.out.println("內(nèi)容:" + EntityUtils.toString(entity)); } } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 6. 釋放資源 try { response.close(); httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } } 
(3) 使用POST方式訪問(wèn)HTTP
public static void main(String[] args) { // 1. 創(chuàng)建HttpClient對(duì)象 CloseableHttpClient httpClient = HttpClientBuilder.create().build(); // 2. 創(chuàng)建HttpPost對(duì)象 HttpPost post = new HttpPost( "http://localhost:8080/Servlet/do_login.do"); // 3. 設(shè)置POST請(qǐng)求傳遞參數(shù) List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("username", "test")); params.add(new BasicNameValuePair("password", "12356")); try { UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params); post.setEntity(entity); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } // 4. 執(zhí)行請(qǐng)求并處理響應(yīng) try { CloseableHttpResponse response = httpClient.execute(post); HttpEntity entity = response.getEntity(); if (entity != null) { System.out.println("響應(yīng)內(nèi)容:"); System.out.println(EntityUtils.toString(entity)); } response.close(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 釋放資源 try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } } 

6.4 OkHttp

6.4.1 簡(jiǎn)介

HttpClient是Apache基金會(huì)的一個(gè)開(kāi)源網(wǎng)絡(luò)庫(kù),功能十分強(qiáng)大,API數(shù)量眾多,但正是由于龐大的API數(shù)量使得我們很難在不破壞兼容性的情況下對(duì)它進(jìn)行升級(jí)和擴(kuò)展,所以Android團(tuán)隊(duì)在提升和優(yōu)化HttpClient方面的工作態(tài)度并不積極。官方在Android 2.3以后就不建議用了,并且在Android 5.0以后廢棄了HttpClient,在Android 6.0更是刪除了HttpClient。

HttpURLConnection是一種多用途、輕量極的HTTP客戶端,提供的API比較簡(jiǎn)單,可以容易地去使用和擴(kuò)展。不過(guò)在Android 2.2版本之前,HttpURLConnection一直存在著一些令人厭煩的bug。比如說(shuō)對(duì)一個(gè)可讀的InputStream調(diào)用close()方法時(shí),就有可能會(huì)導(dǎo)致連接池失效了。那么我們通常的解決辦法就是直接禁用掉連接池的功能。因此一般推薦是在2.2之前使用HttpClient,因?yàn)槠鋌ug較少。在2.2之后推薦使用HttpURLConnection,因?yàn)锳PI簡(jiǎn)單、體積小、有壓縮和緩存機(jī)制,并且Android團(tuán)隊(duì)后續(xù)會(huì)繼續(xù)優(yōu)化HttpURLConnection。

自從Android4.4開(kāi)始,google已經(jīng)開(kāi)始將源碼中的HttpURLConnection替換為OkHttp,而市面上流行的Retrofit同樣是使用OkHttp進(jìn)行再次封裝而來(lái)的。

OkHttp是一個(gè)快速、高效的網(wǎng)絡(luò)請(qǐng)求庫(kù),它的設(shè)計(jì)和實(shí)現(xiàn)的首要目標(biāo)便是高效,有如下特性:

  • 支持HTTP/2, HTTP/2通過(guò)使用多路復(fù)用技術(shù)在一個(gè)單獨(dú)的TCP連接上支持并發(fā), 通過(guò)在一個(gè)連接上一次性發(fā)送多個(gè)請(qǐng)求來(lái)發(fā)送或接收數(shù)據(jù);
  • 如果HTTP/2不可用, 連接池復(fù)用技術(shù)也可以極大減少延時(shí);
  • 支持Gzip壓縮響應(yīng)體,降低傳輸內(nèi)容的大小;
  • 支持Http緩存,避免重復(fù)請(qǐng)求;
  • 如果您的服務(wù)器配置了多個(gè)IP地址, 當(dāng)?shù)谝粋€(gè)IP連接失敗的時(shí)候, OkHttp會(huì)自動(dòng)嘗試下一個(gè)IP;
  • 使用Okio來(lái)簡(jiǎn)化數(shù)據(jù)的訪問(wèn)與存儲(chǔ),提高性能;
  • OkHttp還處理了代理服務(wù)器問(wèn)題和SSL握手失敗問(wèn)題;

6.4.2 OkHttp類與Http請(qǐng)求響應(yīng)的映射

1. Http請(qǐng)求

http請(qǐng)求包含:請(qǐng)求方法, 請(qǐng)求地址, 請(qǐng)求協(xié)議, 請(qǐng)求頭, 請(qǐng)求體這五部分。這些都在okhttp3.Request的類中有體現(xiàn), 這個(gè)類正是代表http請(qǐng)求的類。

public final class Request { final HttpUrl url;//請(qǐng)求地址 final String method;//請(qǐng)求方法 final Headers headers;//請(qǐng)求頭 final RequestBody body;//請(qǐng)求體 final Object tag; ... } 

2. Http響應(yīng)

Http響應(yīng)由訪問(wèn)協(xié)議, 響應(yīng)碼, 描述信息, 響應(yīng)頭, 響應(yīng)體來(lái)組成。

public final class Response implements Closeable { final Request request;//持有的請(qǐng)求 final Protocol protocol;//訪問(wèn)協(xié)議 final int code;//響應(yīng)碼 final String message;//描述信息 final Handshake handshake;//SSL/TLS握手協(xié)議驗(yàn)證時(shí)的信息, final Headers headers;//響應(yīng)頭 final ResponseBody body;//響應(yīng)體 ... } 

6.4.3 相關(guān)方法

1. OkHttpClient

  • OkHttpClient()
  • OkHttpClient(OkHttpClient.Builder builder)
  • newCall(Request request) Call
OkHttpClient.Builder
  • connectTimeout(long timeout, TimeUnit unit)
  • readTimeout(long timeout, TimeUnit unit)
  • writeTimeout(long timeout, TimeUnit unit)
  • pingInterval(long interval, TimeUnit unit)
  • cache(Cache cache) 入?yún)⑷纾?code>new Cache(File directory, long maxSize)
  • cookieJar(CookieJar cookieJar) CookieJar是一個(gè)接口
  • hostnameVerifier(HostnameVerifier hostnameVerifier) HostnameVerifier是一個(gè)接口,只有boolean verify(String hostname, SSLSession session)
  • sslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager)

2. Request

  • Request(Request.Builder builder)
Request.Builder
  • addHeader(String name, String value) 添加鍵值對(duì),不會(huì)覆蓋
  • header(String name, String value) 添加鍵值對(duì),會(huì)覆蓋
  • url(String url)
  • method(String method, RequestBody body)
  • post(RequestBody body) 本質(zhì):method("POST", body)
  • build() Request
RequestBody
  • create(MediaType contentType, final File file) RequestBody
  • create(MediaType contentType, String content) RequestBody
  • create(MediaType contentType, byte[] content) RequestBody
FormBody

RequestBody的子類

FormBody.Builder
  • add(String name, String value) FormBody.Builder
  • build() FormBody
MultipartBody

RequestBody的子類

MultipartBody.Builder
  • Builder()
  • Builder(String boundary)
  • setType(MediaType type)
  • addPart(Headers headers, RequestBody body)
  • addFormDataPart(String name, String filename, RequestBody body)
  • build() MultipartBody

3. Call

Call負(fù)責(zé)發(fā)送請(qǐng)求和讀取響應(yīng)

  • enqueue(Callback responseCallback)加入調(diào)度隊(duì)列,異步執(zhí)行
  • execute() Response 同步執(zhí)行
  • cancel()

4. Response

  • body() ResponseBody
  • code() int http請(qǐng)求的狀態(tài)碼
  • isSuccessful() code為2XX時(shí),返回true,否則false
  • headers() Headers
ResponseBody
  • string() String
  • bytes() byte[]
  • byteStream() InputStream
  • charStream() Reader
  • contentLength() long

5. MediaType

RequestBody的數(shù)據(jù)格式都要指定Content-Type,就是指定MIME,常見(jiàn)的有三種:

  • application/x-www-form-urlencoded 數(shù)據(jù)是個(gè)普通表單(默認(rèn))
  • multipart/form-data 數(shù)據(jù)里有文件
  • application/json 數(shù)據(jù)是個(gè)json

方法:

  • parse(String string) MediaType
參數(shù) 說(shuō)明
text/html HTML格式
text/plain 純文本格式
image/gif gif圖片格式
image/jpeg jpg圖片格式
image/png png圖片格式
application/json JSON數(shù)據(jù)格式
application/pdf pdf格式
application/msword Word文檔格式
application/octet-stream 二進(jìn)制流數(shù)據(jù)
application/x-www-form-urlencoded 普通表單數(shù)據(jù)
multipart/form-data 表單數(shù)據(jù)里有文件

6. 自動(dòng)管理Cookie

Request經(jīng)常都要攜帶Cookie,request創(chuàng)建時(shí)可以通過(guò)header設(shè)置參數(shù),Cookie也是參數(shù)之一。就像下面這樣:

Request request = new Request.Builder() .url(url) .header("Cookie", "xxx") .build(); 

然后可以從返回的response里得到新的Cookie,你可能得想辦法把Cookie保存起來(lái)。
但是OkHttp可以不用我們管理Cookie,自動(dòng)攜帶,保存和更新Cookie。
方法是在創(chuàng)建OkHttpClient設(shè)置管理Cookie的CookieJar:

private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>(); OkHttpClient okHttpClient = new OkHttpClient.Builder() .cookieJar(new CookieJar() { @Override public void saveFromResponse(HttpUrl httpUrl, List<Cookie> list) { cookieStore.put(httpUrl.host(), list); }@Override public List<Cookie> loadForRequest(HttpUrl httpUrl) { List<Cookie> cookies = cookieStore.get(httpUrl.host()); return cookies != null ? cookies : new ArrayList<Cookie>(); } }) .build(); 

這樣以后發(fā)送Request都不用管Cookie這個(gè)參數(shù)也不用去response獲取新Cookie什么的了。還能通過(guò)cookieStore獲取當(dāng)前保存的Cookie。
最后,new OkHttpClient()只是一種快速創(chuàng)建OkHttpClient的方式,更標(biāo)準(zhǔn)的是使用OkHttpClient.Builder()。后者可以設(shè)置一堆參數(shù),例如超時(shí)時(shí)間什么的。

6.4.4 get請(qǐng)求

1. 異步的get請(qǐng)求

//step 1: 創(chuàng)建 OkHttpClient 對(duì)象 OkHttpClient okHttpClient = new OkHttpClient(); //step 2: 創(chuàng)建一個(gè)請(qǐng)求,不指定請(qǐng)求方法時(shí)默認(rèn)是GET。 Request.Builder requestBuilder = new Request.Builder().url("http://www.baidu.com"); //可以省略,默認(rèn)是GET請(qǐng)求 requestBuilder.method("GET", null); //step 3:創(chuàng)建 Call 對(duì)象 Call call = okHttpClient.newCall(requestBuilder.build()); //step 4: 開(kāi)始異步請(qǐng)求 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { }@Override public void onResponse(Call call, Response response) throws IOException { //獲得返回體 ResponseBody body = response.body(); } }); 

首先要將OkHttpClient的對(duì)象與Request的對(duì)象建立起來(lái)聯(lián)系,使用okHttpClient的newCall()方法得到一個(gè)Call對(duì)象,這個(gè)Call對(duì)象的作用就是相當(dāng)于將請(qǐng)求封裝成了一個(gè)任務(wù),既然是任務(wù),自然就會(huì)有execute()cancel()等方法。

最后,我們希望以異步的方式去執(zhí)行請(qǐng)求,所以我們調(diào)用的是call.enqueue,將call加入調(diào)度隊(duì)列,然后等待任務(wù)執(zhí)行完成,我們?cè)贑allback中即可得到結(jié)果。但要注意的是,call的回調(diào)是子線程,所以是不能直接操作界面的。當(dāng)請(qǐng)求成功時(shí)就會(huì)回調(diào)onResponse()方法,我們可以看到返回的結(jié)果是Response對(duì)象,在此我們比較關(guān)注的是請(qǐng)求中的返回體body(ResponseBody類型),大多數(shù)的情況下我們希望獲得字符串從而進(jìn)行json解析獲得數(shù)據(jù),所以可以通過(guò)body.string()的方式獲得字符串。如果希望獲得返回的二進(jìn)制字節(jié)數(shù)組,則調(diào)用response.body().bytes();如果你想拿到返回的inputStream,則調(diào)用response.body().byteStream()。

2. 同步的get請(qǐng)求

調(diào)用Call#execute()方法,在主線程運(yùn)行

6.4.5 post請(qǐng)求

1. Post上傳表單(鍵值對(duì))

//step1: 同樣的需要?jiǎng)?chuàng)建一個(gè)OkHttpClick對(duì)象 OkHttpClient okHttpClient = new OkHttpClient(); //step2: 創(chuàng)建 FormBody.Builder FormBody formBody = new FormBody.Builder() .add("name", "dsd") //添加鍵值對(duì) .build(); //step3: 創(chuàng)建請(qǐng)求 Request request = new Request.Builder().url("http://www.baidu.com") .post(formBody) .build() //step4: 建立聯(lián)系 創(chuàng)建Call對(duì)象 okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { }@Override public void onResponse(Call call, Response response) throws IOException { } }); 

2. Post異步上傳文件

// step 1: 創(chuàng)建 OkHttpClient 對(duì)象 OkHttpClient okHttpClient = new OkHttpClient(); //step 2:創(chuàng)建 RequestBody 以及所需的參數(shù) //2.1 獲取文件 File file = new File(Environment.getExternalStorageDirectory() + "test.txt"); //2.2 創(chuàng)建 MediaType 設(shè)置上傳文件類型 MediaType MEDIATYPE = MediaType.parse("text/plain; charset=utf-8"); //2.3 獲取請(qǐng)求體 RequestBody requestBody = RequestBody.create(MEDIATYPE, file); //step 3:創(chuàng)建請(qǐng)求 Request request = new Request.Builder().url("http://www.baidu.com") .post(requestBody) .build(); //step 4 建立聯(lián)系 okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { }@Override public void onResponse(Call call, Response response) throws IOException { } }); 

3. Post方式提交流

以流的方式POST提交請(qǐng)求體. 請(qǐng)求體的內(nèi)容由流寫(xiě)入產(chǎn)生. 這個(gè)例子是流直接寫(xiě)入Okio的BufferedSink. 你的程序可能會(huì)使用OutputStream, 你可以使用BufferedSink.outputStream()來(lái)獲取. OkHttp的底層對(duì)流和字節(jié)的操作都是基于Okio庫(kù), Okio庫(kù)也是Square開(kāi)發(fā)的另一個(gè)IO庫(kù), 填補(bǔ)I/O和NIO的空缺, 目的是提供簡(jiǎn)單便于使用的接口來(lái)操作IO.

public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8"); private final OkHttpClient client = new OkHttpClient();public void run() throws Exception { RequestBody requestBody = new RequestBody() { @Override public MediaType contentType() { return MEDIA_TYPE_MARKDOWN; }@Override public void writeTo(BufferedSink sink) throws IOException { sink.writeUtf8("Numbers\n"); sink.writeUtf8("-------\n"); for (int i = 2; i <= 997; i++) { sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i))); } }private String factor(int n) { for (int i = 2; i < n; i++) { int x = n / i; if (x * i == n) return factor(x) + " × " + i; } return Integer.toString(n); } };Request request = new Request.Builder() .url("https://api.github.com/markdown/raw") .post(requestBody) .build();Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); System.out.println(response.body().string()); } 

4. Post方式提交String

下面是使用HTTP POST提交請(qǐng)求到服務(wù). 這個(gè)例子提交了一個(gè)markdown文檔到web服務(wù), 以HTML方式渲染markdown. 因?yàn)檎麄€(gè)請(qǐng)求體都在內(nèi)存中, 因此避免使用此api提交大文檔(大于1MB).

public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");private final OkHttpClient client = new OkHttpClient();public void run() throws Exception { String postBody = "" + "Releases\n" + "--------\n" + "\n" + " * _1.0_ May 6, 2013\n" + " * _1.1_ June 15, 2013\n" + " * _1.2_ August 11, 2013\n";Request request = new Request.Builder() .url("https://api.github.com/markdown/raw") .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody)) .build();Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);System.out.println(response.body().string()); } 

5、Post方式提交分塊請(qǐng)求

MultipartBody.Builder可以構(gòu)建復(fù)雜的請(qǐng)求體, 與HTML文件上傳形式兼容. 多塊請(qǐng)求體中每塊請(qǐng)求都是一個(gè)請(qǐng)求體, 可以定義自己的請(qǐng)求頭. 這些請(qǐng)求頭可以用來(lái)描述這塊請(qǐng)求, 例如它的Content-Disposition. 如果Content-Length和Content-Type可用的話, 他們會(huì)被自動(dòng)添加到請(qǐng)求頭中.

private static final String IMGUR_CLIENT_ID = "..."; private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");private final OkHttpClient client = new OkHttpClient();public void run() throws Exception { // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("title", "Square Logo") .addFormDataPart("image", "logo-square.png", RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png"))) .build();Request request = new Request.Builder() .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID) .url("https://api.imgur.com/3/image") .post(requestBody) .build();Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);System.out.println(response.body().string()); } 

六、其他用法

1、提取響應(yīng)頭

典型的HTTP頭是一個(gè)Map<String, String> : 每個(gè)字段都有一個(gè)或沒(méi)有值. 但是一些頭允許多個(gè)值。
當(dāng)寫(xiě)請(qǐng)求頭的時(shí)候, 使用header(name, value)可以設(shè)置唯一的name、value. 如果已經(jīng)有值, 舊的將被移除, 然后添加新的. 使用addHeader(name, value)可以添加多值(添加, 不移除已有的).
當(dāng)讀取響應(yīng)頭時(shí), 使用header(name)返回最后出現(xiàn)的name、value. 通常情況這也是唯一的name、value. 如果沒(méi)有值, 那么header(name)將返回null. 如果想讀取字段對(duì)應(yīng)的所有值, 使用headers(name)`會(huì)返回一個(gè)list.
為了獲取所有的Header, Headers類支持按index訪問(wèn).

private final OkHttpClient client = new OkHttpClient(); public void run() throws Exception { Request request = new Request.Builder() .url("https://api.github.com/repos/square/okhttp/issues") .header("User-Agent", "OkHttp Headers.java") .addHeader("Accept", "application/json; q=0.5") .addHeader("Accept", "application/vnd.github.v3+json") .build(); Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); System.out.println("Server: " + response.header("Server")); System.out.println("Date: " + response.header("Date")); System.out.println("Vary: " + response.headers("Vary")); } 

2、使用Gson來(lái)解析JSON響應(yīng)

Gson是一個(gè)在JSON和Java對(duì)象之間轉(zhuǎn)換非常方便的api庫(kù). 這里我們用Gson來(lái)解析Github API的JSON響應(yīng).
注意: ResponseBody.charStream()使用響應(yīng)頭Content-Type指定的字符集來(lái)解析響應(yīng)體. 默認(rèn)是UTF-8.

private final OkHttpClient client = new OkHttpClient(); private final Gson gson = new Gson();public void run() throws Exception { Request request = new Request.Builder() .url("https://api.github.com/gists/c2a7c39532239ff261be") .build(); Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);Gist gist = gson.fromJson(response.body().charStream(), Gist.class); for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) { System.out.println(entry.getKey()); System.out.println(entry.getValue().content); } }static class Gist { Map<String, GistFile> files; }static class GistFile { String content; } 

3、響應(yīng)緩存

OKHTTP如果要設(shè)置緩存,首要的條件就是設(shè)置一個(gè)緩存文件夾,在Android中為了安全起見(jiàn),一般設(shè)置為私密數(shù)據(jù)空間。通過(guò)getExternalCacheDir()獲取。
如然后通過(guò)調(diào)用OKHttpClient.Builder中的cache()方法。如下面代碼所示:

//緩存文件夾 File cacheFile = new File(getExternalCacheDir().toString(),"cache"); //緩存大小為10M int cacheSize = 10 * 1024 * 1024; //創(chuàng)建緩存對(duì)象 Cache cache = new Cache(cacheFile,cacheSize);OkHttpClient client = new OkHttpClient.Builder() .cache(cache) .build(); 

設(shè)置好Cache我們就可以正常訪問(wèn)了。我們可以通過(guò)獲取到的Response對(duì)象拿到它正常的消息和緩存的消息。

Response的消息有兩種類型,CacheResponse和NetworkResponse。CacheResponse代表從緩存取到的消息,NetworkResponse代表直接從服務(wù)端返回的消息。
示例代碼如下:

private void testCache(){ //緩存文件夾 File cacheFile = new File(getExternalCacheDir().toString(),"cache"); //緩存大小為10M int cacheSize = 10 * 1024 * 1024; //創(chuàng)建緩存對(duì)象 final Cache cache = new Cache(cacheFile,cacheSize);new Thread(new Runnable() { @Override public void run() { OkHttpClient client = new OkHttpClient.Builder() .cache(cache) .build(); //官方的一個(gè)示例的url String url = "http://publicobject.com/helloworld.txt";Request request = new Request.Builder() .url(url) .build(); Call call1 = client.newCall(request); Response response1 = null; try { //第一次網(wǎng)絡(luò)請(qǐng)求 response1 = call1.execute(); Log.i(TAG, "testCache: response1 :"+response1.body().string()); Log.i(TAG, "testCache: response1 cache :"+response1.cacheResponse()); Log.i(TAG, "testCache: response1 network :"+response1.networkResponse()); response1.body().close(); } catch (IOException e) { e.printStackTrace(); }Call call12 = client.newCall(request);try { //第二次網(wǎng)絡(luò)請(qǐng)求 Response response2 = call12.execute(); Log.i(TAG, "testCache: response2 :"+response2.body().string()); Log.i(TAG, "testCache: response2 cache :"+response2.cacheResponse()); Log.i(TAG, "testCache: response2 network :"+response2.networkResponse()); Log.i(TAG, "testCache: response1 equals response2:"+response2.equals(response1)); response2.body().close(); } catch (IOException e) { e.printStackTrace(); } } }).start();} 

我們?cè)谏厦娴拇a中,用同一個(gè)url地址分別進(jìn)行了兩次網(wǎng)絡(luò)訪問(wèn),然后分別用Log打印它們的信息。打印的結(jié)果主要說(shuō)明了一個(gè)現(xiàn)象,第一次訪問(wèn)的時(shí)候,Response的消息是NetworkResponse消息,此時(shí)CacheResponse的值為Null.而第二次訪問(wèn)的時(shí)候Response是CahceResponse,而此時(shí)NetworkResponse為空。也就說(shuō)明了上面的示例代碼能夠進(jìn)行網(wǎng)絡(luò)請(qǐng)求的緩存。

其實(shí)控制緩存的消息頭往往是服務(wù)端返回的信息中添加的如”Cache-Control:max-age=60”。所以,會(huì)有兩種情況。

  1. 客戶端和服務(wù)端開(kāi)發(fā)能夠很好溝通,按照達(dá)成一致的協(xié)議,服務(wù)端按照規(guī)定添加緩存相關(guān)的消息頭。
  2. 客戶端與服務(wù)端的開(kāi)發(fā)根本就不是同一家公司,沒(méi)有辦法也不可能要求服務(wù)端按照客戶端的意愿進(jìn)行開(kāi)發(fā)。

第一種辦法當(dāng)然很好,只要服務(wù)器在返回消息的時(shí)候添加好Cache-Control相關(guān)的消息便好。

第二種情況,就很麻煩,你真的無(wú)法左右別人的行為。怎么辦呢?好在OKHTTP能夠很輕易地處理這種情況。那就是定義一個(gè)攔截器,
人為地添加Response中的消息頭,然后再傳遞給用戶,這樣用戶拿到的Response就有了我們理想當(dāng)中的消息頭Headers,從而達(dá)到控制緩存的意圖,正所謂移花接木。

緩存之?dāng)r截器

因?yàn)閿r截器可以拿到Request和Response,所以可以輕而易舉地加工這些東西。在這里我們?nèi)藶榈靥砑覥ache-Control消息頭。

class CacheInterceptor implements Interceptor{@Override public Response intercept(Chain chain) throws IOException {Response originResponse = chain.proceed(chain.request());//設(shè)置緩存時(shí)間為60秒,并移除了pragma消息頭,移除它的原因是因?yàn)閜ragma也是控制緩存的一個(gè)消息頭屬性 return originResponse.newBuilder().removeHeader("pragma") .header("Cache-Control","max-age=60").build(); } } 

定義好攔截器中后,我們可以添加到OKHttpClient中了。

private void testCacheInterceptor(){ //緩存文件夾 File cacheFile = new File(getExternalCacheDir().toString(),"cache"); //緩存大小為10M int cacheSize = 10 * 1024 * 1024; //創(chuàng)建緩存對(duì)象 final Cache cache = new Cache(cacheFile,cacheSize);OkHttpClient client = new OkHttpClient.Builder() .addNetworkInterceptor(new CacheInterceptor()) .cache(cache) .build(); ....... } 

代碼后面部分有省略。主要通過(guò)在OkHttpClient.Builder()中addNetworkInterceptor()中添加。而這樣也挺簡(jiǎn)單的,就幾步完成了緩存代碼。

攔截器進(jìn)行緩存的缺點(diǎn)

  1. 網(wǎng)絡(luò)訪問(wèn)請(qǐng)求的資源是文本信息,如新聞列表,這類信息經(jīng)常變動(dòng),一天更新好幾次,它們用的緩存時(shí)間應(yīng)該就很短。
  2. 網(wǎng)絡(luò)訪問(wèn)請(qǐng)求的資源是圖片或者視頻,它們變動(dòng)很少,或者是長(zhǎng)期不變動(dòng),那么它們用的緩存時(shí)間就應(yīng)該很長(zhǎng)。

那么,問(wèn)題來(lái)了。
因?yàn)镺KHTTP開(kāi)發(fā)建議是同一個(gè)APP,用同一個(gè)OKHTTPCLIENT對(duì)象這是為了只有一個(gè)緩存文件訪問(wèn)入口。這個(gè)很容易理解,單例模式嘛。但是問(wèn)題攔截器是在OKHttpClient.Builder當(dāng)中添加的。如果在攔截器中定義緩存的方法會(huì)導(dǎo)致圖片的緩存和新聞列表的緩存時(shí)間是一樣的,這顯然是不合理的,真實(shí)的情況不應(yīng)該是圖片請(qǐng)求有它的緩存時(shí)間,新聞列表請(qǐng)求有它的緩存時(shí)間,應(yīng)該是每一個(gè)Request有它的緩存時(shí)間。 那么,有解決的方案嗎? 有的,okhttp官方有建議的方法。

okhttp官方文檔建議緩存方法

okhttp中建議用CacheControl這個(gè)類來(lái)進(jìn)行緩存策略的制定。
它內(nèi)部有兩個(gè)很重要的靜態(tài)實(shí)例。

/**強(qiáng)制使用網(wǎng)絡(luò)請(qǐng)求*/ public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();/*** 強(qiáng)制性使用本地緩存,如果本地緩存不滿足條件,則會(huì)返回code為504*/ public static final CacheControl FORCE_CACHE = new Builder() .onlyIfCached() .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS) .build(); 

我們看到FORCE_NETWORK常量用來(lái)強(qiáng)制使用網(wǎng)絡(luò)請(qǐng)求。FORCE_CACHE只取本地的緩存。它們本身都是CacheControl對(duì)象,由內(nèi)部的Buidler對(duì)象構(gòu)造。下面我們來(lái)看看CacheControl.Builder

CacheControl.Builder

它有如下方法:

  • noCache()//不使用緩存,用網(wǎng)絡(luò)請(qǐng)求
  • noStore()//不使用緩存,也不存儲(chǔ)緩存
  • onlyIfCached()//只使用緩存
  • noTransform()//禁止轉(zhuǎn)碼
  • maxAge(10, TimeUnit.MILLISECONDS)//設(shè)置超時(shí)時(shí)間為10ms。
  • maxStale(10, TimeUnit.SECONDS)//超時(shí)之外的超時(shí)時(shí)間為10s
  • minFresh(10, TimeUnit.SECONDS)//超時(shí)時(shí)間為當(dāng)前時(shí)間加上10秒鐘。

知道了CacheControl的相關(guān)信息,那么它怎么使用呢?不同于攔截器設(shè)置緩存,CacheControl是針對(duì)Request的,所以它可以針對(duì)每個(gè)請(qǐng)求設(shè)置不同的緩存策略。比如圖片和新聞列表。下面代碼展示如何用CacheControl設(shè)置一個(gè)60秒的超時(shí)時(shí)間。

private void testCacheControl(){ //緩存文件夾 File cacheFile = new File(getExternalCacheDir().toString(),"cache"); //緩存大小為10M int cacheSize = 10 * 1024 * 1024; //創(chuàng)建緩存對(duì)象 final Cache cache = new Cache(cacheFile,cacheSize);new Thread(new Runnable() { @Override public void run() { OkHttpClient client = new OkHttpClient.Builder() .cache(cache) .build(); //設(shè)置緩存時(shí)間為60秒 CacheControl cacheControl = new CacheControl.Builder() .maxAge(60, TimeUnit.SECONDS) .build(); Request request = new Request.Builder() .url("http://blog.csdn.net/briblue") .cacheControl(cacheControl) .build(); try { Response response = client.newCall(request).execute(); response.body().close(); } catch (IOException e) { e.printStackTrace(); } } }).start();} 

強(qiáng)制使用緩存

前面有講CacheControl.FORCE_CACHE這個(gè)常量。

public static final CacheControl FORCE_CACHE = new Builder() .onlyIfCached() .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS) 

它內(nèi)部其實(shí)就是調(diào)用onlyIfCached()和maxStale方法。
它的使用方法為

Request request = new Request.Builder() .url("http://blog.csdn.net/briblue") .cacheControl(Cache.FORCE_CACHE) .build(); 

但是如前面后提到的,如果緩存不符合條件會(huì)返回504.這個(gè)時(shí)候我們要根據(jù)情況再進(jìn)行編碼,如緩存不行就再進(jìn)行一次網(wǎng)絡(luò)請(qǐng)求。

Response forceCacheResponse = client.newCall(request).execute();if (forceCacheResponse.code() != 504) {// 資源已經(jīng)緩存了,可以直接使用} else {// 資源沒(méi)有緩存,或者是緩存不符合條件了。}

不使用緩存

前面也有講CacheControl.FORCE_NETWORK這個(gè)常量。

public static final CacheControl FORCE_NETWORK = new Builder().noCache().build(); 

它的內(nèi)部其實(shí)是調(diào)用noCache()方法,也就是不緩存的意思。
它的使用方法為

Request request = new Request.Builder() .url("http://blog.csdn.net/briblue") .cacheControl(Cache.FORCE_NETWORK) .build(); 

還有一種情況將maxAge設(shè)置為0,也不會(huì)取緩存,直接走網(wǎng)絡(luò)。

Request request = new Request.Builder() .url("http://blog.csdn.net/briblue") .cacheControl(new CacheControl.Builder() .maxAge(0, TimeUnit.SECONDS)) .build(); 

4、取消一個(gè)Call

使用Call.cancel()可以立即停止掉一個(gè)正在執(zhí)行的call. 如果一個(gè)線程正在寫(xiě)請(qǐng)求或者讀響應(yīng), 將會(huì)引發(fā)IOException. 當(dāng)call沒(méi)有必要的時(shí)候, 使用這個(gè)api可以節(jié)約網(wǎng)絡(luò)資源. 例如當(dāng)用戶離開(kāi)一個(gè)應(yīng)用時(shí), 不管同步還是異步的call都可以取消.
你可以通過(guò)tags來(lái)同時(shí)取消多個(gè)請(qǐng)求. 當(dāng)你構(gòu)建一請(qǐng)求時(shí), 使用RequestBuilder.tag(tag)來(lái)分配一個(gè)標(biāo)簽, 之后你就可以用OkHttpClient.cancel(tag)來(lái)取消所有帶有這個(gè)tag的call.

 private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);private final OkHttpClient client = new OkHttpClient();public void run() throws Exception { Request request = new Request.Builder() .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay. .build();final long startNanos = System.nanoTime(); final Call call = client.newCall(request);// Schedule a job to cancel the call in 1 second. executor.schedule(new Runnable() { @Override public void run() { System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f); call.cancel(); System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f); } }, 1, TimeUnit.SECONDS);try { System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f); Response response = call.execute(); System.out.printf("%.2f Call was expected to fail, but completed: %s%n", (System.nanoTime() - startNanos) / 1e9f, response); } catch (IOException e) { System.out.printf("%.2f Call failed as expected: %s%n", (System.nanoTime() - startNanos) / 1e9f, e); } } 

5、超時(shí)

沒(méi)有響應(yīng)時(shí)使用超時(shí)結(jié)束call. 沒(méi)有響應(yīng)的原因可能是客戶點(diǎn)鏈接問(wèn)題、服務(wù)器可用性問(wèn)題或者這之間的其他東西. OkHttp支持連接超時(shí), 讀取超時(shí)和寫(xiě)入超時(shí).

private final OkHttpClient client;public ConfigureTimeouts() throws Exception { client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build(); }public void run() throws Exception { Request request = new Request.Builder() .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay. .build();Response response = client.newCall(request).execute(); System.out.println("Response completed: " + response); } 

6、每個(gè)call的配置

使用OkHttpClient, 所有的HTTP Client配置包括代理設(shè)置、超時(shí)設(shè)置、緩存設(shè)置. 當(dāng)你需要為單個(gè)call改變配置的時(shí)候, 調(diào)用OkHttpClient.newBuilder(). 這個(gè)api將會(huì)返回一個(gè)builder, 這個(gè)builder和原始的client共享相同的連接池, 分發(fā)器和配置.
下面的例子中,我們讓一個(gè)請(qǐng)求是500ms的超時(shí)、另一個(gè)是3000ms的超時(shí)。

private final OkHttpClient client = new OkHttpClient();public void run() throws Exception { Request request = new Request.Builder() .url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay. .build();try { // Copy to customize OkHttp for this request. OkHttpClient copy = client.newBuilder() .readTimeout(500, TimeUnit.MILLISECONDS) .build();Response response = copy.newCall(request).execute(); System.out.println("Response 1 succeeded: " + response); } catch (IOException e) { System.out.println("Response 1 failed: " + e); }try { // Copy to customize OkHttp for this request. OkHttpClient copy = client.newBuilder() .readTimeout(3000, TimeUnit.MILLISECONDS) .build();Response response = copy.newCall(request).execute(); System.out.println("Response 2 succeeded: " + response); } catch (IOException e) { System.out.println("Response 2 failed: " + e); } } 

七、處理驗(yàn)證

這部分和HTTP AUTH有關(guān).

1、HTTP AUTH

使用HTTP AUTH需要在server端配置http auth信息, 其過(guò)程如下:

  • 客戶端發(fā)送http請(qǐng)求(沒(méi)有Authorization header)
  • 服務(wù)器發(fā)現(xiàn)配置了http auth, 于是檢查request里面有沒(méi)有"Authorization"的http header
    如果有, 則判斷Authorization里面的內(nèi)容是否在用戶列表里面, Authorization header的典型數(shù)據(jù)為"Authorization: Basic jdhaHY0=", 其中Basic表示基礎(chǔ)認(rèn)證, jdhaHY0=是base64編碼的"user:passwd"字符串. 如果沒(méi)有,或者用戶密碼不對(duì),則返回http code 401頁(yè)面給客戶端.
  • 標(biāo)準(zhǔn)的http瀏覽器在收到401頁(yè)面之后, 應(yīng)該彈出一個(gè)對(duì)話框讓用戶輸入帳號(hào)密碼; 并在用戶點(diǎn)確認(rèn)的時(shí)候再次發(fā)出請(qǐng)求, 這次請(qǐng)求里面將帶上Authorization header.
  • 服務(wù)器端認(rèn)證通過(guò),并返回頁(yè)面
  • 瀏覽器顯示頁(yè)面
    一次典型的訪問(wèn)場(chǎng)景是:

2、OkHttp認(rèn)證

OkHttp會(huì)自動(dòng)重試未驗(yàn)證的請(qǐng)求. 當(dāng)響應(yīng)是401 Not Authorized時(shí),Authenticator會(huì)被要求提供證書(shū). Authenticator的實(shí)現(xiàn)中需要建立一個(gè)新的包含證書(shū)的請(qǐng)求. 如果沒(méi)有證書(shū)可用, 返回null來(lái)跳過(guò)嘗試.
使用Response.challenges()來(lái)獲得任何authentication challenges的 schemes 和 realms. 當(dāng)完成一個(gè)Basic challenge, 使用Credentials.basic(username, password)來(lái)解碼請(qǐng)求頭.

private final OkHttpClient client;public Authenticate() { client = new OkHttpClient.Builder() .authenticator(new Authenticator() { @Override public Request authenticate(Route route, Response response) throws IOException { System.out.println("Authenticating for response: " + response); System.out.println("Challenges: " + response.challenges()); String credential = Credentials.basic("jesse", "password1"); return response.request().newBuilder() .header("Authorization", credential) .build(); } }) .build(); }public void run() throws Exception { Request request = new Request.Builder() .url("http://publicobject.com/secrets/hellosecret.txt") .build();Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);System.out.println(response.body().string()); } 

當(dāng)認(rèn)證無(wú)法工作時(shí), 為了避免多次重試, 你可以返回空來(lái)放棄認(rèn)證. 例如, 當(dāng)exact credentials已經(jīng)嘗試過(guò), 你可能會(huì)直接想跳過(guò)認(rèn)證, 可以這樣做:

if (credential.equals(response.request().header("Authorization"))) { return null; // If we already failed with these credentials, don't retry.} 

當(dāng)重試次數(shù)超過(guò)定義的次數(shù), 你若想跳過(guò)認(rèn)證, 可以這樣做:

if (responseCount(response) >= 3) { return null; // If we've failed 3 times, give up. }private int responseCount(Response response) { int result = 1; while ((response = response.priorResponse()) != null) { result++; } return result; } 

參考文獻(xiàn)

OkHttp使用完全教程


本頁(yè)內(nèi)容由塔燈網(wǎng)絡(luò)科技有限公司通過(guò)網(wǎng)絡(luò)收集編輯所得,所有資料僅供用戶學(xué)習(xí)參考,本站不擁有所有權(quán),如您認(rèn)為本網(wǎng)頁(yè)中由涉嫌抄襲的內(nèi)容,請(qǐng)及時(shí)與我們聯(lián)系,并提供相關(guān)證據(jù),工作人員會(huì)在5工作日內(nèi)聯(lián)系您,一經(jīng)查實(shí),本站立刻刪除侵權(quán)內(nèi)容。本文鏈接:http://www.junxiaosheng.cn/20440.html
相關(guān)開(kāi)發(fā)語(yǔ)言
 八年  行業(yè)經(jīng)驗(yàn)

多一份參考,總有益處

聯(lián)系深圳網(wǎng)站公司塔燈網(wǎng)絡(luò),免費(fèi)獲得網(wǎng)站建設(shè)方案及報(bào)價(jià)

咨詢相關(guān)問(wèn)題或預(yù)約面談,可以通過(guò)以下方式與我們聯(lián)系

業(yè)務(wù)熱線:余經(jīng)理:13699882642

Copyright ? 2013-2018 Tadeng NetWork Technology Co., LTD. All Rights Reserved.    

  • QQ咨詢
  • 在線咨詢
  • 官方微信
  • 聯(lián)系電話
    座機(jī)0755-29185426
    手機(jī)13699882642
  • 預(yù)約上門(mén)
  • 返回頂部
主站蜘蛛池模板: 1313久久国产午夜精品理论片| 亚洲午夜一区二区电影院| 亚洲AV成人无码网天堂| 亚洲精品久久久久一区二区三 | 草莓视频在线播放视频| 最近最新的日本字幕MV| 亚洲人成在线播放无码| 中文字幕亚洲欧美日韩2019 | 中国xxxxxz| chinese东北老年tv视频| 国产高清砖码区| 久久精品国产99欧美精品亚洲| 免费看到湿的小黄文软件APP | 亚洲精品无码午夜福利在线观看| 在线电台收听| 被免费网站在线视频| 国产伦精品一区二区三区精品| 狠狠色丁香婷婷久久综合| 老师你狠狂| 天美传媒色情原创精品| 在线a亚洲视频| 成人网站国产在线视频内射视频| 狠狠色综合7777久夜色撩人| 嫩草影院精品视频在线观看| 小SAO货叫大声点妓女| 最新无码国产在线视频| 粗暴玩烂货调教| 狂野猛交xxxx吃奶| 亚洲电影网址| 风车动漫(p)_在线观看官网| 蜜桃久久久亚洲精品成人| 亚洲日韩KKK444KKK聚色| 短篇合集纯肉高H深陷骚 | 91黄色大片| 久久99精品国产自在自线| 素人约啪第五季| 成人免费视频在线观看| 蜜臀AV999无码精品国产| 怡春院院日本一区二区久久 | 在线观看视频亚洲| 国产亚洲999精品AA片在线爽|