天天看點

【轉】HTTP協定之multipart/form-data請求分析

原文連結:http://blog.csdn.net/five3/article/details/7181521

首先來了解什麼是multipart/form-data請求:

根據http/1.1 rfc 2616的協定規定,我們的請求方式隻有OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE等,那為為何我們還會有multipart/form-data請求之說呢?這就要從頭來說了。

http協定大家都知道是規定了以ASCII碼傳輸,建立在tcp、ip協定之上的應用層規範,規範内容把http請求分為3個部門:狀态行,請求頭,請求體。所有的方法、實作都是圍繞如何運用群組織這三部分來完成的。換句話來說就是萬變不離其中,隻要我們了解了http請求的組成部分後,自然就可以應變任何實際工作中的需求和問題了。

關于狀态行,請求頭,請求體等三部分的具體内容,大家可以參考官方的協定文檔http://www.faqs.org/rfcs/rfc2616.html,這裡主要分析multipart/form-data請求具體是怎麼一回事。

既然http協定本身的原始方法不支援multipart/form-data請求,那這個請求自然就是由這些原始的方法演變而來的,具體如何演變且看下文:

1、multipart/form-data的基礎方法是post,也就是說是由post方法來組合實作的

2、multipart/form-data與post方法的不同之處:請求頭,請求體。

3、multipart/form-data的請求頭必須包含一個特殊的頭資訊:Content-Type,且其值也必須規定為multipart/form-data,同時還需要規定一個内容分割符用于分割請求體中的多個post的内容,如檔案内容和文本内容自然需要分割開來,不然接收方就無法正常解析和還原這個檔案了。具體的頭資訊如下:

Content-Type: multipart/form-data; boundary=${bound}          

//其中${bound} 是一個占位符,代表我們規定的分割符,可以自己任意規定,但為了避免和正常文本重複了,盡量要使用複雜一點的内容。如:--------------------56423498738365

4、multipart/form-data的請求體也是一個字元串,不過和post的請求體不同的是它的構造方式,post是簡單的name=value值連接配接,而multipart/form-data則是添加了分隔符等内容的構造體。具體格式如下:

--${bound}
Content-Disposition: form-data; name="Filename"

HTTP.pdf
--${bound}
Content-Disposition: form-data; name="file000"; filename="HTTP協定詳解.pdf"
Content-Type: application/octet-stream

%PDF-1.5
file content
%%EOF

--${bound}
Content-Disposition: form-data; name="Upload"

Submit Query
--${bound}--      

其中${bound}為之前頭資訊中的分割符,如果頭資訊中規定為123,那麼這裡也要為123,;可以很容易看出,這個請求體是多個相同的部分組成的:每一個部分都是以--加分隔符開始的,然後是該部分内容的描述資訊,然後一個回車,然後是描述資訊的具體内容;如果傳送的内容是一個檔案的話,那麼還會包含檔案名資訊,以及檔案内容的類型。上面的第二個小部分其實是一個檔案體的結構,最後會以--分割符--結尾,表示請求體結束。

綜上,可以知道要發送一個multipart/form-data的請求,其實任何支援post請求的工具或語言都可以支援,隻是自己要稍微包裝一下便可。

參考資料:

百度百科: http://baike.baidu.com/view/9472.htm  

http1.1協定規範: http://www.faqs.org/rfcs/rfc2616.html

分析工具:httpAnalyzer

當然,附上C#源碼,應該很輕松就可以轉Java

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Msf.Engine.Network.NetworkTool
{
    public sealed class MultipartFormData : IDisposable
    {
        public const string Boundary = "--the_great_maoshu--";

        public static string ContentType
        {
            get { return "multipart/form-data; boundary=" + Boundary; }
        }

        private MemoryStream _stream;
        public MemoryStream Stream
        {
            get { return _stream ?? (_stream = new MemoryStream()); }
        }

        public MultipartFormData()
        {
        }

        public void AddContent(string name, string value)
        {
            var sp = string.Format("--{0}\r\n", Boundary);
            sp += string.Format(
                "Content-Disposition: form-data; name=\"{0}\"; \r\n\r\n{1}",
                name,
                value);
            var data = Encoding.UTF8.GetBytes(sp);
            Stream.Write(data, 0, data.Length);
        }

        public void AddContent(string name, string fileName, byte[] fileData)
        {
            var sp = string.Format("--{0}\r\n", Boundary);
            sp += string.Format(
                "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n",
                name,
                fileName);
            var data = Encoding.UTF8.GetBytes(sp);
            Stream.Write(data, 0, data.Length);
            Stream.Write(fileData, 0, fileData.Length);
            data = Encoding.UTF8.GetBytes("\r\n");
            Stream.Write(data, 0, data.Length);
        }

        public byte[] GetPostData()
        {
            var sp = string.Format("--{0}--\r\n", Boundary);
            var data = Encoding.UTF8.GetBytes(sp);
            Stream.Write(data, 0, data.Length);

            Stream.Position = 0;
            return Stream.ToArray();
        }

        public void Dispose()
        {
            if (_stream != null) _stream.Dispose();
            _stream = null;
        }
    }
}      

作者:newcj

出處:http://newcj.cnblogs.com/

歡迎轉載,但還請尊重勞動果實,保留此段聲明并注明原文連結。

繼續閱讀