Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Uploaded file to S3 via PreSigned URL from Flutter App. but the file is corrupted when i download it

I am working on a Flutter App, where I upload image file (PUT Request) to AWS S3 using a presigned URL. The upload is successful as I can see the file in S3. But when I click and download it from the bucket, the downloaded file is corrupted.

I am using Dio library for uploading the file. Uploading the image file as binary via postman works perfectly

uploadFileToPresignedS3(
    File payload, String fileName, String presignedURL) async {
  try {
    Dio dio = new Dio();

    FormData formData = new FormData.from(
        {"name": fileName, "file1": new UploadFileInfo(payload, fileName)});
    dio.put(presignedURL, data: formData);
  } catch (ex) {
    print(ex);
  }
}


Expected: The uploaded file not to be corrupted

Actual result: The uploaded file is corrupted

like image 392
Balaji Avatar asked Jun 13 '19 09:06

Balaji


People also ask

How does Presigned URL work S3?

A user who does not have AWS credentials or permission to access an S3 object can be granted temporary access by using a presigned URL. A presigned URL is generated by an AWS user who has access to the object. The generated URL is then given to the unauthorized user.

Is S3 Presigned URL safe?

Pre-signed URLs can be generated for an S3 object, allowing anyone who has the URL to retrieve the S3 object with an HTTP request. Not only is this more secure due to the custom nature of the URL, but the available options also allow you to set an expiration on the URL, the default being one hour.

What is Presigned URL S3 upload?

A presigned URL is a URL that you can provide to your users to grant temporary access to a specific S3 object. Using the URL, a user can either READ the object or WRITE an Object (or update an existing object). The URL contains specific parameters which are set by your application.


3 Answers

Use this code to upload file (image) to S3 pre-signed-url using Dio and show upload progress:

await dio.put(
    url,
    data: image.openRead(),
    options: Options(
      contentType: "image/jpeg",
      headers: {
        "Content-Length": image.lengthSync(),
      },
    ),
    onSendProgress: (int sentBytes, int totalBytes) {
      double progressPercent = sentBytes / totalBytes * 100;
      print("$progressPercent %");
    },
  );

Note: Do not set Content-Type header along with Content-Length like this:

headers: {
   "Content-Length": image.lengthSync(),
   "Content-Type": "image/jpeg",
},

Due to some reason, it will result in corrupted uploaded file.

Just in case: Instead of print("$progressPercent %") you can use setState() to show updates in UI.

Hope this helps.

like image 145
Rabi Roshan Avatar answered Oct 17 '22 14:10

Rabi Roshan


To piggy back off of Rabi Roshans's comment you need to modify contenType to "application/octet-stream". In your backend's S3 params you need to do the same.

client code

  await dio.put(
    url,
    data: image.openRead(),
    options: Options(
      contentType: "application/octet-stream",
      headers: {
        "Content-Length": image.lengthSync(),
      },
    ),
    onSendProgress: (int sentBytes, int totalBytes) {
      double progressPercent = sentBytes / totalBytes * 100;
      print("$progressPercent %");
    },
  );

s3 backend

  var presignedUrl = s3.getSignedUrl("putObject", {
    Bucket: "your_bucke_name",
    Key: "filename.ext", 
    Expires: 120, // expirations in seconds
    ContentType: "application/octet-stream", // this must be added or you will get 403 error
  })

;

like image 40
Luis Avatar answered Oct 17 '22 15:10

Luis


I created this class to send an 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;

  }
}
like image 33
Jonatas Campos Martins Avatar answered Oct 17 '22 15:10

Jonatas Campos Martins