为何文中最后的 patch 方法要使用 x-www-form-urlencoded 而不是 form-data?

我们知道,HTTP请求中form-data 既可以上传键值对,也可以上传文件,x-www-form-urlencoded只能传键值对。

文中最后部分patch方法那里,去更新User信息时,只传键值对而无文件,理论上应该选择form-data和x-www-form-urlencoded都可以。实际测试只有x-www-form-urlencoded能成功更改用户信息。这是为什么呢?

《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 11
liyu001989

『理论上应该选择 form-data 和 x-www-form-urlencoded 都可以』

http 协议 put 和 patch 只能使用 x-www-form-urlencoded

5年前 评论

@liyu001989 老师,我把这个大致读了一下,

  • In addition, it defines the default ENCTYPE attribute of the FORM element using the POST METHOD to have the default value "application/x-www-form-urlencoded".
  • When the user completes the form, and selects the SUBMIT element, the
    browser should send the form data and the content of the selected
    files. The encoding type application/x-www-form-urlencoded is
    inefficient for sending large quantities of binary data or text
    containing non-ASCII characters. Thus, a new media type,
    multipart/form-data, is proposed as a way of efficiently sending the
    values associated with a filled-out form from client to server.
  • 5.8 File transfer with ENCTYPE=x-www-form-urlencoded
    If a form contains <INPUT TYPE=file> elements but does not contain an
    ENCTYPE in the enclosing <FORM>, the behavior is not specified. It
    is probably inappropriate to attempt to URN-encode large quantities
    of data to servers that don't expect it.

似乎此文是在说明ENCTYPE=x-www-form-urlencoded用来上传图片是不够的,因此引入了ENCTYPE=multipart/form-data

我再次看了源码:

  • 在\Symfony\Component\HttpFoundation\Request::createFromGlobals方法中,可以看到$request->getContent()这个方法,而这个方法中有这么一段代码:

        if (null === $this->content || false === $this->content) {
            $this->content = file_get_contents('php://input');
        }
  • 参考这里,可知 php://input is not available with enctype="multipart/form-data".

  • enctype="multipart/form-data"时,file_get_contents('php://input') 为类似这样的字符串:

    "avatar_image_id"
    
    1
    ----------------------------007603761275937497486210
    Content-Disposition: form-data; name="name"
    
    hustnzj
    ----------------------------007603761275937497486210
    Content-Disposition: form-data; name="email"
    
    123@qq.com
    ----------------------------007603761275937497486210
    Content-Disposition: form-data; name="introduction"
    
    hehe
    ----------------------------007603761275937497486210--
  • 而enctype="x-www-form-urlencoded"时,file_get_contents('php://input') 为这样的字符串。

    avatar_image_id=1&name=hustnzj2&email=123%40qq.com&introduction=hehe
  • 因此,ENCTYPE需要为x-www-form-urlencoded,才能进行下一步的parse_str操作。

5年前 评论
  • 如果不考虑理论的话,或许光看源码也可以得出这个结论。

    • 首先,\App\Http\Controllers\Api\UsersController::update方法中注入的是UserRequest,而UserRequest的最顶层类为\Symfony\Component\HttpFoundation\Request。
    • 在\Symfony\Component\HttpFoundation\Request中,有一个createFromGlobals方法,作用是Creates a new request with values from PHP's super globals. 代码:

      /**
      * Creates a new request with values from PHP's super globals.
      *
      * @return static
      */
      public static function createFromGlobals()
      {
      $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER);
      
      if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
          && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
      ) {
          parse_str($request->getContent(), $data);
          $request->request = new ParameterBag($data);
      }
      
      return $request;
      }
    • 可以看出,只有当CONTENT_TYPE为application/x-www-form-urlencoded,且REQUEST_METHOD为'PUT', 'DELETE', 'PATCH'时,才会将参数放入$request中。
    • 再回到\App\Http\Controllers\Api\UsersController::update方法中来,如果$request->request为空,那么$attributes = $request->only(['name', 'email', 'introduction']);的结果就是空,$user->update($attributes);就是什么也没有update。
5年前 评论
liyu001989

『理论上应该选择 form-data 和 x-www-form-urlencoded 都可以』

http 协议 put 和 patch 只能使用 x-www-form-urlencoded

5年前 评论

@liyu001989 老师,我把这个大致读了一下,

  • In addition, it defines the default ENCTYPE attribute of the FORM element using the POST METHOD to have the default value "application/x-www-form-urlencoded".
  • When the user completes the form, and selects the SUBMIT element, the
    browser should send the form data and the content of the selected
    files. The encoding type application/x-www-form-urlencoded is
    inefficient for sending large quantities of binary data or text
    containing non-ASCII characters. Thus, a new media type,
    multipart/form-data, is proposed as a way of efficiently sending the
    values associated with a filled-out form from client to server.
  • 5.8 File transfer with ENCTYPE=x-www-form-urlencoded
    If a form contains <INPUT TYPE=file> elements but does not contain an
    ENCTYPE in the enclosing <FORM>, the behavior is not specified. It
    is probably inappropriate to attempt to URN-encode large quantities
    of data to servers that don't expect it.

似乎此文是在说明ENCTYPE=x-www-form-urlencoded用来上传图片是不够的,因此引入了ENCTYPE=multipart/form-data

