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
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.
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.
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.
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.
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
})
;
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With