沒有借助任何第三方庫,完全基于JAVA Socket實現一個最小化的HTTP文件下載客戶端。完整的演示如何通過Socket實現下載文件的HTTP請求(request header)發送如何從Socket中接受HTTP響應(Response header, Response body)報文并解析與保存文件內容。如何通過SwingWork實現UI刷新,實時顯示下載進度。
首先看一下UI部分:
【添加下載】按鈕:
點擊彈出URL輸入框,用戶Copy要下載文件URL到輸入框以后,點擊[OK]按鈕即開始
下載
【清除完成】按鈕:
清除所有已經下載完成的文件列表
文件下載狀態分為以下幾種:
1
2
3
4
5
6
7
8
|
package com.gloomyfish.socket.tutorial.http.download; public enum DownLoadStatus { NOT_STARTED, IN_PROCESS, COMPLETED, ERROR } |
UI部分主要是利用Swing組件完成。點擊【添加下載】執行的代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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 ); |
【清除完成】按鈕執行的代碼如下:
1
2
3
4
5
6
7
8
9
10
|
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組件居中顯示的代碼如下:
1
2
3
4
5
6
7
|
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協議實現部分:
概述:HTTP請求頭與相應頭報文基本結構與解釋
HTTP請求:一個標準的HTTP請求報文如
其中請求頭可以有多個,message-body可以沒有,不是必須的。請求行的格式如下:
Request-Line = Method SP Request-URI SPHTTP-Version CRLF 舉例說明如下:
1
|
Request-Line = GET http: //www.w3.org/pub/WWW/TheProject.htmlHTTP/1.1\r\n |
其中SP表示空格, CRLF表示回車換行符\r\n
當你想要上傳文件時候,使用Post方式來填寫數據到message-body中即可。發送一個
簡單的HTTP請求報文如下:
- GET /pub/WWW/TheProject.html HTTP/1.1\r\n
- Host: www.w3.org\r\n
- \r\n
HTTP響應:一個標準的HTTP響應報文如下
最先得到是狀態行,其格式如下:
Status-Line = HTTP-Version SP Status-CodeSP Reason-Phrase CRLF, 一個狀態行的簡單例子如下:Status-Line = HTTP/1.1 200 OK一般大家最喜歡的就是Status-Code會給你很多提示,最常見的就是404,500等狀態碼。狀態碼的意思可以參考RFC2616中的解釋。下載文件最要緊是的檢查HTTP響應頭中的Content-Length與Content-Type兩
個中分別聲明了文件的長度與文件的類型。其它如Accept-Ranges表示接受多少到多少的字節??赡茉诙嗑€程下載中使用。搞清楚了HTTP請求與響應的報文格式以后,我們就可以通過Socket按照報文格式解析內容,發送與讀取HTTP請求與響應。具體步驟
如下:
一、根據用戶輸入的文件URL建立Socket連接
1
2
3
4
5
6
7
8
9
10
11
|
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變成容易解析一點的URL。
二、構造HTTP請求
1
2
3
4
5
6
7
8
9
|
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 - 構造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" ; |
三、發送HTTP請求
1
2
3
4
5
6
7
8
9
|
// 發送HTTP請求 bufferedWriter.write(requestStr); bufferedWriter.write(hostHeader); bufferedWriter.write(acceptHeader); bufferedWriter.write(charsetHeader); bufferedWriter.write(languageHeader); bufferedWriter.write(keepHeader); bufferedWriter.write( "\r\n" ); // 請求頭信息發送結束標志 bufferedWriter.flush(); |
四、接受HTTP響應并解析內容,寫入創建好的文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
// 準備接受HTTP響應頭并解析 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響應頭解析類HttpResponseHeaderParser代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
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,關于HTTP響應頭,請看RFC文檔,描寫的很詳細?。?! */ 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; } } |
以上就是本文的全部內容,希望對大家的學習java程序設計有所幫助。