我再次看了源码:

  • 在\Symfony\Component\HttpFoundation\Request::createFromGlobals方法中,可以看到$request->getContent()这个方法,而这个方法中有这么一段代码:

        if (null === $this->content || false === $this->content) {
            $this->content = file_get_contents('php://input');
        }
  • 参考这里,可知 php://input is not available with enctype="multipart/form-data".

  • enctype="multipart/form-data"时,file_get_contents('php://input') 为类似这样的字符串:

    "avatar_image_id"
    
    1
    ----------------------------007603761275937497486210
    Content-Disposition: form-data; name="name"
    
    hustnzj
    ----------------------------007603761275937497486210
    Content-Disposition: form-data; name="email"
    
    123@qq.com
    ----------------------------007603761275937497486210
    Content-Disposition: form-data; name="introduction"
    
    hehe
    ----------------------------007603761275937497486210--
  • 而enctype="x-www-form-urlencoded"时,file_get_contents('php://input') 为这样的字符串。

    avatar_image_id=1&name=hustnzj2&email=123%40qq.com&introduction=hehe
  • 因此,ENCTYPE需要为x-www-form-urlencoded,才能进行下一步的parse_str操作。

5年前 评论
  • 如果不考虑理论的话,或许光看源码也可以得出这个结论。

    • 首先,\App\Http\Controllers\Api\UsersController::update方法中注入的是UserRequest,而UserRequest的最顶层类为\Symfony\Component\HttpFoundation\Request。
    • 在\Symfony\Component\HttpFoundation\Request中,有一个createFromGlobals方法,作用是Creates a new request with values from PHP's super globals. 代码:

      /**
      * Creates a new request with values from PHP's super globals.
      *
      * @return static
      */
      public static function createFromGlobals()
      {
      $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER);
      
      if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
          && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
      ) {
          parse_str($request->getContent(), $data);
          $request->request = new ParameterBag($data);
      }
      
      return $request;
      }
    • 可以看出,只有当CONTENT_TYPE为application/x-www-form-urlencoded,且REQUEST_METHOD为'PUT', 'DELETE', 'PATCH'时,才会将参数放入$request中。
    • 再回到\App\Http\Controllers\Api\UsersController::update方法中来,如果$request->request为空,那么$attributes = $request->only(['name', 'email', 'introduction']);的结果就是空,$user->update($attributes);就是什么也没有update。
5年前 评论
我在这里弱弱地说两个使用PATCH方法更新图片资源的方法,不喜勿喷。

(官方issue:https://github.com/laravel/framework/issues/13457)

第一种伪造PATCH方法,请求时正常用POST:

在laravel里路由定义了需要使用PATCH方法的话,请求的方法必须是PATCH,不然会报错说方法不允许,但是在使用postman发送请求时,form-data添加 _method=PATCH 这个字段的话,就可以伪造Laravel所需要的PATCH方法,正常的获取数据……

第二种方法:传递base_64编码后的字符串

前端将图片用base_64编码后,在application/x-www-form-urlencode中添加一个键image,值为图片编码后的字符串,后端获取的是base_64的图片数据,使用imagecreatefromstring($str)($str为前端传过来的图片经过base_64编码后的字符串),用这个方法再反方向生成图片就OK了 至于其他文件,待 集思广益…… ~

3年前 评论

php 中 $_POST这样写的:当 HTTP POST 请求的 Content-Type 是 application/x-www-form-urlencoded 或 multipart/form-data 时,会将变量以关联数组形式传入当前脚本。浏览器中,只能是 GET 或者 POST 请求方法,postman 则可以是 PUT 或者 PATCH。 当传统 http 发起 PATCH 请求(表单页面通过 _method 构造了一个假的提交方法,用来匹配路由方法,本质还是 POST 方法),laravel 是通过 $_POST 获取表单数据。postman 等非传统的方式用 PUT 后者 PATCH 提交表单,$_POST 获取不到表单数据,laravel 会判断请求方法,如果是 PUT 或者 PATCH,并且 content-type:x-www-form-urlencoded ,就会从 php://input 获取表单数据,至于 symfony 为什么这么实现,可能是为了遵守规范。
如果非要在 postman 中用 multipart/form-data,可以用 POST 方法模拟网页提交表单数据,并在表单数据中加入变量 _method=PATCH。

4年前 评论

@liyu001989 老师,“http 协议 put 和 patch 只能使用 x-www-form-urlencoded”,我只找到了这个依据:
https://developer.mozilla.org/en-US/docs/W... , 里面写了"Allowed in HTML forms: No"
有没有更权威的依据?谢谢。

5年前 评论

@hustnzj 学习就怕你这种较真的人

5年前 评论

@liyu001989 之前的教程里,PUT、PATCH、DELETE 请求是通过在表单里伪造一个 _method 参数,Api 的这几个请求不是放在表单参数里的吗?

5年前 评论
liyu001989

@hustnzj 我表述的可能有误解,http 默认情况下就是使用 x-www-form-urlencoded ,需要上传二进制数据的时候需要使用 post 并使用 form-data 。

你看一看看 https://tools.ietf.org/html/rfc1867#sectio...

5年前 评论

@_杭城浪子 我觉得他很厉害 会自己去寻找信息 会弄清真正的原理

3年前 评论

@liyu001989 http 协议 put 和 patch 只能使用 x-www-form-urlencoded 这个是哪里来的? 查了下,http标准并没有限制 patchput 请求的数据格式 stackoverflow.com/questions/173758...

这问题是 laravel/synfony 框架设计的问题,github.com/laravel/framework/issue...,而issue里面框架的维护者声称这是php的问题

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!