Joey Yi Zhao
Joey Yi Zhao

Reputation: 42490

How to upload images to s3 presigned URL in flutter?

I create a s3 presigned URL in typescript as below:

const params = {
      Bucket: myBucketName,
      Key: uuidv4(),
      Expires: 3600,
    };
s3.getSignedUrlPromise('putObject', params)

and I used below code in flutter to upload image to this url:

Option1:

var formData = FormData.fromMap({
      "file":
          await MultipartFile.fromFile(image.path, filename: "upload.png"),
    });
    var response = await Dio().put(url, data: formData);

Option2:

return Dio().put(
      url,
      data: image.openRead(),
      options: Options(
        contentType: "multiple/form-data",
        headers: {
          "Content-Length": image].lengthSync(),
        },
      ),
      onSendProgress: (int sentBytes, int totalBytes) {
        double progressPercent = sentBytes / totalBytes * 100;
        print("upload $progressPercent %");
      },
    )

in both cases I got 403 forbidden error response. How can I upload images to the presigned URL? Is there anything wrong in the generated side or flutter side?

I can upload the file with curl command like below:

 curl -XPOST -H "Content-Type: application/x-www-form-urlencoded"  --data-binary "@/Users/Pictures/IMG_4634.jpg" $PRESIGNED_URL

so the url does work.

Upvotes: 5

Views: 9687

Answers (5)

user15701614
user15701614

Reputation:

I have tried all the solutions in this thread but it's not working

finally i found this blog: https://icircuit.net/flutter-upload-file-to-aws-s3/2885

this is the solution that works great with Dio:

Future<Response> sendFile(String url, File file) async {
  Dio dio = new Dio();
  var len = await file.length();
  var response = await dio.put(url,
      data: file.openRead(),
      options: Options(headers: {
        Headers.contentLengthHeader: len,
      } // set content-length
          ));
  return response;
}

Upvotes: 7

Jonatas Campos Martins
Jonatas Campos Martins

Reputation: 106

I created this class to send an single image to s3 using pre-signed url, I'm using camera lib to send a photo to s3.

import 'dart:convert';
import 'dart:io';

import 'package:camera/camera.dart';
import 'package:http/http.dart';
import 'package:http_parser/http_parser.dart';

class AwsApi {
  Future<String> uploadToSignedUrl({required XFile file, required String signedUrl}) async {
    Uri uri = Uri.parse(signedUrl);
    var response = await put(uri, body: await file.readAsBytes(), headers: {"Content-Type": "image/jpg"});

    return response;

  }
}

You can change await file.readAsBytes() to this file.readAsBytesSync(), if you are using File

Upvotes: 5

Nicholas Pesa
Nicholas Pesa

Reputation: 2196

I was getting 403 doing the same thing with presigned s3 urls and had to use a POST request which returned a 204 and successfully saved the file.

Using the http package I did the example like so:

import 'package:http/http.dart' as http;
var req = http.MultipartRequest('POST', Uri.parse(s3Url));
req.files.add(
  http.MultipartFile.fromBytes(
    'file',
    file.readAsBytesSync(),
    filename: filename,
  )
);
var res = await req.send();
print('UPLOAD: ${res.statusCode}');

This returns a StreamedResponse type so to get the body of the response you need to convert the response.

NOTE: I did have to add a lot of fields on the multipart request like AWSAccessKeyId and x-amz-security-token which I was returning from the s3 presigned URL response. I configured it very securely though so you may not need that but wanted to clarify.

Upvotes: 3

Joey Yi Zhao
Joey Yi Zhao

Reputation: 42490

After some debugging, I found that this code can be used to fix it:

http.put(entry.value, body: image.readAsBytesSync())

I don't need to specify any header or any fields. And http.post is google enough to solve the issue.

Upvotes: 2

stefansundin
stefansundin

Reputation: 3044

If you can print the response from S3 somehow, that may reveal the error.

From the information available, I think the problem is that your are using a PUT request and not a POST request. Looking at the Dio documentation, it seems like you can use Dio().post. You may also need to set Content-Type like in your curl example.

If it still doesn't work, try adding -v to your curl command to see all of the headers it sends. It would be useful to get the network request that Dio sends, to compare against.

Upvotes: 2

Related Questions