post表单数据格式完全解析multipart/form-data
参数说明
| 内容 |
boundary | boundary一个字符串,用以分隔不同的参数; string boundary = Guid.NewGuid().ToString(); 会生成如下字符串:9470b619-f08f-436e-a6b2-98fcb02b695b 也可以自定义一个比较复杂的字符串作为分隔符,例如@adsadsadsads123456789@ request.ContentType = "multipart/form-data;charset=utf-8;boundary=" + boundary; --boundary开始分隔符,也就是boundary之前加两个短划线,用以标记每一条数据的起始位置; --boundary--结束分隔符,也就是boundary之前和之后各加两个短划线,用以标记所有数据的结束位置; |
\r\n | 换行符\r\n,用以标记分隔符和参数声明之间、各参数声明之间、参数声明和参数值之间、参数值和下一条数据之间的分隔位置; --boundary和数据声明之间必须有且只有1个换行符; 各条数据声明之间有且只有1个换行符; 数据声明和数据值之间不少于2个换行符; 数据值和下一条数据之间至少1个换行符; |
其他参数 | CContent-Type:text/plain;charset=utf-8表示数据内容的格式为普通文本,编码格式为utf-8; Content-Disposition:form-data表示内容的处置方式,表示将内容作为formdata格式进行处理; name=\"sInputF\"表示传递参数的名称,或者叫键值、或者字段名称; 所有数据,无论是文本数据还是文件流数据,最终都以byte[]的格式进行传输; |
示例 | --boundary//第一条数据 数据声明1 数据声明2 数据 --boundary//第二条数据 数据声明1 数据声明2 数据 ... --boundary--//数据结束 |
结构详解
参数 | 项目 | 内容 |
开始 | 换行符 | 第一个开始分隔符前可以没有换行符\r\n |
参数1 sInputF 文件参数 | 开始分隔符 | "--" + boundary |
换行符 | 有且只有1个换行符\r\n | |
数据声明1: | Content-Disposition:form-data;name=\"sInputF\";filename=\"{Path.GetFileName(mDocPath)}\" | |
换行符 | 有且只有1个换行符\r\n | |
数据声明2: | Content-Type:application/octet-stream | |
换行符 | 声明和数据值之间,至少要有2个换行符\r\n\r\n | |
数据值 | byte[] docFileBytes = ReadFileBytes(mDocPath); | |
换行符 | 至少1个换行符\r\n | |
参数2 sSealF 文件参数 | 开始分隔符 | "--" + boundary |
换行符 | 有且只有1个换行符\r\n | |
数据声明1: | Content-Disposition:form-data;name=\"sSealF\";filename=\"{Path.GetFileName(mSealPath)}\" | |
换行符 | 有且只有1个换行符\r\n | |
数据声明2: | Content-Type:application/octet-stream | |
换行符 | 声明和数据值之间,至少要有2个换行符\r\n\r\n | |
数据值 | byte[] sealFileBytes = ReadFileBytes(mSealPath); | |
换行符 | 至少1个换行符\r\n | |
参数3 sPageNum 文本参数 | 开始分隔符 | "--" + boundary |
换行符 | 有且只有1个换行符\r\n | |
数据声明1: | CContent-Type:text/plain;charset=utf-8 | |
换行符 | 有且只有1个换行符\r\n | |
数据声明2: | Content-Disposition:form-data;name=\"sPageNum\" | |
换行符 | 声明和数据值之间,至少要有2个换行符\r\n\r\n | |
数据值 | 1 | |
换行符 | 至少1个换行符\r\n | |
参数4 fSealW 文本参数 | 开始分隔符 | "--" + boundary |
换行符 | 有且只有1个换行符\r\n | |
数据声明1: | CContent-Type:text/plain;charset=utf-8 | |
换行符 | 有且只有1个换行符\r\n | |
数据声明2: | Content-Disposition:form-data;name=\"fSealW\" | |
换行符 | 声明和数据值之间,至少要有2个换行符\r\n\r\n | |
数据值 | 100 | |
换行符 | 至少1个换行符\r\n | |
参数5 fSealH 文本参数 | 开始分隔符 | "--" + boundary |
换行符 | 有且只有1个换行符\r\n | |
数据声明1: | CContent-Type:text/plain;charset=utf-8 | |
换行符 | 有且只有1个换行符\r\n | |
数据声明2: | Content-Disposition:form-data;name=\"fSealH\" | |
换行符 | 声明和数据值之间,至少要有2个换行符\r\n\r\n | |
数据值 | 1 | |
换行符 | 至少1个换行符\r\n | |
参数6 fSealPosX 文本参数 | 开始分隔符 | "--" + boundary |
换行符 | 有且只有1个换行符\r\n | |
数据声明1: | CContent-Type:text/plain;charset=utf-8 | |
换行符 | 有且只有1个换行符\r\n | |
数据声明2: | Content-Disposition:form-data;name=\"fSealPosX\" | |
换行符 | 声明和数据值之间,至少要有2个换行符\r\n\r\n | |
数据值 | 1 | |
换行符 | 至少1个换行符\r\n | |
参数7 fSealPosY 文本参数 | 开始分隔符 | "--" + boundary |
换行符 | 有且只有1个换行符\r\n | |
数据声明1: | CContent-Type:text/plain;charset=utf-8 | |
换行符 | 有且只有1个换行符\r\n | |
数据声明2: | Content-Disposition:form-data;name=\"fSealPosY\" | |
换行符 | 声明和数据值之间,至少要有2个换行符\r\n\r\n | |
数据值 | 1 | |
换行符 | 至少1个换行符\r\n | |
结束 | 结束分隔符 | "--" + boundary "--",结束分隔符之后可以没有换行符 |
源码1
voidRequest(stringurl) { HttpWebRequest request = WebRequest.CreateHttp(url);//创建request对象,不能通过构造函数创建 request.Method = "POST";//设置请求方法 GET、HEAD、POST、PUT、DELETE、TRACE 或 OPTIONS stringboundary = Guid.NewGuid().ToString(); // 随机分隔线 //设置数据类型为multipart/form-data,多部分/数据表单类型,该类型用于传递文件数据的情形,当然也可以传递文本参数 request.ContentType = "multipart/form-data;charset=utf-8;boundary="+ boundary; //构建数据,无论文本数据还是文件数据,最终都要以字节流的形式进行发送 //构建数据:word文档文件 // "--" + boundary表示一条数据的开头 //第一个"\r\n"表示开始分隔符和数据声明之间的分隔符,二者之间有且只能有1个换行符,如果没有或者超过1个,都将导致数据解析失败 //Content-Disposition:form-data;name=\"sInputF\";filename=\"{Path.GetFileName(mDocPath)}\"表示一条数据声明 //Content-Disposition:form-data表示数据的处置方式为表单数据 //name=\"sInputF\"表示该参数名称、字段名、键 //filename=\"{Path.GetFileName(mDocPath)}\"上传文件,指明文件名称 //第二个\r\n表示第一条数据声明和第二条数据声明之间的分隔符,二者之间有且只能有1个换行符,如果没有或者超过1个,都将导致数据解析失败 //Content - Type:application / octet - stream表示数据类型为字节流 //最后的\r\n\r\n表示数据声明和数据内容之间的分隔符,二者只有至少有2个换行符,如果换行符超过2个,可以正常解析,如果少于2个,将导致数据解析失败 stringdocStr = "--"+ boundary + "\r\n"+ $"Content-Disposition:form-data;name=\"sInputF\";filename=\"{Path.GetFileName(mDocPath)}\"\r\nContent-Type:application/octet-stream\r\n\r\n\r\n\r\n\r\n"; byte[] docFileBytes = ReadFileBytes(mDocPath);//从文件中读取字节数据 //构建数据:图片文件 stringsealStr = "--"+ boundary + "\r\n"+ $"Content-Disposition:form-data;name=\"sSealF\";filename=\"{Path.GetFileName(mSealPath)}\"\r\nContent-Type:application/octet-stream\r\n\r\n"; byte[] sealFileBytes = ReadFileBytes(mSealPath);//从文件中读取字节数据 //构建数据:普通文本参数 //每一条数据都已条目分隔符开始,然后后面按照格式拼接相关数据 //Content-Type: text/plain; charset=utf-8表示内容的格式是普通文本,编码格式为charset=utf-8 //Content-Disposition:form-data表示数据处理方式,作为表单数据进行处理 //name表示参数名、字段名、或者键 //第一个"\r\n",表示开始分隔符和数据声明之间的分隔符,二者之间有且只能有1个换行符,如果没有或者超过1个,都将导致数据解析失败 //第二个\r\n表示第一条数据声明和第二条数据声明之间的分隔符,二者之间有且只能有1个换行符,如果没有或者超过1个,都将导致数据解析失败 //最\r\n\r\n表示数据声明和数据内容之间的分隔符,二者只有至少有2个换行符,如果换行符超过2个,可以正常解析,如果少于2个,将导致数据解析失败 //最后的\r\n表示本条数据内容和下一条数据之间,至少有1个换行符,如果超过1个,可以正常解析,如果没有该换行符,将导致数据解析失败 stringpageStr = "--"+ boundary + "\r\n"+ $"Content-Type:text/plain;charset=utf-8\r\nContent-Disposition:form-data;name=\"sPageNum\"\r\n\r\n\r\n\r\n\r\n{1}\r\n\r\n\r\n\r\n\r\n\r\n"; stringwStr = "--"+ boundary + "\r\n"+ $"Content-Type:text/plain;charset=utf-8\r\nContent-Disposition:form-data;name=\"fSealW\"\r\n\r\n{1}\r\n"; stringhStr = "--"+ boundary + "\r\n"+ $"Content-Type:text/plain;charset=utf-8\r\nContent-Disposition:form-data;name=\"fSealH\"\r\n\r\n{1}\r\n"; stringxStr = "--"+ boundary + "\r\n"+ $"Content-Type:text/plain;charset=utf-8\r\nContent-Disposition:form-data;name=\"fSealPosX\"\r\n\r\n{1}\r\n"; stringyStr = "--"+ boundary + "\r\n"+ $"Content-Type:text/plain;charset=utf-8\r\nContent-Disposition:form-data;name=\"fSealPosY\"\r\n\r\n{1}\r\n"; //将所有数据转换为字节数组,并合并在一起 List //第一个文件 byteList.AddRange(Encoding.UTF8.GetBytes(docStr));//添加文件数据头 byteList.AddRange(docFileBytes);//添加文件数据 byteList.AddRange(Encoding.UTF8.GetBytes("\r\n"));//本条数据内容和下一条数据之间,至少有1个换行符,如果没有该换行符,将导致数据解析失败 //第二个文件 byteList.AddRange(Encoding.UTF8.GetBytes(sealStr));//添加文件数据头 byteList.AddRange(sealFileBytes);//添加文件数据 byteList.AddRange(Encoding.UTF8.GetBytes("\r\n"));//本条数据内容和下一条数据之间,至少有1个换行符,如果没有该换行符,将导致数据解析失败 //所有的文本参数 byteList.AddRange(Encoding.UTF8.GetBytes(pageStr));//添加文本参数数据 byteList.AddRange(Encoding.UTF8.GetBytes(wStr));//添加文本参数数据 byteList.AddRange(Encoding.UTF8.GetBytes(hStr));//添加文本参数数据 byteList.AddRange(Encoding.UTF8.GetBytes(xStr));//添加文本参数数据 byteList.AddRange(Encoding.UTF8.GetBytes(yStr));//添加文本参数数据 //所有数据的最后,要添加结尾分隔符 byteList.AddRange(Encoding.UTF8.GetBytes("--"+ boundary + "--")); //设置写入数据的长度,并将数据写入 request.ContentLength = byteList.Count;//设置数据长度,byte为单位 Stream postStream = request.GetRequestStream();//获取HttpWebRequest的数据流对象 postStream.Write(byteList.ToArray(), 0, byteList.Count);//将数据写入,开始位置为0,写入数量为byteList.Count postStream.Close();//写完后,关闭流对象 WebResponse response = request.GetResponse();//发送请求 获取回应 //获取回应的文本内容 Stream stream = response.GetResponseStream();//获取响应的流 StreamReader reader = newStreamReader(stream); stringcontent = reader.ReadToEnd();//读取响应流 reader.Close();//关闭流 //构建打印信息 stringshowStr = docStr + mDocPath + "\n文件字节数="+ docFileBytes.Length + "\n"+ sealStr + mSealPath + "\n文件字节数="+ sealFileBytes.Length + "\n"+ pageStr + wStr + hStr + xStr + yStr + "\r\n"+ "--"+ boundary + "--"; //显示相关信息 richTextBox1.Text = "返回信息:\n"+ content + "\n\n请求头信息:\n"+ request.Headers.ToString() + "发送的数据:\n"+ showStr; } |
源码2
voidRequest2() { //创建request对象,不能通过构造函数创建 HttpWebRequest request = WebRequest.CreateHttp(mPostUrl); //设置请求方法 GET、HEAD、POST、PUT、DELETE、TRACE 或 OPTIONS request.Method = "POST"; //获取随机分隔符 stringboundary = Guid.NewGuid().ToString(); //设置内容格式 request.ContentType = "multipart/form-data;charset=utf-8;boundary="+ boundary; //通过参数字典,获取所有字节数据 List //设置内容长度 request.ContentLength = byteList.Count; //获取请求的流对象 并写入字节数据 Stream postStream = request.GetRequestStream(); postStream.Write(byteList.ToArray(), 0, byteList.Count); postStream.Close(); //获取响应 WebResponse response = request.GetResponse();//发送请求 获取回应 //获取回应的文本内容 Stream stream = response.GetResponseStream(); StreamReader reader = newStreamReader(stream); stringcontent = reader.ReadToEnd(); reader.Close(); //显示相关信息 richTextBox1.Text = "返回信息:\n"+ content; } //普通参数 Dictionary { { "sPageNum","1"}, { "fSealW","100"}, { "fSealH","100"}, { "fSealPosX","100"}, { "fSealPosY","100"}, }; //文件参数 Dictionary { { "sInputF",mDocPath}, { "sSealF",mSealPath}, }; //获取文件参数的字节数据 List { List stringhead = "--"+ boundary + "\r\n"+ $"Content-Disposition:form-data;name=\"{field}\";filename=\"{Path.GetFileName(path)}\"\r\nContent-Type:application/octet-stream\r\n\r\n"; res.AddRange(Encoding.UTF8.GetBytes(head)); res.AddRange(ReadFileBytes(path)); res.AddRange(Encoding.UTF8.GetBytes("\r\n")); returnres; } //获取普通参数的字节数据 List { List stringhead = "--"+ boundary + "\r\n"+ $"Content-Type:text/plain;charset=utf-8\r\nContent-Disposition:form-data;name=\"{field}\"\r\n\r\n{value}\r\n"; res.AddRange(Encoding.UTF8.GetBytes(head)); returnres; } //获取所有字节数据 List { List foreach(var item infileDic) { List res.AddRange(temp); } foreach(var item inparamDic) { List res.AddRange(temp); } res.AddRange(Encoding.UTF8.GetBytes("\r\n--"+ boundary + "--\r\n")); returnres; } //读取文件中的所有字节 byte[] ReadFileBytes(stringpath) { if(!File.Exists(path)) returnnewbyte[0]; FileStream fs = newFileStream(path, FileMode.Open, FileAccess.Read); intiLenStream = (int)fs.Length; byte[] bArr = newbyte[fs.Length]; fs.Read(bArr, 0, bArr.Length); fs.Close(); returnbArr; } |