精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2012-05-14
最后修改:2012-05-14
首先,实现自动上传文件方式有很多种,其中就有 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
Content-Length 357 Content-Type multipart/form-data; boundary=---------------------------41184676334 body -----------------------------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 即可。下面是我们连接服务器的函数
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 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 步,接受服务器处理结果
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();
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
浏览 10209 次