精华内容
下载资源
问答
  • form-data
    千次阅读
    2021-01-19 22:51:34

    form-data 和 x-www-form-urlencoded的区别

    • multipart/form-data:可以上传文件或者键值对,最后都会转化为一条消息
    • x-www-form-urlencoded:只能上传键值对,而且键值对都是通过&间隔分开的
    (1) application/x-www-form-urlencoded

    这应该是最常见的 POST 提交数据的方式了。浏览器的原生 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。

    <form action="form_action.asp" enctype="text/plain">
      <p>First name: <input type="text" name="fname" /></p>
      <p>Last name: <input type="text" name="lname" /></p>
      <input type="submit" value="Submit" />
    </form>
    

    此时可以看到,

    Content-Type: application/x-www-form-urlencoded;charset=utf-8
    title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3
    

    首先,``Content-Type被指定为application/x-www-form-urlencoded;其次,提交的数据按照key1=val1&key2=val2` 的方式进行编码,key 和 val 都进行了 URL 转码。大部分服务端语言都对这种方式很好的支持,常用的如jQuery中的ajax请求,Content-Type 默认值都是 application/x-www-form-urlencoded;charset=utf-8

    (2) multipart/form-data

    这也是常见的post请求方式,一般用来上传文件,各大服务器的支持也比较好。所以我们使用表单上传文件时,必须让表单的enctype属性值为 multipart/form-data.

    注意:以上两种方式:application/x-www-form-urlencoded 和 multipart/form-data都是浏览器原生支持的。

    (3) application/json

    application/json 作为响应头并不陌生,实际上,现在很多时候也把它作为请求头,用来告诉服务端消息主体是序列化的JSON字符串,除了低版本的 IE,基本都支持。除了低版本的 IE 都支持JSON.stringify() 的方法,服务端也有处理JSON的函数,使用json不会有任何麻烦。例如:

    //请求数据
    var data = {name:'jack',sex:'man'};
    //请求数据序列化处理
    JSON.stingify(data);
    
    //结果:{'name':'jack','sex':'man'};
    

    二、postman的几种参数格式

    form-data、x-www-form-urlencoded、raw、binary 的区别

    1、form-data对应的是以form表单提交传值的情形

    等价于http请求中的 multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type 来表名文件类型;content-disposition,用来说明字段的一些信息;
    由于有 boundary隔离,所以 multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件;

    POST  HTTP/1.1
    Host: test.app.com
    Cache-Control: no-cache
    Postman-Token: 59227787-c438-361d-fbe1-75feeb78047e
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
    
    ------WebKitFormBoundary7MA4YWxkTrZu0gW
    Content-Disposition: form-data; name="filekey"; filename=""
    Content-Type: 
    
    
    ------WebKitFormBoundary7MA4YWxkTrZu0gW
    Content-Disposition: form-data; name="textkey"
    
    tttttt
    ------WebKitFormBoundary7MA4YWxkTrZu0gW--
    

    请求体中的boundary参数指定的就是分隔体,可以看到请求内容被分为了两段,第一段对应filekey,第二段对应textkey

    2、x-www-form-urlencoded

    application/x-www-from-urlencoded,将表单内的数据转换为Key-Value;

    POST  HTTP/1.1
    Host: test.app.com
    Content-Type: application/x-www-form-urlencoded
    Cache-Control: no-cache
    Postman-Token: e00dbaf5-15e8-3667-6fc5-48ee3cc89758
    
    key1=value1&key2=value2
    

    form-data 与 x-www-form-urlencoded 的区别

    • multipart/form-data:可以上传文件或者键值对,最后都会转化为一条消息;
    • x-www-form-urlencoded:只能上传键值对,而且键值对都是通过&间隔分开的;
    3、raw

    对应的是入参是任意格式的可以上传任意格式的【文本】,可以上传text、json、xml、html等;

    4、binary

    相当于Content-Type:application/octet-stream,只可以上传二进制数据,通常用来上传文件,但是一次只能上传一个文件。

    转载:https://www.cnblogs.com/wbl001/p/12050751.html

    更多相关内容
  • 使用multipart/form-data方式提交数据与普通的post方式有一定区别。multipart/form-data的请求头必须包含一个特殊的头信息:Content-Type,其值必须为multipart/form-data。另外还需要规定一个内容分割符用于分割...
  • 使用c#实现的HttpClient拼接multipart/form-data形式参数post提交数据,包含图片内容,有需要的可以下载,希望能帮到有需要的人,
  • c#没有现成的multipart/form-data库,自己封的一个demo供参考。
  • 使用indy自带的idhttp控件,用form表单(multipart/form-data)形式上传文件(图片,视频等)
  • c#下post multipart/form-data和JSON

    热门讨论 2017-01-09 11:47:09
    c#下post 发送 multipart/form-data和JSON数据
  • 用C语言实现multipart/form-data文件上传,没有用到curl之类的库。之前做个小的日志上传程序写的。
  • form-data和x-www-form-urlencode的区别

    千次阅读 2021-12-09 14:07:33
    form-data 就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来表名文件类型;...

    一、解释

    form-data

    就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来表名文件类型;content-disposition,用来说明字段的一些信息;

    由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件。

    x-www-form-urlencoded

    就是application/x-www-from-urlencoded,会将表单内的数据转换为键值对,比如,name=java&age = 23

    multipart/form-data与x-www-form-urlencoded区别

    multipart/form-data:既可以上传文件等二进制数据,也可以上传表单键值对,只是最后会转化为一条信息; x-www-form-urlencoded:只能上传键值对,并且键值对都是间隔分开的。

    二、抓包分析

    看完上面所解释的, 下面我们抓包分析一下http协议中Post两个不同到底有什么区别

    这里使用强大的http测试工具PostMan和抓包工具Wireshark

    1、x-www-form-urlencoded

    postman body选择x-www-form-urlencoded格式,添加2个key和value

     

    可以看到http的header为

     Content-Type: application/x-www-form-urlencoded\r\n

    还有post的数据区只有一条信息,组成规则

    键名1=值1& 键名2=值2

    2、multipart/form-data

     

    可以看到http的header为

     Content-Type: multipart/form-data; boundary=--------------------------862898227956495955231239\r\n

    还有post的数据区有2条信息,由3个boundary隔开

    一条数据组成:Content-Disposition: form-data; name="键名" + 2个换行符 +

    Content-Disposition: form-data; name="键名"\r\n\r\n值

    3、multipart/form-data  上传文件

    在上面的基础上,增加键名为uploadFile,类型选为file会弹出一个对话框选一个jpg文件

    wireshark抓包 

     可以看到3条键值由4个boundary隔开

    而且传文件的时候稍微和普通的键+文本值不同

    规则是:

    Content-Disposition: form-data; name="键名"; filename="文件名"\r\n

    Content-Type: image/jpeg\r\n\r\n + JPG二进制数据

    展开全文
  • Android模拟 HTTP multipart/form-data 请求协议信息实现图片上传
  • 项目场景:APP 端需要上传图片文件,并且需要携带一些相关的参数; 服务端使用 ASP.NET WebAPI,...有文件上传时使用 Content-Type: multipart/form-data 的类型请求【注意:Headers 中千万不要添加 Conte...

    项目场景:APP 端需要上传图片文件,并且需要携带一些相关的参数;
    服务端使用 ASP.NET WebAPI,MultipartFormDataStreamProvider 接收参数,MultipartMemoryStreamProvider 接收文件
    有文件上传时使用 Content-Type: multipart/form-data 的类型请求
    【注意:Headers 中千万不要添加 Content-Type: application/json 否则接收不到参数】
    postman 工具请求参数实例

    POST /api/StudentLeave/Create
    Host: localhost:52644
    Cache-Control: no-cache
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

    Controller接收参数

    [HttpPost]
    public async Task<CreateStudentLeaveResponse> Create()
    {
        string gradeId = HttpContext.Current.Request["GradeId"];
        string classId = HttpContext.Current.Request["ClassId"];
        string studentId = HttpContext.Current.Request["StudentId"];
    
        if (!Request.Content.IsMimeMultipartContent())
            throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
    
        string root = HttpContext.Current.Server.MapPath(StringPlus.GetWebConfigKey("APIClassBannerPath"));
    
        var providerForm = new MultipartFormDataStreamProvider(root);
        await Request.Content.ReadAsMultipartAsync(providerForm);
        Dictionary<string, string> dic = new Dictionary<string, string>();
        foreach (var key in providerForm.FormData.AllKeys)
        {
            //接收FormData  
            dic.Add(key, providerForm.FormData[key]);
        }
        var jsonString = JsonConvert.SerializeObject(dic, Formatting.Indented);
        StudentLeaveRequest studentLeaveModel = JsonConvert.DeserializeObject<StudentLeaveRequest>(jsonString);
    
        var provider = new MultipartMemoryStreamProvider();
        foreach (var file in provider.Contents)
        {
            if (!string.IsNullOrEmpty(file.Headers.ContentDisposition.FileName))
            {
                UpfileDto upfile = new UpfileDto();
                FileInfo fileinfo = new FileInfo(file.Headers.ContentDisposition.FileName.Replace("\"", ""));
                string filename = file.Headers.ContentDisposition.FileName.TrimStart('"').TrimEnd('"');
                //string filename = file.Headers.ContentDisposition.Name.Replace("\"", "");
                string fileExtension = Path.GetExtension(filename).ToLower();//文件的后缀名(小写)
                filename = $"{DateTime.Now.ToString("yyyyMMddHHmmss.fff")}{fileExtension}";
                if (string.IsNullOrEmpty(filename))
                    filename = $"{DateTime.Now.ToString("yyyyMMddHHmmss.fff")}{fileExtension}";
    
                var ms = file.ReadAsStreamAsync().Result;
                using (var br = new BinaryReader(ms))
                {
                       System.Drawing.Image oImage = System.Drawing.Image.FromStream(ms);
                       oImage.Save(upfile.OFullName, System.Drawing.Imaging.ImageFormat.Jpeg);
                }
        }
    }

    ===============================

     [HttpPost]
     public async Task<CreateStudentLeaveResponse> Create()
     {
         List<AttachmentDto> list = new List<AttachmentDto>();
         try
         {
             //if (!Request.Content.IsMimeMultipartContent())
             //    throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
             logger.Info("StudentLeaveController CreateStudentLeaveResponse Ln 234");
             string root = HttpContext.Current.Server.MapPath(StringPlus.GetWebConfigKey("APIStudentLeavePath"));
             #region StudentLeave 操作
    
             string posterType = HttpContext.Current.Request["PosterType"];
             string leaveType = HttpContext.Current.Request["LeaveType"];
             string locationType = HttpContext.Current.Request["LocationType"];
             string gradeId = HttpContext.Current.Request["GradeId"];
             string classId = HttpContext.Current.Request["ClassId"];
             string teacherId = HttpContext.Current.Request["TeacherId"];
             string studentParentId = HttpContext.Current.Request["StudentParentId"];
             string studentId = HttpContext.Current.Request["StudentId"];
             string subject = HttpContext.Current.Request["Subject"];
             string content = HttpContext.Current.Request["Content"];
             string startTime = HttpContext.Current.Request["StartTime"];
             string endTime = HttpContext.Current.Request["EndTime"];
             string reason = HttpContext.Current.Request["Reason"];
             logger.Info("StudentLeaveController CreateStudentLeaveResponse Ln 251");
             StudentLeaveRequest request = new StudentLeaveRequest();
             request.Id = StringPlus.GenerateGuid();
             request.PosterType = Convert.ToInt32(posterType);
             request.LeaveType = leaveType;
             request.LocationType = Convert.ToInt32(locationType);
             request.GradeId = gradeId;
             request.ClassId = classId;
             request.TeacherId = teacherId;
             request.StudentParentId = studentParentId;
             request.StudentId = studentId;
             request.Subject = subject;
             request.Content = content;
             request.SchoolId = schoolId;
             request.StartTime = Convert.ToDateTime(startTime);
             request.EndTime = Convert.ToDateTime(endTime);
             request.Reason = reason;
             request.Approver = string.Empty;
             request.ApprovalStatus = (int)EnumApprovalStatus.UnderApproval;
             //request.CreatedBy = string.Empty;
             //request.ModifyBy = string.Empty;
             logger.Info("StudentLeaveController CreateStudentLeaveResponse Ln 272");
             if (!_relationRepo.IsExistRelation(studentId, studentParentId, schoolId))
             {
                 return ReturnResult((int)CodeEnum.Fail, "学生与家长未建立亲属关系,不能申请请假");
             }
             logger.Info("StudentLeaveController CreateStudentLeaveResponse Ln 277");
             _studentLeaveRepo.Add(Mapper.Map<StudentLeaveDo>(request));
    
             #endregion
             logger.Info("StudentLeaveController CreateStudentLeaveResponse Ln 281");
             #region 发送通知
                    var student = _userInfoRepo.FindFirst(x => x.Id == studentId && x.Status != "D");
                    logger.Info("StudentLeaveController CreateStudentLeaveResponse Ln 284");
                    string studentName = "";
                    if (student != null)
                    {
                        studentName = student.RealName;
                    }
                    string strlocationType = "";
                    switch (request.LocationType)
                    {
                        case 1:
                            strlocationType = "校内";
                            break;
                        case 2:
                            strlocationType = "校外";
                            break;
                        case 3:
                            strlocationType = "宿舍外";
                            break;
                    }
                    string strLeaveType = "";
                    switch (request.LeaveType)
                    {
                        case "2":
                            strLeaveType = "事假";
                            break;
                        case "3":
                            strLeaveType = "病假";
                            break;
                    }
    
                    //var parent = _userInfoRepo.FindFirst(x => x.Id == studentParentId && x.Status != "D");
                    //string parentMobile = "";
                    //if (parent != null)
                    //{
                    //    parentMobile = parent.Mobile;
                    //}
                    var teacher = _userInfoRepo.FindFirst(x => x.Id == teacherId && x.Status != "D");
                    string teacherMobile = "";
                    if (teacher != null)
                    {
                        teacherMobile = teacher.Mobile;
                    }
    
                    if (!string.IsNullOrEmpty(teacherMobile))
                    {
                        string extParams = "{ \"LeaveId\":" + "\"" + request.Id + "\"}";
    
                        AliPushUtil.AccessKeyId = _accessKeyId;
                        AliPushUtil.AccessKeySecret = _accessKeySecret;
                        AliPushUtil.AndroidAppKey = _androidAppKey;
                        AliPushUtil.IOSAppKey = _iOSAppKey;
                        AliPushUtil.TargetValue = teacherMobile;
    
                        AliPushUtil.Initial();
                        string strBody = $"{studentName}{strlocationType}{strLeaveType} {startTime}至{endTime}";
                        AliPushUtil.Push("ALIAS", "NOTICE", "您有一个新的请假通知", strBody, extParams, "DEV", "", "NONE", "", "");
                    }
                    #endregion
             logger.Info("StudentLeaveController CreateStudentLeaveResponse Ln 342");
             #region 上传图片
             logger.Info("StudentLeaveController CreateStudentLeaveResponse Ln 344");
             if (Request.Content.IsMimeMultipartContent())
             {
                 var provider = new MultipartMemoryStreamProvider();
                 StringBuilder sb = new StringBuilder();
                 await Request.Content.ReadAsMultipartAsync(provider);
    
                 int fileTotal = 0;
                 foreach (var file in provider.Contents)
                 {
                     if (file.Headers.ContentType != null
                         && (file.Headers.ContentType.MediaType == "image/jpeg"
                         || file.Headers.ContentType.MediaType == "image/png"
                         || file.Headers.ContentType.MediaType == "image/bmp"
                         || file.Headers.ContentType.MediaType == "image/gif"))
                     {
                         fileTotal += 1;
                     }
                 }
    
                 if (fileTotal < 10)
                 {
                 }
                 else
                 {
                     return ReturnResult(-302, "超过允许的图片数量范围");
                 }
                 logger.Info("StudentLeaveController CreateStudentLeaveResponse Ln 371");
                 foreach (var file in provider.Contents)
                 {
                     if (!string.IsNullOrEmpty(file.Headers.ContentDisposition.FileName))
                     {
                         UpfileDto upfile = new UpfileDto();
                         FileInfo fileinfo = new FileInfo(file.Headers.ContentDisposition.FileName.Replace("\"", ""));
                         string filename = file.Headers.ContentDisposition.FileName.TrimStart('"').TrimEnd('"');
                         //string filename = file.Headers.ContentDisposition.Name.Replace("\"", "");
                         string fileExtension = Path.GetExtension(filename).ToLower();//文件的后缀名(小写)
                         filename = $"{DateTime.Now.ToString("yyyyMMddHHmmss.fff")}{fileExtension}";
                         if (string.IsNullOrEmpty(filename))
                             filename = $"{DateTime.Now.ToString("yyyyMMddHHmmss.fff")}{fileExtension}";
    
                         var ms = file.ReadAsStreamAsync().Result;
                         using (var br = new BinaryReader(ms))
                         {
                             if (ms.Length <= 0 || ms.Length >= 3000000)
                             {
                                 return ReturnResult(0, "指定的文件大小不符合要求!");
                             }
    
                             #region 生成原图(不保存)
    
                             System.Drawing.Image oImage = System.Drawing.Image.FromStream(ms);
    
                             int owidth = oImage.Width; //原图宽度
                             int oheight = oImage.Height; //原图高度
                             int LimitWidth = 3072;
                             int LimitHeight = 2304;
                             int twidth = 300;
                             int theight = 300;
                             string OFileName = "0";
                             string TFileName = "0";
    
                             if (owidth > LimitWidth || oheight > LimitHeight)
                                 return ReturnResult(0, "超过允许的图片尺寸范围!");
    
                             if (owidth >= twidth || oheight >= theight)
                             {
                                 //按比例计算出缩略图的宽度和高度
                                 if (owidth >= oheight)
                                     theight = (int)Math.Floor(Convert.ToDouble(oheight) * (Convert.ToDouble(twidth) / Convert.ToDouble(owidth)));//等比设定高度
                                 else
                                     twidth = (int)Math.Floor(Convert.ToDouble(owidth) * (Convert.ToDouble(theight) / Convert.ToDouble(oheight)));//等比设定宽度
                             }
                             else
                             {
                                 theight = oheight;
                                 twidth = owidth;
                             }
    
                             var data = br.ReadBytes((int)ms.Length);
                             #endregion
    
                             OFileName = "o" + filename;
                             TFileName = "t" + filename;
                             string SavePath = HttpContext.Current.Server.MapPath(StringPlus.GetWebConfigKey("APIStudentLeavePath"));
                             upfile.OFullName = SavePath + OFileName;
                             upfile.TFullName = SavePath + TFileName;
    
                             #region 保存图片
    
                             switch (fileExtension)
                             {
                                 case ".jpeg":
                                 case ".jpg":
                                     {
                                         oImage.Save(upfile.OFullName, System.Drawing.Imaging.ImageFormat.Jpeg);
                                         break;
                                     }
    
                                 case ".gif":
                                     {
                                         oImage.Save(upfile.OFullName, System.Drawing.Imaging.ImageFormat.Gif);
                                         break;
                                     }
    
                                 case ".png":
                                     {
                                         oImage.Save(upfile.OFullName, System.Drawing.Imaging.ImageFormat.Png);
                                         break;
                                     }
    
                                 case ".bmp":
                                     {
                                         oImage.Save(upfile.OFullName, System.Drawing.Imaging.ImageFormat.Bmp);
                                         break;
                                     }
                             }
    
                             list.Add(new AttachmentDto
                             {
                                 Id = Guid.NewGuid().ToString(),
                                 AttType = (int)EnumAttachmentType.StudentLeave,
                                 AttNo = request.Id,
                                 FileType = (int)EnumFileType.Image,
                                 FileName = OFileName,
                                 Path = $"{StringPlus.GetWebConfigKey("APIStudentLeavePath")}{OFileName}",
                                 SchoolId = schoolId,
                                 Status = "A"
                             });
    
                             #endregion
                         }
                         logger.Info("StudentLeaveController CreateStudentLeaveResponse Ln 475");
                     }
                 }
             }
             #endregion
         }
         catch (Exception ex)
         {
             logger.Error("StudentLeaveController CreateStudentLeaveResponse Ln 482" + ex.Message);
             return ReturnResult(500, "服务器无响应" + ex.Message);
         }
         logger.Info("StudentLeaveController CreateStudentLeaveResponse Ln 488");
         _attachmentRepo.BatchInsert(Mapper.Map<List<AttachmentDo>>(list));
         logger.Info("StudentLeaveController CreateStudentLeaveResponse Ln 490");
         return new CreateStudentLeaveResponse { };
     }

    3、
    4、
    5、
    6、
    7、
    8、
    9、
    0、

    展开全文
  • C++解析multipart/form-data

    千次阅读 2021-08-03 14:59:39
    背景 ... application/x-www-form-urlencoded的格式与URL的查询...但multipart/form-data相对而言比较复杂 multipart/form-data multipart/form-data主要是为了解决application/x-www-form-urlencoded编码格式在传输大量二

    背景

    使用boost.beast实现简单的HTTP服务,但是boost.beast没有提供对表单数据的解析,为此需要自己实现解析表单数据。
    application/x-www-form-urlencoded的格式与URL的查询字符串格式一样,只是会被URL编码,比较容易处理
    multipart/form-data相对而言比较复杂

    multipart/form-data

    multipart/form-data主要是为了解决application/x-www-form-urlencoded编码格式在传输大量二进制数据或包含非ASCII字符文本时的低效问题。multipart/form-data的数据由多个part组成,part间通过boundary分隔符进行分割,每个part由header和content组成

    multipart/form-data的格式大致为:

    ----------------------------904587217962624105581666
    Content-Disposition: form-data; name=“projectName”

    testProject
    ----------------------------904587217962624105581666
    Content-Disposition: form-data; name=“clientName”

    aaa
    -----------------------------904587217962624105581666–

    发送multipart/form-data的Http请求头中的Content-Type信息:

    multipart/form-data; boundary=----------------------------904587217962624105581666

    更多的关于multipart/form-data信息可以查看Returning Values from Forms: multipart/form-data

    FormItem

    使用FormItem来表示multipart/form-data中的一个part,FormItem并不复制数据内容,只保存指向表单数据的指针,并通过保存记录本部分数据在表单数据中的起始位置_dataStart和数据长度_dataLength来表示数据,避免拷贝造成的开销。

    除了数据内容外,还需要保存每部分内容的头部信息,包括name,contentType,fileName

    class FormItem{
        /**
         * 将MultipartContentElement作为MultipartContentParse的友元类
         * 使得MultipartContentParse对MultipartContentElement具有完的控制权
         * 因为两个类是强相关的,且MultipartContentElement中的数据应由
         * MultipartContentParse进行设置
         */
        friend class FormDataParser;
        private:
            std::string _fileName;                       ///< 表单元为文件时,具有文件名
            std::string _name;                           ///< 表单元的key
            std::string _contentType;                    ///< 表单元该部分的类型
            const std::shared_ptr<std::string> _content; ///< 指request中的表单内容
            int _dataStart;                              ///< 属于本单元素的内容的起始下标
            int _dataLength;                             ///< 属于本单元素的内容的长度
        
            /**
             * MultipartContentElement对象只能由MultipartContentPars生成
             * 将构造函数的访问权设置为private,防止外部创建
             * @param name 表单元素的名
             * @param fileName 文件时fileName不为空
             * @param contentType 类型
             * @param content 指向表单数据的指针
             * @param start 本表单元素的起始位置
             * @param length 本表单元素的数据长度
             * @return MultipartContentElement对象
             */
            FormItem(const std::string name, 
                const std::string fileName, const std::stringcontentType,
                const std::shared_ptr<std::string> content, 
                const int start, const int length);
        public:
            inline std::string getFileName() const { return_fileName; }
            inline std::string getName() const { return _name; }
            inline std::string getContenType() const {return_contentType; }
            inline bool isFile() const {return !_fileName.empty(); }
            /**
             * 获取具体的内容,不返回指向原始内容的指针
             * 而是复制内容,防止外部对请求作出更改,影响到同一表单中同元素的内容
             * @return 新复制的内容的指针
             */
            std::unique_ptr<std::string>  getContent() const ;
    };
    

    类私有成员变量使用_开头,getContent返回的是复制的数据,而不是指向数据起始位置的指针,这是为了避免外部直接修改表单数据,但是这也造成了一定的开销,使用时可根据具体情况使用不同的返回方式。

    FormItem如果是文件时,_fileName不为空,否则为空,通过_fileName是否为空来判断该部分是否为文件

    构建函数声明为私有,防止外部构造FormItem对象,调用者只需要使用FormItem,不需要也不应该构造该对象

    FormDataParser

    multipart/form-data的boundary在Http的Header中已经包含了,故该值可由调用者提供,FormDataParser只负责解析multipart/form-data请求的body部分的数据。

    class FormDataParser{
        private:
            std::shared_ptr<std::string> _data; ///< 指向表单数据的针
            std::string _boundary;              ///< 不同元素的分割符串
    
            bool _lastBoundaryFound;            ///< 是否找到了最后边界
            int _pos;                           ///< 当前解析到的位置
            int _lineStart;                     ///< 当前行的起始位置
            int _lineLength;                    ///< 当前行的长度
    
            std::string _partName;              ///< 当前元素的名
            std::string _partFileName;          ///< 当前元素的文件名
            std::string _partContentType;       ///< 当前元素的类型
            int _partDataStart;                 ///< 当前元素的数据表单中的起始位置
            int _partDataLength;                ///< 当前元素的数据长度
        public:
            FormDataParser(const std::shared_ptr<std::string> data, 
                const int pos, const std::string boundary);
            /**
             * 调用parse函数后,才会执行对应的解析操作,
             * @return 指向由FormItem组成的vector的unique_ptr
             */
            std::unique_ptr<std::vector<FormItem>> parse();  
        private:
            /**
             * 解析表单数据的头部,即紧跟着boundary后面的一行
             */
            void parseHeaders();
            /**
             * 解析表单元素中的具体数据
             */
            void parseFormData();
            /**
             * 获取下一行的数据,
             * 在此实际上是通过更新类内部的_pos, _lineStart,_lineLength实现的
             * @return 是否成功得到下一行的数据
             */
            bool getNextLine();
            /**
             * 判断是否为边界分割行
             * @return 是边界分割行放回true,否则返回false
             */
            bool atBoundaryLine();
            /**
             * 判断是否到达表单数据的末尾
             */
            inline bool atEndOfData(){
                return _pos >= _data->size() || _lastBoundaryFound;
            }            
            std::string getDispositionValue(
                const std::string source, int pos, const std::stringname);
            /**
             * 去除字符串前后的空白字符
             * @return 去除空白字符的字符串
             */
            inline std::string& trim(std::string &s){
                if(s.empty()){ return s; }
                s.erase(0, s.find_first_not_of(" "));
                s.erase(s.find_last_not_of(" ") + 1);
                return s;
            }
    };
    

    FormDataParser的私有成员变量可以分成三部分,第一部分为表单数据的内容,包括_data_doundary,第二部分是在读取处理表单数据时的状态,用来记录处理表单数据需要记录的临时数据,包括_lastBoundaryFound,_pos,_lineStart_lineLength,最后一部分是读取到一个part时保存的数据,用来构建前文提到的FormItem对象,包括_partName, _partFileName,_partContentType, _partDataStart, _partDataLength

    除了构造函数外,FormDataParser只包含一个parse()函数,因为给类的定位就是解析,不需要其他的功能。parse()返回一个指向FormItem的数组的unique_ptr

    parse

    解析的主要步骤就是通过循环对每个part进行解析并构造FormItem对象存储起来,每个part又包含头部和内容。
    为了避免body中在表单数据之前其他数据,在找到边界时候再开始解析表单数据,具体代码为:

    std::unique_ptr<std::vector<FormItem>> FormDataParser::parse(){
        auto p = std::make_unique<std::vector<FormItem>>();
            
        //跳过空白行,直到遇到边界boundary,表示一个表单数据的开始
        while(getNextLine()){
            if(atBoundaryLine()){
                break;
            }
        }
        do{
            //处理头部
            parseHeaders();
            //头部过后如果没有数据,跳出循环
            if(atEndOfData()){ break; }
            //处理该项表单数据
            parseFormData();
            //将表单数据添加到结果数组中
            FormItem formItem(_partName, _partFileName, _partContentType, 
                _data, _partDataStart, _partDataLength);
            p->push_back(std::move(formItem));
        }while(!atEndOfData());
        
        return p;
    }
    

    由于FormItem的构造函数是private的,因此无法使用emplace_back,必须先构建好对象后再添加到vector中。使用std::move可以避免拷贝

    getNextLine

    getNextLine用于获取表单中的下一行数据

    bool FormDataParser::getNextLine(){
        int i = _pos;
        _lineStart = -1;
    
        while(i < _data->size()){
            //找到一行的末尾
            if(_data->at(i) == '\n'){
                _lineStart = _pos;
                _lineLength = i - _pos;
                _pos = i + 1;
                //忽略'\r'
                if(_lineLength > 0 && _data->at(i - 1) == '\r'){
                    _lineLength--;
                }
                break;
            }
            //到达表单数据的末尾了
            if(++i == _data->size()){
                _lineStart = _pos;
                _lineLength = i - _pos;
                _pos = _data->size();
            }
        }
    
        return _lineStart >= 0;
    }
    

    atBoundaryLine

    判断当前读取到的行是否为边界

    bool FormDataParser::atBoundaryLine(){
        int boundaryLength = _boundary.size();
        //最后的边界会多两个'-'符号
        if(boundaryLength != _lineLength && 
            boundaryLength + 2 != _lineLength){
            return false;
        }
    
        for(int i = 0; i < boundaryLength; ++i){
            if(_data->at(i + _lineStart) != _boundary[i]){ return false; }
        }
    
        if(_lineLength == boundaryLength){ return true; }
        //判断是否是最后的边界
        if(_data->at(boundaryLength + _lineStart) != '-' ||
            _data->at(boundaryLength + _lineStart + 1) != '-'){
            return false;
        }  
    
        //到达最后的边界
        _lastBoundaryFound = true;
        return true;
    }
    

    再表单数据的最后一个分隔符,会多出两个‘-’,因此需要做不同的判断
    在判断两个字符串是否相等之前,先检查长度是否相等,不相等可以不用做后续的比较

    parseHeaders

    处理一项表单数据的头部

    void FormDataParser::parseHeaders(){
        //清除之前的数据
        _partFileName.clear();
        _partName.clear();
        _partContentType.clear();
    
        while(getNextLine()){
            //头部内容结束后,会有一个空白行
            if(_lineLength == 0){ break; }
            const std::string thisLine = _data->substr(_lineStart,_lineLength);
    
            int index = thisLine.find(':');
            if(index < 0){ continue; }
    
            const std::string header = thisLine.substr(0, index);
            if(header == "Content-Disposition"){
                _partName = getDispositionValue(thisLine, index + 1, "name");
                _partFileName = getDispositionValue(thisLine, index + 1, "filename");
            }else if(header == "Content-Type"){
                _partContentType = thisLine.substr(index + 1);
                trim(_partContentType);
            }
        }
    }
    

    处理新的头部,意味着之前的项已经处理完毕了,因此在处理之前先将之前设置了的信息清理掉
    substr会创建出一个新的string对象,但是在header中一行的数据通过不会过程,因此此项开销是可以接受的,而且后续操作也比较方便实现,如果要进一步优化可以考虑直接使用_data进行操作,如计算index可以修改为:

    int index = _data->find(':', _lineStart);
    if(index < _lineStart || index > _lineStart + _lineLength){
        continue;
    }
    

    后续所有用到thisLine的地方都需要进行修改。由于header以及要拿到的name和filename都需要复制其值,因此在此处是否有必要优化,能取得多大的提升或许需要进一步讨论。

    multipart/form-data每一部分的头部中可能包含多行数据,每一行数据表示不同的含义,通过Content-DispositionContent-Type来区分并通过getDispositionValue获取其内容

    getDispositionValue

    std::string FormDataParser::getDispositionValue(
        const std::string source, int pos, const std::string name){
        //头部内容:Content-Disposition: form-data; name="projectName"
        //构建模式串
        std::string pattern = " " + name + "=";
        int i = source.find(pattern, pos);
        //更换格式继续查找位置
        if(i < 0){
            pattern = ";" + name + "=";
            i = source.find(pattern, pos);
        }
        if(i < 0){
            pattern = name + "=";
            i = source.find(pattern, pos);
        }
        //尝试了可能的字符串,还没有找到,返回空字符串        
        if(i < 0){ return std::string(); }
    
        i += pattern.size();
        if(source[i] =='\"'){
            ++i;
            int j = source.find('\"', i);
            if(j < 0 || i == j){ return std::string();}
            return source.substr(i, j - i);
        }else{
            int j = source.find(";", i);
            if(j < 0){ j = source.size(); }
            auto value = source.substr(i, j - i);
            //去掉前后的空白字符
            return trim(value);
        }
    }
    

    parseFormData

    处理表单的实际数据部分

    void FormDataParser::parseFormData(){
        _partDataStart = _pos;
        _partDataLength = -1;
    
        while(getNextLine()){
            if(atBoundaryLine()){
                //内容数据位于分解线前一行
                int indexOfEnd = _lineStart - 1;
                if(_data->at(indexOfEnd) == '\n'){ indexOfEnd--; }
                if(_data->at(indexOfEnd) == '\r'){ indexOfEnd--; }
                _partDataLength = indexOfEnd - _partDataStart + 1;
                break;
            }
        }
    }
    

    在遇到新的分割符时说明本部分的数据结束,但是在数据与分割行之间有换行符,可能是'\n',也可能是'\r\n',需要根据不同的情况减去不同的长度,从而得出当前表单项的数据的长度
    RFC7578要求boundary不能在数据中出现

    4.1. “Boundary” Parameter of multipart/form-data
    As with other multipart types, the parts are delimited with a boundary delimiter, constructed using CRLF, “–”, and the value of the “boundary” parameter. The boundary is supplied as a “boundary” parameter to the multipart/form-data type. As noted in Section 5.1 of [RFC2046], the boundary delimiter MUST NOT appear inside any of the encapsulated parts, and it is often necessary to enclose the “boundary” parameter values in quotes in the Content-Type header field

    总结

    文本介绍了如何使用C++解析multipart/form-data
    multipart/form-data结构也是比较清晰的,因此解析起来不算太麻烦。

    展开全文
  • <form method="post" ENCTYPE="multipart/form-data"> file类型职务的普通参数传递到后台问题解决
  • application/x-www-form-urlencoded 表单代码: <form action="http://localhost:8888/task/" method="POST"> First name: <input type="text" name="firstName" value="Mickey&">&
  • 因为要上传文件所以得用form-data,上传文件同时还想再放入一个List集合对象,实现方式就是前段用JSON.stringify()方法将JavaScript对象或值转换为JSON字符串,后台使用字符串接收然后解析一下。 前段: 图片是...
  • 对multipart/form-data请求参数的处理 对application/x-www-form-urlencoded请求参数的处理 前言 我们用@RequestMapping标识一个Web请求的映射,可以标识在方法上,当我们向服务器发送一个请求时,由Spring解析...
  • 1、form-data: 就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来说明文件类型;...
  • Java发送form-data请求实现文件上传

    千次阅读 2021-10-18 16:09:00
    如何使用Java发送form-data格式的请求上传multipart文件? 封装了以下工具类: package com.leeyaonan.clinkz.common.util; import java.io.File; import java.io.IOException; import java.nio.charset....
  • multipart/form-data 获取form-data数据--java

    千次阅读 2018-11-27 13:08:34
    格式类似于下面这样:用request.getParameter是取不到数据的,这时需要通过request.getInputStream来取数据,不过取到的是个InputStream,所以无法直接获取指定的表单项(需要自己对取到的流进行解析,才能得到表单...
  • 1、form-data方式。 表示http请求中的multipart/form-data方式,会将表单的数据处理为一条消息,用分割符隔开,可以上传键值对或者上传文件;比如按照如下方式传输提交数据。对于一段utf8编码的字节...
  • 1 form-data 等价于http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来表名文件类型;...
  • 本文测试 Content-Type 为 multipart/form-data 的请求详情: 前端页面模仿用户输入:用户名、密码、性别、爱好、城市、上次文件等,可以看到请求头中: Content-Type multipart/form-data; boundary=--------------...
  • 'Content-Type': multipart/form-data 这个问题,然后查阅资料开始解决。 一、首先说一下POST 提交数据方式常用的四种方式 HTTP/1.1 协议规定的 HTTP 请求方法有 OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、...
  • 我必须发送内容类型为multipart / form-data的请求。 因此,当发送带有文件输入的表单并发送enctype = multipart / form-data属性时,我需要进行类似的请求。我尝试了这个:$url = 'here_is_url_for_web_API';$...
  • 表单提交 multipart/form-data 和 x-www-form-urlencoded的区别

    万次阅读 多人点赞 2018-11-19 20:43:36
    表单有两种提交方式,POST和GET。通常我们会使用POST方式,一是因为形式上的安全 ;二是可以上传文件。... 默认 enctype=“application/x-www-form-urlencoded”,数据以键值对的方式传送到服务器,这种方...
  • multipart/form-data中boundary的作用

    千次阅读 2022-03-14 15:50:54
    主要用于分割key-value对,多个kv之间使用boundary来进行分割 参考自: https://stackoverflow.com/questions/3508338/what-is-the-boundary-in-multipart-form-data#:~:text=...multipart/form-data containsbo.
  • 上传文件multipart form-data boundary 说明

    千次阅读 2020-07-22 23:47:36
    含义 ENCTYPE="multipart/form-data" 说明: 通过 http 协议上传文件 rfc1867协议概述,客户端发送内容构造。 概述               &...
  • 以下功能不起作用。... charset = UTF-8”我想更改内容类型=“multipart / form-data”以下功能如下所示.plz提供任何建议function importNow(serverURL, parameters) {document.body.style.cursor = "wait";$....
  • 深入解析 multipart/form-data

    千次阅读 2019-03-26 10:08:16
    原文作者:juniway 原文链接:https://www.jianshu.com/p/29e38bcc8a1d 一个 HTML 表单中的 enctype 有... ... multipart/form-data text-plain 默认情况下是 application/x-www-urlencoded,当表单使用 POST 请求...
  • 一. form-data,x-www-form-urlencoded,raw, binary 区别 使用postman发送json格式数据时,body...由于有boundary隔离,所以multipart/form-data既**可以上传键值对,也可以上传文件**(正因为它采取键值对的方式,所

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 863,920
精华内容 345,568
关键字:

form-data