參考:HttpURLConnection與HttpClient淺析 html
1. GET請求與POST請求apache
HTTP協議是如今Internet上使用得最多、最重要的協議了,愈來愈多的Java應用程序須要直接經過HTTP協議來訪問網絡資源。瀏覽器
在介紹HttpURLConnection前,咱們仍是再來講一下URL請求最經常使用的兩種方式:GET請求與POST請求。緩存
GET請求的數據會附在URL以後(就是把數據放置在HTTP協議頭中),以?分割URL和傳輸數據,參數之間以&相連,如:http://localhost:8080/test.do?name=test&password=123456。安全
GET請求發送的參數若是數據是英文字母或數字,則按原樣發送,若是是空格,則轉換爲+,若是是中文或其餘字符,則直接把字符串用BASE64加密,得出如 %E4%BD%A0%E5%A5%BD
這相似的字符串,其中%XX中的XX爲該符號以16進製表示的ASCII。服務器
POST請求的參數不是放在URL字符串裏面,而是放在HTTP請求的正文內,請求的參數被封裝起來以流的形式發送給服務端。網絡
對於GET方式提交數據的大小,HTTP協議並無硬性限制,但某些瀏覽器及服務器會對它進行限制,如IE對URL長度的限制是2083字節(2K+35)。理論上POST也沒有限制,可傳較大量的數據。app
POST的安全性要比GET的安全性高。好比:經過GET提交數據,用戶名和密碼將明文出如今URL上,由於登陸頁面有可能被瀏覽器緩存,若是其餘人查看瀏覽器的歷史紀錄,那麼別人就能夠拿到你的帳號和密碼了,除此以外,使用GET提交數據還可能會形成Cross-site request forgery(CSRF,跨站請求僞造)攻擊。socket
通常來講,Get是向服務器索取數據的一種請求,而Post是向服務器提交數據的一種請求。
2. HttpURLConnection簡介
在JDK的java.net包中已經提供了訪問HTTP協議的基本功能的類:HttpURLConnection。
HttpURLConnection是Java的標準類,它繼承自URLConnection,可用於向指定網站發送GET請求、POST請求。它在URLConnection的基礎上提供了以下便捷的方法:
int getResponseCode(); // 獲取服務器的響應代碼。 String getResponseMessage(); // 獲取服務器的響應消息。 String getResponseMethod(); // 獲取發送請求的方法。 void setRequestMethod(String method); // 設置發送請求的方法。
3. HttpURLConnection的使用
3.1 使用GET方式訪問HTTP
1 package com.qf.demo; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStreamReader; 6 import java.net.HttpURLConnection; 7 import java.net.URL; 8 9 /** 10 * GET請求示例 11 * 12 * @author 小明 13 * 14 */ 15 public class GetDemo { 16 17 public static void main(String[] args) { 18 try { 19 // 1. 獲得訪問地址的URL 20 URL url = new URL( 21 "http://localhost:8080/Servlet/do_login.do?username=test&password=123456"); 22 // 2. 獲得網絡訪問對象java.net.HttpURLConnection 23 HttpURLConnection connection = (HttpURLConnection) url 24 .openConnection(); 25 /* 3. 設置請求參數(過時時間,輸入、輸出流、訪問方式),以流的形式進行鏈接 */ 26 // 設置是否向HttpURLConnection輸出 27 connection.setDoOutput(false); 28 // 設置是否從httpUrlConnection讀入 29 connection.setDoInput(true); 30 // 設置請求方式 31 connection.setRequestMethod("GET"); 32 // 設置是否使用緩存 33 connection.setUseCaches(true); 34 // 設置此 HttpURLConnection 實例是否應該自動執行 HTTP 重定向 35 connection.setInstanceFollowRedirects(true); 36 // 設置超時時間 37 connection.setConnectTimeout(3000); 38 // 鏈接 39 connection.connect(); 40 // 4. 獲得響應狀態碼的返回值 responseCode 41 int code = connection.getResponseCode(); 42 // 5. 若是返回值正常,數據在網絡中是以流的形式獲得服務端返回的數據 43 String msg = ""; 44 if (code == 200) { // 正常響應 45 // 從流中讀取響應信息 46 BufferedReader reader = new BufferedReader( 47 new InputStreamReader(connection.getInputStream())); 48 String line = null; 49 50 while ((line = reader.readLine()) != null) { // 循環從流中讀取 51 msg += line + "\n"; 52 } 53 reader.close(); // 關閉流 54 } 55 // 6. 斷開鏈接,釋放資源 56 connection.disconnect(); 57 58 // 顯示響應結果 59 System.out.println(msg); 60 } catch (IOException e) { 61 e.printStackTrace(); 62 } 63 } 64 }
3.2 使用POST方式訪問HTTP
1 package com.qf.demo; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStreamReader; 6 import java.io.OutputStream; 7 import java.net.HttpURLConnection; 8 import java.net.MalformedURLException; 9 import java.net.URL; 10 11 /** 12 * POST請求示例 13 * 14 * @author 小明 15 * 16 */ 17 public class PostDemo { 18 19 public static void main(String[] args) { 20 try { 21 // 1. 獲取訪問地址URL 22 URL url = new URL("http://localhost:8080/Servlet/do_login.do"); 23 // 2. 建立HttpURLConnection對象 24 HttpURLConnection connection = (HttpURLConnection) url 25 .openConnection(); 26 /* 3. 設置請求參數等 */ 27 // 請求方式 28 connection.setRequestMethod("POST"); 29 // 超時時間 30 connection.setConnectTimeout(3000); 31 // 設置是否輸出 32 connection.setDoOutput(true); 33 // 設置是否讀入 34 connection.setDoInput(true); 35 // 設置是否使用緩存 36 connection.setUseCaches(false); 37 // 設置此 HttpURLConnection 實例是否應該自動執行 HTTP 重定向 38 connection.setInstanceFollowRedirects(true); 39 // 設置使用標準編碼格式編碼參數的名-值對 40 connection.setRequestProperty("Content-Type", 41 "application/x-www-form-urlencoded"); 42 // 鏈接 43 connection.connect(); 44 /* 4. 處理輸入輸出 */ 45 // 寫入參數到請求中 46 String params = "username=test&password=123456"; 47 OutputStream out = connection.getOutputStream(); 48 out.write(params.getBytes()); 49 out.flush(); 50 out.close(); 51 // 從鏈接中讀取響應信息 52 String msg = ""; 53 int code = connection.getResponseCode(); 54 if (code == 200) { 55 BufferedReader reader = new BufferedReader( 56 new InputStreamReader(connection.getInputStream())); 57 String line; 58 59 while ((line = reader.readLine()) != null) { 60 msg += line + "\n"; 61 } 62 reader.close(); 63 } 64 // 5. 斷開鏈接 65 connection.disconnect(); 66 67 // 處理結果 68 System.out.println(msg); 69 } catch (MalformedURLException e) { 70 e.printStackTrace(); 71 } catch (IOException e) { 72 e.printStackTrace(); 73 } 74 } 75 }
3.3 說明
4. HttpClient簡介
在通常狀況下,若是隻是須要向Web站點的某個簡單頁面提交請求並獲取服務器響應,HttpURLConnection徹底能夠勝任。但在絕大部分狀況下,Web站點的網頁可能沒這麼簡單,這些頁面並非經過一個簡單的URL就可訪問的,可能須要用戶登陸並且具備相應的權限纔可訪問該頁面。在這種狀況下,就須要涉及Session、Cookie的處理了,若是打算使用HttpURLConnection來處理這些細節,固然也是可能實現的,只是處理起來難度就大了。
爲了更好地處理向Web站點請求,包括處理Session、Cookie等細節問題,Apache開源組織提供了一個HttpClient項目,看它的名稱就知道,它是一個簡單的HTTP客戶端(並非瀏覽器),能夠用於發送HTTP請求,接收HTTP響應。但不會緩存服務器的響應,不能執行HTML頁面中嵌入的Javascript代碼;也不會對頁面內容進行任何解析、處理。
簡單來講,HttpClient就是一個加強版的HttpURLConnection,HttpURLConnection能夠作的事情HttpClient所有能夠作;HttpURLConnection沒有提供的有些功能,HttpClient也提供了,但它只是關注於如何發送請求、接收響應,以及管理HTTP鏈接。
5. HttpClient的使用
使用HttpClient發送請求、接收響應很簡單,只要以下幾步便可。
5.1 使用GET方式訪問HTTP
1 package com.qf.client; 2 3 import java.io.IOException; 4 5 import org.apache.http.HttpEntity; 6 import org.apache.http.client.ClientProtocolException; 7 import org.apache.http.client.methods.CloseableHttpResponse; 8 import org.apache.http.client.methods.HttpGet; 9 import org.apache.http.impl.client.CloseableHttpClient; 10 import org.apache.http.impl.client.HttpClientBuilder; 11 import org.apache.http.util.EntityUtils; 12 13 /** 14 * GET請求示例 15 * 16 * @author 小明 17 * 18 */ 19 public class GetDemo { 20 21 public static void main(String[] args) { 22 // 1. 建立HttpClient對象 23 CloseableHttpClient httpClient = HttpClientBuilder.create().build(); 24 // 2. 建立HttpGet對象 25 HttpGet httpGet = new HttpGet( 26 "http://localhost:8080/Servlet/do_login.do?username=test&password=123456"); 27 CloseableHttpResponse response = null; 28 try { 29 // 3. 執行GET請求 30 response = httpClient.execute(httpGet); 31 System.out.println(response.getStatusLine()); 32 // 4. 獲取響應實體 33 HttpEntity entity = response.getEntity(); 34 // 5. 處理響應實體 35 if (entity != null) { 36 System.out.println("長度:" + entity.getContentLength()); 37 System.out.println("內容:" + EntityUtils.toString(entity)); 38 } 39 } catch (ClientProtocolException e) { 40 e.printStackTrace(); 41 } catch (IOException e) { 42 e.printStackTrace(); 43 } finally { 44 // 6. 釋放資源 45 try { 46 response.close(); 47 httpClient.close(); 48 } catch (IOException e) { 49 e.printStackTrace(); 50 } 51 } 52 } 53 }
5.2 使用POST方式訪問HTTP
1 package com.qf.client; 2 3 import java.io.IOException; 4 import java.io.UnsupportedEncodingException; 5 import java.util.ArrayList; 6 import java.util.List; 7 8 import org.apache.http.HttpEntity; 9 import org.apache.http.NameValuePair; 10 import org.apache.http.client.ClientProtocolException; 11 import org.apache.http.client.entity.UrlEncodedFormEntity; 12 import org.apache.http.client.methods.CloseableHttpResponse; 13 import org.apache.http.client.methods.HttpPost; 14 import org.apache.http.impl.client.CloseableHttpClient; 15 import org.apache.http.impl.client.HttpClientBuilder; 16 import org.apache.http.message.BasicNameValuePair; 17 import org.apache.http.util.EntityUtils; 18 19 /** 20 * POST請求測試 21 * 22 * @author 小明 23 * 24 */ 25 public class PostDemo { 26 27 public static void main(String[] args) { 28 // 1. 建立HttpClient對象 29 CloseableHttpClient httpClient = HttpClientBuilder.create().build(); 30 // 2. 建立HttpPost對象 31 HttpPost post = new HttpPost( 32 "http://localhost:8080/Servlet/do_login.do"); 33 // 3. 設置POST請求傳遞參數 34 List<NameValuePair> params = new ArrayList<NameValuePair>(); 35 params.add(new BasicNameValuePair("username", "test")); 36 params.add(new BasicNameValuePair("password", "12356")); 37 try { 38 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params); 39 post.setEntity(entity); 40 } catch (UnsupportedEncodingException e) { 41 e.printStackTrace(); 42 } 43 // 4. 執行請求並處理響應 44 try { 45 CloseableHttpResponse response = httpClient.execute(post); 46 HttpEntity entity = response.getEntity(); 47 if (entity != null){ 48 System.out.println("響應內容:"); 49 System.out.println(EntityUtils.toString(entity)); 50 } 51 response.close(); 52 } catch (ClientProtocolException e) { 53 e.printStackTrace(); 54 } catch (IOException e) { 55 e.printStackTrace(); 56 } finally { 57 // 釋放資源 58 try { 59 httpClient.close(); 60 } catch (IOException e) { 61 e.printStackTrace(); 62 } 63 } 64 } 65 }
5.3 說明
HttpClient相比傳統JDK自帶的URLConnection,增長了易用性和靈活性,它不只使客戶端發送HTTP請求變得容易,並且也方便了開發人員測試接口(基於Http協議的),即提升了開發的效率,也方便提升代碼的健壯性。
-------------------------------------------------
針對JDK中的URLConnection鏈接Servlet的問題,網上有雖然有所涉及,可是隻是說明了某一個或幾個問題,是以FAQ的方式來解決的,並且比較零散,如今對這個類的使用就本人在項目中的使用經驗作以下總結:
1:> URL請求的類別:
分爲二類,GET與POST請求。兩者的區別在於:
a:) get請求能夠獲取靜態頁面,也能夠把參數放在URL字串後面,傳遞給servlet,
b:) post與get的不一樣之處在於post的參數不是放在URL字串裏面,而是放在http請求的正文內。
2:> URLConnection的對象問題:
URLConnection的對象,以下代碼示例:
// 下面的index.jsp由<servlet-mapping>映射到
// 一個Servlet(com.quantanetwork.getClientDataServlet)
// 該Servlet的注意點下邊會提到
3:> HttpURLConnection對象參數問題
4:> HttpURLConnection鏈接問題:
5:> HttpURLConnection寫數據與發送數據問題:
總結:a:) HttpURLConnection的connect()函數,實際上只是創建了一個與服務器的tcp鏈接,並無實際發送http請求。
不管是post仍是get,http請求實際上直到HttpURLConnection的getInputStream()這個函數裏面才正式發送出去。
b:) 在用POST方式發送URL請求時,URL請求參數的設定順序是重中之重,
對connection對象的一切配置(那一堆set函數)
都必需要在connect()函數執行以前完成。而對outputStream的寫操做,又必需要在inputStream的讀操做以前。
這些順序其實是由http請求的格式決定的。
若是inputStream讀操做在outputStream的寫操做以前,會拋出例外:
java.net.ProtocolException: Cannot write output after reading input.......
c:) http請求實際上由兩部分組成,
一個是http頭,全部關於這次http請求的配置都在http頭裏面定義,
一個是正文content。
connect()函數會根據HttpURLConnection對象的配置值生成http頭部信息,所以在調用connect函數以前,
就必須把全部的配置準備好。
d:) 在http頭後面緊跟着的是http請求的正文,正文的內容是經過outputStream流寫入的,
實際上outputStream不是一個網絡流,充其量是個字符串流,往裏面寫入的東西不會當即發送到網絡,
而是存在於內存緩衝區中,待outputStream流關閉時,根據輸入的內容生成http正文。
至此,http請求的東西已經所有準備就緒。在getInputStream()函數調用的時候,就會把準備好的http請求
正式發送到服務器了,而後返回一個輸入流,用於讀取服務器對於這次http請求的返回信息。因爲http
請求在getInputStream的時候已經發送出去了(包括http頭和正文),所以在getInputStream()函數
以後對connection對象進行設置(對http頭的信息進行修改)或者寫入outputStream(對正文進行修改)
都是沒有意義的了,執行這些操做會致使異常的發生。
6:> Servlet端的開發注意點:
a:) 對於客戶端發送的POST類型的HTTP請求,Servlet必須實現doPost方法,而不能用doGet方法。
b:) 用HttpServletRequest的getInputStream()方法取得InputStream的對象,好比:
InputStream inStream = httpRequest.getInputStream();
如今調用inStream.available()(該方法用於「返回此輸入流下一個方法調用能夠不受阻塞地
今後輸入流讀取(或跳過)的估計字節數」)時,永遠都反回0。試圖使用此方法的返回值分配緩衝區,
以保存此流全部數據的作法是不正確的。那麼,如今的解決辦法是
Servlet這一端用以下實現:
InputStream inStream = httpRequest.getInputStream();
ObjectInputStream objInStream = new ObjectInputStream(inStream);
Object obj = objInStream.readObject();
// 作後續的處理
// 。。。。。。
// 。。。 。。。
而客戶端,不管是否發送實際數據都要寫入一個對象(那怕這個對象不用),如:
ObjectOutputStream objOutputStrm = new ObjectOutputStream(outStrm);
objOutputStrm.writeObject(new String("")); // 這裏發送一個空數據
// 甚至能夠發一個null對象,服務端取到後再作判斷處理。
objOutputStrm.writeObject(null);
objOutputStrm.flush();
objOutputStrm.close();
注意:上述在建立對象輸出流ObjectOutputStream時,若是將從HttpServletRequest取得的輸入流
(即:new ObjectOutputStream(outStrm)中的outStrm)包裝在BufferedOutputStream流裏面,
則必須有objOutputStrm.flush();這一句,以便將流信息刷入緩衝輸出流.以下:
ObjectOutputStream objOutputStrm = new ObjectOutputStream(new BufferedOutputStream(outStrm));
objOutputStrm.writeObject(null);
objOutputStrm.flush(); // <======此處必需要有.
objOutputStrm.close();
HttpURLConnection是基於HTTP協議的,其底層經過socket通訊實現。若是不設置超時(timeout),在網絡異常的狀況下,可能會致使程序僵死而不繼續往下執行。能夠經過如下兩個語句來設置相應的超時:
System.setProperty("sun.net.client.defaultConnectTimeout", 超時毫秒數字符串);
System.setProperty("sun.net.client.defaultReadTimeout", 超時毫秒數字符串);
其中: sun.net.client.defaultConnectTimeout:鏈接主機的超時時間(單位:毫秒)
sun.net.client.defaultReadTimeout:從主機讀取數據的超時時間(單位:毫秒)
例如:
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");
System.setProperty("sun.net.client.defaultReadTime
Java中可使用HttpURLConnection來請求WEB資源。
HttpURLConnection對象不能直接構造,須要經過URL.openConnection()來得到HttpURLConnection對象,示例代碼以下:
HttpURLConnection是基於HTTP協議的,其底層經過socket通訊實現。若是不設置超時(timeout),在網絡異常的狀況下,可能會致使程序僵死而不繼續往下執行。能夠經過如下兩個語句來設置相應的超時:
System.setProperty("sun.net.client.defaultConnectTimeout", 超時毫秒數字符串);
System.setProperty("sun.net.client.defaultReadTimeout", 超時毫秒數字符串);
其中: sun.net.client.defaultConnectTimeout:鏈接主機的超時時間(單位:毫秒)
sun.net.client.defaultReadTimeout:從主機讀取數據的超時時間(單位:毫秒)
例如:
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");
System.setProperty("sun.net.client.defaultReadTimeout", "30000");
JDK 1.5之前的版本,只能經過設置這兩個系統屬性來控制網絡超時。在1.5中,還可使用HttpURLConnection的父類URLConnection的如下兩個方法:
setConnectTimeout:設置鏈接主機超時(單位:毫秒)
setReadTimeout:設置從主機讀取數據超時(單位:毫秒)
例如:
須要注意的是,筆者在JDK1.4.2環境下,發如今設置了defaultReadTimeout的狀況下,若是發生網絡超時,HttpURLConnection會自動從新提交一次請求,出現一次請求調用,請求服務器兩次的問題(Trouble)。我認爲這是JDK1.4.2的一個bug。在JDK1.5.0中,此問題已獲得解決,不存在自動重發現象。out", "30000");
-------------------------------------