Multiple files uploaded via Guzzle multipart/form-data request are not recognized by Symfony

不想你离开。 提交于 2021-02-07 03:39:59

问题


Environment: Guzzle 6 Symfony 2.3

Uploading multiple files via a Guzzle POST request should be done with a multipart request. So I configure my $options array like so:

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] =>

)

The resources in the multipart-array are the results of an fopen().

And send the request using

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

Using an already created client.

On the accepting Symfony-Controllers side, I can't get the files sent:

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

However, both of these:

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

Return data on the input stream:

/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--
"

How might I get in the receiving controller in a Symfony-way?

A curiosity to consider: The Controller reports

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

My gut feeling says this might be important.


回答1:


When using the multipart option, you must not specify the Content-Type header yourself. Guzzle will take care of this and - more important for this issue - of the boundary that separates the content parts in the raw request body. The main Content-Type header defines that boundary like so:

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

Check Client.php#L308: Guzzle creates the request body as GuzzleHttp\Psr7\MultipartStream object when multipart option was used.

Then check the MultipartStream constructor at MultipartStream.php#L30: A random value is created to be used as the boundary: sha1(uniqid('', true))

Guzzle will then use the boundary to set the correct content type on its own at Client.php#L383.

But as Guzzle does not override an option that you have specified explicitly, the main header specifies an empty boundary and the raw body parts will be separated by the one created by the MultpartStream constructor. On the receiving end PHP cannot separate the content parts any more.

This code works for me:

(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
        ] ,
    ]
);

Please note that all of this is not Symfony related, i.e. Symfony just creates it Request object from what it can get via PHP's native sources like $_FILES, $_POST etc. You were able to see all the content in the raw request body ($request->getContent()) because it was sent like this, but PHP could not split the content parts because it didn't knew of the correct boundary.




回答2:


This code worked for me. I think that your API is receiving a var type array called file.

$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);


来源:https://stackoverflow.com/questions/47550801/multiple-files-uploaded-via-guzzle-multipart-form-data-request-are-not-recognize

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!