Uploading Multipart Files with Uppy/Laravel/Vue

I’m using Uppy’s Vue components to handle uploading large files and I’m having trouble getting it to work.

I’ve followed the suggestion by Janko here which involves setting the companionUrl in Uppy to point to my server, then write the necessary routes/functions to process the requests.

Everything is working until I try to fire the AWS “completeMultipartUpload” call.

$result = $client->completeMultipartUpload([
    'Bucket'          => 'bucket-name',
    'Key'             => $key,
    'UploadId'        => $uploadId,
    'MultipartUpload' => [
        'Parts' => $formattedParts,
    ],
]);

I get the following error:

"Error executing "CompleteMultipartUpload" on "https://bucket-name.s3.amazonaws.com/NewProject.png?uploadId=nlWLdbNgB9zgarpLBXnj17eOIGAmQM_xyBArymtwdM71fhbFvveggDmL6fz4blz.B95TLhMatDvodbMb5p2ZMKqdlLeLFoSW1qcu33aRQTlt6NbiP_dkDO90DFO.pWGH"; AWS HTTP error: Client error: `POST https://bucket-name.s3.amazonaws.com/NewProject.png?uploadId=nlWLdbNgB9zgarpLBXnj17eOIGAmQM_xyBArymtwdM71fhbFvveggDmL6fz4blz.B95TLhMatDvodbMb5p2ZMKqdlLeLFoSW1qcu33aRQTlt6NbiP_dkDO90DFO.pWGH` resulted in a `400 Bad Request` response:
    <Error><Code>InvalidPart</Code><Message>One or more of the specified parts could not be found.  The part may not have be (truncated...)
    InvalidPart (client): One or more of the specified parts could not be found.  The part may not have been uploaded, or the specified entity tag may not match the part's entity tag. - <Error><Code>InvalidPart</Code><Message>One or more of the specified parts could not be found.  The part may not have been uploaded, or the specified entity tag may not match the part's en"

I think that the problem is I’ve tried translating the JS found here to PHP, but although it’s returning a URL, it’s not correct. The notable difference is that I’m not passing an “UploadId” or a “PartNumber”.

I’ve scoured the AWS docs, Google, Stackoverflow, Ask Jeeves, etc and couldn’t find a PHP equivalent of the “getSignedUrl” method

public function signPartUpload(Request $request, $uploadId, $partNumber)
{
    $client = new S3Client([
        'version' => 'latest',
        'region'  => 'us-east-1',
    ]);

    $key = $request->has('key') ? $request->get('key') : null;

    if (!is_string($key)) {
        return response()->json(['error' => 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"'], 400);
    }

    if (!intval($partNumber)) {
        return response()->json(['error' => 's3: the part number must be a number between 1 and 10000.'], 400);
    }

    //Creating a presigned URL
    $cmd = $client->getCommand('PutObject', [
        'Bucket' => 'bucket-name',
        'Key' => $key
    ]);

    $response = $client->createPresignedRequest($cmd, '+20 minutes');
    $presignedUrl = (string)$response->getUri();

    return response()->json(['url' => $presignedUrl]);
}

Any help would be greatly appreciated!

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

I found the PHP equivalent of the getSignedUrl method.

$command = $this->client->getCommand('UploadPart', [
    'Bucket'        => $this->bucket,
    'Key'           => $key,
    'PartNumber'    => $partNumber,
    'UploadId'      => $uploadId,
    'Body'          => '',
]);

Here’s my solution:

/**
 * Create a pre-signed URL for parts to be uploaded to.
 * @param Request $request
 * @param string $uploadId
 * @param int $partNumber
 * @return JsonResponse
 */
public function signPartUpload(Request $request, string $uploadId, int $partNumber)
{
    $key = $request->has('key') ? $request->get('key') : null;

    // Check key
    if (! is_string($key)) {
        return response()->json(['error' => 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"'], 400);
    }

    // Check part number
    if (! intval($partNumber)) {
        return response()->json(['error' => 's3: the part number must be a number between 1 and 10000.'], 400);
    }

    // Create the upload part command and get the pre-signed URL
    try {
        $command = $this->client->getCommand('UploadPart', [
            'Bucket'        => $this->bucket,
            'Key'           => $key,
            'PartNumber'    => $partNumber,
            'UploadId'      => $uploadId,
            'Body'          => '',
        ]);

        $presignedUrl = $this->client->createPresignedRequest($command, '+20 minutes');
    } catch (Exception $e) {
        return response()->json(['error' => $e->getMessage()], 400);
    }

    // Convert the pre-signed URL to a string
    $presignedUrlString = (string) $presignedUrl->getUri();

    return response()->json(['url' => $presignedUrlString]);
}


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x