To build a better world !

          Android之基于HTTP協議的下載


          轉載請注明出處:http://www.aygfsteel.com/zh-weir/archive/2010/05/02/319892.html 

              Android系統中本身是有下載機制的,比如瀏覽器使用的DownloadManager。可遺憾的是,DownloadManager只提供給瀏覽器使用,一般的應用程序沒法調用它。 另外,如果下載調用頻繁的話,使用DownloadManager其實是很沒有效率的做法。為了解決這些問題,我想我們最好的辦法就是自己實現下載,本文就是基于HTTP協議的下載的一些簡單介紹。


          一、HTTP協議簡 

              HTTP是一個屬于應用層的面向對象的協議,由于其簡捷、快速的方式,適用于分布式超媒體信息系統。它于1990年提出,經過幾年的使用與發展,得到不斷地完善和擴展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的規范化工作正在進行之中,而且HTTP-NG(Next Generation of HTTP)的建議已經提出。
           
              HTTP協議的主要特點可概括如下:

              1.支持客戶/服務器模式。

              2.簡單快速:客戶向服務器請求服務時,只需傳送請求方法和路徑。請求方法常用的有GETHEADPOST。每種方法規定了客戶與服務器聯系的類型不同。由于HTTP協議簡單,使得HTTP服務器的程序規模小,因而通信速度很快。

              3.靈活:HTTP允許傳輸任意類型的數據對象。正在傳輸的類型由Content-Type加以標記。

              4.無連接:無連接的含義是限制每次連接只處理一個請求。服務器處理完客戶的請求,并收到客戶的應答后,即斷開連接。采用這種方式可以節省傳輸時間。

              5.無狀態:HTTP協議是無狀態協議。無狀態是指協議對于事務處理沒有記憶能力。缺少狀態意味著如果后續處理需要前面的信息,則它必須重傳,這樣可能導致每次連接傳送的數據量增大。另一方面,在服務器不需要先前信息時它的應答就較快。

              1.1 URL

              HTTP URL (URL是一種特殊類型的URI,包含了用于查找某個資源的足夠的信息)的格式如下:

             http://host[":"port][abs_path]

              http表示要通過HTTP協議來定位網絡資源;

              host表示合法的Internet主機域名或者IP地址;

              port指定一個端口號,為空則使用缺省端口80

              abs_path指定請求資源的URI
              
              注:如果URL中沒有給出abs_path,那么當它作為請求URI時,必須以“/”的形式給出,通常這個工作瀏覽器自動幫我們完成。

              例如:

              1、輸入:
          www.guet.edu.cn
              瀏覽器自動轉換成:
          http://www.guet.edu.cn/

              2http:192.168.0.116:8080/index.jsp 


              1.2 請求

              http
          請求由三部分組成,分別是:請求行、消息報頭、請求正文。

              1.2.1 請求行

              請求行以一個方法符號開頭,以空格分開,后面跟著請求的URI和協議的版本,格式如下:

              Method Request-URI HTTP-Version CRLF

              其中:

              Method表示請求方法;
              Request-URI是一個統一資源標識符;
              HTTP-Version表示請求的HTTP協議版本;
              CRLF表示回車和換行(除了作為結尾的CRLF外,不允許出現單獨的CR或LF字符)。

              例如:

              POST /hello.htm HTTP/1.1(“/r/n”)
              
              1) 請求方法:
              
              請求方法(所有方法全為大寫)有多種,各個方法的解釋如下:
              
              GET    請求獲取Request-URI所標識的資源

              POST    在Request-URI所標識的資源后附加新的數據

              HEAD    請求獲取由Request-URI所標識的資源的響應消息報頭

              PUT    請求服務器存儲一個資源,并用Request-URI作為其標識

              DELETE    請求服務器刪除Request-URI所標識的資源

              TRACE    請求服務器回送收到的請求信息,主要用于測試或診斷

              CONNECT    保留將來使用

              OPTIONS    請求查詢服務器的性能,或者查詢與資源相關的選項和需求

              2) Request-URI:

              用于標識要訪問的網絡資源。通常只要給出相對于服務器的根目錄的相對目錄即可,因此以“/”開頭。
              
              3) 協議版本。


              1.2.2 消息報頭

              HTTP消息由客戶端到服務器的請求和服務器到客戶端的響應組成。請求消息和響應消息都是由開始行(對于請求消息,開始行就是請求行,對于響應消息,開始行就是狀態行),消息報頭(可選),空行(只有CRLF的行),消息正文(可選)組成。

              1) 普通報頭:

              在普通報頭中,有少數報頭域用于所有的請求和響應消息,但并不用于被傳輸的實體,只用于傳輸的消息。

               Cache-Control:用于指定緩存指令,緩存指令是單向的(響應中出現的緩存指令在請求中未必會出現),且是獨立的(一個消息的緩存指令不會影響另一個消息處理的緩存機制),HTTP1.0使用的類似的報頭域為Pragma。

              請求時的緩存指令包括:no-cache(用于指示請求或響應消息不能緩存)、no-store、max-age、max-stale、min-fresh、only-if-cached;
              響應時的緩存指令包括:public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age、s-maxage 。

              Date:普通報頭域表示消息產生的日期和時間。

              Connection:普通報頭域允許發送指定連接的選項。例如指定連接是連續,或者指定“close”選項,通知服務器,在響應完成后,關閉連接。

              2) 請求報頭:

              允許客戶端向服務器端傳遞請求的附加信息以及客戶端自身的信息。常用的請求報頭如下:

              Accept:

              Accept請求報頭域用于指定客戶端接受哪些類型的信息。eg:Accept:image/gif,表明客戶端希望接受GIF圖象格式的資源;Accept:text/html,表明客戶端希望接受html文本。

              Accept-Charset:

              Accept-Charset請求報頭域用于指定客戶端接受的字符集。eg:Accept-Charset:iso-8859-1,gb2312.如果在請求消息中沒有設置這個域,缺省是任何字符集都可以接受。

              Accept-Encoding:

              Accept-Encoding請求報頭域類似于Accept,但是它是用于指定可接受的內容編碼。eg:Accept-Encoding:gzip.deflate.如果請求消息中沒有設置這個域服務器假定客戶端對各種內容編碼都可以接受。

              Accept-Language:

              Accept-Language請求報頭域類似于Accept,但是它是用于指定一種自然語言。eg:Accept-Language:zh-cn.如果請求消息中沒有設置這個報頭域,服務器假定客戶端對各種語言都可以接受。

              Authorization:

              Authorization請求報頭域主要用于證明客戶端有權查看某個資源。當瀏覽器訪問一個頁面時,如果收到服務器的響應代碼為401(未授權),可以發送一個包含Authorization請求報頭域的請求,要求服務器對其進行驗證。

              Host(發送請求時,該報頭域是必需的):

              Host請求報頭域主要用于指定被請求資源的Internet主機和端口號,它通常從HTTP URL中提取出來的。
              
              eg:我們在瀏覽器中輸入:
          http://www.guet.edu.cn/index.html瀏覽器發送的請求消息中,就會包含Host請求報頭域,如下:

              Host:www.guet.edu.cn

              此處使用缺省端口號80,若指定了端口號,則變成:Host:www.guet.edu.cn:指定端口號

              User-Agent:

              我們上網登陸論壇的時候,往往會看到一些歡迎信息,其中列出了你的操作系統的名稱和版本,你所使用的瀏覽器的名稱和版本,這往往讓很多人感到很神奇,實際上,服務器應用程序就是從User-Agent這個請求報頭域中獲取到這些信息。User-Agent請求報頭域允許客戶端將它的操作系統、瀏覽器和其它屬性告訴服務器。不過,這個報頭域不是必需的,如果我們自己編寫一個瀏覽器,不使用User-Agent請求報頭域,那么服務器端就無法得知我們的信息了。 

              
          請求報頭舉例:

              GET /form.html HTTP/1.1 (CRLF)
              Accept:image/gif,image/x-xbitmap,image/jpeg,application/x-shockwave-flash,application/vnd.ms-excel,application/vnd.ms-
              powerpoint,application/msword,*/* (CRLF)
              Accept-Language:zh-cn (CRLF)
              Accept-Encoding:gzip,deflate (CRLF)
              If-Modified-Since:Wed,05 Jan 2007 11:21:25 GMT (CRLF)
              If-None-Match:W/"80b1a4c018f3c41:8317" (CRLF)
              User-Agent:Mozilla/4.0(compatible;MSIE6.0;Windows NT 5.0) (CRLF)
              Host:www.guet.edu.cn (CRLF)
              Connection:Keep-Alive (CRLF)
              (CRLF)

              3) 響應報頭:

              響應報頭允許服務器傳遞不能放在狀態行中的附加響應信息,以及關于服務器的信息和對Request-URI所標識的資源進行下一步訪問的信息。
              
              常用的響應報頭:

              Location:

              Location響應報頭域用于重定向接受者到一個新的位置。Location響應報頭域常用在更換域名的時候。

              Server:

              Server響應報頭域包含了服務器用來處理請求的軟件信息。與User-Agent請求報頭域是相對應的。下面是Server響應報頭域的一個例子:

              Server:Apache-Coyote/1.1

              WWW-Authenticate:

              WWW-Authenticate響應報頭域必須被包含在401(未授權的)響應消息中,客戶端收到401響應消息時候,并發送Authorization報頭域請求服務器對其進行驗證時,服務端響應報頭就包含該報頭域。eg:WWW-Authenticate:Basic realm="Basic Auth Test!"  //可以看出服務器對請求資源采用的是基本驗證機制。

              4) 實體報頭:

              請求和響應消息都可以傳送一個實體。一個實體由實體報頭域和實體正文組成,但并不是說實體報頭域和實體正文要在一起發送,可以只發送實體報頭域。實體報頭定義了關于實體正文(eg:有無實體正文)和請求所標識的資源的元信息。

              常用的實體報頭:

              Content-Encoding:

              Content-Encoding實體報頭域被用作媒體類型的修飾符,它的值指示了已經被應用到實體正文的附加內容的編碼,因而要獲得Content-Type報頭域中所引用的媒體類型,必須采用相應的解碼機制。Content-Encoding這樣用于記錄文檔的壓縮方法。eg:Content-Encoding:gzip

              Content-Language:

              Content-Language實體報頭域描述了資源所用的自然語言。沒有設置該域則認為實體內容將提供給所有的語言閱讀者。eg:Content-Language:da

              Content-Length:

              Content-Length實體報頭域用于指明實體正文的長度,以字節方式存儲的十進制數字來表示。

              Content-Type:

              Content-Type實體報頭域用語指明發送給接收者的實體正文的媒體類型。eg:Content-Type:text/html;charset=ISO-8859-1、Content-Type:text/html;charset=GB2312

              Last-Modified:

              Last-Modified實體報頭域用于指示資源的最后修改日期和時間。

              Expires:

              Expires實體報頭域給出響應過期的日期和時間。為了讓代理服務器或瀏覽器在一段時間以后更新緩存中(再次訪問曾訪問過的頁面時,直接從緩存中加載,縮短響應時間和降低服務器負載)的頁面,我們可以使用Expires實體報頭域指定頁面過期的時間。eg:Expires:Thu,15 Sep 2006 16:23:12 GMT

              HTTP1.1的客戶端和緩存必須將其他非法的日期格式(包括0)看作已經過期。eg:為了讓瀏覽器不要緩存頁面,我們也可以利用Expires實體報頭域,設置為0,jsp中程序如下:response.setDateHeader("Expires","0");


              1.3 響應


              在接收和解釋請求消息后,服務器返回一個HTTP響應消息。HTTP響應也是由三個部分組成,分別是:狀態行、消息報頭、響應正文。


              主要說一下狀態行。狀態行格式如下:

              HTTP-Version Status-Code Reason-Phrase CRLF

              其中:

              HTTP-Version表示服務器HTTP協議的版本;
              Status-Code表示服務器發回的響應狀態代碼;
              Reason-Phrase表示狀態代碼的文本描述。

              狀態代碼有三位數字組成,第一個數字定義了響應的類別,且有五種可能取值:

              1xx:指示信息--表示請求已接收,繼續處理
              2xx:成功--表示請求已被成功接收、理解、接受
              3xx:重定向--要完成請求必須進行更進一步的操作
              4xx:客戶端錯誤--請求有語法錯誤或請求無法實現
              5xx:服務器端錯誤--服務器未能實現合法的請求

              常見狀態代碼、狀態描述、說明:

              
          200 OK    //客戶端請求成功
              400 Bad Request    //客戶端請求有語法錯誤,不能被服務器所理解
              401 Unauthorized    //請求未經授權,這個狀態代碼必須和WWW-Authenticate報頭域一起使用 
              403 Forbidden    //服務器收到請求,但是拒絕提供服務
              404 Not Found    //請求資源不存在,eg:輸入了錯誤的URL
              500 Internal Server Error    //服務器發生不可預期的錯誤
              503 Server Unavailable    //服務器當前不能處理客戶端的請求,一段時間后,可能恢復正常

              eg:HTTP/1.1 200 OK (CRLF)



          二、HTTP協議之下載

              在了解了HTTP協議的基本規則之后,我們就可以將其應用于文件的下載了。這一節將介紹的是,通過HTTP協議下載的原理。

              2.1 文件請求

              向服務器發送如下請求:

              GET /Path/FileName HTTP/1.0
              Host: www.server.com:80
              Accept: */*
              User-Agent: GeneralDownloadApplication
              Connection: close

              每行用一個“回車換行”分隔,末尾再追加一個“回車換行”作為整個請求的結束。

              Host字段表示主機名和端口號,如果端口號是默認的80則可以不寫。
              Accept字段中的*/*表示接收任何類型的數據。
              User-Agent表示用戶代理,這個字段可有可無,但強烈建議加上,因為它是服務器統計、追蹤以及識別客戶端的依據。
              Connection字段中的close表示使用非持久連接。

              2.2 服務器應答

              如果服務器成功收到該請求,并且沒有出現任何錯誤,則會返回類似下面的數據: 

              HTTP/1.0 200 OK
              Content-Length: 13057672
              Content-Type: application/octet-stream
              Last-Modified: Wed, 10 Oct 2005 00:56:34 GMT
              Accept-Ranges: bytes
              ETag: "2f38a6cac7cec51:160c"
              Server: Microsoft-IIS/6.0
              X-Powered-By: ASP.NET
              Date: Wed, 16 Nov 2005 01:57:54 GMT
              Connection: close

              Content-Length字段是一個比較重要的字段,它標明了服務器返回數據的長度,這個長度是不包含HTTP頭長度的。換句話說,我們的請求中并沒有Range字段(后面會說到),表示我們請求的是整個文件,所以Content-Length就是整個文件的大小。其余各字段是一些關于文件和服務器的屬性信息。

              這段返回數據同樣是以最后一行的結束標志(回車換行)和一個額外的回車換行作為結束,即“\r\n\r\n”。而“\r\n\r\n”后面緊接的就是文件的內容了,這樣我們就可以找到“\r\n\r\n”,并從它后面的第一個字節開始,源源不斷的讀取,再寫到文件中了。

              2.3 斷點續傳

              斷點續傳的實現非常簡單,只要在請求中加一個Range字段就可以了。假如一個文件有1000個字節,那么其范圍就是0-999,則:

              Range: bytes=500-          表示讀取該文件的500-999字節,共500字節。 
              Range: bytes=500-599    表示讀取該文件的500-599字節,共100字節。 

              
          Range還有其它幾種寫法,但上面這兩種是最常用的,對于斷點續傳也足矣了。如果HTTP請求中包含Range字段,那么服務器會返回206(Partial Content),同時HTTP頭中也會有一個相應的Content-Range字段,類似下面的格式: 

              
          Content-Range: bytes 500-999/1000 

              
          Content-Range字段說明服務器返回了文件的某個范圍及文件的總長度。這時Content-Length字段就不是整個文件的大小了,而是對應文件這個范圍的字節數,這一點一定要注意。

              2.4 重定向

              
          很多軟件下載網站的文件下載鏈接都是通過程序重定向的,比如pchome的ACDSee的HTTP下載地址是:
              
              http://download.pchome.net/php/tdownload2.php?sid=5547&url=/multimedia/viewer/acdc31sr1b051007.exe&svr=1&typ=0 

              這種地址并沒有直接標識文件的位置,而是通過程序進行了重定向。如果向服務器請求這樣的URL,服務器就會返回302(Moved Temporarily),意思就是需要重定向,同時在HTTP頭中會包含一個Location字段,Location字段的值就是重定向后的目的URL。這時就需要斷開當前的連接,而向這個重定向后的服務器發請求。


          三、HttpClient

              雖然在 JDK 的 java.net 包中已經提供了訪問 HTTP 協議的基本功能,但是對于大部分應用程序來說,JDK 庫本身提供的功能還不夠豐富和靈活。HttpClient 是 Apache Jakarta Common 下的子項目,用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,并且它支持 HTTP 協議最新的版本和建議。HttpClient 已經應用在很多的項目中,比如 Apache Jakarta 上很著名的另外兩個開源項目 Cactus 和 HTMLUnit 都使用了 HttpClient。HttpClient 項目非常活躍,使用的人還是非常多的。目前 HttpClient 版本是在 2005.10.11 發布的 3.0 RC4 。

              HttpClient的主要功能有以下一些:

              1)    實現了所有 HTTP 的方法(GET,POST,PUT,HEAD 等);
              2)    支持自動轉向;
              3)    支持 HTTPS 協議;
              4)    支持代理服務器等。


              3.1 環境搭建及所需包

              需要Java開發環境JDK,需要可以訪問網絡。Android程序需要有“android.permission.INTERNET”的permission。

              所需包:

              1、commons-httpclient-3.1.jar: 包括Http協議所需的類。
              2、commons-logging-1.1.jar: 包括記錄程序運行時的活動日志記錄的類。
              3、commons-codec-1.3.jar: 包括編碼解碼的類。

              這些包都是Apache的開源項目,可以在Apache開源組織官網http://www.apache.org/上找到。


              3.2 HttpClient實現HTTP協議基本通信操作

              在實現所有操作之前必須首先實例化一個HttpClient,即初始化一個客戶端。

          HttpClient client = new HttpClient();

              3.2.1 請求

              
          以GET請求為例。


              a、實例化一個請求方法。

          HttpMethod method = new GetMethod("http://www.google.cn");


              注:

              ①    雖然Google已經將服務器搬出了大陸,但是HttpClient可實現自動轉向,即自動重定向。所以當服務器返回的狀態代碼為3××時,將自動重定向,知道到達文件實際位置)。

              
          ②    GetMethod構造函數中的字符串表示的是文件的URI地址。這里只是因為之前沒有指定服務器主機地址,所以需要完整名。其實也可以這樣:

          client.getHostConfiguration().setHost("80"http");

              ……

          HttpMethod method 
          = new GetMethod("/simcard.php?simcard=1330227");


              b、添加需要的消息報頭信息。

          method.addRequestHeader("Range""bytes=500-");


              HttpClient會構建必須的消息報頭信息,如果沒有特殊要求可以不用修改。但如果需要在消息報頭添加一些特殊信息,例如下載時需要斷點續傳等,則可用上述方法修改。

              
          c、發出請求(執行命令)。

          int statusCode = client.executeMethod(method);


              此時,程序實際向服務器發出請求,連接成功后,函數返回,返回值為狀態代碼。

              3.2.2 響應

              接上例。

              
          a、返回狀態代碼。

              上例中的“statusCode”即為狀態代碼。除此方法之外,還可以:

          int statusCode = method.getStatusCode();

              
          注:在httpclient的包中有一個名為“HttpStatus”類,其中定義了大多數的狀態代碼。如:

              HttpStatus.SC_OK
              HttpStatus.SC_FORBIDDEN 等。

              
          b、響應報頭。

          Header[] headers = method.getResponseHeaders();

              
          獲取所有服務器端返回的響應報頭。

          Header header = method.getRequestHeader("Content-Type");

              
          獲取響應報頭中指定的鍵值對。

              
          之后可以通過調用header.getName()、header.getValue()來得到相關信息。

              
          c、響應正文。

          byte[] bytes = method.getResponseBody();

          InputStream inputStream = method.getResponseBodyAsStream(); 

          String string = method.getResponseBodyAsString(); 

               以上三種方法,視情況選用。

              3.2.3 斷開連接

          method.releaseConnection();

              斷開連接。


              3.2.4 其他

              
          其他包括一些和下載無關,但卻非常基本和有用的東西

              
          a、POST數據。

              
          POST請求和GET請求大致相同,唯一需要注意的是,如何在POST信息中加入自己所需傳輸的信息。

          postMethod.setRequestBody(InputStream body);

          postMethod.setRequestBody(NameValuePair[] parameterBody);

          postMethod.setRequestBody(String body);


              b、代理服務器。

              只需指定httpClient的實例的代理就可以了,基于此實例的所有操作將經由此代理。

          httpClient.getHostConfiguration().setProxy(hostName,port);


               c、字符編碼。

              
          某目標頁的編碼可能出現在兩個地方:

              
          第一個地方是服務器返回的http頭中(RequestHeader的Content-Type、Content-Encoding字段);
              
              
          另外一個地方是得到的html/xml頁面中。如:

              
          <meta http-equiv="Content-Type" content="text/html; charset=gb2312"/>
              或者 <?xml version="1.0" encoding="gb2312"?>

              
          d、自動跳轉。

              
          HttpClient對GET請求可實現自動跳轉。但是對于POST和PUT請求要求接受后繼服務的,暫不支持自動跳轉。

              當服務器返回的狀態代碼為3××時,需要根據消息報頭的“Location”字段的地址來實現跳轉。注意,“Location”字段的地址可能是相對地址,需要自己進行處理。

              還有一種可能就是在頁面中實現的跳轉。例如,在HTML中,<meta http-equiv="refresh" content="5; url=http://www.ibm.com/us">。

              
          e、Https協議。

              
          參看: 
          《HttpClient入門》


          參考文獻

              本文中自己的東西并不多,大多數都是來自網上的一些資料的摘抄、引用和總結。主要的文獻資料有以下一些,感謝這些文章的作者共享其資料。

              1、 《HTTP協議(收藏)》
              2、 《文件下載原理詳解1 http協議》
              3、 《HttpClient入門》
              4、 《HttpClient入門教程》


          轉載請注明出處:http://www.aygfsteel.com/zh-weir/archive/2010/05/02/319892.html 



          posted on 2010-05-02 22:25 zh.weir 閱讀(18830) 評論(3)  編輯  收藏 所屬分類: Android網絡編程

          評論

          # re: Android之基于HTTP協議的下載 2010-05-04 11:39 麗可酷

          學習,收藏  回復  更多評論   

          # re: Android之基于HTTP協議的下載[未登錄] 2012-06-19 14:52 劉勇

          電源  回復  更多評論   

          # re: Android之基于HTTP協議的下載 2013-07-19 10:50 沒重點

          寫的是個毛啊  回復  更多評論   


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           

          公告

          大家好!歡迎光臨我的 Android 技術博客!



          本博客旨在交流與 Android 操作系統相關的各種技術及信息。

          博客內的文章會盡量以開源的形式提供給大家,希望我們能相互交流,共同提高!

          有不足之處,請不吝賜教!

          我的郵箱:zh.weir@gmail.com
          我的新浪微博:@囧虎張建偉

           

          導航

          <2010年5月>
          2526272829301
          2345678
          9101112131415
          16171819202122
          23242526272829
          303112345

          統計

          留言簿(19)

          隨筆分類(24)

          隨筆檔案(18)

          文章檔案(1)

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 大庆市| 特克斯县| 沽源县| 井陉县| 东阿县| 凤山县| 宾川县| 曲松县| 通海县| 塔城市| 刚察县| 岳阳县| 白河县| 灵璧县| 永年县| 梁河县| 宣城市| 瓦房店市| 霸州市| 浪卡子县| 明光市| 深水埗区| 鹤壁市| 麦盖提县| 樟树市| 咸阳市| 玉树县| 五家渠市| 宝山区| 昌乐县| 什邡市| 梅州市| 高碑店市| 新巴尔虎右旗| 都兰县| 荣昌县| 含山县| 阿克苏市| 镇坪县| 珠海市| 益阳市|