'Problem Uploading Images to S3 Bucket Using AlamoFire and Presigned URL

My approach for uploading to S3 for my app has been to send a get request to my PHP backend to generate a presigned URL. I know the backend is set up correctly because running the following command successfully uploads an image to the S3 bucket:

curl -v -H "Content-Type: image/jpeg" -T ./test.jpeg '<presignedURL>'

However, I am running into issues when trying to upload the image in Swift. Here is my current implementation (please ignore the garbage, hard-coded, non error checking):

Backend

<?php
require '../vendor/autoload.php';

use Aws\S3\S3Client;
use Aws\Exception\AwsException;

$response = array();

$client = S3Client::factory(array(
    'profile' => 'default',
    'version' => 'latest',
    'region' => 'us-east-2',
    'signature' => 'v4'
));

$command = $client->getCommand('PutObject', array(
    'Bucket'      => 'test',
    'Key'         => 'test.jpeg',
    'ContentType' => 'image/jpeg',
    'Body'        => ''
));

$signedUrl = $command->createPresignedUrl('+5 minutes');

$response['error'] = false;
$response['url'] = $signedUrl;

echo json_encode($response);

Swift Code

import Foundation
import Alamofire

let getTokenURL = "http://192.168.1.59:8000/v1/upload.php"

func submitImage(image: UIImage, completion: @escaping (NSDictionary) -> Void) {
    
    AF.request(getTokenURL, method: .get).responseJSON { response in
        
        switch response.result {
        case.success(let value):
            let jsonData = value as! NSDictionary
            let url = jsonData.value(forKey: "url") as! String
            
            performUpload(image: image, postURL: url)
        
        case.failure(_):
            let error_msg: NSDictionary = [
                "error" : true,
                "message" : "Unknown error occurred. Please try again",
            ]
            
            //completion(error_msg)
        }
    }
                                
}

func performUpload(image: UIImage, postURL: String) {
    let imageData = image.jpegData(compressionQuality: 0.50)!
    
    AF.upload(imageData, to: postURL, headers: ["Content-Type":"image/jpeg"])    //likely the culprit line
}

Currently the URL is returned from the get request in submitImage(), and performUpload() is called, making the culprit (likely) the very last lime of my Swift code segment. I'm having trouble figuring out what I should do while reading the documentation, and most guides on this subject are old and outdated because AlamoFire has changed their syntax. Any help would be greatly appreciated. Thank you!

Edit: I have tweaked the performUpload() function. It now uploads the data to the s3 bucket, however the image is not able to be opened. I suspect this is because of an incorrect header in the request. From debugging I can tell the Content-Type header is "multipart/form-data" no matter what, so I'm not sure if this approach is viable:

struct HTTPBinResponse: Decodable { let url: String }

func performUpload(image: UIImage, postURL: String) {
    let imageData = image.jpegData(compressionQuality: 0.50)!
    
    AF.upload(multipartFormData: { multipartFormData in
        multipartFormData.append(imageData, withName: "file", mimeType: "image/jpeg")
    }, to: postURL, method: .put, headers: ["Content-Type":"image/jpeg"]).responseDecodable(of: HTTPBinResponse.self) { response in
            debugPrint(response)
    }  
}


Solution 1:[1]

For future readers, the key here is to add method: .put! Everything else is fine in this question.

Also I found that you have to use empty content-type headers. S3 is weird.

AF.upload(imageData, to: postURL, method: .put, headers: ["Content-Type": ""])

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1