使用multipart/form-data上传文件


1.使用POST发送数据

 以POST方式发送数据主要是为了向服务器发送较大量的客户端的数据,它不受URL的长度限制。POST请求将

数据以URL编码的形式放在HTTP正文中,字段形式为fieldname=value,用&分隔每个字段。注意所有的字段都

被作为字符串处理。实际上我们要做的就是模拟浏览器POST 一个表单。以下是IE发送一个登陆表单的POST请

求:

POST http://127.0.0.1/login.do HTTP/1.0
Accept: image/gif, image/jpeg, image/pjpeg, */*
Accept-Language: en-us,zh-cn;q=0.5
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Content-Length: 28

username=admin&password=1234



要在MIDP应用程序中模拟浏览器发送这个POST请求,首先设置HttpConnection的请求方式为POST:

hc.setRequestMethod(HttpConnection.POST);



然后构造出HTTP正文:

byte[] data = "username=admin&password=1234".getBytes();

并计算正文长度,填入Content-Type和Content-Length:

hc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");hc.setRequestProperty("Content-Length", String.valueOf(data.length));



然后打开OutputStream将正文写入:

OutputStream output = hc.openOutputStream();output.write(data);



需要注意的是,数据仍需要以URL编码格式编码,由于MIDP库中没有J2SE中与之对应的URLEncoder类,因此,

需要自己动手编写这个 encode()方法,可以参考java.net.URLEncoder.java的源码。剩下的便是读取服务器响

应,代码与GET一致,这里就不再详述。

2.使用multipart/form-data发送文件

如果要在MIDP客户端向服务器上传文件,我们就必须模拟一个POST multipart/form-data类型的请求,Content-

Type必须是multipart/form-data。

以multipart/form-data编码的POST请求格式与application/x-www-form-urlencoded完全不同,multipart/form-

data需要首先在HTTP请求头设置一个分隔符,例如ABCD:


hc.setRequestProperty("Content-Type", "multipart/form-data; boundary=ABCD");

然后,将每个字段用“--分隔符”分隔,最后一个“--分隔符--”表示结束。例如,要上传一个title字段"Today"和一

个文件C:\1.txt,HTTP正文如下:

--ABCD

Content-Disposition: form-data; name="title"

\r\n

Today

--ABCD

Content-Disposition: form-data; name="1.txt"; filename="C:\1.txt"

Content-Type: text/plain

\r\n

<这里是1.txt文件的内容>

--ABCD--

\r\n

3.使用post请求上传文件:模拟一个POST multipart/form-data类型的请求的例子

/*打开url连接         


                    * 此处的urlConnection对象实际上是根据URL的 请求协议(此处是http)生成的URLConnection类         


                    * 的子类HttpURLConnection,故此处最好将其转化为HttpURLConnection类型的对象,         


                    * 以便用到HttpURLConnection更多的API*/         


                    HttpURLConnection httpURLConnection = (HttpURLConnection) url         


                    .openConnection();         


                    


                    /*http头         


                    * 设置每次传输的流大小,可以有效防止手机因为内存不足崩溃,此方法用于在预先不知道内容长度时启用,         


                    * 没有进行内部缓冲的 HTTP 请求正文的流。*/         


                    httpURLConnection.setChunkedStreamingMode(          128           *           1024          );          // 128K         


                    // 允许输入输出流         


                    httpURLConnection.setDoInput(          true          );          // 设置是否从httpUrlConnection读入,默认情况下是true         


                    /*设置是否向httpUrlConnection输出,因为这个是post请求,参数要放在          


                    http正文内,因此需要设为true, 默认情况下是false*/         


                    httpURLConnection.setDoOutput(          true          );         


                    // Post 请求不能使用缓存         


                    httpURLConnection.setUseCaches(          false          );          //没有进行内部缓冲         


                    // 设定请求的方法为"POST",默认是GET         


                    httpURLConnection.setRequestMethod(          "POST"          );         


                    httpURLConnection.setRequestProperty(          "Connection"          ,           "Keep-Alive"          );         


                    httpURLConnection.setRequestProperty(          "Charset"          ,           "UTF-8"          );         


                    //首先在HTTP请求头设置一个分隔符*****         


                    httpURLConnection.setRequestProperty(          "Content-Type"          ,         


                    "multipart/form-data;boundary="           + boundary);         


                    //httpURLConnection.connect(); // 连接         


                    


                    /*http请求的正文,正文的内容是通过outputStream流写入的         


                    * 此处getOutputStream会隐含的进行connect(即:如同调用上面的connect()方法,          


                    所以在开发中不调用上述的connect()也可以)。*/         


                    


                    DataOutputStream dos =           new           DataOutputStream(         


                    httpURLConnection.getOutputStream());         


                    //每个字段用“--”分隔         


                    dos.writeBytes(twoHyphens + boundary + end);         


                    /*srcPath.substring(srcPath.lastIndexOf("/") + 1):表示文件名字         


                    * */         


                    dos.writeBytes(          "Content-Disposition: form-data; name=\"uploadedfile\"; filename=\""         


                    + srcPath.substring(srcPath.lastIndexOf(          "/"          ) +           1          )         


                    +           "\""         


                    + end);         


                    dos.writeBytes(end);         


                    //上传的文件的内容         


                    FileInputStream fis =           new           FileInputStream(srcPath);         


                    byte          [] buffer =           new           byte          [          8192          ];           // 8k         


                    int           count =           0          ;         


                    // 读取文件         


                    while           ((count = fis.read(buffer)) != -          1          ){         


                    dos.write(buffer,           0          , count);         


                    }         


                    fis.close();         


                    


                    dos.writeBytes(end);         


                    dos.writeBytes(twoHyphens + boundary + twoHyphens + end);         


                    dos.flush();         


                    


                    // 在调用下边的getInputStream()函数时才将内存缓冲区中封装好的完整的HTTP请求电文发送到服务端。          


                    InputStream is = httpURLConnection.getInputStream();         


                    //至此,http请求已经被发送到服务器         


                    InputStreamReader isr =           new           InputStreamReader(is,           "utf-8"          );         


                    BufferedReader br =           new           BufferedReader(isr);         


                    //BufferedReader br = new BufferedReader(new InputStreamReader         


                    //        (httpURLConnection.getInputStream(),"utf-8"));         


                    /*获取返回结果.         


                    * 在getInputStream()函数调用的时候,就会把准备好的http请求 正式发送到服务器了,         


                    * 然后返回一个输入流,用于读取服务器对于此次http请求的返回信息。*/         


                    String result = br.readLine();         


                    Toast.makeText(c, result, Toast.LENGTH_SHORT).show();         


                    dos.close();         


                    is.close();




