[转]详解HTTP包
?一、超文本傳輸協(xié)議及HTTP包
 ??? HTTP協(xié)議用于在Internet上發(fā)送和接收消息。HTTP協(xié)議是一種請(qǐng)求-應(yīng)答式的協(xié)議——客戶端發(fā)送一個(gè)請(qǐng)求,服務(wù)器返回該請(qǐng)求的應(yīng)答,所有的請(qǐng)求與應(yīng)答都是HTTP包。HTTP協(xié)議使用可靠的TCP連接,默認(rèn)端口是80。HTTP的第一個(gè)版本是HTTP/0.9,后來(lái)發(fā)展到了HTTP/1.0,現(xiàn)在最新的版本是HTTP/1.1。HTTP/1.1由RFC 2616 定義。
 ??? 在HTTP中,Client/Server之間的會(huì)話總是由客戶端通過(guò)建立連接和發(fā)送HTTP請(qǐng)求包初始化,服務(wù)器不會(huì)主動(dòng)聯(lián)系客戶端或要求與客戶端建立連接。瀏覽器和服務(wù)器都可以隨時(shí)中斷連接,例如,在瀏覽網(wǎng)頁(yè)時(shí)你可以隨時(shí)點(diǎn)擊“停止”按鈕中斷當(dāng)前的文件下載過(guò)程,關(guān)閉與Web服務(wù)器的HTTP連接。 
   1 HTTP請(qǐng)求包
   HTTP請(qǐng)求包(GET、POST等請(qǐng)求方法)由三個(gè)部分構(gòu)成,分別是:方法-URI-協(xié)議/版本,請(qǐng)求頭,請(qǐng)求正文。下面是一個(gè)HTTP請(qǐng)求包(GET)的例子: 
 GET /index.jsp HTTP/1.1
 Accept-Language: zh-cn
 Connection: Keep-Alive 
 Host: 192.168.0.106
 Content-Length: 37
userName=new_andy&password=new_andy
 ?
 ??? 請(qǐng)求包的第一行是方法-URI-協(xié)議/版本:
 ??? GET就是請(qǐng)求方法,根據(jù)HTTP標(biāo)準(zhǔn),HTTP請(qǐng)求可以使用多種請(qǐng)求方法。HTTP 1.1支持七種請(qǐng)求方法:GET、POST、HEAD、OPTIONS、PUT、DELETE和TRACE等,常用的為請(qǐng)求方法是GET和POST。
 ??? /index.jsp表示URI。URI指定了要訪問(wèn)的網(wǎng)絡(luò)資源。
 ??? HTTP/1.1是協(xié)議和協(xié)議的版本。
 ??? 最后一行userName=new_andy&password=new_andy為正文,正文與HTTP頭部有一個(gè)空行(\r\n)分隔。這里需要說(shuō)明的一點(diǎn),其中Content-Length說(shuō)明正文的長(zhǎng)度,有的正文長(zhǎng)度沒(méi)有在頭部說(shuō)明,只是標(biāo)明Transfer-Encoding: chunked。關(guān)于chunked類型的長(zhǎng)度計(jì)算方法,見(jiàn)RFC 1626。
 ??? 請(qǐng)求包的頭部還會(huì)包含許多有關(guān)客戶端環(huán)境和請(qǐng)求正文的有用信息,這里不再描述。
   2 HTTP應(yīng)答包
和HTTP請(qǐng)求包相似,由三個(gè)部分構(gòu)成,分別是:協(xié)議-狀態(tài)代碼-描述,應(yīng)答頭,應(yīng)答正文。下面是一個(gè)HTTP應(yīng)答的例子:
HTTP/1.1 200 OK
 Server: Microsoft-IIS/4.0
 Date: Mon, 3 Jan 2005 13:13:33 GMT
 Content-Type: text/html
 Last-Modified: Mon, 11 Jan 2004 13:23:42 GMT
 Content-Length: 90
