URLConnection-URL连接
【README】
- 本文介紹了 URLConnection java類,通過 URLConnection 如何獲取網絡資源;
- 本文還梳理了涉及網絡編程的java類的進化過程;從 URL -> URLConnection -> HttpURLConnection 或 HttpClient ;?
- URL與URI的介紹, refer2?? java URL和URI_PacosonSWJTU的博客-CSDN博客
1. 按理URL 有獲取網絡資源的接口(如getContent(), getFile()),為啥還要封裝 URLConnection ? URL 獲取資源方法如下:
2. 很顯然, URL 只提供了客戶端與服務器簡單交互的功能,沒有提供復雜的交互功能,比如請求頭,緩存,字符編碼,鑒權,等等,并根據請求頭做出不同響應啊等等;所以引入 URLConnection來封裝客戶端與服務器間的復雜功能;
- 2.1 URL 與 URLConnection 兩者間還有一個重要區別:
- 2.1.1 URL 只能讀取網絡資源內容(單向),而 URLConnection 提供了不僅從服務器讀取數據,還有向服務器寫入數據的功能,是雙向交互;
3. 小結: URL 和 URLConnection的不同點
- URLConnection 提供了對http的首部訪問接口;
- URLConnection 可以配置發送給服務器的請求參數;
- URLConnection除了可以讀取數據之外,還可以向服務器寫入數據; (數據流是雙向)
4.? 那為啥還要在 URLConnection的基礎上 引入 HttpURLConnection ?
abstract public class HttpURLConnection extends URLConnection {?HttpURLConnection? 繼承了 URLConnection, 前者對后者的功能進行了擴展,以提供基于http協議的api,最顯著的差別是,提供了獲取錯誤輸入流,響應碼等方法,這是 URLConnection做不到的(根據下圖的方法列表,HttpURLConnection也只是一點點擴展);
5.? 我們再深入一下,既然有了 HttpURLConnection,那為啥 apache 還提供了 HttpClient-http客戶端工具包進行網絡編程?
在通常狀況下,若是只是須要向Web站點的某個簡單頁面提交請求并獲取服務器響應,HttpURLConnection徹底能夠勝任。
但訪問一些頁面需要復制操作如鑒權,這就涉及Session、Cookie的處理了,若是打算使用HttpURLConnection來處理這些細節,固然也是可能實現的,只是處理起來難度就大了。
因此 為了簡化復雜的http網絡編程,apache提供了HttpClient工具包;
補充: 第5點并不是說不用 HttpURLConnection,全部用 HttpClient ;
要知道, 性能上 HttpURLConnection高于 HttpClient,簡單網絡請求用 HttpURLConnection更快,復雜請求如 保存會話session,cookie,使用緩存等 使用 HttpClient;可以理解為 HttpURLConnection 是 輕量級網絡請求, HttpClient 封裝了其他額外的功能,比較重量級
(但是我們寫底層框架的時候,一般用的都是 HttpURLConnection,因為它是jdk自帶的,HttpClient 需要額外引入apache依賴,不便于后期框架代碼維護);
6. 下面給出 使用 URL, URLConnection, HttpURLConnection, HttpClient 讀取網絡資源的代碼示例
- 6.1 根據 URL 獲取資源
- 6.2? 根據 URLConnection 獲取資源
- 6.3 根據 HttpURLConnection 獲取資源
- 6.4 根據 apache HttpClient 獲取資源
?【1】URLConnection 簡要介紹
1)URLConnection 是java的協議處理器機制的一部分,這個機制還包括 URLStatementHandler 類;
【1.1】打開 URLConnection
1)打開 URLConnection 與服務器交互步驟
【1.2】URLConnection 讀取首部(請求頭或響應頭)
1)http首部包括 所請求網絡資源的內容類型,長度,內容編碼字符集,日期時間,內容過期時間,內容最后修改時間;
【1.2.0】常用首部包括:
【1.2.1】 getContentType 返回響應主體的 MIME內容類型 (多用途互聯網郵件擴展類型)
1)常用的 mime類型包括
- text/html
- text/plain
- image/gif
- application/xml
- image/jpeg
- application/json
2)如果內容類型是某種類型文本,那這個首部可能還包含一個字符集部分來標識文檔的字符編碼格式,如:
Content-type: text/html; charset=UTF-8 或? Content-type: application/json; charset=UTF-8
【1.2.2】getContentLength 獲取響應報文體字節個數
- 1)getContentLength 返回是 int 類型,最多標識 2gb(2^31=2g=20億字節);
- 2)隨著網絡發展,實際上很有可能資源大小超過 2gb; 在這種情況下 getContentLength 返回-1;
java7 新增了 getContentLengthLong 方法 返回類型是long, 2^63 個字節,理論上可以接收 8000PB=800萬TB 個字節,足夠使用了;
【1.2.3】getContentEncoding() 獲取內容編碼格式
web上常用的內容編碼格式 可能是 x-gzip? 或? GZipInputStream 直接解碼;
【1.2.4】getDate()? 指出文檔發送給客戶端的時間;
【1.2.5】getExpiration()? 文檔在服務器的過期時間
- 1)提示客戶端應該何時從緩存中刪除文檔,并從服務器重新下載;
- 2)如果http首部沒有 expiration字段,getExpiration() 返回0, 表示文檔不會過期,將永遠保留在緩存中;
【1.2.6】getLastModified 返回文檔的最后修改時間
例子1, 讀取http常用響應頭
// 讀取http常用響應頭@Testpublic void f0() throws Exception {// 格式化器SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 讀取 URLConnection 的常用響應頭URL url = new URL("http://www.baidu.com");URLConnection urlConnection = url.openConnection();// 讀取資源內容類型, mime 類型System.out.println("內容類型 = " + urlConnection.getContentType());// 讀取資源內容長度System.out.println("內容長度 = " + urlConnection.getContentLength());// 讀取資源內容編碼(如gzip,不是字符編碼,字符編碼在 content-type中的mime類型指定)System.out.println("內容編碼 = " + urlConnection.getContentEncoding());// 讀取指出文檔發送給客戶端的時間System.out.println("指出文檔發送給客戶端的時間 = " + simpleDateFormat.format(urlConnection.getDate()));// 讀取文檔在服務器的過期時間System.out.println("文檔在服務器的過期時間 = " + simpleDateFormat.format(urlConnection.getExpiration()));// 讀取 文檔的最后修改時間System.out.println("文檔的最后修改時間 = " + simpleDateFormat.format(urlConnection.getLastModified ()));}結果:
內容類型 = text/html
內容長度 = 2381
內容編碼 = null
指出文檔發送給客戶端的時間 = 2021-11-06 12:02:00
文檔在服務器的過期時間 = 1970-01-01 08:00:00
文檔的最后修改時間? = 1970-01-01 08:00:00
例子2,讀取二進制資源文件,如圖片(百度上隨便搜索一張),mime類型為 image/jpeg ;
@Testpublic void f1() throws Exception {URL url = new URL("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F18%2F08%2F24%2F05dbcc82c8d3bd356e57436be0922357.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1638764377&t=703444b029255bfa2ddba73484dd7c7c");// 打開連接,判斷是否為圖片格式URLConnection urlConnection = url.openConnection();String contentType = urlConnection.getContentType(); // long contentLength = urlConnection.getContentLengthLong();int contentLength = urlConnection.getContentLength();System.out.println("文件內容類型=" + contentType + ", 內容長度=" + contentLength);if (!contentType.startsWith("image") || contentLength < 1) {throw new IOException("不是一個圖片文件");}// 讀取資源try (BufferedInputStream bufferedInputStream = new BufferedInputStream(url.openStream())) {byte[] data = new byte[contentLength];int offset = 0;int byteread = 0;while(offset < contentLength) {if ((byteread = bufferedInputStream.read(data, offset, data.length - offset)) == -1) {break;}offset += byteread;}if (offset != contentLength) {throw new IOException(MessageFormat.format("讀取失敗, 讀了{0}個字節,期望讀取{1}個字節", offset, contentLength));}// 寫入文件,獲取文件名String filename = url.getFile();System.out.println("資源文件名 = " + filename);filename = "temp01." + contentType.split("[\\W+]")[1];// 非詞字符正則分割try (FileOutputStream fos = new FileOutputStream("D:/temp/" + filename)) {fos.write(data);fos.flush();}}}結果:
文件內容類型=image/jpeg, 內容長度=46417
【1.3】獲取任意首部
// 獲取任意首部@Testpublic void f1() throws Exception {URL url = new URL("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F18%2F08%2F24%2F05dbcc82c8d3bd356e57436be0922357.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1638764377&t=703444b029255bfa2ddba73484dd7c7c");URLConnection urlConnection = url.openConnection();// 獲取所有首部(響應頭)的鍵值對Map<String, List<String>> headerFileds = urlConnection.getHeaderFields();Set<Map.Entry<String, List<String>>> entrySet = headerFileds.entrySet();entrySet.forEach(entry -> {String key = entry.getKey();System.out.println("key = " + key + ", 值列表=" + entry.getValue());}); // 獲取單個首部System.out.println("獲取單個首部");System.out.println("Content-Type = " + urlConnection.getHeaderField("Content-Type"));}結果:
key = null, 值列表=[HTTP/1.1 200 OK]
key = Server, 值列表=[JSP3/2.0.14]
key = Access-Control-Allow-Origin, 值列表=[*]
key = Ohc-Upstream-Trace, 值列表=[118.112.225.100]
key = Connection, 值列表=[keep-alive]
key = Last-Modified, 值列表=[Thu, 01 Jan 1970 00:00:00 GMT]
key = Ohc-File-Size, 值列表=[46417]
key = Date, 值列表=[Sat, 06 Nov 2021 04:42:29 GMT]
key = Accept-Ranges, 值列表=[bytes]
key = Ohc-Cache-HIT, 值列表=[cd6ct100 [4], bdix100 [4]]
key = Ohc-Response-Time, 值列表=[1 0 0 0 0 0]
key = ETag, 值列表=[927f0861741bd2135df3cbac979cdded]
key = Timing-Allow-Origin, 值列表=[*]
key = Expires, 值列表=[Sat, 04 Dec 2021 06:42:40 GMT]
key = Content-Length, 值列表=[46417]
key = Age, 值列表=[1287]
key = Content-Type, 值列表=[image/jpeg]
獲取單個首部
Content-Type = image/jpeg
【2】 緩存
【2.1】緩存涉及的 http報文頭
1)Expires 報文頭指示這個資源可以緩存,期限是指定的時間為止;
2)Cache-control 首部提供了更加細粒度的緩存策略;
Cache-control 會覆蓋 Expires ,即優先級 前者高于后者;服務器可以在一個首部中發送多個 Cache-control 首部,只要它們沒有沖突;?
3)Last-modified 指示資源最后一次修改日期。只有當本地緩存的副本早于這個日期,才會到服務器獲取資源,否則客戶端一直使用本地緩存;
4)Etag首部: 資源改變時的唯一標識; 當客戶端資源副本的Etag 與 服務器不同時,才會到服務器獲取資源,否則客戶端一直使用本地緩存;
5)緩存策略(干貨):
6)java自帶的web緩存類
默認情況下,java并沒有緩存。需要安裝URL類使用的系統級緩存,需要有:
- ResponseCache的子類;
- CacheRequest的子類;
- CacheResponse的子類;
【3】配置 URLConnection連接
【3.1】配置屬性及其方法
1)URLConnection定義了7個保護字段,定義了客戶端如何向服務器發送請求;
- 1.1)protected URL url, 指定這個 URLConnection 連接 URL;初始化一次,不能修改;
- 1.2)protected boolean connected,是否連接;連接打開,該值為true,連接關閉,該值為false;connect(), getInputStream(), getOutputStream() 都會打開連接; disconnect() 關閉連接;?
- 1.3)protected boolean allowUserInteraction, 指示了是否允許用戶交互;只能在打開連接前設置,連接后設置拋出異常;
- 1.4)protected boolean doInput,是否可以從服務器服務器讀取資源;補充 URConnection 可以用于讀取服務器,寫入服務器,或讀寫服務器;但響應字段要設置為true;
- 1.5)protected boolean doOutput,是否可以向服務器寫入數據; 當為一個 http URL的doOutput設置為 true時,請求方法從 GET 修改為 POST;
- 1.6)protected boolean ifModifiedSince,設置為true,則客戶端請求報文頭包括一個首部 If-Modified-Since,值日期時間格式;如果服務器文檔在這個時間之后修改,則發送該文檔,否則不發送(而發送響應碼 304 Not Modified),客戶端使用本地緩存;? URLConnection的 ifModifiedSince 字段指定了 放置在 If-Modified-Since 首部字段中的日期,調用 setIfModifiedSince(long ifModifiedSince毫秒數) 來設置;
- 1.7)protected boolean useCaches, 客戶端是否使用本地緩存; URCConnection.setUseCahces(false) 用于禁用本地緩存;?
2)URLConnection 連接屬性設置例子
// URCConnection 連接屬性設置例子@Testpublic void f3() throws Exception {URL u = new URL("http://www.baidu.com");// 打開連接獲取 URLConnection對象URLConnection uc = u.openConnection();uc.setAllowUserInteraction(true); // 允許交互uc.setDoInput(true); // 可以從服務器讀取數據uc.setDoOutput(true); // 可以向服務器寫入數據uc.setIfModifiedSince(new Date().getTime()); // 設置資源最后修改時間uc.setUseCaches(false); // 禁用緩存// try資源塊-自動關閉輸入流try (InputStream inputStream = uc.getInputStream()) {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));// 打印從服務器讀取的報文String line = "";while ((line = bufferedReader.readLine()) != null) {System.out.println(line);}}}【3.2】超時
- setConnectionTimeout(int),設置連接獲取超時時間;
- setReadTimeout(int) ,設置讀取服務器資源超時時間;?
【4】配置客戶端請求http首部
【4.1】增加首部字段
1) URLConnection.setRequestProperty(String name, String value) 為http首部增加字段;
其允許一個name,有多個value,多個value通過逗號隔開; 該方法只能在 打開連接前使用;
【4.2】向服務器寫入數據
// 向服務器寫入數據@Testpublic void f4() throws Exception {URL u = new URL("http://www.baidu.com");// 打開連接,把請求方法從get變為postURLConnection uc = u.openConnection();uc.setDoOutput(true); // get 變為 postuc.setRequestProperty("cookie", "username=zhangsan; password=123456; session=B2C7E8A1F2F5E8"); // 設置cookie請求頭 // 寫入數據到servertry(OutputStream outputStream = uc.getOutputStream()) {outputStream.write("四川省成都市高新區".getBytes(StandardCharsets.UTF_8));}System.out.println("寫入數據成功,bingo");}【5】HttpURLConnection
HttpURLConnection 是抽象類,構造函數是保護類型,所以不能直接創建;
URL.openConnection() 返回的就是一個 HttpURLConnection的一個實例; 如下:
URL u = new URL("http://www.baidu.com"); // 獲取 HttpURLConnection 連接 HttpURLConnection httpUc = (HttpURLConnection) u.openConnection();【5.1】 請求方法
1)改變請求方法
HttpURLConnection.setRequestMethod() 用于修改請求方法;
2)請求方法包括:
- GET,請求資源,但沒有請求體;
- POST,請求資源, 有請求體;
- HEAD,告訴服務器只返回http首部,不用實際發送文件 ;常見用途是檢查文件的最后修改時間;
- PUT,html編輯器或向上傳文件到服務器使用put方法;
- DELETE,刪除web服務器上的文件;
- OPTIONS,詢問某個url 支持哪些選項;
- TRACE ,trace會發送http報文頭,服務器接收這個報文頭;可以查看 服務器和客戶端之間的代理服務器做了哪些修改;
3)restful api中 方法類型與業務操作對應關系;
| 序號 | 方法類型 | 業務操作 |
| 1 | get | 查詢數據 |
| 2 | post | 新增數據 |
| 3 | put | 修改或更新數據 |
| 4 | delete | 刪除數據 |
【5.2】斷開與服務器的連接
調用 HttpURLConnection.disconnect() 方法可以關閉連接,同時關閉流;但關閉流不會關閉連接;
【5.3】處理服務器響應
1)響應報文示例
?
(注意: 響應報文頭 與 響應實體間 有一個空行(作為分隔符),圖片中我忘記標識出來了,特此說明)
?2)獲取響應碼和響應報文
// 獲取響應碼和響應報文@Testpublic void f6() throws Exception {URL u = new URL("http://www.baidu.com");// 打開連接,把請求方法從get變為postHttpURLConnection httpURLConnection = (HttpURLConnection) u.openConnection();System.out.println("響應碼 = " + httpURLConnection.getResponseCode());System.out.println("響應消息 = " + httpURLConnection.getResponseMessage());// 獲取所有響應頭for (int i = 1; ; i++) {String header = httpURLConnection.getHeaderField(i);String key = httpURLConnection.getHeaderFieldKey(i);if (header == null || key == null) break;System.out.println("key=" + key + ", value=" + header);}}打印結果:
響應碼? = 200
響應消息? = OK
key=Content-Length, value=2381
key=Content-Type, value=text/html
key=Server, value=bfe
key=Date, value=Sat, 06 Nov 2021 08:51:40 GMT
3)補充:HttpURLConnection 封裝了很多常量響應碼
總結
以上是生活随笔為你收集整理的URLConnection-URL连接的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 360浏览器崩溃了解决方法
- 下一篇: kafak消费者从头开始消费(消费者组)