Multipart/form-data POST文件上传详解


目录

Multipart/form-data POST文件上传详解

理论

简单的HTTP POST

大家通过HTTP向服务器发送POST请求提交数据,都是通过form表单提交的,代码如下:

<form method="post"action="http://w.sohu.com" >
          <inputtype="text" name="txt1">
          <inputtype="text" name="txt2">
  </form>

提交时会向服务器端发出这样的数据(已经去除部分不相关的头信息),数据如下:

POST / HTTP/1.1

Content-Type:application/x-www-form-urlencoded

Accept-Encoding: gzip, deflate

Host: w.sohu.com

Content-Length: 21

Connection: Keep-Alive

Cache-Control: no-cache

txt1=hello&txt2=world

对于普通的HTML Form POST请求,它会在头信息里使用Content-Length注明内容长度。头信息每行一条,空行之后便是Body,即“内容”(entity)。它的Content-Type是application/x-www-form-urlencoded,这意味着消息内容会经过URL编码,就像在GET请 求时URL里的QueryString那样。txt1=hello&txt2=world

POST上传文件

最早的HTTP POST是不支持文件上传的,给编程开发带来很多问题。但是在1995年,ietf出台了rfc1867,也就是《RFC 1867 -Form-based File Upload in HTML》,用以支持文件上传。所以Content-Type的类型扩充了multipart/form-data用以支持向服务器发送二进制数据。因此发送post请求时候,表单<form>属性enctype共有二个值可选,这个属性管理的是表单的MIME编码:

 ①application/x-www-form-urlencoded(默认值)
 ②multipart/form-data
