从原理角度解析Android (Java) http 文件上传

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/23781773

文件上传是我们项目中经常使用的功能,一般我们的服务器可能都是web服务器,当我们使用非浏览器客户端上传文件时,比如手机(Android)等上传,可能就需要对传输的数据进行规范化的拼接,说白了,就是我们得自己完成浏览器帮我们做的事。

我首先写了服务器端代码,用来接收我们的数据,一会会贴出源码。然后写了个web页面用于上次,便于我们看其中的原理。


当点击了上传以后,这里我使用了firefox的firebug来观察网络信息,可以看到发出了一个POST请求,下面我框出的是请求头信息。里面包含一些请求的配置数据。


接下来看这张图:

我们可以看到我们发送的数据,一个是name为username的普通表单数据,一个为name为uploadFile的一个文件数据,可以看得出来,浏览器把文件数据转化成了2进制然后按特定的格式发给服务器了。


好了,下面开始实现上传,模拟浏览器的操作。

1、使用HttpUrlConnection

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. private static final String BOUNDARY = "----WebKitFormBoundaryT1HoybnYeFOGFlBR";  
  2.   
  3.     /** 
  4.      *  
  5.      * @param params 
  6.      *            传递的普通参数 
  7.      * @param uploadFile 
  8.      *            需要上传的文件名 
  9.      * @param fileFormName 
  10.      *            需要上传文件表单中的名字 
  11.      * @param newFileName 
  12.      *            上传的文件名称,不填写将为uploadFile的名称 
  13.      * @param urlStr 
  14.      *            上传的服务器的路径 
  15.      * @throws IOException 
  16.      */  
  17.     public void uploadForm(Map<String, String> params, String fileFormName,  
  18.             File uploadFile, String newFileName, String urlStr)  
  19.             throws IOException {  
  20.         if (newFileName == null || newFileName.trim().equals("")) {  
  21.             newFileName = uploadFile.getName();  
  22.         }  
  23.   
  24.         StringBuilder sb = new StringBuilder();  
  25.         /** 
  26.          * 普通的表单数据 
  27.          */  
  28.         for (String key : params.keySet()) {  
  29.             sb.append("--" + BOUNDARY + "\r\n");  
  30.             sb.append("Content-Disposition: form-data; name=\"" + key + "\""  
  31.                     + "\r\n");  
  32.             sb.append("\r\n");  
  33.             sb.append(params.get(key) + "\r\n");  
  34.         }  
  35.         /** 
  36.          * 上传文件的头 
  37.          */  
  38.         sb.append("--" + BOUNDARY + "\r\n");  
  39.         sb.append("Content-Disposition: form-data; name=\"" + fileFormName  
  40.                 + "\"; filename=\"" + newFileName + "\"" + "\r\n");  
  41.         sb.append("Content-Type: image/jpeg" + "\r\n");// 如果服务器端有文件类型的校验,必须明确指定ContentType  
  42.         sb.append("\r\n");  
  43.   
  44.         byte[] headerInfo = sb.toString().getBytes("UTF-8");  
  45.         byte[] endInfo = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("UTF-8");  
  46.         System.out.println(sb.toString());  
  47.         URL url = new URL(urlStr);  
  48.         HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
  49.         conn.setRequestMethod("POST");  
  50.         conn.setRequestProperty("Content-Type",  
  51.                 "multipart/form-data; boundary=" + BOUNDARY);  
  52.         conn.setRequestProperty("Content-Length", String  
  53.                 .valueOf(headerInfo.length + uploadFile.length()  
  54.                         + endInfo.length));  
  55.         conn.setDoOutput(true);  
  56.   
  57.         OutputStream out = conn.getOutputStream();  
  58.         InputStream in = new FileInputStream(uploadFile);  
  59.         out.write(headerInfo);  
  60.   
  61.         byte[] buf = new byte[1024];  
  62.         int len;  
  63.         while ((len = in.read(buf)) != -1)  
  64.             out.write(buf, 0, len);  
  65.   
  66.         out.write(endInfo);  
  67.         in.close();  
  68.         out.close();  
  69.         if (conn.getResponseCode() == 200) {  
  70.             System.out.println("上传成功");  
  71.         }  
  72.   
  73.     }  
我详细解释一下,首先我拼接了需要发送的数据,其实就是咱们在图三中看到的数据,然后使用HttpUrlConnetion设置了一系列属性其实就是在设置图二中看到的请求头信息。

于是,我们完成了请求头的设置,以及需要上传数据的拼接,所以我们完成了浏览器的工作,自然就实现文件上传了。

