Java基于Socket實(shí)現(xiàn)HTTP下載客戶端
沒(méi)有借助任何第三方庫(kù),完全基于JAVA Socket實(shí)現(xiàn)一個(gè)最小化的HTTP文件下載客戶端。完整的演示如何通過(guò)Socket實(shí)現(xiàn)下載文件的HTTP請(qǐng)求(request header)發(fā)送如何從Socket中接受HTTP響應(yīng)(Response header, Response body)報(bào)文并解析與保存文件內(nèi)容。如何通過(guò)SwingWork實(shí)現(xiàn)UI刷新,實(shí)時(shí)顯示下載進(jìn)度。
首先看一下UI部分:
【添加下載】按鈕:
點(diǎn)擊彈出URL輸入框,用戶Copy要下載文件URL到輸入框以后,點(diǎn)擊[OK]按鈕即開(kāi)始
下載
【清除完成】按鈕:
清除所有已經(jīng)下載完成的文件列表
文件下載狀態(tài)分為以下幾種:
package com.gloomyfish.socket.tutorial.http.download; public enum DownLoadStatus { NOT_STARTED, IN_PROCESS, COMPLETED, ERROR }
UI部分主要是利用Swing組件完成。點(diǎn)擊【添加下載】執(zhí)行的代碼如下:
final JDialog dialog = new JDialog(this,"Add File Link",true); dialog.getContentPane().setLayout(new BorderLayout()); // dialog.setSize(new Dimension(400,200)); final URLFilePanel panel = new URLFilePanel(); panel.setUpListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent e) { if("OK".equals(e.getActionCommand())){ if(panel.validateInput()) { DownloadDetailStatusInfoModel data = new DownloadDetailStatusInfoModel(panel.getValidFileURL()); tableModel.getData().add(data); startDownlaod(); refreshUI(); } dialog.setVisible(false); dialog.dispose(); } else if("Cancel".equals(e.getActionCommand())) { dialog.setVisible(false); dialog.dispose(); } }}); dialog.getContentPane().add(panel, BorderLayout.CENTER); dialog.pack(); centre(dialog); dialog.setVisible(true);
【清除完成】按鈕執(zhí)行的代碼如下:
private void clearDownloaded() { List<DownloadDetailStatusInfoModel> downloadedList = new ArrayList<DownloadDetailStatusInfoModel>(); for(DownloadDetailStatusInfoModel fileStatus : tableModel.getData()) { if(fileStatus.getStatus().toString().equals(DownLoadStatus.COMPLETED.toString())) { downloadedList.add(fileStatus); } } tableModel.getData().removeAll(downloadedList); refreshUI(); }
讓JFrame組件居中顯示的代碼如下:
public static void centre(Window w) { Dimension us = w.getSize(); Dimension them = Toolkit.getDefaultToolkit().getScreenSize(); int newX = (them.width - us.width) / 2; int newY = (them.height - us.height) / 2; w.setLocation(newX, newY); }
HTTP協(xié)議實(shí)現(xiàn)部分:
概述:HTTP請(qǐng)求頭與相應(yīng)頭報(bào)文基本結(jié)構(gòu)與解釋
HTTP請(qǐng)求:一個(gè)標(biāo)準(zhǔn)的HTTP請(qǐng)求報(bào)文如
其中請(qǐng)求頭可以有多個(gè),message-body可以沒(méi)有,不是必須的。請(qǐng)求行的格式如下:
Request-Line = Method SP Request-URI SPHTTP-Version CRLF 舉例說(shuō)明如下:
Request-Line = GET http://www.w3.org/pub/WWW/TheProject.htmlHTTP/1.1\r\n
其中SP表示空格, CRLF表示回車換行符\r\n
當(dāng)你想要上傳文件時(shí)候,使用Post方式來(lái)填寫(xiě)數(shù)據(jù)到message-body中即可。發(fā)送一個(gè)
簡(jiǎn)單的HTTP請(qǐng)求報(bào)文如下:
- GET /pub/WWW/TheProject.html HTTP/1.1\r\n
- Host: www.w3.org\r\n
- \r\n
HTTP響應(yīng):一個(gè)標(biāo)準(zhǔn)的HTTP響應(yīng)報(bào)文如下
最先得到是狀態(tài)行,其格式如下:
Status-Line = HTTP-Version SP Status-CodeSP Reason-Phrase CRLF, 一個(gè)狀態(tài)行的簡(jiǎn)單例子如下:Status-Line = HTTP/1.1 200 OK一般大家最喜歡的就是Status-Code會(huì)給你很多提示,最常見(jiàn)的就是404,500等狀態(tài)碼。狀態(tài)碼的意思可以參考RFC2616中的解釋。下載文件最要緊是的檢查HTTP響應(yīng)頭中的Content-Length與Content-Type兩
個(gè)中分別聲明了文件的長(zhǎng)度與文件的類型。其它如Accept-Ranges表示接受多少到多少的字節(jié)??赡茉诙嗑€程下載中使用。搞清楚了HTTP請(qǐng)求與響應(yīng)的報(bào)文格式以后,我們就可以通過(guò)Socket按照?qǐng)?bào)文格式解析內(nèi)容,發(fā)送與讀取HTTP請(qǐng)求與響應(yīng)。具體步驟
如下:
一、根據(jù)用戶輸入的文件URL建立Socket連接
URL url = new URL(fileInfo.getFileURL()); String host = url.getHost(); int port = (url.getPort() == -1) ? url.getDefaultPort():url.getPort(); System.out.println("Host Name = " + host); System.out.println("port = " + port); System.out.println("File URI = " + url.getFile()); // create socket and start to construct the request line Socket socket = new Socket(); SocketAddress address = new InetSocketAddress(host, port); socket.connect(address);
用了URL類來(lái)把用戶輸入的url string變成容易解析一點(diǎn)的URL。
二、構(gòu)造HTTP請(qǐng)求
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF8")); String requestStr = "GET " + url.getFile() + " HTTP/1.1\r\n"; // request line // construct the request header - 構(gòu)造HTTP請(qǐng)求頭(request header) String hostHeader = "Host: " + host + "\r\n"; String acceptHeader = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"; String charsetHeader = "Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3\r\n"; String languageHeader = "Accept-Language: zh-CN,zh;q=0.8\r\n"; String keepHeader = "Connection: close\r\n";
三、發(fā)送HTTP請(qǐng)求
// 發(fā)送HTTP請(qǐng)求 bufferedWriter.write(requestStr); bufferedWriter.write(hostHeader); bufferedWriter.write(acceptHeader); bufferedWriter.write(charsetHeader); bufferedWriter.write(languageHeader); bufferedWriter.write(keepHeader); bufferedWriter.write("\r\n"); // 請(qǐng)求頭信息發(fā)送結(jié)束標(biāo)志 bufferedWriter.flush();
四、接受HTTP響應(yīng)并解析內(nèi)容,寫(xiě)入創(chuàng)建好的文件
// 準(zhǔn)備接受HTTP響應(yīng)頭并解析 CustomDataInputStream input = new CustomDataInputStream(socket.getInputStream()); File myFile = new File(fileInfo.getStoreLocation() + File.separator + fileInfo.getFileName()); String content = null; HttpResponseHeaderParser responseHeader = new HttpResponseHeaderParser(); BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(myFile)); boolean hasData = false; while((content = input.readHttpResponseHeaderLine()) != null) { System.out.println("response header contect -->> " + content); responseHeader.addResponseHeaderLine(content); if(content.length() == 0) { hasData = true; } if(hasData) { int totalBytes = responseHeader.getFileLength(); if(totalBytes == 0) break; // no response body and data int offset = 0; byte[] myData = null; if(totalBytes >= 2048) { myData = new byte[2048]; } else { myData = new byte[totalBytes]; } int numOfBytes = 0; while((numOfBytes = input.read(myData, 0, myData.length)) > 0 && offset < totalBytes) { offset += numOfBytes; float p = ((float)offset) / ((float)totalBytes) * 100.0f; if(offset > totalBytes) { numOfBytes = numOfBytes + totalBytes - offset; p = 100.0f; } output.write(myData, 0, numOfBytes); updateStatus(p); } hasData = false; break; } }
簡(jiǎn)單的HTTP響應(yīng)頭解析類HttpResponseHeaderParser代碼如下:
package com.gloomyfish.socket.tutorial.http.download; import java.util.HashMap; import java.util.Map; /** * it can parse entity header, response head * and response line <status code, CharSet, ect...> * refer to RFC2616,關(guān)于HTTP響應(yīng)頭,請(qǐng)看RFC文檔,描寫(xiě)的很詳細(xì)?。?! */ public class HttpResponseHeaderParser { public final static String CONTENT_LENGTH = "Content-Length"; public final static String CONTENT_TYPE = "Content-Type"; public final static String ACCEPT_RANGES = "Accetp-Ranges"; private Map<String, String> headerMap; public HttpResponseHeaderParser() { headerMap = new HashMap<String, String>(); } /** * <p> get the response header key value pair </p> * @param responseHeaderLine */ public void addResponseHeaderLine(String responseHeaderLine) { if(responseHeaderLine.contains(":")) { String[] keyValue = responseHeaderLine.split(": "); if(keyValue[0].equalsIgnoreCase(CONTENT_LENGTH)) { headerMap.put(CONTENT_LENGTH, keyValue[1]); } else if(keyValue[0].equalsIgnoreCase(CONTENT_TYPE)) { headerMap.put(CONTENT_TYPE, keyValue[1]); } else { headerMap.put(keyValue[0], keyValue[1]); } } } public int getFileLength() { if(headerMap.get(CONTENT_LENGTH) == null){ return 0; } return Integer.parseInt(headerMap.get(CONTENT_LENGTH)); } public String getFileType() { return headerMap.get(CONTENT_TYPE); } public Map<String, String> getAllHeaders() { return headerMap; } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)java程序設(shè)計(jì)有所幫助。
- java實(shí)現(xiàn)上傳文件到oss(阿里云)功能示例
- JAVA演示阿里云圖像識(shí)別API,印刷文字識(shí)別-營(yíng)業(yè)執(zhí)照識(shí)別
- 淺談使用java實(shí)現(xiàn)阿里云消息隊(duì)列簡(jiǎn)單封裝
- javaWeb項(xiàng)目部署到阿里云服務(wù)器步驟詳解
- JAVA通過(guò)HttpURLConnection 上傳和下載文件的方法
- java編寫(xiě)Http服務(wù)器下載工具
- java使用http實(shí)現(xiàn)文件下載學(xué)習(xí)示例
- Java下載https文件并上傳阿里云oss服務(wù)器
相關(guān)文章
Java?中很好用的數(shù)據(jù)結(jié)構(gòu)(你絕對(duì)沒(méi)用過(guò))
今天跟大家介紹的就是?java.util.EnumMap,也是?java.util?包下面的一個(gè)集合類,同樣的也有對(duì)應(yīng)的的?java.util.EnumSet,對(duì)java數(shù)據(jù)結(jié)構(gòu)相關(guān)知識(shí)感興趣的朋友一起看看吧2022-05-05spring boot整合scurity做簡(jiǎn)單的登錄校驗(yàn)的實(shí)現(xiàn)
這篇文章主要介紹了spring boot整合scurity做簡(jiǎn)單的登錄校驗(yàn)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04Java游戲服務(wù)器系列之Netty相關(guān)知識(shí)總結(jié)
今天帶大家來(lái)學(xué)習(xí)Java游戲服務(wù)器的相關(guān)知識(shí),文中對(duì)Netty作了非常詳細(xì)的介紹,對(duì)正在學(xué)習(xí)java的小伙伴們有很好的幫助,需要的朋友可以參考下2021-05-05Mybatis如何從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)存為L(zhǎng)ist類型(存為model)
這篇文章主要介紹了Mybatis如何從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)存為L(zhǎng)ist類型(存為model),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01