Java基于Socket實現(xiàn)HTTP下載客戶端
沒有借助任何第三方庫,完全基于JAVA Socket實現(xiàn)一個最小化的HTTP文件下載客戶端。完整的演示如何通過Socket實現(xiàn)下載文件的HTTP請求(request header)發(fā)送如何從Socket中接受HTTP響應(yīng)(Response header, Response body)報文并解析與保存文件內(nèi)容。如何通過SwingWork實現(xiàn)UI刷新,實時顯示下載進(jìn)度。
首先看一下UI部分:

【添加下載】按鈕:
點(diǎn)擊彈出URL輸入框,用戶Copy要下載文件URL到輸入框以后,點(diǎn)擊[OK]按鈕即開始
下載

【清除完成】按鈕:
清除所有已經(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é)議實現(xiàn)部分:
概述:HTTP請求頭與相應(yīng)頭報文基本結(jié)構(gòu)與解釋
HTTP請求:一個標(biāo)準(zhǔn)的HTTP請求報文如

其中請求頭可以有多個,message-body可以沒有,不是必須的。請求行的格式如下:
Request-Line = Method SP Request-URI SPHTTP-Version CRLF 舉例說明如下:
Request-Line = GET http://www.w3.org/pub/WWW/TheProject.htmlHTTP/1.1\r\n
其中SP表示空格, CRLF表示回車換行符\r\n
當(dāng)你想要上傳文件時候,使用Post方式來填寫數(shù)據(jù)到message-body中即可。發(fā)送一個
簡單的HTTP請求報文如下:
- GET /pub/WWW/TheProject.html HTTP/1.1\r\n
- Host: www.w3.org\r\n
- \r\n
HTTP響應(yīng):一個標(biāo)準(zhǔn)的HTTP響應(yīng)報文如下

最先得到是狀態(tài)行,其格式如下:
Status-Line = HTTP-Version SP Status-CodeSP Reason-Phrase CRLF, 一個狀態(tài)行的簡單例子如下:Status-Line = HTTP/1.1 200 OK一般大家最喜歡的就是Status-Code會給你很多提示,最常見的就是404,500等狀態(tài)碼。狀態(tài)碼的意思可以參考RFC2616中的解釋。下載文件最要緊是的檢查HTTP響應(yīng)頭中的Content-Length與Content-Type兩
個中分別聲明了文件的長度與文件的類型。其它如Accept-Ranges表示接受多少到多少的字節(jié)。可能在多線程下載中使用。搞清楚了HTTP請求與響應(yīng)的報文格式以后,我們就可以通過Socket按照報文格式解析內(nèi)容,發(fā)送與讀取HTTP請求與響應(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類來把用戶輸入的url string變成容易解析一點(diǎn)的URL。
二、構(gòu)造HTTP請求
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請求頭(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請求
// 發(fā)送HTTP請求
bufferedWriter.write(requestStr);
bufferedWriter.write(hostHeader);
bufferedWriter.write(acceptHeader);
bufferedWriter.write(charsetHeader);
bufferedWriter.write(languageHeader);
bufferedWriter.write(keepHeader);
bufferedWriter.write("\r\n"); // 請求頭信息發(fā)送結(jié)束標(biāo)志
bufferedWriter.flush();
四、接受HTTP響應(yīng)并解析內(nèi)容,寫入創(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;
}
}
簡單的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)頭,請看RFC文檔,描寫的很詳細(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;
}
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)java程序設(shè)計有所幫助。
相關(guān)文章
idea?springBoot項目自動注入mapper為空報錯的解決方法
這篇文章主要介紹了idea?springBoot項目自動注入mapper為空報錯的解決方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
Java中的HashMap弱引用之WeakHashMap詳解
這篇文章主要介紹了Java中的HashMap弱引用之WeakHashMap詳解,當(dāng)內(nèi)存空間不足,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強(qiáng)引用的對象來解決內(nèi)存不足的問題,需要的朋友可以參考下2023-09-09
servlet之session工作原理簡介_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了servlet之session工作原理簡介,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07
Spring Boot右鍵maven build成功但是直接運(yùn)行main方法出錯的解決方案
這篇文章主要介紹了Spring Boot-右鍵maven build成功但是直接運(yùn)行main方法出錯的解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-08-08

