如果要相對(duì)清楚的明白一次普通的網(wǎng)絡(luò)請(qǐng)求的流程,首先要明白Http協(xié)議的一些基礎(chǔ),接下來(lái)的內(nèi)容基于常用的Http1.1協(xié)議
請(qǐng)求URL
這里以普通的Http/Https為例
平常在使用瀏覽器的時(shí)候都會(huì)有一個(gè)請(qǐng)求資源標(biāo)識(shí)符,比方說(shuō)http://www.baidu1.com這種,具體的含義就是[scheme]://[host]:[post(默認(rèn)80,可以隱藏)]/path/...這種。
scheme:請(qǐng)求協(xié)議。
在一個(gè)完整的通話(huà)流程中,為了保證發(fā)送數(shù)據(jù)、接收數(shù)據(jù)、傳輸數(shù)據(jù)等操作的統(tǒng)一性,必須先預(yù)先定義一個(gè)協(xié)議,這個(gè)協(xié)議內(nèi)部會(huì)預(yù)先定義好一系列的規(guī)則,接下來(lái)的操作會(huì)基于這一套規(guī)則進(jìn)行。
host:請(qǐng)求域名。
網(wǎng)絡(luò)請(qǐng)求實(shí)際上就是某一個(gè)主機(jī)訪(fǎng)問(wèn)另一個(gè)主機(jī)的過(guò)程,那么對(duì)于被訪(fǎng)問(wèn)的主機(jī)必須要有標(biāo)識(shí),這個(gè)就是域名。
實(shí)際的過(guò)程中會(huì)相對(duì)復(fù)雜,單純通過(guò)一個(gè)域名是無(wú)法找到主機(jī)的,因?yàn)樵趥鬏數(shù)倪^(guò)程中基本的標(biāo)識(shí)是IP地址,那么中間會(huì)需要DNS服務(wù)器將域名轉(zhuǎn)換為確切的一個(gè)IP地址,接著再通過(guò)這個(gè)IP地址去訪(fǎng)問(wèn)對(duì)應(yīng)的主機(jī)
port:請(qǐng)求端口
當(dāng)通過(guò)某一個(gè)IP地址訪(fǎng)問(wèn)到對(duì)應(yīng)主機(jī)之后,但是一個(gè)主機(jī)可能有多個(gè)服務(wù),比方說(shuō)Http和Https這兩個(gè)不同的協(xié)議,同一臺(tái)主機(jī)應(yīng)該是可以處理這兩種不同的協(xié)議,那么就是通過(guò)不同的端口來(lái)進(jìn)行區(qū)分,目前默認(rèn)的Http是80端口,Https是443端口。
path:當(dāng)確切訪(fǎng)問(wèn)到主機(jī)的某一個(gè)端口后,為了區(qū)別訪(fǎng)問(wèn)的具體文件,需要通過(guò)path來(lái)指定具體的訪(fǎng)問(wèn)對(duì)象
Http協(xié)議
這里以Http1.1協(xié)議為例
在一次的通信過(guò)程中,傳輸?shù)幕締挝环Q(chēng)為報(bào)文,這里看一下報(bào)文的基本結(jié)構(gòu),這個(gè)也是傳遞過(guò)程中的數(shù)據(jù),報(bào)文主要關(guān)注請(qǐng)求報(bào)文和響應(yīng)報(bào)文即可
請(qǐng)求報(bào)文
協(xié)議中定義的報(bào)文有著自己的格式,接下來(lái)看一下一個(gè)Http1.1的請(qǐng)求報(bào)文格式
起始行
請(qǐng)求方式 host/path/...?參數(shù) 協(xié)議
舉例說(shuō)明:(注意空格)
POST www.hh.com?a=1 HTTP/1.1
請(qǐng)求頭部
起始行結(jié)束之后要進(jìn)行換行,然后接著寫(xiě)入請(qǐng)求頭部數(shù)據(jù),請(qǐng)求頭部的基本格式是鍵值對(duì)模式。
需要添加的頭部1: 參數(shù)1
需要添加的頭部2: 參數(shù)2
...其余需要添加的頭部
換行符
這里以一個(gè)常用的頭部作為例子說(shuō)明一下:
Connection: Keep-alive(長(zhǎng)連接)
Content-Length: 10248(數(shù)據(jù)的字節(jié)數(shù))
Content-Type: multipart/form-data(表單格式數(shù)據(jù))
Host: api.hh.com(請(qǐng)求域名)
換行符
請(qǐng)求數(shù)據(jù)
這里以form表單為例子,傳輸一個(gè)文本和一個(gè)文件,注意空格
--boundary(一串字符)
Content-Disposition: form-data; name="text1"
Content-Length: 5
text1
--boundary(一串字符)
Content-Disposition: form-data; name="file1";
filename="water.png"
Content-Type: application/octet-stream
Content-Length: 該文件的字節(jié)數(shù)
##########(總之就是該文件的二進(jìn)制流數(shù)據(jù))
--boundary(一串字符)--
這就是定義的表單數(shù)據(jù)格式,通過(guò)分隔符開(kāi)始,然后通過(guò)特定的分隔符結(jié)束。接著在獲取對(duì)應(yīng)數(shù)據(jù)的時(shí)候只要獲得name,然后去讀取對(duì)應(yīng)數(shù)據(jù)即可。
傳輸模式
數(shù)據(jù)拼接完成之后,接下來(lái)要做的就是將這些數(shù)據(jù)傳輸?shù)綄?duì)應(yīng)主機(jī)(服務(wù)器)上面,在Java層面來(lái)說(shuō),一旦通過(guò)socket進(jìn)行TCP連接之后,將會(huì)在發(fā)起連接的主機(jī)和被連接的主機(jī)之間建立一個(gè)流通道,那么只需要通過(guò)向被連接主機(jī)的輸出流寫(xiě)入上述報(bào)文數(shù)據(jù),這樣就完成了發(fā)送數(shù)據(jù)請(qǐng)求的流程。
總結(jié)
這里簡(jiǎn)單的模擬一個(gè)完整報(bào)文用于明確請(qǐng)求報(bào)文數(shù)據(jù)
POST api.bi.com HTTP/1.1
Content-Type: multipart/form-data
Content-Length: 10005
Connection: Keep-alive
--abcdefghijklmn
Content-Disposition: form-data;name="text1"
Content-Length: 5
text1
--abcdefghijklmn
Content-Disposition: form-data;name="file1";filename="water.png"
Content-Type: application/octet-stream
Content-Length: 10000
#########(二進(jìn)制數(shù)據(jù),看上去就是一堆亂碼)
--abcdefghijklmn--
響應(yīng)報(bào)文
發(fā)送一個(gè)請(qǐng)求到服務(wù)器之后,服務(wù)器進(jìn)行一些業(yè)務(wù)上面的處理,接著便是返回結(jié)果通知發(fā)送請(qǐng)求的主機(jī),以方便主機(jī)進(jìn)行后續(xù)操作。
和上面相似的,也是通過(guò)報(bào)文的格式返回
起始行
協(xié)議 狀態(tài)碼 狀態(tài)碼對(duì)應(yīng)說(shuō)明
舉例:
http/1.1 200 OK
表示當(dāng)前協(xié)議為Http1.1,并且請(qǐng)求成功。
Http默認(rèn)定義了很多狀態(tài)碼,這個(gè)可以自行查閱資料,比方說(shuō)200表示成功、304表示緩存沒(méi)有變化、404表示請(qǐng)求資源未找到等等
響應(yīng)頭部
以請(qǐng)求一張圖片為例子:
Date: Tue, 15 Aug 2017 03:38:02 GMT(服務(wù)端處理時(shí)間)
Content-Type: image/png
Content-Length: 10005
Expires: Fri, 13 Aug 2027 03:38:02 GMT(響應(yīng)數(shù)據(jù)過(guò)期時(shí)間)
Last-Modified: Fri, 11 Aug 2017 08:55:57 GMT(響應(yīng)數(shù)據(jù)在服務(wù)端最后一次修改時(shí)間)
Location: http://www.ba.com(如果當(dāng)前返回重定向,則該頭部就是用來(lái)標(biāo)記需要重新訪(fǎng)問(wèn)的URL)
Cache-Control: no-cache, no-store, must-revalidate
換行符
響應(yīng)數(shù)據(jù)
實(shí)際上就是字節(jié)流數(shù)據(jù),需要更具自身的需求進(jìn)行處理
傳輸模式
這里服務(wù)端需要往發(fā)起請(qǐng)求的主機(jī)的輸出流中寫(xiě)入數(shù)據(jù),從發(fā)起請(qǐng)求的主機(jī)角度上面來(lái)看,就是通過(guò)獲取服務(wù)端的輸入流來(lái)讀取響應(yīng)數(shù)據(jù)即可。
Android的一個(gè)例子
這里通過(guò)HttpURLConnection來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)易的連接
try {
//請(qǐng)求URL
URL url = new URL("http://api.kdniao.cc/Ebusiness/EbusinessOrderHandle.aspx");
//通過(guò)該URL打開(kāi)一個(gè)TCP連接通道
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setReadTimeout(30000);//讀取超時(shí)
connection.setConnectTimeout(20000);//連接超時(shí)
connection.addRequestProperty("Connection","Keep-Alive");//添加請(qǐng)求頭部
connection.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
connection.setRequestProperty("Accept-Encoding","utf-8");
connection.setDoOutput(true);
//進(jìn)行POST傳參
connection.setRequestMethod("POST");
OutputStream os = connection.getOutputStream();
String text = "RequestType=1002&EBusinessID=1";
os.write(text.getBytes("UTF-8"));
os.flush();
os.close();
//該接口不需要傳參
int code = connection.getResponseCode();//獲取狀態(tài)碼
if(code == 200){//當(dāng)前請(qǐng)求成功
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//獲取響應(yīng)流
BufferedInputStream bufferedInputStream = new BufferedInputStream(connection.getInputStream());
int num = -1;
while (-1 != (num = bufferedInputStream.read())){//讀取服務(wù)端返回的數(shù)據(jù)
outputStream.write(num);
}
bufferedInputStream.close();
Log.i("tag1",outputStream.toString());//打印數(shù)據(jù)
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
實(shí)際上該接口的參數(shù)沒(méi)有傳完整,這里只是通過(guò)一個(gè)例子說(shuō)明,這個(gè)接口要求數(shù)據(jù)格式為application/x-www-form-urlencoded,這個(gè)是要求參數(shù)通過(guò)a1=123&a2=123這種方式進(jìn)行傳遞。如果是上述說(shuō)過(guò)的form-data模式,那么text在拼接的時(shí)候處理不同,要手動(dòng)拼接boundary之類(lèi)的數(shù)據(jù)。
總結(jié)
這篇文章僅僅希望能夠入門(mén)網(wǎng)絡(luò)請(qǐng)求基礎(chǔ),實(shí)際在使用的時(shí)候,一般不會(huì)自己進(jìn)行數(shù)據(jù)拼接和獲取,畢竟現(xiàn)在現(xiàn)成封裝后的庫(kù)還是很多的,不過(guò)基礎(chǔ)還是要理解的。