<html>
 <head>
 <title>解讀HTTP包示例</title></head><body>
 Hello WORLD!
 </body>
 </html>
  HTTP應(yīng)答包的第一行類似于HTTP請(qǐng)求的第一行,表示所用的協(xié)議是HTTP 1.1,服務(wù)器處理請(qǐng)求的狀態(tài)碼200。 
   應(yīng)答頭也和請(qǐng)求頭一樣包含許多有用的信息,例如服務(wù)器類型、日期時(shí)間、內(nèi)容類型和長(zhǎng)度等。應(yīng)答的正文就是服務(wù)器返回的HTML頁(yè)面。應(yīng)答頭和正文之間也用CRLF分隔。 
 二、Socket類與ServerSocket類 
   在Java中,通信端點(diǎn)由java.net.Socket類(客戶端)或java.net.ServerSocket類(服務(wù)器端)表示。應(yīng)用程序通過(guò)端點(diǎn)向網(wǎng)絡(luò)發(fā)送或從網(wǎng)絡(luò)讀取數(shù)據(jù)。位于兩臺(tái)不同機(jī)器上的應(yīng)用軟件通過(guò)網(wǎng)絡(luò)連接發(fā)送和接收字節(jié)流,從而實(shí)現(xiàn)通信。要把HTTP包發(fā)送給另一個(gè)應(yīng)用,首先要知道對(duì)方的IP地址以及其通信端點(diǎn)的端口號(hào)。
 ?? Socket類代表的是客戶端,它是一個(gè)連接遠(yuǎn)程服務(wù)器應(yīng)用時(shí)臨時(shí)創(chuàng)建的端點(diǎn)。
 ?? ServerSocker類代表的是服務(wù)器端,它啟動(dòng)后等待來(lái)自客戶端的連接請(qǐng)求;一旦接收到請(qǐng)求,ServerSocket創(chuàng)建一個(gè)Socket實(shí)例來(lái)處理與該客戶端的通信。對(duì)于服務(wù)器應(yīng)用,我們不知道客戶端應(yīng)用什么時(shí)候會(huì)試圖連接服務(wù)器,服務(wù)器必須一直處于等待連接的狀態(tài)。
  下面是ServerSocket提供了四個(gè)構(gòu)造函數(shù),常用的構(gòu)造函數(shù)的的一種形式為:
 ? public ServerSocket(int port, int backLog, InetAddress bindingAddress);
 ? 參數(shù):port指定服務(wù)器端監(jiān)聽(tīng)客戶端的端口;
 ? backlog為連接請(qǐng)求的最大隊(duì)列長(zhǎng)度,一旦超越這個(gè)長(zhǎng)度,服務(wù)器端點(diǎn)開(kāi)始拒絕客戶端的連接請(qǐng)求。
 ? bindingAddress是一個(gè)java.net.InetAddress的實(shí)例,指定綁定IP地址。
    創(chuàng)建好ServerSocket實(shí)例之后,調(diào)用它的accept方法,要求它等待傳入的連接請(qǐng)求。只有出現(xiàn)了連接請(qǐng)求時(shí),accept方法才會(huì)返回,它的返回值是一個(gè)Socket類的實(shí)例。隨后,這個(gè)Socket對(duì)象就可以用來(lái)與客戶端應(yīng)用通信。
 ? 
 ? Socket類有許多構(gòu)造函數(shù),常用的為:
 ? public Socket(String host, int port)。參數(shù)是主機(jī)名稱(IP地址或域名)和端口號(hào)。
    參數(shù)host是遠(yuǎn)程機(jī)器的名字或IP地址,port是遠(yuǎn)程應(yīng)用的端口號(hào)。
 ?  成功創(chuàng)建了Socket類的實(shí)例之后,我們就可以用它來(lái)發(fā)送和接收字節(jié)流形式的數(shù)據(jù),數(shù)據(jù)一般為HTTP包。
 ?? 
 ?? 要發(fā)送字節(jié)流,首先要調(diào)用Socket類的getOutputStream方法獲得一個(gè)java.io.OutputStream對(duì)象;要從連接的另一端接收字節(jié)流,首先要調(diào)用Socket類的getInputStream方法獲得一個(gè)java.io.InputStream對(duì)象。 
 ?  下面的代碼片斷創(chuàng)建一個(gè)與本地HTTP服務(wù)器(127.0.0.1代表本地主機(jī)的IP地址)通信的Socket,發(fā)送一個(gè)HTTP請(qǐng)求包,準(zhǔn)備接收服務(wù)器的應(yīng)答。
 ? Socket socket??? = new Socket("127.0.0.1", "80");
 ? OutputStream os? = socket.getOutputStream();
 ? InputStream? ins = socket.getInputStream();
 ? StringBuffer sb=new StringBuffer();
 ? sb.append("GET /index.jsp HTTP/1.1\r\n");//注意\r\n為回車換行
 ? sb.append("Accept-Language: zh-cn\r\n");
 ? sb.append("Connection: Keep-Alive\r\n");
 ? sb.append("Host: 192.168.0.106\r\n");
 ? sb.append("Content-Length: 37\r\n");
 ? sb.append("\r\n");
 ? sb.append("userName=new_andy&password=new_andy\r\n");
 ? sb.append("\r\n");
 ? 
 ? //向Web服務(wù)器發(fā)送一個(gè)HTTP請(qǐng)求包
 ? os.write(sb.toString().getBytes());? 
 ? 
 ? 服務(wù)器端的代碼在大致結(jié)構(gòu)為:
 ? while (!shutdown) {
 ??????? Socket socket = null;
 ??????? try {
 ??????????? socket = serverSocket.accept(); //等待客戶以送HTTP請(qǐng)求包
 ??????????? // 創(chuàng)建HTTP請(qǐng)求包處理線程
 ??????????? RequestThread request = new RequestThread(socket);
 ??????????? request.start();
 ??????????? if(shutdown) System.exit(0);
 ??????? }
 ??????? catch (Exception e) {
 ??????????? e.printStackTrace();
 ??????? }
 ??? }
 ? RequestThread線程分析HTTP請(qǐng)求包,跟根據(jù)請(qǐng)求包內(nèi)容在服務(wù)端生成一個(gè)HTTP應(yīng)答包。下一節(jié)說(shuō)明怎樣分析HTTP包。
 ? InputStream? input = socket.getInputStream();? //從此字節(jié)數(shù)據(jù)流獲得HTTP請(qǐng)求包內(nèi)容
 ??? OutputStream output= socket.getOutputStream(); //向此字節(jié)流寫入HTTP應(yīng)答包內(nèi)容
 ??? 
 三、讀取HTTP包
 ? 以下我自己設(shè)計(jì)的一個(gè)讀取HTTP包的類SocketRequest。
 ? public class SocketRequest {? //從指定的Socket的InputStream中讀取數(shù)據(jù)
? private InputStream? input;
 ? private String???? uri;
 ? private StringBuffer? request=new StringBuffer();? //用于保存所有內(nèi)容
 ? private int?????? CONTENT_LENGTH=0;? //實(shí)際包內(nèi)容數(shù)據(jù)長(zhǎng)
 ? private boolean??? bePost = false;
 ? private boolean??? beHttpResponse = false;
 ? private boolean??? beChucked = false;
 ? private boolean??? beGet = false;
 ? private byte?????? crlf13 = (byte)13; //'\r'
 ? private byte?????? crlf10 = (byte)10;? //'\n'
? public SocketRequest(InputStream input) {
 ??? this.input = input;
 ? }
 ?public SocketRequest(Socket socket) {
 ??? this.input = socket.getInputStream();
 ? }
? public void ReadData() {? //解析 獲得InputStream的數(shù)據(jù)
 ?
 ? ReadHeader();? //頭部
? if(beChucked) //為Chucked
 ? {
 ?? int ChuckSize=0;
 ?? while((ChuckSize=getChuckSize())>0) //多個(gè)Chucked
 ?? {
 ??? readLenData(ChuckSize+2);//讀取定長(zhǎng)數(shù)據(jù)
 ?? }
 ?? readLenData(2); //最后的2位? 
 ? }
 ? 
 ? if(CONTENT_LENGTH>0)
 ? {
 ?? readLenData(CONTENT_LENGTH);//讀取定長(zhǎng)數(shù)據(jù)
 ? }
 ? 
 ??? uri = "";//parseUri(new String(request));
 ? }
 ? 
 ? private void readLenData(int size)? //讀取定長(zhǎng)數(shù)據(jù)
 ? {
 ?? int readed=0;? //已經(jīng)讀取數(shù)
 ?? try{
 ??? int available=0;//input.available(); //可讀數(shù)
 ??? if(available>(size-readed)) available=size-readed;
 ??? while( readed<size )
 ???? {
 ?????? while(available==0){? //等到有數(shù)據(jù)可讀
 ???????? available = input.available(); //可讀數(shù)
 ??????? }
 ??????? if(available>(size-readed)) available= size-readed; //size-readed--剩余數(shù)
 ??????? if(available>2048) available= 2048; //size-readed--剩余數(shù)
 ?????? byte[] buffer = new byte[available];
 ?????? int reading = input.read(buffer);
 ?????? request=request.append(new String(buffer,0,reading));? //byte數(shù)組相加
 ??????? readed+=reading;? //已讀字符
 ?? }
 ?? }catch(IOException e){
 ???? System.out.println("Read readLenData Error!");
 ?? }
 ?}
 ?
 ?? private void? ReadHeader() //讀取頭部 并獲得大小
 ?? {
 ??? byte[]? crlf?? = new byte[1];
 ?? int???? crlfNum= 0;?? //已經(jīng)連接的回車換行數(shù) crlfNum=4為頭部結(jié)束
 ??? try{
 ???? while( input.read(crlf)!=-1 )?? //讀取頭部
 ???? {
 ????? if(crlf[0]==crlf13 || crlf[0]==crlf10)
 ????? {
 ???????? crlfNum++; 
 ????? }
 ????? else
 ????? {? crlfNum=0;? } //不是則清
 ????? request=request.append(new String(crlf,0,1));? //byte數(shù)組相加
 ????? if(crlfNum==4) break;
 ???? }
 ?? }catch(IOException e){
 ???? System.out.println("Read Http Header Error!");
 ???? return;
 ??? }
 ?? 
 ??? String tempStr=(new String(request)).toUpperCase();
 ??? 
 ??? //這里我只處理了GET與POST方法
 ??? String? strMethod? = tempStr.substring(0,4);
 ??? if(strMethod.equals("GET ")) //前
 ??? {? beGet=true;??? 
 ??? }
 ??? else if(strMethod.equals("POST"))
 ??? {
 ???? bePost=true;
 ???? getContentlen_Chucked(tempStr);
 ??? }
 ??? else {
 ???? System.out.println("不支持的HTTP包類型");
 ???? 
 ??? } //其它的其它類型 暫不支持
 ? }
? private void getContentlen_Chucked(String tempStr)? //獲得長(zhǎng)度 CONTENT-LENGTH 或 是否為CHUNKED型
 ? {
 ?? String ss1="CONTENT-LENGTH:";
 ?? String ss2=new String("TRANSFER-ENCODING: CHUNKED");
 ?? 
 ?? int clIndex?? = tempStr.indexOf(ss1);
 ??? int chuckIndex = tempStr.indexOf(ss2);? //為CHUNKED型
 ??? byte requst[]= tempStr.getBytes();
 ??? if(clIndex!=-1)
 ??? { //從clIndex+1起至\r\n
 ?????? StringBuffer sb=new StringBuffer();
 ?????? 
 ?????? for(int i=(clIndex+16);;i++)
 ?????? {
 ??????? if(requst[i]!=(byte)13 && requst[i]!=(byte)10 )
 ??????? {
 ????????? sb.append((char)requst[i]);
 ??????? }
 ??????? else 
 ???????? break;
 ?????? }
 ?????? 
 ?????? CONTENT_LENGTH=Integer.parseInt(sb.toString());? //正式的HTML文件的大小
 ?????? //System.out.println("CONTENT_LENGTH==? "+CONTENT_LENGTH);
 ?? }
 ?? if(chuckIndex!=-1) beChucked=true;
 ? }
 ?? 
 ? private int? getChuckSize() //Chuck大小
 ?? {
 ??? byte[]? crlf?? = new byte[1];
 ??? StringBuffer? sb1?? = new StringBuffer();
??? int???? crlfNum= 0;?? //已經(jīng)連接的回車換行數(shù) crlfNum=4為頭部結(jié)束
 ??? 
 ??? try{
 ???? while(input.read(crlf)!=-1)?? //讀取頭部
 ???? {
 ????? if(crlf[0]==crlf13 || crlf[0]==crlf10)
 ????? {? crlfNum++; }
 ????? else
 ????? {? crlfNum=0;? } //不是則清
 ????? sb1.append((char)crlf[0]);
 ????? request=request.append(new String(crlf,0,1));? //byte數(shù)組相加
 ????? if(crlfNum==2) break;
 ???? }
 ?? }catch(IOException e){
 ???? System.out.println("Read Http Package Error!");
 ???? return 0;
 ??? }
 ?? 
 ?? return Integer.parseInt((sb1.toString()).trim(),16); //16進(jìn)控制
 ?}
 ? //通過(guò)此來(lái)進(jìn)行過(guò)濾,是否為發(fā)至目標(biāo)服務(wù)器的HTTP包
 ? private String parseUri(String requestString) { 
 ??? int index1, index2;
 ??? index1 = requestString.indexOf(' ');
 ??? if (index1 != -1) {
 ????? index2 = requestString.indexOf(' ', index1 + 1);
 ????? if (index2 > index1)
 ??????? return requestString.substring(index1 + 1, index2);
 ??? }
 ??? return null;
 ? }
? public String getData() {
 ??? return request.toString();
 ? }
 }
