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

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

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

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

 

接下来看这张图:

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

 

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

1、使用HttpUrlConnection

 

 
  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文件上传。

 

 
  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的方式一致。我们也完成了数据的上传。

 

最后测试我们的代码:

 

 
  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、付费专栏及课程。

余额充值