Symfony 无法识别通过 Guzzle multipart/form-data 请求上传的多个文件

Posted

技术标签:

【中文标题】Symfony 无法识别通过 Guzzle multipart/form-data 请求上传的多个文件【英文标题】:Multiple files uploaded via Guzzle multipart/form-data request are not recognized by Symfony 【发布时间】:2018-05-13 01:04:53 【问题描述】:

环境: 狂饮 6 Symfony 2.3

通过 Guzzle POST 请求 should be done with a multipart request 上传多个文件。所以我像这样配置我的 $options 数组:

Array
(
[multipart] => Array
    (
        [0] => Array
            (
                [name] => filename-0
                [contents] => Resource id #440
                [filename] => filename-0
            )

        [1] => Array
            (
                [name] => filename-1
                [contents] => Resource id #441
                [filename] => filename-1
            )

        [2] => Array
            (
                [name] => filename-2
                [contents] => Resource id #442
                [filename] => filename-2
            )

    )

[headers] => Array
    (
        [Accept] => application/json
        [Content-Type] => multipart/form-data
        [Accept-Language] => de
    )

[allow_redirects] =>
[http_errors] =>

)

多部分数组中的资源是 fopen() 的结果。

并使用发送请求

$response = $this->client->post(
    '/someUrl/someAction',
    $options
);

使用已创建的客户端。

在接受 Symfony-Controllers 方面,我无法发送文件:

var_dump($_FILES); // array(0) 
var_dump($_POST);  // array(0) 
var_dump(count($request->files->all())); // int(0)

但是,这两个:

var_dump(file_get_contents("php://input"));
var_dump($request->getContent());

在输入流上返回数据:

/myPath/FileController.php:xx:
string(601) "--e55f849feb078da4a9e35ba77da3ded02ec813a7
Content-Disposition: form-data; name="filename-0"; filename="filename-0"
Content-Length: 43

This is a testfile...
--e55f849feb078da4a9e35ba77da3ded02ec813a7
Content-Disposition: form-data; name="filename-1"; filename="filename-1"
Content-Length: 43

This is a testfile...
--e55f849feb078da4a9e35ba77da3ded02ec813a7
Content-Disposition: form-data; name="filename-2"; filename="filename-2"
Content-Length: 43

This is a testfile...
--e55f849feb078da4a9e35ba77da3ded02ec813a7--
"

我怎样才能以 Symfony 的方式进入接收控制器?

需要考虑的好奇心: 控制器报告

var_dump($request->getContentType()); // NULL

我的直觉表明这可能很重要。

【问题讨论】:

【参考方案1】:

当使用multipart 选项时,您不能自己指定Content-Type 标头。 Guzzle 会处理这个问题,而且对于这个问题更重要的是 - boundary 将原始请求正文中的内容部分分开。主要的 Content-Type 标头定义了该边界,如下所示:

Content-Type: multipart/form-data; boundary=unique-string-that-is-hopefully-not-used-in-the-real-content-because-it-separates-the-content-parts`

检查Client.php#L308: 当使用 multipart 选项时,Guzzle 将请求正文创建为 GuzzleHttp\Psr7\MultipartStream 对象。

然后检查MultipartStream.php#L30 处的MultipartStream 构造函数: 创建一个随机值用作边界:sha1(uniqid('', true))

然后Guzzle 将使用边界在Client.php#L383 上自行设置正确的内容类型。

但由于 Guzzle 没有覆盖您明确指定的选项,因此主标题指定了一个空边界,原始正文部分将由 MultpartStream 构造函数创建的部分分隔。在接收端,PHP 不能再分离内容部分了。

此代码适用于我:

(new GuzzleHttp\Client())->request(
    'POST',
    'http://localhost/upload',
    [
        'multipart' => [
            [
                'name' => 'filename-0',
                'contents' => fopen(__DIR__.'/sample-0.txt', 'rb'),
                'filename' => 'filename-0',
            ],
            [
                'name' => 'filename-1',
                'contents' => fopen(__DIR__.'/sample-1.txt', 'rb'),
                'filename' => 'filename-1',
            ],
            [
                'name' => 'filename-2',
                'contents' => fopen(__DIR__.'/sample-2.txt', 'rb'),
                'filename' => 'filename-2',
            ],
        ],
        'headers' => [
            # Do not override the Content-Type header here, Guzzle takes care of it
        ] ,
    ]
);

请注意,所有这些都与 Symfony 无关,即 Symfony 只是从它可以通过 PHP 的本机源(如 $_FILES$_POST 等)获得的内容创建它 Request 对象。您可以看到所有内容在原始请求正文 ($request->getContent()) 中,因为它是这样发送的,但 PHP 无法拆分内容部分,因为它不知道正确的边界。

【讨论】:

非常感谢。我一拿到电脑就马上试试。 它就像一个魅力。非常感谢你。这个问题困扰了我超过 3 天。【参考方案2】:

这段代码对我有用。我认为您的 API 正在接收一个名为 file 的 var 类型数组。

$fopen=fopen("/etc/file.txt");//verify that file exists
$fopen2=fopen("/etc/file.txt");//verify that file exists

$apiRequest = $client->request('POST', 'http://xxx.xx.xxx.xx/upload/1/9', 
      [
      'multipart' => [
          [
            'name'     => 'file[]',
            'contents' => $fopen
          ],
          [
            'name'     => 'file[]',
            'contents' => $fopen2              
          ]
        ]
      ]);
        $response = json_decode($apiRequest->getBody());

dd($response);

【讨论】:

以上是关于Symfony 无法识别通过 Guzzle multipart/form-data 请求上传的多个文件的主要内容,如果未能解决你的问题,请参考以下文章

带有 Symfony2 的 Doctrine2 无法识别数据库字符集和排序规则

Symfony2 注销 CSRF 保护:csrf_provider 无法识别

Guzzle 获取文件并转发

为什么Symfony 5.1无法识别在“ routes.php”文件上配置的路由?

Laravel Guzzle 使用踩雷及指南

如何在 Laravel 4 中自动加载 Guzzle?