前言
近期 Flutter IM 的模块优化,需要将素材生成 AWS Signature v4签名以上传到s3。后端新增两个接口,一个是预上传的接口获取上传 aws 相关参数,及获取签名需要 session_token 的接口。做了下遇到问题的笔记
遇到问题
预上传
- 在预上传接口获取到 aws 需要的字段,原先封装的表单上传但却抛异常如下:
flutter: DioError [DioErrorType.other]: HttpException: Connection closed while receiving data, uri = https://xxxxxxx.s3.ap-northeast-1.amazonaws.com
- 经排查发现是编码问题调用了
MultipartFile.fromString('$value');
在 dio 默认是使用 UTF-8 编码了
/// Creates a new [MultipartFile] from a string.
///
/// The encoding to use when translating [value] into bytes is taken from
/// [contentType] if it has a charset set. Otherwise, it defaults to UTF-8.
/// [contentType] currently defaults to `text/plain; charset=utf-8`, but in
/// the future may be inferred from [filename].
factory MultipartFile.fromString(
String value, {
String? filename,
MediaType? contentType,
final Map<String, List<String>>? headers,
}) {
contentType ??= MediaType('text', 'plain');
var encoding = encodingForCharset(contentType.parameters['charset'], utf8);
contentType = contentType.change(parameters: {'charset': encoding.name});
return MultipartFile.fromBytes(
encoding.encode(value),
filename: filename,
contentType: contentType,
headers: headers,
);
}
- 封装的文件上传方法如下
/// 上传文件
Future<dynamic> uploadFile(
String url,
String filePath, {
String customFileParamKey = 'file',
Map<String, dynamic>? params,
}) async {
var file = File(filePath);
if (!file.existsSync()) {
return TWApiResponse(message: '文件不存在');
}
var map = <String, dynamic>{};
map[customFileParamKey] = await MultipartFile.fromFile(filePath);
if (params != null) {
params.forEach((key, value) {
map[key] = value;
});
}
FormData formData = FormData.fromMap(map);
var options = Options(
contentType: 'multipart/form-data;boundary=${formData.boundary}',
);
try {
final response = (await client.post(
url,
options: options,
data: formData,
));
final code = response.statusCode;
TWLog('statusCode: ${response.statusCode}');
if (code == 204) {
return TWApiResponse(code: TWChatResultCode.success.value);
}
return TWApiResponse(message: '上傳失敗');
} catch (e) {
debugPrint(e.toString());
return TWApiResponse(message: '上傳失敗');
}
}
v4 签名 URL
在 Flutter 端对 URL 做预签名,查阅官方文档后及 Flutter SDK 后,在 issue 找到提问 How to use Amazon STS to upload/down file with amplify-flutter 参考 demo。 注意不同 demo 地方
1、queryParameters 补充:X-Amz-Content-Sha256: UNSIGNED-PAYLOAD。
2、AWSCredentials 初始化需传 expiration
- 封装方法如下
/// 获取签名后的 url
Future<String> fetchSignatureUrl({
required String fileName,
required String region,
required String bucket,
required String sessionToken,
required String secretAccessKey,
required String accessKeyId,
required String expiration,
int minutes = 55,
}) async {
try {
final credentials = AWSCredentials(
accessKeyId,
secretAccessKey,
sessionToken,
expiration.getDateTime,
);
final signer = AWSSigV4Signer(
credentialsProvider: AWSCredentialsProvider(credentials),
);
// Set up S3 values
final scope = AWSCredentialScope(
region: region,
service: AWSService.s3,
);
final host = '$bucket.s3.$region.amazonaws.com';
final serviceConfiguration = S3ServiceConfiguration();
// Create a pre-signed URL for downloading the file
final urlRequest = AWSHttpRequest.get(
Uri.https(
host,
fileName,
{
AWSHeaders.contentSHA256:
S3ServiceConfiguration.unsignedPayloadHash,
},
),
headers: {
AWSHeaders.host: host,
},
);
final signedUrl = await signer.presign(
urlRequest,
credentialScope: scope,
serviceConfiguration: serviceConfiguration,
expiresIn: Duration(minutes: minutes),
);
TWLog('Download URL: $signedUrl');
return signedUrl.toString();
} catch (e) {
TWLog('fetchSignatureUrl ===> $e');
return '$e';
}
}
Thanks
- 本文链接:https://zhengzeqin.netlify.app/2023/01/08/Flutter-aws-signature-v4-%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub Issues