?使用此類:
 ?SocketRequest request = new SocketRequest(socket); //socket為ServerSocket.accept()返回的Socket實(shí)例
 ?request.ReadData();? //讀取數(shù)據(jù)
 ?request.getData();
 ?為什么我要用這么大的力量去讀取呢,尤其是在因?yàn)镾ocket連接在發(fā)送數(shù)據(jù)時(shí),由于網(wǎng)絡(luò)的原因經(jīng)常會(huì)發(fā)生延遲現(xiàn)象,可能在服務(wù)器端開(kāi)始接收數(shù)據(jù)時(shí)可能只有部分?jǐn)?shù)據(jù)可以從InputStream中獲得,在一些地方處理不當(dāng)時(shí),可能只能獲得不完整的數(shù)據(jù)或是錯(cuò)誤的數(shù)據(jù)。
 ?從InputStream讀取字節(jié)時(shí)有多種辦法:
 ?常用int read()與int read(byte[] b)。在用read(byte[])時(shí),程序員經(jīng)常會(huì)犯錯(cuò)誤,因?yàn)樵诰W(wǎng)絡(luò)環(huán)境中,讀取的數(shù)據(jù)量不一定等于參數(shù)的大小。
 ?
 ?希望我的這篇文章能給你帶來(lái)一些幫助。
轉(zhuǎn)載于:https://blog.51cto.com/2483526/471854
總結(jié)
以上是生活随笔為你收集整理的[转]详解HTTP包的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
                            
                        - 上一篇: 一项调查结果:你的分析/挖掘计算机的配置
 - 下一篇: 又拍网架构中的分库设计