2、使用Socket实现文件上传,参数基本一致,使用HttpUrlConnection上传有一个很致命的问题就是,当上传文件很大时,会发生内存溢出,手机分配给我们app的内存更小,所以就更需要解决这个问题,于是我们可以使用Socket模拟POST进行HTTP文件上传。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  *  
  3.  * @param params 
  4.  *            传递的普通参数 
  5.  * @param uploadFile 
  6.  *            需要上传的文件名 
  7.  * @param fileFormName 
  8.  *            需要上传文件表单中的名字 
  9.  * @param newFileName 
  10.  *            上传的文件名称,不填写将为uploadFile的名称 
  11.  * @param urlStr 
  12.  *            上传的服务器的路径 
  13.  * @throws IOException 
  14.  */  
  15. public void uploadFromBySocket(Map<String, String> params,  
  16.         String fileFormName, File uploadFile, String newFileName,  
  17.         String urlStr) throws IOException {  
  18.     if (newFileName == null || newFileName.trim().equals("")) {  
  19.         newFileName = uploadFile.getName();  
  20.     }  
  21.   
  22.     StringBuilder sb = new StringBuilder();  
  23.     /** 
  24.      * 普通的表单数据 
  25.      */  
  26.   
  27.     if (params != null)  
  28.         for (String key : params.keySet()) {  
  29.             sb.append("--" + BOUNDARY + "\r\n");  
  30.             sb.append("Content-Disposition: form-data; name=\"" + key  
  31.                     + "\"" + "\r\n");  
  32.             sb.append("\r\n");  
  33.             sb.append(params.get(key) + "\r\n");  
  34.         }                                                                                                                                                  else{ab.append("\r\n");}  
  35.     /** 
  36.      * 上传文件的头 
  37.      */  
  38.     sb.append("--" + BOUNDARY + "\r\n");  
  39.     sb.append("Content-Disposition: form-data; name=\"" + fileFormName  
  40.             + "\"; filename=\"" + newFileName + "\"" + "\r\n");  
  41.     sb.append("Content-Type: image/jpeg" + "\r\n");// 如果服务器端有文件类型的校验,必须明确指定ContentType  
  42.     sb.append("\r\n");  
  43.   
  44.     byte[] headerInfo = sb.toString().getBytes("UTF-8");  
  45.     byte[] endInfo = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("UTF-8");  
  46.   
  47.     System.out.println(sb.toString());  
  48.   
  49.     URL url = new URL(urlStr);  
  50.     Socket socket = new Socket(url.getHost(), url.getPort());  
  51.     OutputStream os = socket.getOutputStream();  
  52.     PrintStream ps = new PrintStream(os, true"UTF-8");  
  53.   
  54.     // 写出请求头  
  55.     ps.println("POST " + urlStr + " HTTP/1.1");  
  56.     ps.println("Content-Type: multipart/form-data; boundary=" + BOUNDARY);  
  57.     ps.println("Content-Length: "  
  58.             + String.valueOf(headerInfo.length + uploadFile.length()  
  59.                     + endInfo.length));  
  60.     ps.println("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");  
  61.   
  62.     InputStream in = new FileInputStream(uploadFile);  
  63.     // 写出数据  
  64.     os.write(headerInfo);  
  65.   
  66.     byte[] buf = new byte[1024];  
  67.     int len;  
  68.     while ((len = in.read(buf)) != -1)  
  69.         os.write(buf, 0, len);  
  70.   
  71.     os.write(endInfo);  
  72.   
  73.     in.close();  
  74.     os.close();  
  75. }  

这里因为我们使用的是Socket,所以自然对于请求头,我们也需要自己拼接了,没有什么属性设置了。参考图二框出的部分,我们使用PrintStream完成了请求头的拼接,接下来就是数据的拼接,这和使用HttpUrlConnection的方式一致。我们也完成了数据的上传。

最后测试我们的代码:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public static void main(String[] args) {  
  2.         try {  
  3.               
  4.             File file = new File("D:/dtd""dwr30.dtd");  
  5.   
  6.             new Test().uploadForm(null"uploadFile", file, "helloworld.txt",  
  7.                     "http://localhost:8080/strurts2fileupload/uploadAction");  
  8.   
  9.             new Test().uploadFromBySocket(null"uploadFile", file,  
  10.                     "hibernate-configuration-3.0.dtd",  
  11.                     "http://localhost:8080/strurts2fileupload/uploadAction");  
  12.   
  13.         } catch (Exception e) {  
  14.             e.printStackTrace();  
  15.         }  
  16.     }  

效果:




如果这篇文章对你有帮助,赞一下~


源码点击此处下载


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值