URLConnection-URL连接
【README】
- 本文介紹了 URLConnection java類,通過 URLConnection 如何獲取網(wǎng)絡(luò)資源;
- 本文還梳理了涉及網(wǎng)絡(luò)編程的java類的進(jìn)化過程;從 URL -> URLConnection -> HttpURLConnection 或 HttpClient ;?
- URL與URI的介紹, refer2?? java URL和URI_PacosonSWJTU的博客-CSDN博客
1. 按理URL 有獲取網(wǎng)絡(luò)資源的接口(如getContent(), getFile()),為啥還要封裝 URLConnection ? URL 獲取資源方法如下:
2. 很顯然, URL 只提供了客戶端與服務(wù)器簡(jiǎn)單交互的功能,沒有提供復(fù)雜的交互功能,比如請(qǐng)求頭,緩存,字符編碼,鑒權(quán),等等,并根據(jù)請(qǐng)求頭做出不同響應(yīng)啊等等;所以引入 URLConnection來封裝客戶端與服務(wù)器間的復(fù)雜功能;
- 2.1 URL 與 URLConnection 兩者間還有一個(gè)重要區(qū)別:
- 2.1.1 URL 只能讀取網(wǎng)絡(luò)資源內(nèi)容(單向),而 URLConnection 提供了不僅從服務(wù)器讀取數(shù)據(jù),還有向服務(wù)器寫入數(shù)據(jù)的功能,是雙向交互;
3. 小結(jié): URL 和 URLConnection的不同點(diǎn)
- URLConnection 提供了對(duì)http的首部訪問接口;
- URLConnection 可以配置發(fā)送給服務(wù)器的請(qǐng)求參數(shù);
- URLConnection除了可以讀取數(shù)據(jù)之外,還可以向服務(wù)器寫入數(shù)據(jù); (數(shù)據(jù)流是雙向)
4.? 那為啥還要在 URLConnection的基礎(chǔ)上 引入 HttpURLConnection ?
abstract public class HttpURLConnection extends URLConnection {?HttpURLConnection? 繼承了 URLConnection, 前者對(duì)后者的功能進(jìn)行了擴(kuò)展,以提供基于http協(xié)議的api,最顯著的差別是,提供了獲取錯(cuò)誤輸入流,響應(yīng)碼等方法,這是 URLConnection做不到的(根據(jù)下圖的方法列表,HttpURLConnection也只是一點(diǎn)點(diǎn)擴(kuò)展);
5.? 我們?cè)偕钊胍幌?#xff0c;既然有了 HttpURLConnection,那為啥 apache 還提供了 HttpClient-http客戶端工具包進(jìn)行網(wǎng)絡(luò)編程?
在通常狀況下,若是只是須要向Web站點(diǎn)的某個(gè)簡(jiǎn)單頁面提交請(qǐng)求并獲取服務(wù)器響應(yīng),HttpURLConnection徹底能夠勝任。
但訪問一些頁面需要復(fù)制操作如鑒權(quán),這就涉及Session、Cookie的處理了,若是打算使用HttpURLConnection來處理這些細(xì)節(jié),固然也是可能實(shí)現(xiàn)的,只是處理起來難度就大了。
因此 為了簡(jiǎn)化復(fù)雜的http網(wǎng)絡(luò)編程,apache提供了HttpClient工具包;
補(bǔ)充: 第5點(diǎn)并不是說不用 HttpURLConnection,全部用 HttpClient ;
要知道, 性能上 HttpURLConnection高于 HttpClient,簡(jiǎn)單網(wǎng)絡(luò)請(qǐng)求用 HttpURLConnection更快,復(fù)雜請(qǐng)求如 保存會(huì)話session,cookie,使用緩存等 使用 HttpClient;可以理解為 HttpURLConnection 是 輕量級(jí)網(wǎng)絡(luò)請(qǐng)求, HttpClient 封裝了其他額外的功能,比較重量級(jí)
(但是我們寫底層框架的時(shí)候,一般用的都是 HttpURLConnection,因?yàn)樗莏dk自帶的,HttpClient 需要額外引入apache依賴,不便于后期框架代碼維護(hù));
6. 下面給出 使用 URL, URLConnection, HttpURLConnection, HttpClient 讀取網(wǎng)絡(luò)資源的代碼示例
- 6.1 根據(jù) URL 獲取資源
- 6.2? 根據(jù) URLConnection 獲取資源
- 6.3 根據(jù) HttpURLConnection 獲取資源
- 6.4 根據(jù) apache HttpClient 獲取資源
?【1】URLConnection 簡(jiǎn)要介紹
1)URLConnection 是java的協(xié)議處理器機(jī)制的一部分,這個(gè)機(jī)制還包括 URLStatementHandler 類;
【1.1】打開 URLConnection
1)打開 URLConnection 與服務(wù)器交互步驟
【1.2】URLConnection 讀取首部(請(qǐng)求頭或響應(yīng)頭)
1)http首部包括 所請(qǐng)求網(wǎng)絡(luò)資源的內(nèi)容類型,長(zhǎng)度,內(nèi)容編碼字符集,日期時(shí)間,內(nèi)容過期時(shí)間,內(nèi)容最后修改時(shí)間;
【1.2.0】常用首部包括:
【1.2.1】 getContentType 返回響應(yīng)主體的 MIME內(nèi)容類型 (多用途互聯(lián)網(wǎng)郵件擴(kuò)展類型)
1)常用的 mime類型包括
- text/html
- text/plain
- image/gif
- application/xml
- image/jpeg
- application/json
2)如果內(nèi)容類型是某種類型文本,那這個(gè)首部可能還包含一個(gè)字符集部分來標(biāo)識(shí)文檔的字符編碼格式,如:
Content-type: text/html; charset=UTF-8 或? Content-type: application/json; charset=UTF-8
【1.2.2】getContentLength 獲取響應(yīng)報(bào)文體字節(jié)個(gè)數(shù)
- 1)getContentLength 返回是 int 類型,最多標(biāo)識(shí) 2gb(2^31=2g=20億字節(jié));
- 2)隨著網(wǎng)絡(luò)發(fā)展,實(shí)際上很有可能資源大小超過 2gb; 在這種情況下 getContentLength 返回-1;
java7 新增了 getContentLengthLong 方法 返回類型是long, 2^63 個(gè)字節(jié),理論上可以接收 8000PB=800萬TB 個(gè)字節(jié),足夠使用了;
【1.2.3】getContentEncoding() 獲取內(nèi)容編碼格式
web上常用的內(nèi)容編碼格式 可能是 x-gzip? 或? GZipInputStream 直接解碼;
【1.2.4】getDate()? 指出文檔發(fā)送給客戶端的時(shí)間;
【1.2.5】getExpiration()? 文檔在服務(wù)器的過期時(shí)間
- 1)提示客戶端應(yīng)該何時(shí)從緩存中刪除文檔,并從服務(wù)器重新下載;
- 2)如果http首部沒有 expiration字段,getExpiration() 返回0, 表示文檔不會(huì)過期,將永遠(yuǎn)保留在緩存中;
【1.2.6】getLastModified 返回文檔的最后修改時(shí)間
例子1, 讀取http常用響應(yīng)頭
// 讀取http常用響應(yīng)頭@Testpublic void f0() throws Exception {// 格式化器SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 讀取 URLConnection 的常用響應(yīng)頭URL url = new URL("http://www.baidu.com");URLConnection urlConnection = url.openConnection();// 讀取資源內(nèi)容類型, mime 類型System.out.println("內(nèi)容類型 = " + urlConnection.getContentType());// 讀取資源內(nèi)容長(zhǎng)度System.out.println("內(nèi)容長(zhǎng)度 = " + urlConnection.getContentLength());// 讀取資源內(nèi)容編碼(如gzip,不是字符編碼,字符編碼在 content-type中的mime類型指定)System.out.println("內(nèi)容編碼 = " + urlConnection.getContentEncoding());// 讀取指出文檔發(fā)送給客戶端的時(shí)間System.out.println("指出文檔發(fā)送給客戶端的時(shí)間 = " + simpleDateFormat.format(urlConnection.getDate()));// 讀取文檔在服務(wù)器的過期時(shí)間System.out.println("文檔在服務(wù)器的過期時(shí)間 = " + simpleDateFormat.format(urlConnection.getExpiration()));// 讀取 文檔的最后修改時(shí)間System.out.println("文檔的最后修改時(shí)間 = " + simpleDateFormat.format(urlConnection.getLastModified ()));}結(jié)果:
內(nèi)容類型 = text/html
內(nèi)容長(zhǎng)度 = 2381
內(nèi)容編碼 = null
指出文檔發(fā)送給客戶端的時(shí)間 = 2021-11-06 12:02:00
文檔在服務(wù)器的過期時(shí)間 = 1970-01-01 08:00:00
文檔的最后修改時(shí)間? = 1970-01-01 08:00:00
例子2,讀取二進(jìn)制資源文件,如圖片(百度上隨便搜索一張),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("文件內(nèi)容類型=" + contentType + ", 內(nèi)容長(zhǎng)度=" + contentLength);if (!contentType.startsWith("image") || contentLength < 1) {throw new IOException("不是一個(gè)圖片文件");}// 讀取資源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("讀取失敗, 讀了{(lán)0}個(gè)字節(jié),期望讀取{1}個(gè)字節(jié)", 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();}}}結(jié)果:
文件內(nèi)容類型=image/jpeg, 內(nèi)容長(zhǎng)度=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();// 獲取所有首部(響應(yīng)頭)的鍵值對(duì)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());}); // 獲取單個(gè)首部System.out.println("獲取單個(gè)首部");System.out.println("Content-Type = " + urlConnection.getHeaderField("Content-Type"));}結(jié)果:
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]
獲取單個(gè)首部
Content-Type = image/jpeg
【2】 緩存
【2.1】緩存涉及的 http報(bào)文頭
1)Expires 報(bào)文頭指示這個(gè)資源可以緩存,期限是指定的時(shí)間為止;
2)Cache-control 首部提供了更加細(xì)粒度的緩存策略;
Cache-control 會(huì)覆蓋 Expires ,即優(yōu)先級(jí) 前者高于后者;服務(wù)器可以在一個(gè)首部中發(fā)送多個(gè) Cache-control 首部,只要它們沒有沖突;?
3)Last-modified 指示資源最后一次修改日期。只有當(dāng)本地緩存的副本早于這個(gè)日期,才會(huì)到服務(wù)器獲取資源,否則客戶端一直使用本地緩存;
4)Etag首部: 資源改變時(shí)的唯一標(biāo)識(shí); 當(dāng)客戶端資源副本的Etag 與 服務(wù)器不同時(shí),才會(huì)到服務(wù)器獲取資源,否則客戶端一直使用本地緩存;
5)緩存策略(干貨):
6)java自帶的web緩存類
默認(rèn)情況下,java并沒有緩存。需要安裝URL類使用的系統(tǒng)級(jí)緩存,需要有:
- ResponseCache的子類;
- CacheRequest的子類;
- CacheResponse的子類;
【3】配置 URLConnection連接
【3.1】配置屬性及其方法
1)URLConnection定義了7個(gè)保護(hù)字段,定義了客戶端如何向服務(wù)器發(fā)送請(qǐng)求;
- 1.1)protected URL url, 指定這個(gè) URLConnection 連接 URL;初始化一次,不能修改;
- 1.2)protected boolean connected,是否連接;連接打開,該值為true,連接關(guān)閉,該值為false;connect(), getInputStream(), getOutputStream() 都會(huì)打開連接; disconnect() 關(guān)閉連接;?
- 1.3)protected boolean allowUserInteraction, 指示了是否允許用戶交互;只能在打開連接前設(shè)置,連接后設(shè)置拋出異常;
- 1.4)protected boolean doInput,是否可以從服務(wù)器服務(wù)器讀取資源;補(bǔ)充 URConnection 可以用于讀取服務(wù)器,寫入服務(wù)器,或讀寫服務(wù)器;但響應(yīng)字段要設(shè)置為true;
- 1.5)protected boolean doOutput,是否可以向服務(wù)器寫入數(shù)據(jù); 當(dāng)為一個(gè) http URL的doOutput設(shè)置為 true時(shí),請(qǐng)求方法從 GET 修改為 POST;
- 1.6)protected boolean ifModifiedSince,設(shè)置為true,則客戶端請(qǐng)求報(bào)文頭包括一個(gè)首部 If-Modified-Since,值日期時(shí)間格式;如果服務(wù)器文檔在這個(gè)時(shí)間之后修改,則發(fā)送該文檔,否則不發(fā)送(而發(fā)送響應(yīng)碼 304 Not Modified),客戶端使用本地緩存;? URLConnection的 ifModifiedSince 字段指定了 放置在 If-Modified-Since 首部字段中的日期,調(diào)用 setIfModifiedSince(long ifModifiedSince毫秒數(shù)) 來設(shè)置;
- 1.7)protected boolean useCaches, 客戶端是否使用本地緩存; URCConnection.setUseCahces(false) 用于禁用本地緩存;?
2)URLConnection 連接屬性設(shè)置例子
// URCConnection 連接屬性設(shè)置例子@Testpublic void f3() throws Exception {URL u = new URL("http://www.baidu.com");// 打開連接獲取 URLConnection對(duì)象URLConnection uc = u.openConnection();uc.setAllowUserInteraction(true); // 允許交互uc.setDoInput(true); // 可以從服務(wù)器讀取數(shù)據(jù)uc.setDoOutput(true); // 可以向服務(wù)器寫入數(shù)據(jù)uc.setIfModifiedSince(new Date().getTime()); // 設(shè)置資源最后修改時(shí)間uc.setUseCaches(false); // 禁用緩存// try資源塊-自動(dòng)關(guān)閉輸入流try (InputStream inputStream = uc.getInputStream()) {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));// 打印從服務(wù)器讀取的報(bào)文String line = "";while ((line = bufferedReader.readLine()) != null) {System.out.println(line);}}}【3.2】超時(shí)
- setConnectionTimeout(int),設(shè)置連接獲取超時(shí)時(shí)間;
- setReadTimeout(int) ,設(shè)置讀取服務(wù)器資源超時(shí)時(shí)間;?
【4】配置客戶端請(qǐng)求http首部
【4.1】增加首部字段
1) URLConnection.setRequestProperty(String name, String value) 為http首部增加字段;
其允許一個(gè)name,有多個(gè)value,多個(gè)value通過逗號(hào)隔開; 該方法只能在 打開連接前使用;
【4.2】向服務(wù)器寫入數(shù)據(jù)
// 向服務(wù)器寫入數(shù)據(jù)@Testpublic void f4() throws Exception {URL u = new URL("http://www.baidu.com");// 打開連接,把請(qǐng)求方法從get變?yōu)閜ostURLConnection uc = u.openConnection();uc.setDoOutput(true); // get 變?yōu)?postuc.setRequestProperty("cookie", "username=zhangsan; password=123456; session=B2C7E8A1F2F5E8"); // 設(shè)置cookie請(qǐng)求頭 // 寫入數(shù)據(jù)到servertry(OutputStream outputStream = uc.getOutputStream()) {outputStream.write("四川省成都市高新區(qū)".getBytes(StandardCharsets.UTF_8));}System.out.println("寫入數(shù)據(jù)成功,bingo");}【5】HttpURLConnection
HttpURLConnection 是抽象類,構(gòu)造函數(shù)是保護(hù)類型,所以不能直接創(chuàng)建;
URL.openConnection() 返回的就是一個(gè) HttpURLConnection的一個(gè)實(shí)例; 如下:
URL u = new URL("http://www.baidu.com"); // 獲取 HttpURLConnection 連接 HttpURLConnection httpUc = (HttpURLConnection) u.openConnection();【5.1】 請(qǐng)求方法
1)改變請(qǐng)求方法
HttpURLConnection.setRequestMethod() 用于修改請(qǐng)求方法;
2)請(qǐng)求方法包括:
- GET,請(qǐng)求資源,但沒有請(qǐng)求體;
- POST,請(qǐng)求資源, 有請(qǐng)求體;
- HEAD,告訴服務(wù)器只返回http首部,不用實(shí)際發(fā)送文件 ;常見用途是檢查文件的最后修改時(shí)間;
- PUT,html編輯器或向上傳文件到服務(wù)器使用put方法;
- DELETE,刪除web服務(wù)器上的文件;
- OPTIONS,詢問某個(gè)url 支持哪些選項(xiàng);
- TRACE ,trace會(huì)發(fā)送http報(bào)文頭,服務(wù)器接收這個(gè)報(bào)文頭;可以查看 服務(wù)器和客戶端之間的代理服務(wù)器做了哪些修改;
3)restful api中 方法類型與業(yè)務(wù)操作對(duì)應(yīng)關(guān)系;
| 序號(hào) | 方法類型 | 業(yè)務(wù)操作 |
| 1 | get | 查詢數(shù)據(jù) |
| 2 | post | 新增數(shù)據(jù) |
| 3 | put | 修改或更新數(shù)據(jù) |
| 4 | delete | 刪除數(shù)據(jù) |
【5.2】斷開與服務(wù)器的連接
調(diào)用 HttpURLConnection.disconnect() 方法可以關(guān)閉連接,同時(shí)關(guān)閉流;但關(guān)閉流不會(huì)關(guān)閉連接;
【5.3】處理服務(wù)器響應(yīng)
1)響應(yīng)報(bào)文示例
?
(注意: 響應(yīng)報(bào)文頭 與 響應(yīng)實(shí)體間 有一個(gè)空行(作為分隔符),圖片中我忘記標(biāo)識(shí)出來了,特此說明)
?2)獲取響應(yīng)碼和響應(yīng)報(bào)文
// 獲取響應(yīng)碼和響應(yīng)報(bào)文@Testpublic void f6() throws Exception {URL u = new URL("http://www.baidu.com");// 打開連接,把請(qǐng)求方法從get變?yōu)閜ostHttpURLConnection httpURLConnection = (HttpURLConnection) u.openConnection();System.out.println("響應(yīng)碼 = " + httpURLConnection.getResponseCode());System.out.println("響應(yīng)消息 = " + httpURLConnection.getResponseMessage());// 獲取所有響應(yīng)頭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);}}打印結(jié)果:
響應(yīng)碼? = 200
響應(yīng)消息? = 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)補(bǔ)充:HttpURLConnection 封裝了很多常量響應(yīng)碼
總結(jié)
以上是生活随笔為你收集整理的URLConnection-URL连接的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 360浏览器崩溃了解决方法
- 下一篇: 奥特曼大结局 你一定想不到