31

I am able to upload a file to an API endpoint using Postman.

I am trying to translate that into uploading a file from a form, uploading it using Laravel and posting to the endpoint using Guzzle 6.

Screenshot of how it looks in Postman (I purposely left out the POST URL) enter image description here

Below is the text it generates when you click the "Generate Code" link in POSTMAN:

POST /api/file-submissions HTTP/1.1
Host: strippedhostname.com
Authorization: Basic 340r9iu34ontoeioir
Cache-Control: no-cache
Postman-Token: 6e0c3123-c07c-ce54-8ba1-0a1a402b53f1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="FileContents"; filename=""
Content-Type: 


----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="FileInfo"

{ "name": "_aaaa.txt", "clientNumber": "102425", "type": "Writeoff" }
----WebKitFormBoundary7MA4YWxkTrZu0gW

Below is controller function for saving the file and other info. The file uploads correctly, I am able to get the file info.

I think the problem I am having is setting the multipart and headers array with the correct data.

public function fileUploadPost(Request $request)
{
    $data_posted = $request->input();
    $endpoint = "/file-submissions";
    $response = array();
    $file = $request->file('filename');
    $name = time() . '_' . $file->getClientOriginalName();
    $path = base_path() .'/public_html/documents/';

    $resource = fopen($file,"r") or die("File upload Problems");

    $file->move($path, $name);

    // { "name": "test_upload.txt", "clientNumber": "102425", "type": "Writeoff" }
    $fileinfo = array(
        'name'          =>  $name,
        'clientNumber'  =>  "102425",
        'type'          =>  'Writeoff',
    );

    $client = new \GuzzleHttp\Client();

    $res = $client->request('POST', $this->base_api . $endpoint, [
        'auth' => [env('API_USERNAME'), env('API_PASSWORD')],
        'multipart' => [
            [
                'name'  =>  $name,
                'FileContents'  => fopen($path . $name, 'r'),
                'contents'      => fopen($path . $name, 'r'),
                'FileInfo'      => json_encode($fileinfo),
                'headers'       =>  [
                    'Content-Type' => 'text/plain',
                    'Content-Disposition'   => 'form-data; name="FileContents"; filename="'. $name .'"',
                ],
                // 'contents' => $resource,
            ]
        ],
    ]);

    if($res->getStatusCode() != 200) exit("Something happened, could not retrieve data");

    $response = json_decode($res->getBody());

    var_dump($response);
    exit();
}

The error I am receiving, screenshot of how it displays using Laravel's debugging view:

enter image description here

Brad
  • 12,054
  • 44
  • 118
  • 187

3 Answers3

73

The way you are POSTing data is wrong, hence received data is malformed.

Guzzle docs:

The value of multipart is an array of associative arrays, each containing the following key value pairs:

name: (string, required) the form field name

contents:(StreamInterface/resource/string, required) The data to use in the form element.

headers: (array) Optional associative array of custom headers to use with the form element.

filename: (string) Optional string to send as the filename in the part.

Using keys out of above list and setting unnecessary headers without separating each field into one array will result in making a bad request.

$res = $client->request('POST', $this->base_api . $endpoint, [
    'auth'      => [ env('API_USERNAME'), env('API_PASSWORD') ],
    'multipart' => [
        [
            'name'     => 'FileContents',
            'contents' => file_get_contents($path . $name),
            'filename' => $name
        ],
        [
            'name'     => 'FileInfo',
            'contents' => json_encode($fileinfo)
        ]
    ],
]);
revo
  • 47,783
  • 14
  • 74
  • 117
  • the above solution works when i don't add filename' => $name as argument. When i add filename nothing get send over. – Aditya Joshi Aug 14 '18 at 17:33
  • 4
    Should you send the file_get_contents or an fopen? – zardilior Nov 27 '18 at 15:38
  • 2
    @zardilior As I looked at `stream_for` function defined in `guzzlehttp/psr7` package both are handled properly. – revo Nov 28 '18 at 13:43
  • @AdityaJoshi in my case I had it as a binary string and it worked only by sending it with filename and receving it in laravel with request->has – zardilior Nov 28 '18 at 15:44
3
$body = fopen('/path/to/file', 'r');
$r = $client->request('POST', 'http://httpbin.org/post', ['body' => $body]);

http://docs.guzzlephp.org/en/latest/quickstart.html?highlight=file

Pawel
  • 105
  • 1
  • 20
    Please don't post code only answers. Add some text to accompany your code. – rollstuhlfahrer Feb 05 '18 at 12:48
  • What you posted is what worked for me. http://docs.guzzlephp.org/en/latest/quickstart.html?highlight=file#sending-form-files – andreshg112 Jul 13 '18 at 16:46
  • This is not a correct answer since the OP asked for multipart upload but it is still useful. – Akira Yamamoto Jan 22 '21 at 00:24
  • @Pawel how would I post a file which is online somewhere? file is at `https://example.com/file.mp4` how would I send this via the `$client->request(...` without downloading the file first? is this possible? – Dean Van Greunen Oct 08 '21 at 13:08
-1

In Laravel 8 with guzzle I am using this:

The idea is that you are reading the file with fread or file_get_content, then you can use the Laravel getPathname() which points to the file in /tmp

        $response = $this
            ->apiClient
            ->setUserKey($userToken)
            ->post(
                '/some/url/to/api',
                [
                    'multipart' => [
                        'name'     => 'avatar',
                        'contents' => file_get_contents($request->file('avatar')->getPathname()),
                        'filename' => 'avata.' . $request->file('avatar')->getClientOriginalExtension()
                    ]
                ]
            );
alex toader
  • 2,300
  • 1
  • 17
  • 20