其实form表单在你不写enctype属性时,也默认为其添加了enctype属性值,默认值是enctype="application/x- www-form-urlencoded".

通过form表单提交文件操作如下:

<form method="post"action="http://w.sohu.com/t2/upload.do" enctype=”multipart/form-data”>
          <inputtype="text" name="desc">
          <inputtype="file" name="pic">
  </form>

浏览器将会发送以下数据:

POST /t2/upload.do HTTP/1.1
 User-Agent: SOHUWapRebot
 Accept-Language: zh-cn,zh;q=0.5
 Accept-Charset: GBK,utf-8;q=0.7,*;q=0.7
 Connection: keep-alive
 Content-Length: 60408
 Content-Type:multipart/form-data; boundary=ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
 Host: w.sohu.com
 --ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
 Content-Disposition: form-data;name="desc"
 Content-Type: text/plain; charset=UTF-8
 Content-Transfer-Encoding: 8bit
 [......][......][......][......]...........................
 --ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
 Content-Disposition: form-data;name="pic"; filename="photo.jpg"
 Content-Type: application/octet-stream
 Content-Transfer-Encoding: binary
 [图片二进制数据]
 --ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC--

我们来分析下数据,第一个空行之前自然还是HTTP header,之后则是Entity,而此时的Entity也比之前要复杂一些。根据RFC 1867定义,我们需要选择一段数据作为“分割边界”( boundary属性),这个“边界数据”不能在内容其他地方出现,一般来说使用一段从概率上说“几乎不可能”的数据即可。 不同浏览器的实现不同,例如火狐某次post的  boundary=---------------------------32404670520626 , opera为boundary=----------E4SgDZXhJMgNE8jpwNdOAX ,每次post浏览器都会生成一个随机的30-40位长度的随机字符串,浏览器一般不会遍历这次post的所有数据找到一个不可能出现在数据中的字符串,这样代价太大了。一般都是随机生成,如果你遇见boundary值和post的内容一样,那样的话这次上传肯定失败,不过我建议你去买彩票,你太幸运了。Rfc1867这样说明{A boundary is selected that does not occur in any of the data. (This selection is sometimes done probabilisticly.)}。

选择了这个边界之后,浏览器便把它放在Content-Type 里面传递给服务器,服务器根据此边界解析数据。下面的数据便根据boundary划分段,每一段便是一项数据。(每个field被分成小部分,而且包含一个value是"form-data"的"Content-Disposition"的头部;一个"name"属性对应field的ID,等等,文件的话包括一个filename)

  • IE和Chrome在filename的选择策略上有所不同,前者是文件的完整路径,而后者则仅仅是文件名。
  • 数据内容以两条横线结尾,并同样以一个换行结束。在网络协议中一般都以连续的CR、LF(即\r、\n,或0x0D、Ox0A)字符作为换行,这与Windows的标准一致。如果您使用其他操作系统,则需要考虑它们的换行符

 另外Content-length 指的是所用数据的长度。

实现

httpClient4如何实现

httpClient4使用http-mime.jar包的MultipartEntity实现,代码如下(为了简洁,处理了异常处理代码):

HttpPost httpPost = newHttpPost(url);
 Log.debug("post url:"+url);
 httpPost.setHeader("User-Agent","SOHUWapRebot");
 httpPost.setHeader("Accept-Language","zh-cn,zh;q=0.5");
 httpPost.setHeader("Accept-Charset","GBK,utf-8;q=0.7,*;q=0.7");
 httpPost.setHeader("Connection","keep-alive");
 MultipartEntity mutiEntity = newMultipartEntity();
 File file = new File("d:/photo.jpg");
 mutiEntity.addPart("desc",new StringBody("美丽的西双版纳", Charset.forName("utf-8")));
 mutiEntity.addPart("pic", newFileBody(file));
 httpPost.setEntity(mutiEntity);
 HttpResponse  httpResponse = httpClient.execute(httpPost);
 HttpEntity httpEntity =  httpResponse.getEntity();
 String content = EntityUtils.toString(httpEntity);

参考:

Rfc1867:http://www.ietf.org/rfc/rfc1867

Rfc1867:http://www.vivtek.com/rfc1867.html