HttpUrlConnection 自动上传文件
2016-01-01 17:24
686 查看
HttpUrlConnection 自动上传文件
首先,实现自动上传文件方式有很多种,其中就有SOCKET,RMI,HTTP等,考虑到服务器本身是个网站服务器,使用SOCKET和RMI需要单独开发端口,HTTP则可以直接融合到网站中,也没有特殊的要求,所以采用了HTTP方式。HTTP方式又有好几种,其中就有hessian和HttpUrlConnection。使用hessian的话,hessian提供的流上传有缺陷,可能会导致内存溢出,因此只能是将文件以多个字节数组方式传输到服务器,然后程序再整合字节成完整文件,无疑增加了代码复杂度。所以最终选择了HttpUrlConnection。
通过HttpUrlConnection自动上传文件,服务端和用户通过网页上传文件到服务器的程序一模一样,只是客户端,我们用HttpUrlConnection来代替浏览器向服务器提交数据。因此我们首先要弄清楚浏览器发送数据的格式。
第1步,数据格式:
发送文件,method是POST;multipart/form-data;我们通过firefox的firebug查看具体上传
下面数据是iteye上上传一个空文件是的firebug提交的数据。
文件格式如下:
head
Html代码
Content-Length 357
Content-Type
multipart/form-data; boundary=---------------------------41184676334
body
Html代码
-----------------------------41184676334
Content-Disposition: form-data; name="authenticity_token"
vggMNy0klrhNiSh9bQkSYeN/c3tx11I/lS0T7YDpc9U=
-----------------------------41184676334
Content-Disposition: form-data; name="attachment[uploaded_data]"; filename="test.zip" Content-Type: application/zip
-----------------------------41184676334—
格式解析:
Content-Length 定义了发送数据的总字节数;
Content-Type定义格式和数据分界符boundary,下面发送的数据将用这个boundary分隔每一个变量,boundary中的41184676334是由浏览器随机生成,我们可以直接使用。
发送2个变量,分别是authenticity_token和空文件test.zip
变量开头是 “--”+boundary,然后下一行Content-Disposition定义变量名称,下一行是content-type定义数据格式,这个不是必须的,发送文件数据时,content-type需要指定,
这里指出了文件类型,我们可以用application/octet-stream告诉服务器发送的是流,再空两行(即\r\n\r\n),是变量的值或者文件的内容。然后下一行开始是另外一个变量。最后一个变量以 “—“+boundary+”—“结束。
第2步 HTTPS处理
有时候网站采用的是HTTPS协议,因此我们需要相应的证书,我们考虑是让程序接受所有证书,就像浏览器上设置接受所有证书,不管签名与否。实际是我们也无需证书,服务器也是我们自己的,我当然相信自己编写的客户端当然也相信自己的服务端了。
要接受所有服务器上的证书,我们需要实现两个接口
javax.net.ssl.X509TrustManager和javax.net.ssl.HostnameVerifier
接口中的函数都以空函数覆盖即可。其中javax.net.ssl.HostnameVerifier的verify返回true即可。下面是我们连接服务器的函数
Java代码
HttpURLConnection getHttpConnection(String urlString) throws Exception
{
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if ("https".equalsIgnoreCase(url.getProtocol()))
{
HttpsURLConnection httpsConn=(HttpsURLConnection) conn;
TrustManager[] managers ={new MyX509TrustManager()};
SSLContext sslContext=SSLContext.getInstance("TLS");
sslContext.init(null, managers, new SecureRandom());
SSLSocketFactory ssf=sslContext.getSocketFactory();
httpsConn.setSSLSocketFactory(ssf);
httpsConn.setHostnameVerifier(new MyHostnameVerifier());
return httpsConn;
}
else
{
return conn;
}
}
其中MyX509TrustManager 和MyHostnameVerifier 分别是
javax.net.ssl.X509TrustManager 和javax.net.ssl.HostnameVerifier的实现类。
第3步,发送数据
连接了服务器,就可以发送数据了。
首先定义连接的header和连接方式和boundary
Java代码
conn = getHttpConnection(urlString);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("POST");
String BOUNDARY = "---------------------------41184676334";
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("Charsert", "UTF-8");
conn.setRequestProperty("user-agent",
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0"); //可以不指定
conn.setRequestProperty("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY);
conn.setRequestProperty("Content-Length",
String.valueOf(contentLen)); //不清楚长度,可以不写
conn.setAllowUserInteraction(false); //无需用户交互,即弹出https等的对话框。
conn.setChunkedStreamingMode(blockSize); //告诉HttpUrlConnection,我们需要采用流方式上传数据,无需本地缓存数据。HttpUrlConnection默认是将所有数据读到本地缓存,然后再发送给服务器,这样上传大文件时就会导致内存溢出。
byte[] end_data = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
StringBuffer sb = new StringBuffer();
sb.append("--").append(BOUNDARY).append("\r\n");
sb.append("Content-Disposition: form-data; name=\"username\"\r\n\r\n");
sb.append(username);
sb.append("\r\n--").append(BOUNDARY).append("\r\n");
sb.append("--").append(BOUNDARY).append("\r\n");
sb.append("Content-Disposition: form-data; name=\"password\"\r\n\r\n");
sb.append(password);
sb.append("\r\n--").append(BOUNDARY).append("\r\n");
sb.append("--").append(BOUNDARY).append("\r\n");
sb.append("Content-Disposition: form-data; name=\"file\"; filename=\identify.zip\"\r\n");
sb.append("Content-Type: application/octet-stream\r\n\r\n");
byte[] data = sb.toString().getBytes();
long fLen = f.length();
long contentLen = data.length + fLen + end_data.length;
os.write(data); //发送非文件数据
fis = new FileInputStream(f); //读取文件内容,发送数据,数据要一点点发送,不能一下全部读取发送,否则会内存溢出。
int rn;
byte[] buf = new byte[blockSize];
while ((rn = fis.read(buf, 0, blockSize)) > 0) {
os.write(buf, 0, rn);
}
os.write(end_data);
os.flush();
os.close();
fis.close();
第4步,接受服务器处理结果
Java代码
ByteArrayOutputStream bo = new ByteArrayOutputStream();
InputStream is = conn.getInputStream();
byte[] inbuf = new byte[blockSize/10];
while ((rn = is.read(inbuf, 0, blockSize/10)) > 0) {
bo.write(inbuf);
}
is.close();
String ret = bo.toString();
bo.close();
首先,实现自动上传文件方式有很多种,其中就有SOCKET,RMI,HTTP等,考虑到服务器本身是个网站服务器,使用SOCKET和RMI需要单独开发端口,HTTP则可以直接融合到网站中,也没有特殊的要求,所以采用了HTTP方式。HTTP方式又有好几种,其中就有hessian和HttpUrlConnection。使用hessian的话,hessian提供的流上传有缺陷,可能会导致内存溢出,因此只能是将文件以多个字节数组方式传输到服务器,然后程序再整合字节成完整文件,无疑增加了代码复杂度。所以最终选择了HttpUrlConnection。
通过HttpUrlConnection自动上传文件,服务端和用户通过网页上传文件到服务器的程序一模一样,只是客户端,我们用HttpUrlConnection来代替浏览器向服务器提交数据。因此我们首先要弄清楚浏览器发送数据的格式。
第1步,数据格式:
发送文件,method是POST;multipart/form-data;我们通过firefox的firebug查看具体上传
下面数据是iteye上上传一个空文件是的firebug提交的数据。
文件格式如下:
head
Html代码
Content-Length 357
Content-Type
multipart/form-data; boundary=---------------------------41184676334
body
Html代码
-----------------------------41184676334
Content-Disposition: form-data; name="authenticity_token"
vggMNy0klrhNiSh9bQkSYeN/c3tx11I/lS0T7YDpc9U=
-----------------------------41184676334
Content-Disposition: form-data; name="attachment[uploaded_data]"; filename="test.zip" Content-Type: application/zip
-----------------------------41184676334—
格式解析:
Content-Length 定义了发送数据的总字节数;
Content-Type定义格式和数据分界符boundary,下面发送的数据将用这个boundary分隔每一个变量,boundary中的41184676334是由浏览器随机生成,我们可以直接使用。
发送2个变量,分别是authenticity_token和空文件test.zip
变量开头是 “--”+boundary,然后下一行Content-Disposition定义变量名称,下一行是content-type定义数据格式,这个不是必须的,发送文件数据时,content-type需要指定,
这里指出了文件类型,我们可以用application/octet-stream告诉服务器发送的是流,再空两行(即\r\n\r\n),是变量的值或者文件的内容。然后下一行开始是另外一个变量。最后一个变量以 “—“+boundary+”—“结束。
第2步 HTTPS处理
有时候网站采用的是HTTPS协议,因此我们需要相应的证书,我们考虑是让程序接受所有证书,就像浏览器上设置接受所有证书,不管签名与否。实际是我们也无需证书,服务器也是我们自己的,我当然相信自己编写的客户端当然也相信自己的服务端了。
要接受所有服务器上的证书,我们需要实现两个接口
javax.net.ssl.X509TrustManager和javax.net.ssl.HostnameVerifier
接口中的函数都以空函数覆盖即可。其中javax.net.ssl.HostnameVerifier的verify返回true即可。下面是我们连接服务器的函数
Java代码
HttpURLConnection getHttpConnection(String urlString) throws Exception
{
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if ("https".equalsIgnoreCase(url.getProtocol()))
{
HttpsURLConnection httpsConn=(HttpsURLConnection) conn;
TrustManager[] managers ={new MyX509TrustManager()};
SSLContext sslContext=SSLContext.getInstance("TLS");
sslContext.init(null, managers, new SecureRandom());
SSLSocketFactory ssf=sslContext.getSocketFactory();
httpsConn.setSSLSocketFactory(ssf);
httpsConn.setHostnameVerifier(new MyHostnameVerifier());
return httpsConn;
}
else
{
return conn;
}
}
其中MyX509TrustManager 和MyHostnameVerifier 分别是
javax.net.ssl.X509TrustManager 和javax.net.ssl.HostnameVerifier的实现类。
第3步,发送数据
连接了服务器,就可以发送数据了。
首先定义连接的header和连接方式和boundary
Java代码
conn = getHttpConnection(urlString);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("POST");
String BOUNDARY = "---------------------------41184676334";
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("Charsert", "UTF-8");
conn.setRequestProperty("user-agent",
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0"); //可以不指定
conn.setRequestProperty("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY);
conn.setRequestProperty("Content-Length",
String.valueOf(contentLen)); //不清楚长度,可以不写
conn.setAllowUserInteraction(false); //无需用户交互,即弹出https等的对话框。
conn.setChunkedStreamingMode(blockSize); //告诉HttpUrlConnection,我们需要采用流方式上传数据,无需本地缓存数据。HttpUrlConnection默认是将所有数据读到本地缓存,然后再发送给服务器,这样上传大文件时就会导致内存溢出。
byte[] end_data = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
StringBuffer sb = new StringBuffer();
sb.append("--").append(BOUNDARY).append("\r\n");
sb.append("Content-Disposition: form-data; name=\"username\"\r\n\r\n");
sb.append(username);
sb.append("\r\n--").append(BOUNDARY).append("\r\n");
sb.append("--").append(BOUNDARY).append("\r\n");
sb.append("Content-Disposition: form-data; name=\"password\"\r\n\r\n");
sb.append(password);
sb.append("\r\n--").append(BOUNDARY).append("\r\n");
sb.append("--").append(BOUNDARY).append("\r\n");
sb.append("Content-Disposition: form-data; name=\"file\"; filename=\identify.zip\"\r\n");
sb.append("Content-Type: application/octet-stream\r\n\r\n");
byte[] data = sb.toString().getBytes();
long fLen = f.length();
long contentLen = data.length + fLen + end_data.length;
os.write(data); //发送非文件数据
fis = new FileInputStream(f); //读取文件内容,发送数据,数据要一点点发送,不能一下全部读取发送,否则会内存溢出。
int rn;
byte[] buf = new byte[blockSize];
while ((rn = fis.read(buf, 0, blockSize)) > 0) {
os.write(buf, 0, rn);
}
os.write(end_data);
os.flush();
os.close();
fis.close();
第4步,接受服务器处理结果
Java代码
ByteArrayOutputStream bo = new ByteArrayOutputStream();
InputStream is = conn.getInputStream();
byte[] inbuf = new byte[blockSize/10];
while ((rn = is.read(inbuf, 0, blockSize/10)) > 0) {
bo.write(inbuf);
}
is.close();
String ret = bo.toString();
bo.close();
相关文章推荐
- HTTP协议入门——1.1版本
- 神经网络浅讲:从神经元到深度学习
- 如何一步步拿到伐册 book申请http://www.bosshr.com/shownews_47767.html
- 全站 HTTPS 来了
- Linux网络相关命令小结
- Android开发之使用URL訪问网络资源
- win7系统关闭“window安全网络身份验证”窗口的设置方法
- 错误消息: 为了配置 TCP/IP,必须安装并启用的网络适配器
- [CSAPP] 第三部分 第11章 网络 IP地址
- Linux内核工程导论——网络:Filter(LSF、BPF)
- 转帖不会乱码的,powershell网络蜘蛛
- Http状态码(apache解析php的403错误)
- CentOS下拨号上网(网络转载)
- 用开源软件建垂直搜索引擎 转载 http://news.cnblogs.com/n/60041/
- 深入理解TCP(二)
- okhttp使用入门
- Web端为什么不会发送http请求给服务器的原因
- QT5下获取本机IP地址、计算机名、网络连接名、MAC地址、子网掩码、广播地址
- Linux虚拟机桥接网络
- 网络安全书籍推荐列表