Java基于Socket实现HTTP下载客户端
没有借助任何第三方库,完全基于java socket实现一个最小化的http文件下载客户端。完整的演示如何通过socket实现下载文件的http请求(request header)发送如何从socket中接受http响应(response header, response body)报文并解析与保存文件内容。如何通过swingwork实现ui刷新,实时显示下载进度。
首先看一下ui部分:
【添加下载】按钮:
点击弹出url输入框,用户copy要下载文件url到输入框以后,点击[ok]按钮即开始
下载
【清除完成】按钮:
清除所有已经下载完成的文件列表
文件下载状态分为以下几种:
package com.gloomyfish.socket.tutorial.http.download; public enum downloadstatus { not_started, in_process, completed, error }
ui部分主要是利用swing组件完成。点击【添加下载】执行的代码如下:
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);
【清除完成】按钮执行的代码如下:
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协议实现部分:
概述:http请求头与相应头报文基本结构与解释
http请求:一个标准的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
当你想要上传文件时候,使用post方式来填写数据到message-body中即可。发送一个
简单的http请求报文如下:
- get /pub/www/theproject.html http/1.1\r\n
- host:
- \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连接
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请求
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请求
// 发送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响应并解析内容,写入创建好的文件
// 准备接受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代码如下:
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程序设计有所帮助。