HTTP中Post的multipart/form-data请求分析

8/30/2022 3:22:51 PM
1499
0

http协议大家都知道是规定了以ASCII码传输,建立在tcp、ip协议之上的应用层规范,规范内容把http请求分为3个部分:状态行,请求头,请求体。所有的方法、实现都是围绕如何运用和组织这三部分来完成的。换句话来说就是万变不离其中,只要我们了解了http请求的组成部分后,自然就可以应变任何实际工作中的需求和问题了。

关于状态行,请求头,请求体等三部分的具体内容,如图所示

服务器解析http请求时候按 httphead的 Content-Type解析。

  • 上传   Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryHQ3VpLyt7CnS0qH8
  • 传json    Content-Type: application/json;
  • 传键值对:Content-Type:  application/x-www-form-urlencoded; charset=UTF-8
  • 传text: Content-Type:application/text; 

 

这里主要分析multipart/form-data请求具体是怎么一回事。

既然http协议本身的原始方法不支持multipart/form-data请求,那这个请求自然就是由这些原始的方法演变而来的,具体如何演变且看下文:

  • multipart/form-data的基础是post请求,即基于post请求来实现的
  • multipart/form-data形式的post与普通post请求的不同之处体现在请求头,请求体2个部分

multipart/form-data请求头

必须包含Content-Type信息,且其值也必须规定为multipart/form-data,同时还需要规定一个内容分割符用于分割请求体中不同参数的内容(普通post请求的参数分割符默认为&,参数与参数值的分隔符为=)。具体的头信息格式如下:

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

其中${bound} 是一个占位符,代表我们规定的具体分割符;可以自己任意规定,但为了避免和正常文本重复了,尽量要使用复杂一点的内容。如:--------------------56423498738365

样例:

Content-Type: multipart/form-data; boundary=--------------------56423498738365


multipart/form-data请求体:

它也是一个字符串,不过和普通post请求体不同的是它的构造方式。普通post请求体是简单的键值对连接,格式如下:

k1=v1&k2=v2&k3=v3

而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(其作用等同于普通post请求中的&符号的作用,即分割参数)。很明显如果按照之前请求头的样例来,则这里的${bound}值应该替换为--------------------56423498738365,注意最后一个${bound}后需要固定添加两个横杆--

另外可以很容易的看出,这个请求体被分隔符划分为3个部分,而每个部分其实就是一个参数的键值描述(其作用等同于普通post请求的k1=v1的部分),但对参数信息的描述可以比普通post请求更加丰富,这就是为什么multipart/form-data能发送文件的原因。

每一个部分都是以分隔符开始的,接着是该部分内容的描述信息,然后是一个回车,最后是描述信息的具体内容。如果传送的内容是一个文件的话,那么还会包含文件名信息,以及文件内容的类型;上面的第二个小部分其实是一个文件体的结构,最后的分割符后会以--结尾,表示请求体结束。

综上,可以知道要发送一个multipart/form-data的请求,其实任何支持post请求的工具或语言都可以支持,只是自己要稍微包装一下便可。

看下用工具抓包的的数据

 

下面的方法是C#的一个封装

/// <summary>
        /// 发起httpPost 请求,可以上传文件
        /// </summary>
        /// <param name="url">请求的地址</param>
        /// <param name="files">文件</param>
        /// <param name="input">表单数据</param>
        /// <param name="endoding">编码</param>
        /// <returns></returns>
        public static string PostResponse(string url, UpLoadFile[] files, Dictionary<string, string> input, Encoding endoding)
        {

            string boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x");
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.ContentType = "multipart/form-data; boundary=" + boundary;
            request.Method = "POST";
            request.KeepAlive = true;
            //request.Credentials = CredentialCache.DefaultCredentials;
            request.Expect = "";

            MemoryStream stream = new MemoryStream();


            byte[] line = Encoding.ASCII.GetBytes("--" + boundary + "\r\n");
            byte[] enterER = Encoding.ASCII.GetBytes("\r\n");
            ////提交文件
            if (files != null)
            {
                string fformat = "Content-Disposition:form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type:{2}\r\n\r\n";
                foreach (UpLoadFile file in files)
                {

                    stream.Write(line, 0, line.Length);        //项目分隔符
                    string s = string.Format(fformat, file.Name, file.FileName, file.Content_Type);
                    byte[] data = Encoding.UTF8.GetBytes(s);
                    stream.Write(data, 0, data.Length);
                    stream.Write(file.Data, 0, file.Data.Length);
                    stream.Write(enterER, 0, enterER.Length);  //添加\r\n
                }
            }


            //提交文本字段
            if (input != null)
            {
                string format = "--" + boundary + "\r\nContent-Disposition:form-data;name=\"{0}\"\r\n\r\n{1}\r\n";    //自带项目分隔符
                foreach (string key in input.Keys)
                {
                    string s = string.Format(format, key, input[key]);
                    byte[] data = Encoding.UTF8.GetBytes(s);
                    stream.Write(data, 0, data.Length);
                }

            }

            byte[] foot_data = Encoding.UTF8.GetBytes("--" + boundary + "--\r\n");      //项目最后的分隔符字符串需要带上--
            stream.Write(foot_data, 0, foot_data.Length);

            request.ContentLength = stream.Length;
            Stream requestStream = request.GetRequestStream(); //写入请求数据
            stream.Position = 0L;
            stream.CopyTo(requestStream); //
            stream.Close();

            requestStream.Close();



            try
            {


                HttpWebResponse response;
                try
                {
                    response = (HttpWebResponse)request.GetResponse();

                    try
                    {
                        using (var responseStream = response.GetResponseStream())
                        using (var mstream = new MemoryStream())
                        {
                            responseStream.CopyTo(mstream);
                            string message = endoding.GetString(mstream.ToArray());
                            return message;
                        }
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
                }
                catch (WebException ex)
                {
                    //response = (HttpWebResponse)ex.Response;


                    //if (response.StatusCode == HttpStatusCode.BadRequest)
                    //{
                    //    using (Stream data = response.GetResponseStream())
                    //    {
                    //        using (StreamReader reader = new StreamReader(data))
                    //        {
                    //            string text = reader.ReadToEnd();
                    //            Console.WriteLine(text);
                    //        }
                    //    }
                    //}

                    throw ex;
                }


            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

 

全部评论



提问