RFC1867協議做爲HTTP協議的附加協議,詳細描述了File Upload的規則。本文主要內容是給出一個RFC1867協議的客戶端實現(服務器端的實現如,JspSmartUpload,FileUpload等組件都已比較成熟,這裏很少做介紹)。javascript
1. RFC1867協議介紹html
RFC1867協議主要是在HTTP協議的基礎上爲INPUT標籤增長了file屬性,同時限定了Form的method必須爲POST,ENCTYPE必須爲multipart/form-data。固然還增長了一些與此相關屬性,但都不是很重要,咱們在此不做討論。java
在通常的基於Web的程序中,咱們每每使用<input type=」file」>標籤,該標籤在被瀏覽器解析後會產生一個文本框和一個瀏覽按鈕,單擊瀏覽按鈕會出現系統的文件選擇框。其經典表示以下圖所示。瀏覽器
2. 執行上傳及<input type=」file」>標籤的一些特性服務器
在上圖選擇相應的文件,按Upload按鈕便可把選擇的文件上傳到服務器(服務器端可用JspSmartUpload等組件接受文件)。歸根結底上傳的全部操做都是由瀏覽器做的,用戶所作的只是簡單地選擇了一下文件而已,接下來的問題是,如何能把一個目錄中全部的文件實現一次性上傳?app
(1) 由於目錄下的文件數量是不定的,所以咱們基本不可能經過增長多個<input type=」file」>標籤的方式來解決問題。socket
(2) 若是在Jsp中咱們能夠考慮如下方式來解決:經過Jsp動態建立<input type=」file」>標籤,並使所建立的標籤不可見。把每一個標籤的Value屬性設置爲每一個文件的路徑。在按Upload時再實行一次性上傳。在咱們試驗了以後就會發現,對<input type=」file」>的Value屬性賦值是徒勞的行爲,由於RFC1867協議並無要求瀏覽器的實現者必定實現Value屬性,而IE剛好忽略了Value屬性。jsp
即如下代碼將是徒勞的(IE中)this
<script language=」javascript」>url
//對Value賦值
Form1.file1.value=」c://aa.txt」;
//執行後,IE將忽略此賦值
<.script>
上述兩種方式均沒法完成咱們須要的功能,接下來咱們只能剖析IE是如何完成上傳功能,把具體的實現方法用ActiveX或(Applet)來完成。
3. HTTP協議的簡單介紹
通常說來咱們認爲HTTP協議是構建在TCP/IP之上的協議,其實HTTP協議自己無此限制,但因現實中多數狀況均是如此,咱們就姑且如此認爲。HTTP數據整體說來分三大部分:
(1) 請求行,以下格式
(Request) POST SP URL SP HTTP/1.1 /r/n
請求方法+空格+請求URL+空格+HTTP協議版本+回車換行
如:POST http://localhost:8080/test/test.jsp HTTP1.1/r/n
(Response)HTTP/1.1 SP 200 SP OK /r/n
HTTP協議應答版本+空格+狀態碼+狀態描述+回車換行
如:HTTP/1.1 200 OK /r/n
請求行主要是描述請求的URL,HTTP協議版本,應答狀態等信息。
(2) 請求頭
在HttpServletRequest接口裏已經封裝了對HTTP頭操做的方法。如Content-type,Content-length,User-Agent,Host等都是HTTP頭。HTTP頭主要描述了HTTP所傳輸數據的一些信息,如主機,數據內容類型,數據長度,代理類型等。
如:
User-Agent: myselfHttp/1.1/r/n
Accept: www/source; text/html; image/gif; */*/r/n
HTTP頭+:+空格+頭信息+回車換行
(3) HTTP實體
HTTP實體存放着,HTTP請求的內容,如參數信息,文本框的內容,隱含控件的值,ListBox的值等。若是在頁面上存在:
<input type=」text」 name=」userName」 value=」zhangsan」>
<input type=」password」 name=」password」 value=」123」>
HTTP實體會出現如下形式:(POST提交)
userName=zhangsan&password=123
GET提交的時候須要解析HTTP請求行中的URL,在此很少做討論。
4. RFC1867協議的數據格式
(1) RFC1867對HTTP頭的變動
RFC1867對HTTP頭做了適當地變動,但變動很小。首先content-type頭由之前的:
content-type: application/x-www-form-urlencoded
變爲
content-type: multipart/form-data; +空格+
boundary=---------------------------7d52b133509e2
即增長了boundary,所謂的boundary其實就是分割線,下文將看到,RFC1867利用boundary分割HTTP實體數據。boundary中數字字符區是隨機生成的。
(2) 對HTTP實體的變動
由於RFC1867增長了文件上傳得功能,而上傳文件內容天然也會被加入到HTTP的實體中。如今由於既有HTTP通常的參數實體,又有上傳文件的實體,因此用boundary把每種實體進行了分割,HTTP的實體看起來將是下面的樣子:
-----------------------------7d52b133509e2
Content-Disposition: form-data; name="file1"; filename="c:/aa.txt"
Content-Type: text/plain
文件內容在此處
-----------------------------7d52b133509e2
Content-Disposition: form-data; name="userName"
zhangsan
-----------------------------7d52b133509e2
Content-Disposition: form-data; name="password"
123
-----------------------------7d52b133509e2—
很明顯,增長了文件上傳後,HTTP實體變得稍微複雜了,首先是經過boundary把實體分開,以便於讀取,而後對FileUpload的格式也做了限制。
(3) RFC1867協議的數據格式
根據RFC1867協議,在HTTP實體中必須對每一個上傳得文件有說明頭,如:
Content-Disposition: form-data; name="file1";
filename="c:/aa.txt"
Content-Disposition:指明內容類型是form-data
name="file1":指明頁面上<input type=」file」>標籤的名字是file1
filename="c:/aa.txt":指明上傳文件在客戶端上的全路徑
空行:文件頭說明完畢後,要加一空行,以表示後面的數據是文件的內容
文件內容:再接下來就是文件的內容
從這個角度說,徹底能夠利用HTTP協議+RFC1867協議開發基於文檔管理應用程序。
5. 協議的實現(客戶端)
協議的好處就是,只要你提供的數據符合協議的要求,Server端就能夠正確解析你的請求。而不論數據是由IE產生的,或有你本身的Application產生的。經過上面的分析,咱們已經基本清楚了RFC1867協議的要求,只要咱們打開指定的端口,把數據按照協議的要求寫進去就會模擬出IE上傳的功能。用程序實現是很是Easy的事。附件將給出Java實現版本,程序只是簡單地實現了上傳,根據咱們前面的分析實現文件上傳,參數傳遞這種稍麻煩的形式也是比較簡單的。另外,該程序並無實現返回數據的解析,一樣根據咱們前面的分析,按照HTTP協議去解析返回的數據也不是難事。總之,但願本程序能起到拋磚引玉的做用,關於RFC1867更深刻的實現或應用,請跟做者聯繫。
6. 代碼實現
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class HttpClient {
private String boundary =
"---------------------------7d51372f1c05a8";
private String contentType = "multipart/form-data;
boundary="+boundary;
private static final byte CR = (byte)'/r';
private static final byte LF = (byte)'/n';
private static final byte[] CRLF = new byte[]{CR,LF};
private Socket socket;
private String host;
private int port;
public static void main(String[] args) {
try{
HttpClient client = new HttpClient("localhost",8080);
//upload file array
File[] files = new File[1];
for(int i=0;i<files.length;i++){
files[i] = new File("d://aa.txt");
}
client.uploadFile(files);
}catch(Exception e){
e.printStackTrace();
}
}
public HttpClient(String host,int port){
this.host = host;
this.port = port;
}
private void openServer() throws Exception {
socket = new Socket(host,port);
}
private void closeServer() throws Exception {
if(socket!=null){
socket.close();
}
}
private void addHead(int contentLength,OutputStream out) throws
IOException {
//request line,end withd CRLF
write(out,"POST http://localhost:8080/test1/upload/1 HTTP/1.1");
out.write(CRLF);
//request head fields
write(out,"User-Agent: SysmitAgent");
out.write(CRLF);
write(out,"Host: localhost:8080");
out.write(CRLF);
write(out,"Accept: www/source; text/html; image/gif; */*");
out.write(CRLF);
write(out,"Accept-Encoding: gzip, deflate");
out.write(CRLF);
//entity head fields
write(out,"Content-Type: "+contentType);
out.write(CRLF);
write(out,"Content-Length: "+contentLength);
out.write(CRLF);
out.write(CRLF);
}
private void addBody(File file,OutputStream out) throws IOException {
//write boundary
write(out,boundary);
out.write(CRLF);
//write file info
String disposition = "Content-Disposition:"
+" form-data;"
+" name=/"file1/";"
+" filename=/""
+file.getAbsolutePath()+"/"";
write(out,disposition);
out.write(CRLF);
//write file content type info
write(out,"Content-Type: text/html");
out.write(CRLF);
//write SP(empty line)
out.write(CRLF);
//write file content
InputStream is = new FileInputStream(file);
byte[] b = new byte[1024];
int count = is.read(b);
while(count!=-1){
out.write(b,0,count);
count = is.read(b);
}
is.close();
//write crlf
out.write(CRLF);
}
public void uploadFile(File[] files) throws Exception {
//open server
openServer();
//open stream
OutputStream out = socket.getOutputStream();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
for(int i=0;i<files.length;i++){
addBody(files[i],bos);
}
write(bos,boundary+"--");
bos.write(CRLF);
addHead(bos.size(),bos);
bos.writeTo(out);
bos.close();
out.flush();
//close server
closeServer();
}
private void write(OutputStream out,String msg) throws IOException
{
out.write(msg.getBytes());
}
}