从原理角度解析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. * @param params
  5. * 传递的普通参数
  6. * @param uploadFile
  7. * 需要上传的文件名
  8. * @param fileFormName
  9. * 需要上传文件表单中的名字
  10. * @param newFileName
  11. * 上传的文件名称,不填写将为uploadFile的名称
  12. * @param urlStr
  13. * 上传的服务器的路径
  14. * @throws IOException
  15. */
  16. public void uploadForm(Map<String, String> params, String fileFormName,
  17. File uploadFile, String newFileName, String urlStr)
  18. throws IOException {
  19. if (newFileName == null || newFileName.trim().equals( "")) {
  20. newFileName = uploadFile.getName();
  21. }
  22. StringBuilder sb = new StringBuilder();
  23. /**
  24. * 普通的表单数据
  25. */
  26. for (String key : params.keySet()) {
  27. sb.append( "--" + BOUNDARY + "\r\n");
  28. sb.append( "Content-Disposition: form-data; name=\"" + key + "\""
  29. + "\r\n");
  30. sb.append( "\r\n");
  31. sb.append(params.get(key) + "\r\n");
  32. }
  33. /**
  34. * 上传文件的头
  35. */
  36. sb.append( "--" + BOUNDARY + "\r\n");
  37. sb.append( "Content-Disposition: form-data; name=\"" + fileFormName
  38. + "\"; filename=\"" + newFileName + "\"" + "\r\n");
  39. sb.append( "Content-Type: image/jpeg" + "\r\n"); // 如果服务器端有文件类型的校验,必须明确指定ContentType
  40. sb.append( "\r\n");
  41. byte[] headerInfo = sb.toString().getBytes( "UTF-8");
  42. byte[] endInfo = ( "\r\n--" + BOUNDARY + "--\r\n").getBytes( "UTF-8");
  43. System.out.println(sb.toString());
  44. URL url = new URL(urlStr);
  45. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  46. conn.setRequestMethod( "POST");
  47. conn.setRequestProperty( "Content-Type",
  48. "multipart/form-data; boundary=" + BOUNDARY);
  49. conn.setRequestProperty( "Content-Length", String
  50. .valueOf(headerInfo.length + uploadFile.length()
  51. + endInfo.length));
  52. conn.setDoOutput( true);
  53. OutputStream out = conn.getOutputStream();
  54. InputStream in = new FileInputStream(uploadFile);
  55. out.write(headerInfo);
  56. byte[] buf = new byte[ 1024];
  57. int len;
  58. while ((len = in.read(buf)) != - 1)
  59. out.write(buf, 0, len);
  60. out.write(endInfo);
  61. in.close();
  62. out.close();
  63. if (conn.getResponseCode() == 200) {
  64. System.out.println( "上传成功");
  65. }
  66. }
我详细解释一下,首先我拼接了需要发送的数据,其实就是咱们在图三中看到的数据,然后使用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. StringBuilder sb = new StringBuilder();
  22. /**
  23. * 普通的表单数据
  24. */
  25. if (params != null)
  26. for (String key : params.keySet()) {
  27. sb.append( "--" + BOUNDARY + "\r\n");
  28. sb.append( "Content-Disposition: form-data; name=\"" + key
  29. + "\"" + "\r\n");
  30. sb.append( "\r\n");
  31. sb.append(params.get(key) + "\r\n");
  32. } else{ab.append( "\r\n");}
  33. /**
  34. * 上传文件的头
  35. */
  36. sb.append( "--" + BOUNDARY + "\r\n");
  37. sb.append( "Content-Disposition: form-data; name=\"" + fileFormName
  38. + "\"; filename=\"" + newFileName + "\"" + "\r\n");
  39. sb.append( "Content-Type: image/jpeg" + "\r\n"); // 如果服务器端有文件类型的校验,必须明确指定ContentType
  40. sb.append( "\r\n");
  41. byte[] headerInfo = sb.toString().getBytes( "UTF-8");
  42. byte[] endInfo = ( "\r\n--" + BOUNDARY + "--\r\n").getBytes( "UTF-8");
  43. System.out.println(sb.toString());
  44. URL url = new URL(urlStr);
  45. Socket socket = new Socket(url.getHost(), url.getPort());
  46. OutputStream os = socket.getOutputStream();
  47. PrintStream ps = new PrintStream(os, true, "UTF-8");
  48. // 写出请求头
  49. ps.println( "POST " + urlStr + " HTTP/1.1");
  50. ps.println( "Content-Type: multipart/form-data; boundary=" + BOUNDARY);
  51. ps.println( "Content-Length: "
  52. + String.valueOf(headerInfo.length + uploadFile.length()
  53. + endInfo.length));
  54. ps.println( "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
  55. InputStream in = new FileInputStream(uploadFile);
  56. // 写出数据
  57. os.write(headerInfo);
  58. byte[] buf = new byte[ 1024];
  59. int len;
  60. while ((len = in.read(buf)) != - 1)
  61. os.write(buf, 0, len);
  62. os.write(endInfo);
  63. in.close();
  64. os.close();
  65. }

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

最后测试我们的代码:

  1. public static void main(String[] args) {
  2. try {
  3. File file = new File( "D:/dtd", "dwr30.dtd");
  4. new Test().uploadForm( null, "uploadFile", file, "helloworld.txt",
  5. "http://localhost:8080/strurts2fileupload/uploadAction");
  6. new Test().uploadFromBySocket( null, "uploadFile", file,
  7. "hibernate-configuration-3.0.dtd",
  8. "http://localhost:8080/strurts2fileupload/uploadAction");
  9. } catch (Exception e) {
  10. e.printStackTrace();
  11. }
  12. }

效果:

猜你喜欢

转载自blog.csdn.net/suyimin2010/article/details/81025051